summaryrefslogtreecommitdiff
path: root/odb-tests/common
diff options
context:
space:
mode:
Diffstat (limited to 'odb-tests/common')
-rw-r--r--odb-tests/common/access/buildfile45
-rw-r--r--odb-tests/common/access/driver.cxx262
-rw-r--r--odb-tests/common/access/test.hxx592
-rw-r--r--odb-tests/common/access/testscript33
-rw-r--r--odb-tests/common/as/buildfile49
-rw-r--r--odb-tests/common/as/driver.cxx348
-rw-r--r--odb-tests/common/as/test.hxx270
-rw-r--r--odb-tests/common/as/testscript33
-rw-r--r--odb-tests/common/blob/buildfile40
-rw-r--r--odb-tests/common/blob/driver.cxx76
-rw-r--r--odb-tests/common/blob/test.hxx71
-rw-r--r--odb-tests/common/blob/testscript33
-rw-r--r--odb-tests/common/buildfile6
-rw-r--r--odb-tests/common/bulk/buildfile49
-rw-r--r--odb-tests/common/bulk/driver.cxx1203
-rw-r--r--odb-tests/common/bulk/test.hxx211
-rw-r--r--odb-tests/common/bulk/testscript503
-rw-r--r--odb-tests/common/callback/buildfile41
-rw-r--r--odb-tests/common/callback/driver.cxx184
-rw-r--r--odb-tests/common/callback/test.hxx43
-rw-r--r--odb-tests/common/callback/testscript100
-rw-r--r--odb-tests/common/changelog/.gitignore3
-rw-r--r--odb-tests/common/changelog/add-column-mssql-diff.xml16
-rw-r--r--odb-tests/common/changelog/add-column-mssql-patch.xml13
-rw-r--r--odb-tests/common/changelog/add-column-mysql-diff.xml16
-rw-r--r--odb-tests/common/changelog/add-column-mysql-patch.xml13
-rw-r--r--odb-tests/common/changelog/add-column-oracle-diff.xml16
-rw-r--r--odb-tests/common/changelog/add-column-oracle-patch.xml13
-rw-r--r--odb-tests/common/changelog/add-column-pgsql-diff.xml16
-rw-r--r--odb-tests/common/changelog/add-column-pgsql-patch.xml13
-rw-r--r--odb-tests/common/changelog/add-column-sqlite-diff.xml16
-rw-r--r--odb-tests/common/changelog/add-column-sqlite-patch.xml13
-rw-r--r--odb-tests/common/changelog/add-column.hxx20
-rw-r--r--odb-tests/common/changelog/add-foreign-key-diff.xml28
-rw-r--r--odb-tests/common/changelog/add-foreign-key-mssql-diff.xml28
-rw-r--r--odb-tests/common/changelog/add-foreign-key-mssql-patch.xml25
-rw-r--r--odb-tests/common/changelog/add-foreign-key-mysql-diff.xml28
-rw-r--r--odb-tests/common/changelog/add-foreign-key-mysql-patch.xml25
-rw-r--r--odb-tests/common/changelog/add-foreign-key-oracle-diff.xml28
-rw-r--r--odb-tests/common/changelog/add-foreign-key-oracle-patch.xml25
-rw-r--r--odb-tests/common/changelog/add-foreign-key-pgsql-diff.xml28
-rw-r--r--odb-tests/common/changelog/add-foreign-key-pgsql-patch.xml25
-rw-r--r--odb-tests/common/changelog/add-foreign-key-sqlite-diff.xml28
-rw-r--r--odb-tests/common/changelog/add-foreign-key-sqlite-patch.xml25
-rw-r--r--odb-tests/common/changelog/add-foreign-key.hxx29
-rw-r--r--odb-tests/common/changelog/add-index-mssql-diff.xml21
-rw-r--r--odb-tests/common/changelog/add-index-mssql-patch.xml18
-rw-r--r--odb-tests/common/changelog/add-index-mysql-diff.xml21
-rw-r--r--odb-tests/common/changelog/add-index-mysql-patch.xml18
-rw-r--r--odb-tests/common/changelog/add-index-oracle-diff.xml21
-rw-r--r--odb-tests/common/changelog/add-index-oracle-patch.xml18
-rw-r--r--odb-tests/common/changelog/add-index-pgsql-diff.xml21
-rw-r--r--odb-tests/common/changelog/add-index-pgsql-patch.xml18
-rw-r--r--odb-tests/common/changelog/add-index-sqlite-diff.xml21
-rw-r--r--odb-tests/common/changelog/add-index-sqlite-patch.xml18
-rw-r--r--odb-tests/common/changelog/add-index.hxx24
-rw-r--r--odb-tests/common/changelog/add-table-mssql-diff.xml45
-rw-r--r--odb-tests/common/changelog/add-table-mssql-patch.xml44
-rw-r--r--odb-tests/common/changelog/add-table-mysql-diff.xml45
-rw-r--r--odb-tests/common/changelog/add-table-mysql-patch.xml44
-rw-r--r--odb-tests/common/changelog/add-table-oracle-diff.xml45
-rw-r--r--odb-tests/common/changelog/add-table-oracle-patch.xml44
-rw-r--r--odb-tests/common/changelog/add-table-pgsql-diff.xml45
-rw-r--r--odb-tests/common/changelog/add-table-pgsql-patch.xml44
-rw-r--r--odb-tests/common/changelog/add-table-sqlite-diff.xml45
-rw-r--r--odb-tests/common/changelog/add-table-sqlite-patch.xml44
-rw-r--r--odb-tests/common/changelog/add-table.hxx34
-rw-r--r--odb-tests/common/changelog/alter-column-mssql-diff.xml17
-rw-r--r--odb-tests/common/changelog/alter-column-mssql-patch.xml13
-rw-r--r--odb-tests/common/changelog/alter-column-mysql-diff.xml17
-rw-r--r--odb-tests/common/changelog/alter-column-mysql-patch.xml13
-rw-r--r--odb-tests/common/changelog/alter-column-oracle-diff.xml17
-rw-r--r--odb-tests/common/changelog/alter-column-oracle-patch.xml13
-rw-r--r--odb-tests/common/changelog/alter-column-pgsql-diff.xml17
-rw-r--r--odb-tests/common/changelog/alter-column-pgsql-patch.xml13
-rw-r--r--odb-tests/common/changelog/alter-column-sqlite-diff.xml17
-rw-r--r--odb-tests/common/changelog/alter-column-sqlite-patch.xml13
-rw-r--r--odb-tests/common/changelog/alter-column.hxx21
-rw-r--r--odb-tests/common/changelog/buildfile30
-rw-r--r--odb-tests/common/changelog/drop-column-mssql-diff.xml17
-rw-r--r--odb-tests/common/changelog/drop-column-mssql-patch.xml12
-rw-r--r--odb-tests/common/changelog/drop-column-mysql-diff.xml17
-rw-r--r--odb-tests/common/changelog/drop-column-mysql-patch.xml12
-rw-r--r--odb-tests/common/changelog/drop-column-oracle-diff.xml17
-rw-r--r--odb-tests/common/changelog/drop-column-oracle-patch.xml12
-rw-r--r--odb-tests/common/changelog/drop-column-pgsql-diff.xml17
-rw-r--r--odb-tests/common/changelog/drop-column-pgsql-patch.xml12
-rw-r--r--odb-tests/common/changelog/drop-column-sqlite-diff.xml17
-rw-r--r--odb-tests/common/changelog/drop-column-sqlite-patch.xml12
-rw-r--r--odb-tests/common/changelog/drop-column.hxx20
-rw-r--r--odb-tests/common/changelog/drop-foreign-key-mssql-diff.xml30
-rw-r--r--odb-tests/common/changelog/drop-foreign-key-mssql-patch.xml18
-rw-r--r--odb-tests/common/changelog/drop-foreign-key-mysql-diff.xml30
-rw-r--r--odb-tests/common/changelog/drop-foreign-key-mysql-patch.xml18
-rw-r--r--odb-tests/common/changelog/drop-foreign-key-oracle-diff.xml30
-rw-r--r--odb-tests/common/changelog/drop-foreign-key-oracle-patch.xml18
-rw-r--r--odb-tests/common/changelog/drop-foreign-key-pgsql-diff.xml30
-rw-r--r--odb-tests/common/changelog/drop-foreign-key-pgsql-patch.xml18
-rw-r--r--odb-tests/common/changelog/drop-foreign-key-sqlite-diff.xml30
-rw-r--r--odb-tests/common/changelog/drop-foreign-key-sqlite-patch.xml18
-rw-r--r--odb-tests/common/changelog/drop-foreign-key.hxx29
-rw-r--r--odb-tests/common/changelog/drop-index-mssql-diff.xml20
-rw-r--r--odb-tests/common/changelog/drop-index-mssql-patch.xml13
-rw-r--r--odb-tests/common/changelog/drop-index-mysql-diff.xml20
-rw-r--r--odb-tests/common/changelog/drop-index-mysql-patch.xml13
-rw-r--r--odb-tests/common/changelog/drop-index-oracle-diff.xml20
-rw-r--r--odb-tests/common/changelog/drop-index-oracle-patch.xml13
-rw-r--r--odb-tests/common/changelog/drop-index-pgsql-diff.xml20
-rw-r--r--odb-tests/common/changelog/drop-index-pgsql-patch.xml13
-rw-r--r--odb-tests/common/changelog/drop-index-sqlite-diff.xml20
-rw-r--r--odb-tests/common/changelog/drop-index-sqlite-patch.xml13
-rw-r--r--odb-tests/common/changelog/drop-index.hxx21
-rw-r--r--odb-tests/common/changelog/drop-table-mssql-diff.xml47
-rw-r--r--odb-tests/common/changelog/drop-table-mssql-patch.xml13
-rw-r--r--odb-tests/common/changelog/drop-table-mysql-diff.xml47
-rw-r--r--odb-tests/common/changelog/drop-table-mysql-patch.xml13
-rw-r--r--odb-tests/common/changelog/drop-table-oracle-diff.xml47
-rw-r--r--odb-tests/common/changelog/drop-table-oracle-patch.xml13
-rw-r--r--odb-tests/common/changelog/drop-table-pgsql-diff.xml47
-rw-r--r--odb-tests/common/changelog/drop-table-pgsql-patch.xml13
-rw-r--r--odb-tests/common/changelog/drop-table-sqlite-diff.xml47
-rw-r--r--odb-tests/common/changelog/drop-table-sqlite-patch.xml13
-rw-r--r--odb-tests/common/changelog/drop-table.hxx34
l---------odb-tests/common/changelog/model-mssql-diff.xml1
l---------odb-tests/common/changelog/model-mssql-patch.xml1
-rw-r--r--odb-tests/common/changelog/model-mssql.xml56
l---------odb-tests/common/changelog/model-mysql-diff.xml1
l---------odb-tests/common/changelog/model-mysql-patch.xml1
-rw-r--r--odb-tests/common/changelog/model-mysql.xml56
l---------odb-tests/common/changelog/model-oracle-diff.xml1
l---------odb-tests/common/changelog/model-oracle-patch.xml1
-rw-r--r--odb-tests/common/changelog/model-oracle.xml56
l---------odb-tests/common/changelog/model-pgsql-diff.xml1
l---------odb-tests/common/changelog/model-pgsql-patch.xml1
-rw-r--r--odb-tests/common/changelog/model-pgsql.xml56
l---------odb-tests/common/changelog/model-sqlite-diff.xml1
l---------odb-tests/common/changelog/model-sqlite-patch.xml1
-rw-r--r--odb-tests/common/changelog/model-sqlite.xml56
-rw-r--r--odb-tests/common/changelog/model.hxx46
-rw-r--r--odb-tests/common/changelog/testscript66
-rw-r--r--odb-tests/common/circular/multiple/.gitignore6
-rw-r--r--odb-tests/common/circular/multiple/buildfile49
-rw-r--r--odb-tests/common/circular/multiple/driver.cxx69
-rw-r--r--odb-tests/common/circular/multiple/test1.hxx27
-rw-r--r--odb-tests/common/circular/multiple/test2.hxx17
-rw-r--r--odb-tests/common/circular/multiple/testscript31
-rw-r--r--odb-tests/common/circular/single/buildfile41
-rw-r--r--odb-tests/common/circular/single/driver.cxx40
-rw-r--r--odb-tests/common/circular/single/test.hxx28
-rw-r--r--odb-tests/common/circular/single/testscript33
-rw-r--r--odb-tests/common/composite/buildfile41
-rw-r--r--odb-tests/common/composite/driver.cxx229
-rw-r--r--odb-tests/common/composite/test.hxx250
-rw-r--r--odb-tests/common/composite/testscript33
-rw-r--r--odb-tests/common/const-member/buildfile40
-rw-r--r--odb-tests/common/const-member/driver.cxx119
-rw-r--r--odb-tests/common/const-member/test.hxx109
-rw-r--r--odb-tests/common/const-member/testscript33
-rw-r--r--odb-tests/common/const-object/buildfile41
-rw-r--r--odb-tests/common/const-object/driver.cxx216
-rw-r--r--odb-tests/common/const-object/test.hxx51
-rw-r--r--odb-tests/common/const-object/testscript33
-rw-r--r--odb-tests/common/container/basics/buildfile40
-rw-r--r--odb-tests/common/container/basics/driver.cxx523
-rw-r--r--odb-tests/common/container/basics/test.hxx245
-rw-r--r--odb-tests/common/container/basics/testscript33
-rw-r--r--odb-tests/common/container/change-tracking/buildfile40
-rw-r--r--odb-tests/common/container/change-tracking/driver.cxx729
-rw-r--r--odb-tests/common/container/change-tracking/test.hxx106
-rw-r--r--odb-tests/common/container/change-tracking/testscript33
-rw-r--r--odb-tests/common/ctor/buildfile41
-rw-r--r--odb-tests/common/ctor/driver.cxx79
-rw-r--r--odb-tests/common/ctor/test.hxx29
-rw-r--r--odb-tests/common/ctor/testscript33
-rw-r--r--odb-tests/common/default/buildfile41
-rw-r--r--odb-tests/common/default/driver.cxx81
-rw-r--r--odb-tests/common/default/test.hxx67
-rw-r--r--odb-tests/common/default/testscript33
-rw-r--r--odb-tests/common/definition/.gitignore6
-rw-r--r--odb-tests/common/definition/buildfile52
-rw-r--r--odb-tests/common/definition/driver.cxx56
-rw-r--r--odb-tests/common/definition/test.hxx26
-rw-r--r--odb-tests/common/definition/testscript33
-rw-r--r--odb-tests/common/definition/time-mapping.hxx17
-rw-r--r--odb-tests/common/enum/buildfile41
-rw-r--r--odb-tests/common/enum/driver.cxx84
-rw-r--r--odb-tests/common/enum/test.hxx47
-rw-r--r--odb-tests/common/enum/testscript33
-rw-r--r--odb-tests/common/erase-query/buildfile41
-rw-r--r--odb-tests/common/erase-query/driver.cxx181
-rw-r--r--odb-tests/common/erase-query/test.hxx46
-rw-r--r--odb-tests/common/erase-query/testscript33
-rw-r--r--odb-tests/common/id/auto/buildfile40
-rw-r--r--odb-tests/common/id/auto/driver.cxx96
-rw-r--r--odb-tests/common/id/auto/test.hxx40
-rw-r--r--odb-tests/common/id/auto/testscript33
-rw-r--r--odb-tests/common/id/composite/buildfile42
-rw-r--r--odb-tests/common/id/composite/driver.cxx731
-rw-r--r--odb-tests/common/id/composite/test.hxx519
-rw-r--r--odb-tests/common/id/composite/testscript33
-rw-r--r--odb-tests/common/id/nested/buildfile41
-rw-r--r--odb-tests/common/id/nested/driver.cxx266
-rw-r--r--odb-tests/common/id/nested/test.hxx217
-rw-r--r--odb-tests/common/id/nested/testscript33
-rw-r--r--odb-tests/common/include/.gitignore17
-rw-r--r--odb-tests/common/include/buildfile51
-rw-r--r--odb-tests/common/include/driver.cxx42
-rw-r--r--odb-tests/common/include/obj1.hxx25
-rw-r--r--odb-tests/common/include/obj2.hxx25
-rw-r--r--odb-tests/common/include/obj3.hxx25
-rw-r--r--odb-tests/common/include/objs1.hxx13
-rw-r--r--odb-tests/common/include/objs2.hxx13
-rw-r--r--odb-tests/common/include/objs3.hxx11
-rw-r--r--odb-tests/common/include/objs4.hxx11
-rw-r--r--odb-tests/common/include/test1.hxx16
-rw-r--r--odb-tests/common/include/test2.hxx15
-rw-r--r--odb-tests/common/include/test3.hxx12
-rw-r--r--odb-tests/common/include/test4.hxx12
-rw-r--r--odb-tests/common/include/testscript31
-rw-r--r--odb-tests/common/index/buildfile40
-rw-r--r--odb-tests/common/index/driver.cxx44
-rw-r--r--odb-tests/common/index/test.hxx142
-rw-r--r--odb-tests/common/index/testscript33
-rw-r--r--odb-tests/common/inheritance/polymorphism/.gitignore76
-rw-r--r--odb-tests/common/inheritance/polymorphism/buildfile52
-rw-r--r--odb-tests/common/inheritance/polymorphism/driver.cxx2093
-rw-r--r--odb-tests/common/inheritance/polymorphism/test1.hxx115
-rw-r--r--odb-tests/common/inheritance/polymorphism/test10.hxx78
-rw-r--r--odb-tests/common/inheritance/polymorphism/test11.hxx78
-rw-r--r--odb-tests/common/inheritance/polymorphism/test12.hxx79
-rw-r--r--odb-tests/common/inheritance/polymorphism/test13.hxx46
-rw-r--r--odb-tests/common/inheritance/polymorphism/test14.hxx99
-rw-r--r--odb-tests/common/inheritance/polymorphism/test15.hxx44
-rw-r--r--odb-tests/common/inheritance/polymorphism/test2.hxx105
-rw-r--r--odb-tests/common/inheritance/polymorphism/test3.hxx146
-rw-r--r--odb-tests/common/inheritance/polymorphism/test4.hxx84
-rw-r--r--odb-tests/common/inheritance/polymorphism/test5.hxx92
-rw-r--r--odb-tests/common/inheritance/polymorphism/test6.hxx64
-rw-r--r--odb-tests/common/inheritance/polymorphism/test7.hxx54
-rw-r--r--odb-tests/common/inheritance/polymorphism/test8.hxx129
-rw-r--r--odb-tests/common/inheritance/polymorphism/test9.hxx161
-rw-r--r--odb-tests/common/inheritance/polymorphism/testscript80
-rw-r--r--odb-tests/common/inheritance/reuse/buildfile41
-rw-r--r--odb-tests/common/inheritance/reuse/driver.cxx237
-rw-r--r--odb-tests/common/inheritance/reuse/test.hxx163
-rw-r--r--odb-tests/common/inheritance/reuse/testscript33
-rw-r--r--odb-tests/common/inheritance/transient/buildfile41
-rw-r--r--odb-tests/common/inheritance/transient/driver.cxx80
-rw-r--r--odb-tests/common/inheritance/transient/test.hxx60
-rw-r--r--odb-tests/common/inheritance/transient/testscript33
-rw-r--r--odb-tests/common/inverse/buildfile42
-rw-r--r--odb-tests/common/inverse/driver.cxx502
-rw-r--r--odb-tests/common/inverse/test.hxx391
-rw-r--r--odb-tests/common/inverse/testscript33
-rw-r--r--odb-tests/common/lazy-ptr/buildfile41
-rw-r--r--odb-tests/common/lazy-ptr/driver.cxx360
-rw-r--r--odb-tests/common/lazy-ptr/test.hxx147
-rw-r--r--odb-tests/common/lazy-ptr/testscript33
-rw-r--r--odb-tests/common/lifecycle/buildfile40
-rw-r--r--odb-tests/common/lifecycle/driver.cxx248
-rw-r--r--odb-tests/common/lifecycle/test.hxx27
-rw-r--r--odb-tests/common/lifecycle/testscript33
-rw-r--r--odb-tests/common/no-id/buildfile41
-rw-r--r--odb-tests/common/no-id/driver.cxx102
-rw-r--r--odb-tests/common/no-id/test.hxx21
-rw-r--r--odb-tests/common/no-id/testscript33
-rw-r--r--odb-tests/common/object/buildfile41
-rw-r--r--odb-tests/common/object/driver.cxx84
-rw-r--r--odb-tests/common/object/test.hxx49
-rw-r--r--odb-tests/common/object/testscript33
-rw-r--r--odb-tests/common/optimistic/buildfile41
-rw-r--r--odb-tests/common/optimistic/driver.cxx300
-rw-r--r--odb-tests/common/optimistic/test.hxx76
-rw-r--r--odb-tests/common/optimistic/testscript33
-rw-r--r--odb-tests/common/pragma/buildfile39
-rw-r--r--odb-tests/common/pragma/driver.cxx27
-rw-r--r--odb-tests/common/pragma/test.hxx40
-rw-r--r--odb-tests/common/pragma/testscript31
-rw-r--r--odb-tests/common/prepared/buildfile43
-rw-r--r--odb-tests/common/prepared/driver.cxx444
-rw-r--r--odb-tests/common/prepared/test.hxx32
-rw-r--r--odb-tests/common/prepared/testscript33
-rw-r--r--odb-tests/common/query/array/buildfile43
-rw-r--r--odb-tests/common/query/array/driver.cxx220
-rw-r--r--odb-tests/common/query/array/test.hxx70
-rw-r--r--odb-tests/common/query/array/testscript33
-rw-r--r--odb-tests/common/query/basics/buildfile42
-rw-r--r--odb-tests/common/query/basics/driver.cxx668
-rw-r--r--odb-tests/common/query/basics/test.hxx117
-rw-r--r--odb-tests/common/query/basics/testscript150
-rw-r--r--odb-tests/common/query/one/buildfile42
-rw-r--r--odb-tests/common/query/one/driver.cxx205
-rw-r--r--odb-tests/common/query/one/test.hxx26
-rw-r--r--odb-tests/common/query/one/testscript33
-rw-r--r--odb-tests/common/readonly/buildfile40
-rw-r--r--odb-tests/common/readonly/driver.cxx324
-rw-r--r--odb-tests/common/readonly/test.hxx225
-rw-r--r--odb-tests/common/readonly/testscript33
-rw-r--r--odb-tests/common/relationship/basics/buildfile41
-rw-r--r--odb-tests/common/relationship/basics/driver.cxx150
-rw-r--r--odb-tests/common/relationship/basics/test.hxx260
-rw-r--r--odb-tests/common/relationship/basics/testscript33
-rw-r--r--odb-tests/common/relationship/on-delete/buildfile43
-rw-r--r--odb-tests/common/relationship/on-delete/driver.cxx82
-rw-r--r--odb-tests/common/relationship/on-delete/test.hxx58
-rw-r--r--odb-tests/common/relationship/on-delete/testscript33
-rw-r--r--odb-tests/common/relationship/query/buildfile42
-rw-r--r--odb-tests/common/relationship/query/driver.cxx168
-rw-r--r--odb-tests/common/relationship/query/test.hxx140
-rw-r--r--odb-tests/common/relationship/query/testscript33
-rw-r--r--odb-tests/common/schema/embedded/basics/buildfile42
-rw-r--r--odb-tests/common/schema/embedded/basics/driver.cxx59
-rw-r--r--odb-tests/common/schema/embedded/basics/test.hxx23
-rw-r--r--odb-tests/common/schema/embedded/basics/testscript31
-rw-r--r--odb-tests/common/schema/embedded/order/.gitignore6
-rw-r--r--odb-tests/common/schema/embedded/order/buildfile48
-rw-r--r--odb-tests/common/schema/embedded/order/driver.cxx65
-rw-r--r--odb-tests/common/schema/embedded/order/test1.hxx23
-rw-r--r--odb-tests/common/schema/embedded/order/test2.hxx17
-rw-r--r--odb-tests/common/schema/embedded/order/testscript31
-rw-r--r--odb-tests/common/schema/namespace/buildfile41
-rw-r--r--odb-tests/common/schema/namespace/driver.cxx113
-rw-r--r--odb-tests/common/schema/namespace/test.hxx158
-rw-r--r--odb-tests/common/schema/namespace/testscript33
-rw-r--r--odb-tests/common/section/basics/buildfile41
-rw-r--r--odb-tests/common/section/basics/driver.cxx1735
-rw-r--r--odb-tests/common/section/basics/test.hxx628
-rw-r--r--odb-tests/common/section/basics/testscript33
-rw-r--r--odb-tests/common/section/polymorphism/buildfile41
-rw-r--r--odb-tests/common/section/polymorphism/driver.cxx1807
-rw-r--r--odb-tests/common/section/polymorphism/test.hxx542
-rw-r--r--odb-tests/common/section/polymorphism/testscript33
-rw-r--r--odb-tests/common/session/cache/buildfile41
-rw-r--r--odb-tests/common/session/cache/driver.cxx83
-rw-r--r--odb-tests/common/session/cache/test.hxx50
-rw-r--r--odb-tests/common/session/cache/testscript33
-rw-r--r--odb-tests/common/session/custom/buildfile43
-rw-r--r--odb-tests/common/session/custom/driver.cxx231
-rw-r--r--odb-tests/common/session/custom/session.cxx57
-rw-r--r--odb-tests/common/session/custom/session.hxx191
-rw-r--r--odb-tests/common/session/custom/session.txx159
-rw-r--r--odb-tests/common/session/custom/test.hxx118
-rw-r--r--odb-tests/common/session/custom/testscript33
-rw-r--r--odb-tests/common/statement/processing/buildfile8
-rw-r--r--odb-tests/common/statement/processing/driver.cxx619
-rw-r--r--odb-tests/common/statement/processing/testscript6
-rw-r--r--odb-tests/common/threads/buildfile49
-rw-r--r--odb-tests/common/threads/driver.cxx236
-rw-r--r--odb-tests/common/threads/test.hxx29
-rw-r--r--odb-tests/common/threads/testscript50
-rw-r--r--odb-tests/common/transaction/basics/buildfile13
-rw-r--r--odb-tests/common/transaction/basics/driver.cxx151
-rw-r--r--odb-tests/common/transaction/basics/testscript62
-rw-r--r--odb-tests/common/transaction/callback/buildfile13
-rw-r--r--odb-tests/common/transaction/callback/driver.cxx231
-rw-r--r--odb-tests/common/transaction/callback/testscript72
-rw-r--r--odb-tests/common/types/buildfile39
-rw-r--r--odb-tests/common/types/driver.cxx37
-rw-r--r--odb-tests/common/types/test.hxx55
-rw-r--r--odb-tests/common/types/testscript6
-rw-r--r--odb-tests/common/view/basics/buildfile42
-rw-r--r--odb-tests/common/view/basics/driver.cxx846
-rw-r--r--odb-tests/common/view/basics/test.hxx640
-rw-r--r--odb-tests/common/view/basics/testscript33
-rw-r--r--odb-tests/common/view/olv/.gitignore46
-rw-r--r--odb-tests/common/view/olv/buildfile50
-rw-r--r--odb-tests/common/view/olv/driver.cxx654
-rw-r--r--odb-tests/common/view/olv/test1.hxx116
-rw-r--r--odb-tests/common/view/olv/test2.hxx122
-rw-r--r--odb-tests/common/view/olv/test3.hxx106
-rw-r--r--odb-tests/common/view/olv/test4.hxx151
-rw-r--r--odb-tests/common/view/olv/test5.hxx86
-rw-r--r--odb-tests/common/view/olv/test6.hxx57
-rw-r--r--odb-tests/common/view/olv/test7.hxx57
-rw-r--r--odb-tests/common/view/olv/test8.hxx54
-rw-r--r--odb-tests/common/view/olv/test9.hxx78
-rw-r--r--odb-tests/common/view/olv/testscript39
-rw-r--r--odb-tests/common/virtual/buildfile42
-rw-r--r--odb-tests/common/virtual/driver.cxx154
-rw-r--r--odb-tests/common/virtual/test.hxx171
-rw-r--r--odb-tests/common/virtual/testscript33
-rw-r--r--odb-tests/common/wrapper/buildfile40
-rw-r--r--odb-tests/common/wrapper/driver.cxx216
-rw-r--r--odb-tests/common/wrapper/test.hxx214
-rw-r--r--odb-tests/common/wrapper/testscript33
385 files changed, 37493 insertions, 0 deletions
diff --git a/odb-tests/common/access/buildfile b/odb-tests/common/access/buildfile
new file mode 100644
index 0000000..f1264d9
--- /dev/null
+++ b/odb-tests/common/access/buildfile
@@ -0,0 +1,45 @@
+# file : common/access/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 access_ \
+ --generate-schema \
+ --accessor-regex '#(.+)#Get\u\1#' \
+ --modifier-regex '#(.+)#Set\u\1#'
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+# @@ BUILD2: Eventually we should be able to mark it as test.input once
+# this is supported for testscript tests.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/access/driver.cxx b/odb-tests/common/access/driver.cxx
new file mode 100644
index 0000000..b40e73c
--- /dev/null
+++ b/odb-tests/common/access/driver.cxx
@@ -0,0 +1,262 @@
+// file : common/access/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test accessor/modifier expressions.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ // Test basic accessor/modifier functionality.
+ //
+ {
+ using namespace test1;
+
+ object o (1, 623, 723);
+ o.i1 () = 123;
+ o.i2 (223);
+ o.i3 () = 323;
+ o.i4 () = 423;
+ o.set_i5 (523);
+ o.s1 ("1bc");
+ memcpy (o.b1 (), "123456789012345", 16);
+ o.b2 ("123456789012345");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id ()));
+ t.commit ();
+
+ assert (o == *p);
+ }
+ }
+
+ // Test composite accessor/modifier functionality.
+ //
+ {
+ using namespace test2;
+
+ object o (1);
+
+ o.v1 () = value (1123, 1234);
+ o.v2 (value (2123, 2234));
+ o.v3_i1 (3123);
+ o.v3_i2 (3223);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id ()));
+ t.commit ();
+
+ assert (o == *p);
+ }
+ }
+
+ // Test object pointer accessor/modifier functionality.
+ //
+ {
+ using namespace test3;
+
+ object2 o (1);
+ o.p1 ().reset (new object1 (1));
+ o.p2 (object1_ptr (new object1 (2)));
+
+ {
+ transaction t (db->begin ());
+ const object1_ptr& ptr (o.p1 ());
+ db->persist (ptr);
+ db->persist (o.p2 ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object2> p (db->load<object2> (o.id ()));
+ t.commit ();
+
+ assert (p->p1 ()->id () == o.p1 ()->id () &&
+ p->p2 ()->id () == o.p2 ()->id ());
+ }
+ }
+
+ // Test container accessor/modifier functionality.
+ //
+ {
+ using namespace test4;
+
+ object o (1);
+ o.c1 ().push_back (1123);
+ o.c1 ().push_back (1124);
+ o.c1 ().push_back (1125);
+
+ {
+ std::vector<int> v;
+ v.push_back (2123);
+ v.push_back (2124);
+ v.push_back (2125);
+ o.c2 (v);
+ }
+
+ o.v1 ().c1 ().push_back (1123);
+ o.v1 ().c1 ().push_back (1124);
+ o.v1 ().c1 ().push_back (1125);
+
+ {
+ std::vector<int> v;
+ v.push_back (2123);
+ v.push_back (2124);
+ v.push_back (2125);
+ o.v1 ().c2 (v);
+ }
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id ()));
+ t.commit ();
+
+ assert (o == *p);
+ }
+ }
+
+ // Test id accessor/modifier functionality.
+ //
+ {
+ using namespace test5;
+
+ object1 o1;
+ object2 o2;
+ object3 o3;
+ object4 o4;
+ o4.id (uuid ("\x60\x1D\x17\xF0-\x60\x05-\x47\x23-\x95\x37-"
+ "\xC1\xF8\x94\x41\x2B\xEC"));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ db->persist (o3);
+ db->persist (o4);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object1> p1 (db->load<object1> (o1.id ()));
+ unique_ptr<object2> p2 (db->load<object2> (o2.id ()));
+ unique_ptr<object3> p3 (db->load<object3> (o3.id_));
+ unique_ptr<object4> p4 (db->load<object4> (o4.id ()));
+ t.commit ();
+ }
+ }
+
+ // Test version accessor/modifier functionality.
+ //
+ {
+ using namespace test6;
+
+ object1 o1 (1);
+ object2 o2;
+ object3 o3 (1);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ db->persist (o3);
+ t.commit ();
+
+ assert (o1.version () == 1);
+ assert (o2.version () == 1);
+ assert (o3.version_ == 1);
+ }
+
+ {
+ transaction t (db->begin ());
+ db->update (o1);
+ db->update (o2);
+ db->update (o3);
+ t.commit ();
+
+ assert (o1.version () == 2);
+ assert (o2.version () == 2);
+ assert (o3.version_ == 2);
+ }
+ }
+
+ // Test basic accessor/modifier functionality.
+ //
+ {
+ using namespace test7;
+
+ object o (1);
+ o.i1 () = 123;
+ o.set_i2 (223);
+ o.setI3 (323);
+ o.seti4 (423);
+ o.i5 () = 523;
+ o.i6 () = 623;
+ o.SetI7 (723);
+ memcpy (o.b1 (), "123456789012345", 16);
+ o.b2 ("123456789012345");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id_));
+ t.commit ();
+
+ assert (o == *p);
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/access/test.hxx b/odb-tests/common/access/test.hxx
new file mode 100644
index 0000000..3a3424d
--- /dev/null
+++ b/odb-tests/common/access/test.hxx
@@ -0,0 +1,592 @@
+// file : common/access/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <vector>
+#include <cstring> // std::memcpy, std::memcmp, std::memset
+#include <memory> // std::unique_ptr
+#include <utility> // std::move
+
+#include <odb/core.hxx>
+
+#ifdef ODB_COMPILER
+# if defined(ODB_DATABASE_MYSQL)
+# define BINARY16_TYPE "BINARY(16)"
+# elif defined(ODB_DATABASE_SQLITE)
+# define BINARY16_TYPE "BLOB"
+# elif defined(ODB_DATABASE_PGSQL)
+# define BINARY16_TYPE "BYTEA"
+# elif defined(ODB_DATABASE_ORACLE)
+# define BINARY16_TYPE "RAW(16)"
+# elif defined(ODB_DATABASE_MSSQL)
+# define BINARY16_TYPE "BINARY(16)"
+# elif defined(ODB_DATABASE_COMMON)
+# define BINARY16_TYPE ""
+# else
+# error unknown database
+# endif
+#endif
+
+// Test basic accessor/modifier functionality.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ #pragma db object
+ struct object
+ {
+ object (): i6_ (0), i7_ (0) {}
+ object (unsigned long id, int i6, int i7): id_ (id), i6_ (i6), i7_ (i7) {}
+
+ public:
+ unsigned long id () const {return id_;}
+ void id (unsigned long id) {id_ = id;}
+ private:
+ #pragma db id access(id)
+ unsigned long id_;
+
+ public:
+ int i1 () const {return i1_;}
+ int& i1 () {return i1_;}
+ private:
+ #pragma db access(i1)
+ int i1_;
+
+ public:
+ int i2 () const {return i2_;}
+ void i2 (int i2) {i2_ = i2;}
+ private:
+ #pragma db access(i2)
+ int i2_;
+
+ // Prefer reference modifier.
+ //
+ public:
+ int i3 () const {return i3_;}
+ int& i3 () {return i3_;}
+ void i3 (int i3);
+ private:
+ #pragma db access(i3)
+ int i3_;
+
+ // Prefer reference modifier (reverse function order).
+ //
+ public:
+ int i4 () const {return i4_;}
+ void i4 (int i4);
+ int& i4 () {return i4_;}
+ private:
+ #pragma db access(i4)
+ int i4_;
+
+ public:
+ int get_i5 () const {return i5_;}
+ void set_i5 (int i5) {i5_ = i5;}
+ private:
+ #pragma db get(get_i5) set(set_i5)
+ int i5_;
+
+ // Const member via reference.
+ //
+ public:
+ const int& i6 () const {return i6_;}
+ private:
+ #pragma db get(i6) set(const_cast<int&> (this.i6 ()))
+ const int i6_;
+
+ // Const member via modifier.
+ //
+ public:
+ int i7 () const {return i7_;}
+ void i7 (int i7) const {const_cast<int&> (i7_) = i7;}
+ private:
+ #pragma db access(i7)
+ const int i7_;
+
+ public:
+ const char* s1 () const {return s1_.c_str ();}
+ void s1 (const char* s1) {s1_ = s1;}
+ //std::string s1 () const {return s1_;}
+ //void s1 (std::string s1) {s1_ = s1;}
+ private:
+ #pragma db get(s1) set(s1((?).c_str ()))
+ //#pragma db access(s1)
+ std::string s1_;
+
+ // Array member via ref.
+ //
+ public:
+ const char* b1 () const {return b1_;}
+ char* b1 () {return b1_;}
+ private:
+ #pragma db type(BINARY16_TYPE) access(b1)
+ char b1_[16];
+
+ // Array member via modifier.
+ //
+ public:
+ const char* b2 () const {return b2_;}
+ void b2 (const char* b2) {std::memcpy (b2_, b2, sizeof (b2_));}
+ private:
+ #pragma db type(BINARY16_TYPE) access(b2)
+ char b2_[16];
+
+ public:
+ bool operator== (const object& o) const
+ {
+ return id_ == o.id_ &&
+ i1_ == o.i1_ &&
+ i2_ == o.i2_ &&
+ i3_ == o.i3_ &&
+ i4_ == o.i4_ &&
+ i5_ == o.i5_ &&
+ i6_ == o.i6_ &&
+ i7_ == o.i7_ &&
+ s1_ == o.s1_ &&
+ std::memcmp (b1_, o.b1_, sizeof (b1_)) == 0 &&
+ std::memcmp (b2_, o.b2_, sizeof (b2_)) == 0;
+ }
+ };
+}
+
+// Test composite accessor/modifier functionality.
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ #pragma db value
+ struct value
+ {
+ value () {}
+ value (int i1, int i2): i1_ (i1), i2_ (i2) {}
+
+ bool operator== (const value& v) const
+ {
+ return i1_ == v.i1_ && i2_ == v.i2_;
+ }
+
+ public:
+ int i1 () const {return i1_;}
+ int& i1 () {return i1_;}
+ private:
+ #pragma db access(i1)
+ int i1_;
+
+ public:
+ int i2 () const {return i2_;}
+ void i2 (int i2) {i2_ = i2;}
+ private:
+ #pragma db access(i2)
+ int i2_;
+ };
+
+ #pragma db object
+ struct object
+ {
+ object () {}
+ object (unsigned long id): id_ (id) {}
+
+ bool operator== (const object& o) const
+ {
+ return id_ == o.id_ &&
+ v1_ == o.v1_ &&
+ v2_ == o.v2_ &&
+ v3_ == o.v3_;
+ }
+
+ public:
+ unsigned long id () const {return id_;}
+ void id (unsigned long id) {id_ = id;}
+ private:
+ #pragma db id access(id)
+ unsigned long id_;
+
+ public:
+ const value& v1 () const {return v1_;}
+ value& v1 () {return v1_;}
+ private:
+ #pragma db access(v1)
+ value v1_;
+
+ public:
+ const value& v2 () const {return v2_;}
+ void v2 (const value& v2) {v2_ = v2;}
+ private:
+ #pragma db access(v2)
+ value v2_;
+
+ public:
+ int v3_i1 () const {return v3_.i1 ();}
+ int v3_i2 () const {return v3_.i2 ();}
+ void v3_i1 (int i1) {v3_.i1 () = i1;}
+ void v3_i2 (int i2) {v3_.i2 (i2);}
+ private:
+ #pragma db get(test2::value (this.v3_i1 (), this.v3_i2 ())) \
+ set(this.v3_i1 ((?).i1 ()); this.v3_i2 ((?).i2 ()))
+ value v3_;
+ };
+}
+
+// Test object pointer accessor/modifier functionality.
+//
+#pragma db namespace table("t3_")
+namespace test3
+{
+ struct object1;
+
+ typedef std::unique_ptr<object1> object1_ptr;
+
+ #pragma db object pointer(object1_ptr)
+ struct object1
+ {
+ object1 () {}
+ object1 (unsigned long id): id_ (id) {}
+
+ public:
+ unsigned long id () const {return id_;}
+ void id (unsigned long id) {id_ = id;}
+ private:
+ #pragma db id access(id)
+ unsigned long id_;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 () {}
+ object2 (unsigned long id): id_ (id) {}
+
+ public:
+ unsigned long id () const {return id_;}
+ void id (unsigned long id) {id_ = id;}
+ private:
+ #pragma db id access(id)
+ unsigned long id_;
+
+ public:
+ const object1_ptr& p1 () const {return p1_;}
+ object1_ptr& p1 () {return p1_;}
+ private:
+ #pragma db access(p1)
+ object1_ptr p1_;
+
+ public:
+ const object1_ptr& p2 () const {return p2_;}
+
+ void p2 (object1_ptr p2) {p2_ = std::move (p2);}
+
+ private:
+ #pragma db get(p2) set(p2 (std::move (?)))
+
+ object1_ptr p2_;
+ };
+}
+
+// Test container accessor/modifier functionality.
+//
+#pragma db namespace table("t4_")
+namespace test4
+{
+ #pragma db value
+ struct value
+ {
+ value (): c3_ (3, 999) {}
+ value (int v): c1_ (3, v), c2_ (3, v + 1), c3_ (3, v + 2) {}
+
+ bool operator== (const value& v) const
+ {
+ return c1_ == v.c1_ && c2_ == v.c2_ && c3_ == v.c3_;
+ }
+
+ public:
+ const std::vector<int>& c1 () const {return c1_;}
+ std::vector<int>& c1 () {return c1_;}
+ private:
+ #pragma db access(c1)
+ std::vector<int> c1_;
+
+ public:
+ const std::vector<int>& c2 () const {return c2_;}
+ void c2 (const std::vector<int>& c2) {c2_ = c2;}
+ private:
+ #pragma db access(c2)
+ std::vector<int> c2_;
+
+ public:
+ const std::vector<int> c3_;
+ };
+
+ #pragma db object
+ struct object
+ {
+ object () {}
+ object (unsigned long id): id_ (id), c3_ (3, 3123), v2_ (2123) {}
+
+ bool operator== (const object& o) const
+ {
+ return id_ == o.id_ &&
+ c1_ == o.c1_ &&
+ c2_ == o.c2_ &&
+ c3_ == o.c3_ &&
+ v1_ == o.v1_ &&
+ v2_ == o.v2_;
+ }
+
+ public:
+ unsigned long id () const {return id_;}
+ void id (unsigned long id) {id_ = id;}
+ private:
+ #pragma db id access(id)
+ unsigned long id_;
+
+ public:
+ const std::vector<int>& c1 () const {return c1_;}
+ std::vector<int>& c1 () {return c1_;}
+ private:
+ #pragma db access(c1)
+ std::vector<int> c1_;
+
+ public:
+ const std::vector<int>& c2 () const {return c2_;}
+ void c2 (const std::vector<int>& c2) {c2_ = c2;}
+ private:
+ #pragma db access(c2)
+ std::vector<int> c2_;
+
+ public:
+ const std::vector<int>& c3 () const {return c3_;}
+ private:
+ #pragma db get(c3) set(const_cast<std::vector<int>&> (this.c3 ()))
+ const std::vector<int> c3_;
+
+ public:
+ const value& v1 () const {return v1_;}
+ value& v1 () {return v1_;}
+ private:
+ #pragma db access(v1)
+ value v1_;
+
+ public:
+ const value v2_;
+ };
+}
+
+// Test id accessor/modifier functionality.
+//
+#pragma db namespace table("t5_")
+namespace test5
+{
+ #pragma db object
+ struct object1
+ {
+ object1 (): id_ (0) {}
+
+ public:
+ unsigned long id () const {return id_;}
+ void id (unsigned long id) {id_ = id;}
+ private:
+ #pragma db id auto access(id)
+ unsigned long id_;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 (): id_ (0) {}
+
+ public:
+ unsigned long id () const {return id_;}
+ unsigned long& id () {return id_;}
+ private:
+ #pragma db id auto access(id)
+ unsigned long id_;
+ };
+
+ #pragma db object
+ struct object3
+ {
+ object3 (): id_ (0) {}
+
+ #pragma db id auto
+ const unsigned long id_;
+ };
+
+ #pragma db value
+ struct uuid
+ {
+ uuid () {std::memset (data_, 0, sizeof (data_));}
+ explicit uuid (const char* d) {data (d);}
+
+ public:
+ const char* data () const {return data_;}
+ void data (const char* d) {std::memcpy (data_, d, sizeof (data_));}
+ private:
+ #pragma db type(BINARY16_TYPE) column("") access(data)
+ char data_[16];
+ };
+
+ #pragma db object
+ struct object4
+ {
+ public:
+ const uuid& id () const {return id_;}
+ void id (const uuid& id) {id_ = id;}
+ private:
+ #pragma db id access(id)
+ uuid id_;
+ };
+}
+
+// Test version accessor/modifier functionality.
+//
+#pragma db namespace table("t6_")
+namespace test6
+{
+ #pragma db object optimistic
+ struct object1
+ {
+ object1 (unsigned long id = 0): id_ (id), version_ (0) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ public:
+ unsigned long version () const {return version_;}
+ void version (unsigned long version) {version_ = version;}
+ private:
+ #pragma db version access(version)
+ unsigned long version_;
+ };
+
+ #pragma db object optimistic
+ struct object2
+ {
+ object2 (): version_ (0) {}
+
+ #pragma db id auto
+ unsigned long id_;
+
+ public:
+ unsigned long version () const {return version_;}
+ unsigned long& version () {return version_;}
+ private:
+ #pragma db version access(version)
+ unsigned long version_;
+ };
+
+ #pragma db object optimistic
+ struct object3
+ {
+ object3 (unsigned long id = 0): id_ (id), version_ (0) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db version
+ const unsigned long version_;
+ };
+}
+
+// Test automatic discovery of accessor/modifier functions.
+//
+#pragma db namespace table("t7_")
+namespace test7
+{
+ #pragma db object
+ struct object
+ {
+ object () {}
+ object (unsigned long id): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ public:
+ int i1 () const {return i1_;}
+ int& i1 () {return i1_;}
+ private:
+ int i1_;
+
+ public:
+ const int& get_i2 () const {return i2_;}
+ void set_i2 (int i2) {i2_ = i2;}
+ private:
+ int i2_;
+
+ public:
+ const int& getI3 () const {return i3_;}
+ void setI3 (const int& i3) {i3_ = i3;}
+ private:
+ int i3_;
+
+ public:
+ int geti4 () const {return i4;}
+ int seti4 (int v) {int r (i4); i4 = v; return r;}
+ private:
+ int i4;
+
+ // Prefer reference modifier.
+ //
+ public:
+ int i5 () const {return i5_;}
+ int& i5 () {return i5_;}
+ void i5 (int i5);
+ private:
+ int i5_;
+
+ // Prefer reference modifier (reverse function order).
+ //
+ public:
+ int i6 () const {return i6_;}
+ void i6 (int i6);
+ int& i6 () {return i6_;}
+ private:
+ int i6_;
+
+ // Custom accessor/modifier regex.
+ //
+ public:
+ int GetI7 () const {return i7_;}
+ void SetI7 (int i7) {i7_ = i7;}
+ private:
+ int i7_;
+
+ // Array member via ref.
+ //
+ public:
+ const char* b1 () const {return b1_;}
+ char* b1 () {return b1_;}
+ private:
+ #pragma db type(BINARY16_TYPE)
+ char b1_[16];
+
+ // Array member via modifier.
+ //
+ public:
+ const char* b2 () const {return b2_;}
+ void b2 (const char* b2) {std::memcpy (b2_, b2, sizeof (b2_));}
+ private:
+ #pragma db type(BINARY16_TYPE)
+ char b2_[16];
+
+ public:
+ bool operator== (const object& o) const
+ {
+ return id_ == o.id_ &&
+ i1_ == o.i1_ &&
+ i2_ == o.i2_ &&
+ i3_ == o.i3_ &&
+ i4 == o.i4 &&
+ i5_ == o.i5_ &&
+ i6_ == o.i6_ &&
+ i7_ == o.i7_ &&
+ std::memcmp (b1_, o.b1_, sizeof (b1_)) == 0 &&
+ std::memcmp (b2_, o.b2_, sizeof (b2_)) == 0;
+ }
+ };
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/access/testscript b/odb-tests/common/access/testscript
new file mode 100644
index 0000000..04f0ec0
--- /dev/null
+++ b/odb-tests/common/access/testscript
@@ -0,0 +1,33 @@
+# file : common/access/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/as/buildfile b/odb-tests/common/as/buildfile
new file mode 100644
index 0000000..dcdc961
--- /dev/null
+++ b/odb-tests/common/as/buildfile
@@ -0,0 +1,49 @@
+# file : common/as/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 as_ \
+ --generate-schema \
+ --generate-query \
+ --generate-session
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# @@ BUILD@ Temporarily suppress the following warning:
+#
+# test-odb.cxx(6234): warning C4244: 'argument': conversion from 'id_type::value_type' to 'test5::version_type::value_type', possible loss of data
+#
+if ($cxx.class == 'msvc')
+ cxx.coptions += /wd4244
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/as/driver.cxx b/odb-tests/common/as/driver.cxx
new file mode 100644
index 0000000..578eb23
--- /dev/null
+++ b/odb-tests/common/as/driver.cxx
@@ -0,0 +1,348 @@
+// file : common/as/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test C++ type mapping (#pragma map type as ...).
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/session.hxx>
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ // Test basic type mapping functionality.
+ //
+ {
+ using namespace test1;
+
+ object o1 (true, green, 123, 234);
+ o1.m[false] = 123;
+ o1.v.push_back (o1.ip);
+ o1.cv.push_back (red);
+ o1.cv.push_back (green);
+
+ object o2 (false, blue, 234, 456);
+ o2.m[true] = 234;
+ o2.v.push_back (o2.ip);
+ o2.cv.push_back (green);
+ o2.cv.push_back (blue);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id));
+ unique_ptr<object> p2 (db->load<object> (o2.id));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ }
+
+ o1.b = false;
+ o1.c = blue;
+ o1.ip.first++;
+ o1.ip.second--;
+ o1.m[false]++;
+ o1.m[true] = 234;
+ o1.v.back () = o1.ip;
+ o1.cv.modify_front () = green;
+ o1.cv.push_back (red);
+
+ o2.b = true;
+ o2.c = red;
+ o2.ip.first--;
+ o2.ip.second++;
+ o2.m[true]--;
+ o2.m[false] = 345;
+ o2.v.push_back (o2.ip);
+ o2.cv.pop_back ();
+
+ {
+ transaction t (db->begin ());
+ db->update (o1);
+ db->update (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id));
+ unique_ptr<object> p2 (db->load<object> (o2.id));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ }
+ }
+
+ // Test wrapped simple type mapping.
+ //
+ {
+ using namespace test2;
+
+ object o1;
+ o1.v.push_back (null_bool ());
+ o1.v.push_back (false);
+
+ object o2;
+ o2.b = true;
+ o2.v.push_back (true);
+ o2.v.push_back (null_bool ());
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id));
+ unique_ptr<object> p2 (db->load<object> (o2.id));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ }
+
+ o1.b = false;
+ o1.v[0] = true;
+ o1.v[1].reset ();
+
+ o2.b.reset ();
+ o2.v.push_back (false);
+
+ {
+ transaction t (db->begin ());
+ db->update (o1);
+ db->update (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id));
+ unique_ptr<object> p2 (db->load<object> (o2.id));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ }
+ }
+
+ // Test wrapped composite type mapping.
+ //
+ {
+ using namespace test3;
+
+ object o1;
+ o1.ip = intp (0, 0); // NULL
+ o1.npv.push_back (o1.np);
+ o1.ipv.push_back (o1.ip);
+
+ object o2;
+ o2.np = intp (123, 234);
+ o1.ip = intp (234, 123);
+ o2.npv.push_back (o2.np);
+ o2.ipv.push_back (o2.ip);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id));
+ unique_ptr<object> p2 (db->load<object> (o2.id));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ }
+
+ o1.np = o1.npv[0] = intp (234, 456);
+ o1.ip = o1.ipv.modify_at (0) = intp (456, 234);
+
+ o2.np.reset ();
+ o2.npv[0].reset ();
+ o2.ip = o2.ipv.modify_at (0) = intp (0, 0); // NULL
+
+ {
+ transaction t (db->begin ());
+ db->update (o1);
+ db->update (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id));
+ unique_ptr<object> p2 (db->load<object> (o2.id));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ }
+ }
+
+ // Test id type mapping.
+ //
+ {
+ using namespace test4;
+
+ object o1 (123);
+ o1.v.push_back (1);
+ o1.v.push_back (2);
+ o1.v.push_back (3);
+
+ object o2 (234);
+ o2.v.push_back (3);
+ o2.v.push_back (2);
+ o2.v.push_back (1);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id));
+ unique_ptr<object> p2 (db->load<object> (o2.id));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ }
+
+ o1.i++;
+ o1.v.pop_back ();
+ o1.v.modify_front ()++;
+
+ o2.i--;
+ o2.v.clear ();
+ o2.v.push_back (4);
+
+ {
+ transaction t (db->begin ());
+ db->update (o1);
+ db->update (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id));
+ unique_ptr<object> p2 (db->load<object> (o2.id));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ }
+ }
+
+ // Test version type mapping.
+ //
+ {
+ using namespace test5;
+
+ object o1 (100, 123);
+ object o2 (200, 234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id));
+ unique_ptr<object> p2 (db->load<object> (o2.id));
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+
+ p1->i--;
+ p2->i++;
+
+ db->update (*p1);
+ db->update (*p2);
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ for (;;)
+ {
+ o1.i++;
+ o2.i--;
+
+ try
+ {
+
+ db->update (o1);
+ db->update (o2);
+ break;
+ }
+ catch (const odb::object_changed&)
+ {
+ db->reload (o1);
+ db->reload (o2);
+ }
+ }
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id));
+ unique_ptr<object> p2 (db->load<object> (o2.id));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/as/test.hxx b/odb-tests/common/as/test.hxx
new file mode 100644
index 0000000..963abeb
--- /dev/null
+++ b/odb-tests/common/as/test.hxx
@@ -0,0 +1,270 @@
+// file : common/as/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <map>
+#include <vector>
+#include <string>
+#include <utility> // pair
+
+#include <odb/core.hxx>
+#include <odb/vector.hxx>
+#include <odb/nullable.hxx>
+
+// Test basic type mapping functionality.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ enum color {red, green, blue};
+
+ inline const char*
+ color_to_string (color c)
+ {
+ return c == red ? "RED" : (c == green ? "GREEN" : "BLUE");
+ }
+
+ inline color
+ string_to_color (const std::string& s)
+ {
+ return s == "RED" ? red : (s == "GREEN" ? green : blue);
+ }
+
+ #pragma db map type(color) as(std::string) \
+ to(test1::color_to_string (?)) \
+ from(test1::string_to_color (?))
+
+ typedef std::pair<int, int> intp;
+
+ #pragma db value
+ struct comp
+ {
+ comp () {}
+ comp (int n1_, int n2_): n1 (n1_), n2 (n2_) {}
+
+ int n1;
+ int n2;
+ };
+
+ #pragma db map type(intp) as(comp) \
+ to(test1::comp ((?).first, (?).second)) \
+ from(test1::intp ((?).n1, (?).n2))
+
+ #pragma db object
+ struct object
+ {
+ // Class-scope mapping.
+ //
+ #pragma db map type(bool) as(std::string) \
+ to((?) ? "true" : "false") \
+ from((?) == "true")
+
+ #pragma db id auto
+ unsigned long id;
+
+ bool b;
+ color c;
+ intp ip;
+
+ std::map<bool, int> m;
+ std::vector<intp> v;
+ odb::vector<color> cv;
+
+ object () {}
+ object (bool b_, color c_, int n1, int n2): b (b_), c (c_), ip (n1, n2) {}
+ };
+
+ inline bool
+ operator== (const object& x, const object y)
+ {
+ return
+ x.b == y.b &&
+ x.c == y.c &&
+ x.ip == y.ip &&
+ x.m == y.m &&
+ x.v == y.v &&
+ x.cv == y.cv;
+ }
+}
+
+// Test wrapped simple type mapping.
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ #pragma db map type(bool) as(std::string) \
+ to((?) ? "true" : "false") \
+ from((?) == "true")
+
+ typedef odb::nullable<bool> null_bool;
+ typedef odb::nullable<std::string> null_string;
+
+ /*
+ #pragma db map type(null_bool) as(null_string) \
+ to((?) \
+ ? test2::null_string (*(?) ? "true" : "false") \
+ : test2::null_string ()) \
+ from((?) \
+ ? test2::null_bool (*(?) == "true") \
+ : test2::null_bool ())
+ */
+
+ #pragma db map type(null_bool) as(std::string) \
+ to((?) ? (*(?) ? "true" : "false") : "null") \
+ from((?) != "null" \
+ ? test2::null_bool ((?) == "true") \
+ : test2::null_bool ())
+
+ #pragma db object
+ struct object
+ {
+ #pragma db id auto
+ unsigned long id;
+
+ odb::nullable<bool> b;
+ std::vector<odb::nullable<bool> > v;
+ };
+
+ inline bool
+ operator== (const object& x, const object y)
+ {
+ return x.b == y.b && x.v == y.v;
+ }
+}
+
+// Test wrapped simple type mapping.
+//
+#pragma db namespace table("t3_")
+namespace test3
+{
+ typedef std::pair<int, int> intp;
+
+ #pragma db value
+ struct comp
+ {
+ comp () {}
+ comp (int n1_, int n2_): n1 (n1_), n2 (n2_) {}
+
+ int n1;
+ int n2;
+ };
+
+ typedef odb::nullable<intp> null_intp;
+ typedef odb::nullable<comp> null_comp;
+
+ #pragma db map type(null_intp) as(null_comp) \
+ to((?) \
+ ? test3::null_comp (test3::comp ((?)->first, (?)->second)) \
+ : test3::null_comp ()) \
+ from((?) \
+ ? test3::null_intp (test3::intp ((?)->n1, (?)->n2)) \
+ : test3::null_intp ())
+
+ // Map int pair with both members equal 0 to NULL comp.
+ //
+ #pragma db map type(intp) as(null_comp) \
+ to((?).first != 0 || (?).second != 0 \
+ ? test3::null_comp (test3::comp ((?).first, (?).second)) \
+ : test3::null_comp ()) \
+ from((?) \
+ ? test3::intp (test3::intp ((?)->n1, (?)->n2)) \
+ : test3::intp (0, 0))
+
+ #pragma db object
+ struct object
+ {
+ #pragma db id auto
+ unsigned long id;
+
+ odb::nullable<intp> np;
+ intp ip;
+
+ std::vector<odb::nullable<intp> > npv;
+ odb::vector<intp> ipv;
+ };
+
+ inline bool
+ operator== (const object& x, const object y)
+ {
+ return x.np == y.np && x.ip == y.ip && x.npv == y.npv && x.ipv == y.ipv;
+ }
+}
+
+// Test id type mapping.
+//
+struct id_type
+{
+ typedef unsigned long value_type;
+ value_type value;
+
+ id_type (value_type v = 0): value (v) {}
+ operator value_type () const {return value;}
+};
+
+#pragma db map type(id_type) as(id_type::value_type)
+
+#pragma db namespace table("t4_")
+namespace test4
+{
+ #pragma db object
+ struct object
+ {
+ #pragma db id auto
+ id_type id;
+
+ int i;
+ odb::vector<int> v;
+
+ object () {}
+ object (int i_): i (i_) {}
+ };
+
+ inline bool
+ operator== (const object& x, const object y)
+ {
+ return x.id == y.id && x.i == y.i && x.v == y.v;
+ }
+}
+
+// Test version type mapping.
+//
+#pragma db namespace table("t5_")
+namespace test5
+{
+ struct version_type
+ {
+ typedef unsigned short value_type;
+ value_type value;
+
+ version_type (value_type v = 0): value (v) {}
+ operator value_type () const {return value;}
+ version_type& operator++ () {value++; return *this;}
+ };
+
+ #pragma db map type(version_type) as(id_type::value_type)
+
+ #pragma db object optimistic
+ struct object
+ {
+ #pragma db id
+ id_type id;
+
+ #pragma db version
+ version_type v;
+
+ int i;
+
+ object () {}
+ object (id_type id_, int i_): id (id_), i (i_) {}
+ };
+
+ inline bool
+ operator== (const object& x, const object y)
+ {
+ return x.id == y.id && x.v == y.v && x.i == y.i;
+ }
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/as/testscript b/odb-tests/common/as/testscript
new file mode 100644
index 0000000..12d9753
--- /dev/null
+++ b/odb-tests/common/as/testscript
@@ -0,0 +1,33 @@
+# file : common/as/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/blob/buildfile b/odb-tests/common/blob/buildfile
new file mode 100644
index 0000000..cc6d164
--- /dev/null
+++ b/odb-tests/common/blob/buildfile
@@ -0,0 +1,40 @@
+# file : common/blob/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 blob_ \
+ --generate-schema
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/blob/driver.cxx b/odb-tests/common/blob/driver.cxx
new file mode 100644
index 0000000..269f415
--- /dev/null
+++ b/odb-tests/common/blob/driver.cxx
@@ -0,0 +1,76 @@
+// file : common/blob/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test BLOB mapping.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ const char data[] =
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B"
+ "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B"
+ "cccccccccccccccccccccccccccccccccccccccccccccccc"
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B"
+ "dddddddddddddddddddddddddddddddddddddddddddddddd"
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B"
+ "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B"
+ "ffffffffffffffffffffffffffffffffffffffffffffffff";
+
+ const unsigned char* udata = reinterpret_cast<const unsigned char*> (data);
+
+ object o (1);
+ o.vc.assign (data, data + sizeof (data));
+ o.vuc.assign (udata, udata + sizeof (data));
+ memcpy (o.c, data, sizeof (data));
+ memcpy (o.uc, udata, sizeof (data));
+ memcpy (o.a.data (), data, sizeof (data));
+ memcpy (o.ua.data (), udata, sizeof (data));
+ o.cont.push_back (1);
+ o.cont.push_back (2);
+ o.cont.push_back (3);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o1 (db->load<object> (1));
+ t.commit ();
+
+ assert (o == *o1);
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/blob/test.hxx b/odb-tests/common/blob/test.hxx
new file mode 100644
index 0000000..9602ca2
--- /dev/null
+++ b/odb-tests/common/blob/test.hxx
@@ -0,0 +1,71 @@
+// file : common/blob/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <array>
+#include <vector>
+#include <cstring> // std::memcmp
+
+#include <odb/core.hxx>
+
+#ifdef ODB_COMPILER
+# if defined(ODB_DATABASE_PGSQL)
+# define BLOB_TYPE "BYTEA"
+# elif defined(ODB_DATABASE_MSSQL)
+//# define BLOB_TYPE "VARBINARY(1024)"
+# define BLOB_TYPE "VARBINARY(max)"
+# else
+//# define BLOB_TYPE "RAW(1024)"
+# define BLOB_TYPE "BLOB"
+# endif
+#endif
+
+#pragma db object
+struct object
+{
+ object () {}
+ object (unsigned long id): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db type(BLOB_TYPE)
+ std::vector<char> vc;
+
+ #pragma db type(BLOB_TYPE)
+ std::vector<unsigned char> vuc;
+
+ #pragma db type(BLOB_TYPE)
+ char c[1024];
+
+ #pragma db type(BLOB_TYPE)
+ unsigned char uc[1024];
+
+ #pragma db type(BLOB_TYPE)
+ std::array<char, 1024> a;
+
+ #pragma db type(BLOB_TYPE)
+ std::array<char, 1024> ua;
+
+ // Make sure we can still use std::vector<char> and std::array<char>
+ // as containers.
+ //
+ std::vector<unsigned char> cont;
+};
+
+inline bool
+operator== (const object& x, const object& y)
+{
+ return x.id_ == y.id_
+ && x.vc == y.vc
+ && x.vuc == y.vuc
+ && std::memcmp (x.c, y.c, sizeof (x.c)) == 0
+ && std::memcmp (x.uc, y.uc, sizeof (x.uc)) == 0
+ && x.a == y.a
+ && x.ua == y.ua
+ && x.cont == y.cont;
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/blob/testscript b/odb-tests/common/blob/testscript
new file mode 100644
index 0000000..4fb9955
--- /dev/null
+++ b/odb-tests/common/blob/testscript
@@ -0,0 +1,33 @@
+# file : common/blob/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/buildfile b/odb-tests/common/buildfile
new file mode 100644
index 0000000..cb9c748
--- /dev/null
+++ b/odb-tests/common/buildfile
@@ -0,0 +1,6 @@
+# file : common/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+./: {*/ -bulk/}
+
+./: bulk/: include = (!$pgsql || $pgsql_bulk || $size($databases) != 1)
diff --git a/odb-tests/common/bulk/buildfile b/odb-tests/common/bulk/buildfile
new file mode 100644
index 0000000..417eb22
--- /dev/null
+++ b/odb-tests/common/bulk/buildfile
@@ -0,0 +1,49 @@
+# file : common/bulk/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+assert (!$pgsql || $pgsql_bulk || $size($databases) != 1) \
+"bulk operations are disabled for pgsql which is specified as single database"
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+{
+ if ($db != 'pgsql' || $pgsql_bulk)
+ import libs += libodb-$db%lib{odb-$db}
+}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: \
+ include = ($multi && ($db != 'pgsql' || $pgsql_bulk))
+
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 bulk_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/bulk/driver.cxx b/odb-tests/common/bulk/driver.cxx
new file mode 100644
index 0000000..23b49ad
--- /dev/null
+++ b/odb-tests/common/bulk/driver.cxx
@@ -0,0 +1,1203 @@
+// file : common/bulk/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test bulk database operations.
+//
+
+#include <memory> // std::unique_ptr
+#include <vector>
+#include <iostream>
+#include <iterator>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <odb/details/meta/remove-pointer.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_*
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+// Sun CC with non-standard STL does not have iterator_traits in which
+// case we assume iterator is just a pointer.
+//
+template <typename I,
+#ifndef _RWSTD_NO_CLASS_PARTIAL_SPEC
+ typename T = typename iterator_traits<I>::value_type
+#else
+ typename T = typename odb::details::meta::remove_pointer<I>::result
+#endif
+ >
+struct element_traits;
+
+template <typename I, typename T>
+struct element_traits
+{
+ typedef T type;
+ typedef T* pointer;
+ typedef std::unique_ptr<T> unique_ptr;
+
+ static T& ref (T& x) {return x;}
+ static T* ptr (T* p) {return p;}
+};
+
+template <typename I, typename T>
+struct element_traits<I, T*>
+{
+ typedef T type;
+ typedef T* pointer;
+ typedef std::unique_ptr<T> unique_ptr;
+
+ static T& ref (T* p) {return *p;}
+ static T* ptr (T* p) {return p;}
+};
+
+template <typename I, typename T>
+struct element_traits<I, std::unique_ptr<T> >
+{
+ typedef T type;
+ typedef std::unique_ptr<T> pointer;
+ typedef std::unique_ptr<T> unique_ptr;
+
+ static T& ref (const unique_ptr& p) {return *p;}
+ static T* ptr (const unique_ptr& p) {return p.get ();}
+};
+
+template <typename I>
+void
+persist (const unique_ptr<database>& db, I b, I e, bool cont = true)
+{
+ typedef element_traits<I> traits;
+ typedef typename traits::type type;
+ typedef typename traits::unique_ptr unique_ptr;
+
+ {
+ transaction t (db->begin ());
+ db->persist (b, e, cont);
+ t.commit ();
+ }
+
+ // Verify we can load the objects via their ids.
+ //
+ {
+ transaction t (db->begin ());
+
+ for (I i (b); i != e; ++i)
+ {
+ type& x (traits::ref (*i));
+ unique_ptr p (db->load<type> (x.id));
+ assert (p->n == x.n && p->s == x.s);
+ }
+
+ t.commit ();
+ }
+}
+
+template <typename I>
+void
+try_persist (const unique_ptr<database>& db, I b, I e, bool cont = true)
+{
+ try
+ {
+ persist (db, b, e, cont);
+ assert (false);
+ }
+ catch (const multiple_exceptions& e)
+ {
+ cout << e.what () << endl << endl;
+ }
+}
+
+template <typename I>
+void
+update (const unique_ptr<database>& db, I b, I e,
+ bool modify = true, bool cont = true)
+{
+ typedef element_traits<I> traits;
+ typedef typename traits::type type;
+ typedef typename traits::unique_ptr unique_ptr;
+
+ if (modify)
+ {
+ for (I i (b); i != e; ++i)
+ {
+ type& x (traits::ref (*i));
+ x.n++;
+ x.s[0]++;
+ }
+ }
+
+ {
+ transaction t (db->begin ());
+ db->update (b, e, cont);
+ t.commit ();
+ }
+
+ // Verify changes.
+ //
+ {
+ transaction t (db->begin ());
+
+ for (I i (b); i != e; ++i)
+ {
+ type& x (traits::ref (*i));
+ unique_ptr p (db->load<type> (x.id));
+ assert (p->n == x.n && p->s == x.s);
+ }
+
+ t.commit ();
+ }
+}
+
+template <typename I>
+void
+try_update (const unique_ptr<database>& db, I b, I e, bool cont = true)
+{
+ try
+ {
+ update (db, b, e, false, cont);
+ assert (false);
+ }
+ catch (const multiple_exceptions& e)
+ {
+ cout << e.what () << endl << endl;
+ }
+}
+
+template <typename I>
+void
+erase (const unique_ptr<database>& db, I b, I e)
+{
+ typedef element_traits<I> traits;
+ typedef typename traits::type type;
+
+ {
+ transaction t (db->begin ());
+ db->erase (b, e);
+ t.commit ();
+ }
+
+ // Verify the objects are gone.
+ //
+ {
+ transaction t (db->begin ());
+
+ for (I i (b); i != e; ++i)
+ {
+ type& x (traits::ref (*i));
+ typename traits::pointer p (db->find<type> (x.id));
+ assert (traits::ptr (p) == 0);
+ }
+
+ t.commit ();
+ }
+}
+
+template <typename T, typename I>
+void
+erase_id (const unique_ptr<database>& db, I b, I e, bool cont = true)
+{
+ typedef element_traits<T*> traits;
+ typedef T type;
+
+ {
+ transaction t (db->begin ());
+ db->erase<T> (b, e, cont);
+ t.commit ();
+ }
+
+ // Verify the objects are gone.
+ //
+ {
+ transaction t (db->begin ());
+
+ for (I i (b); i != e; ++i)
+ assert (traits::ptr (db->find<type> (*i)) == 0);
+
+ t.commit ();
+ }
+}
+
+template <typename T, typename A>
+void
+try_erase (const unique_ptr<database>& db, const A& a, bool cont = true)
+{
+ try
+ {
+ erase_id<T> (db, a, a + sizeof (a) / sizeof (a[0]), cont);
+ assert (false);
+ }
+ catch (const multiple_exceptions& e)
+ {
+ cout << e.what () << endl << endl;
+ }
+}
+
+
+template <typename I>
+void
+test (const unique_ptr<database>& db, I b, I e)
+{
+ persist (db, b, e);
+ update (db, b, e);
+ erase (db, b, e);
+}
+
+template <typename T>
+vector<T>
+fill (std::size_t count)
+{
+ vector<T> r;
+
+ unsigned int n (1);
+ std::string s ("a");
+
+ for (size_t i (0); i != count; ++i)
+ {
+ r.push_back (T (n, s));
+ n++;
+ s[0] = (s[0] == 'z' ? 'a' : s[0] + 1);
+ }
+
+ return r;
+}
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv));
+
+#if !defined(MULTI_DATABASE) && \
+ (defined(DATABASE_ORACLE) || \
+ defined(DATABASE_MSSQL) || \
+ defined(DATABASE_PGSQL))
+
+ // Test database class API with various forms of containers
+ // and elements (test #6 is a copy).
+ //
+ {
+ using namespace test1;
+
+ {
+ object a[2];
+ a[0] = object (1, "a");
+ a[1] = object (2, "b");
+ test (db, a, a + sizeof (a) / sizeof (a[0]));
+ }
+
+ {
+ vector<object> v;
+ v.push_back (object (1, "a"));
+ v.push_back (object (2, "b"));
+ test (db, v.begin (), v.end ());
+ }
+
+ {
+ object o1 (1, "a");
+ object o2 (2, "b");
+ object* a[2] = {&o1, &o2};
+ test (db, a, a + sizeof (a) / sizeof (a[0]));
+ }
+
+ {
+ object o1 (1, "a");
+ object o2 (2, "b");
+ vector<object*> v;
+ v.push_back (&o1);
+ v.push_back (&o2);
+ test (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<unique_ptr<unique_object>> v;
+ v.push_back (unique_ptr<unique_object> (new unique_object (1, "a")));
+ v.push_back (unique_ptr<unique_object> (new unique_object (2, "b")));
+ test (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v;
+ v.push_back (object (1, "a"));
+ v.push_back (object (2, "b"));
+ persist (db, v.begin (), v.end ());
+
+ unsigned long id[2] = {v[0].id, v[1].id};
+ erase_id<object> (db, id, id + sizeof (id) / sizeof (id[0]));
+ }
+
+ {
+ vector<object> v;
+ v.push_back (object (1, "a"));
+ v.push_back (object (2, "b"));
+ persist (db, v.begin (), v.end ());
+
+ vector<unsigned long> id;
+ id.push_back (v[0].id);
+ id.push_back (v[1].id);
+ erase_id<object> (db, id.begin (), id.end ());
+ }
+ }
+
+ // Test various batch sizes.
+ //
+ {
+ using namespace test1;
+
+ {
+ vector<object> v; // 0
+ test (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v (fill<object> (1)); // 1
+ test (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v (fill<object> (2)); // batch - 1
+ test (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v (fill<object> (3)); // batch
+ test (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v (fill<object> (4)); // batch + 1
+ test (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v (fill<object> (5)); // 2 * batch - 1
+ test (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v (fill<object> (6)); // 2 * batch
+ test (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v (fill<object> (100)); // 100
+ test (db, v.begin (), v.end ());
+ }
+ }
+
+ // Test object with manually assigned id.
+ //
+ {
+ using namespace test2;
+
+ {
+ vector<object> v;
+ v.push_back (object ("1", 1, "a"));
+ v.push_back (object ("2", 2, "b"));
+ test (db, v.begin (), v.end ());
+ }
+
+ {
+ typedef unique_ptr<unique_object> unique_ptr;
+
+ vector<unique_ptr> v;
+ v.push_back (unique_ptr (new unique_object ("1", 1, "a")));
+ v.push_back (unique_ptr (new unique_object ("2", 2, "b")));
+ test (db, v.begin (), v.end ());
+ }
+
+ // Test const objects.
+ //
+
+ {
+ const object a[1];
+ const object* e (a + sizeof (a) / sizeof (a[0]));
+
+ transaction t (db->begin ());
+ db->persist (a, e);
+ db->erase (a, e);
+ t.commit ();
+ }
+
+ {
+ object o1 ("1", 1, "a");
+ object o2 ("2", 2, "b");
+
+ vector<const object*> v;
+ v.push_back (&o1);
+ v.push_back (&o2);
+
+ transaction t (db->begin ());
+ db->persist (v.begin (), v.end ());
+ db->erase (v.begin (), v.end ());
+ t.commit ();
+ }
+ }
+
+ // Test failure.
+ //
+ {
+ using namespace test3;
+
+ vector<object> v;
+ v.push_back (object (6, 6));
+ v.push_back (object (7, 7));
+ v.push_back (object (8, 8));
+ v.push_back (object (9, 9));
+ v.push_back (object (10, 10));
+ v.push_back (object (11, 11));
+
+ persist (db, v.begin (), v.end ());
+
+ // persist
+ //
+ {
+ {
+ vector<object> v; // 1
+ v.push_back (object (6, 6));
+ try_persist (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // 2
+ v.push_back (object (6, 6));
+ v.push_back (object (7, 7));
+ try_persist (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // batch
+ v.push_back (object (6, 6));
+ v.push_back (object (7, 7));
+ v.push_back (object (8, 8));
+ try_persist (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // batch + 1
+ v.push_back (object (6, 6));
+ v.push_back (object (7, 7));
+ v.push_back (object (8, 8));
+ v.push_back (object (9, 9));
+ try_persist (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // 2 x batch - 1
+ v.push_back (object (6, 6));
+ v.push_back (object (7, 7));
+ v.push_back (object (8, 8));
+ v.push_back (object (9, 9));
+ v.push_back (object (10, 10));
+ try_persist (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // 2 x batch
+ v.push_back (object (6, 6));
+ v.push_back (object (7, 7));
+ v.push_back (object (8, 8));
+ v.push_back (object (9, 9));
+ v.push_back (object (10, 10));
+ v.push_back (object (11, 11));
+ try_persist (db, v.begin (), v.end ());
+ }
+
+ // Mixture of success and failure.
+ //
+
+ {
+ vector<object> v; // 1
+ v.push_back (object (0, 0));
+ v.push_back (object (6, 6));
+ try_persist (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // 1
+ v.push_back (object (6, 6));
+ v.push_back (object (0, 0));
+ try_persist (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // 2
+ v.push_back (object (0, 0));
+ v.push_back (object (6, 6));
+ v.push_back (object (7, 7));
+ try_persist (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // 2
+ v.push_back (object (6, 6));
+ v.push_back (object (0, 0));
+ v.push_back (object (7, 7));
+ try_persist (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // 2
+ v.push_back (object (6, 6));
+ v.push_back (object (7, 7));
+ v.push_back (object (0, 0));
+ try_persist (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // batch
+ v.push_back (object (6, 6));
+ v.push_back (object (7, 7));
+ v.push_back (object (0, 0));
+ v.push_back (object (8, 8));
+ try_persist (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // batch
+ v.push_back (object (6, 6));
+ v.push_back (object (7, 7));
+ v.push_back (object (8, 8));
+ v.push_back (object (0, 0));
+ try_persist (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // mixture
+ v.push_back (object (0, 0));
+ v.push_back (object (6, 6));
+ v.push_back (object (1, 1));
+ v.push_back (object (7, 7));
+ v.push_back (object (2, 2));
+ v.push_back (object (8, 8));
+ v.push_back (object (3, 3));
+ try_persist (db, v.begin (), v.end ());
+ }
+
+ // Test stopping after failure.
+ //
+ {
+ vector<object> v; // batch
+ v.push_back (object (0, 0));
+ v.push_back (object (1, 1));
+ v.push_back (object (6, 6));
+ v.push_back (object (2, 2));
+ v.push_back (object (3, 3));
+ try_persist (db, v.begin (), v.end (), false);
+ }
+ }
+
+ // update
+ //
+ {
+ {
+ vector<object> v; // 1
+ v.push_back (object (0, 0));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // 2
+ v.push_back (object (0, 0));
+ v.push_back (object (1, 1));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // batch
+ v.push_back (object (0, 0));
+ v.push_back (object (1, 1));
+ v.push_back (object (2, 2));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // batch + 1
+ v.push_back (object (0, 0));
+ v.push_back (object (1, 1));
+ v.push_back (object (2, 2));
+ v.push_back (object (3, 3));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // 2 x batch - 1
+ v.push_back (object (0, 0));
+ v.push_back (object (1, 1));
+ v.push_back (object (2, 2));
+ v.push_back (object (3, 3));
+ v.push_back (object (4, 4));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // 2 x batch
+ v.push_back (object (0, 0));
+ v.push_back (object (1, 1));
+ v.push_back (object (2, 2));
+ v.push_back (object (3, 3));
+ v.push_back (object (4, 4));
+ v.push_back (object (5, 5));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ // Mixture of success and failure.
+ //
+
+ {
+ vector<object> v; // 1
+ v.push_back (object (6, 6));
+ v.push_back (object (0, 0));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // 1
+ v.push_back (object (0, 0));
+ v.push_back (object (6, 6));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // 2
+ v.push_back (object (6, 6));
+ v.push_back (object (0, 0));
+ v.push_back (object (1, 1));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // 2
+ v.push_back (object (0, 0));
+ v.push_back (object (6, 6));
+ v.push_back (object (1, 1));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // 2
+ v.push_back (object (0, 0));
+ v.push_back (object (1, 1));
+ v.push_back (object (6, 6));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // batch
+ v.push_back (object (0, 0));
+ v.push_back (object (1, 1));
+ v.push_back (object (6, 6));
+ v.push_back (object (2, 2));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // batch
+ v.push_back (object (0, 0));
+ v.push_back (object (1, 1));
+ v.push_back (object (2, 2));
+ v.push_back (object (6, 6));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // mixture
+ v.push_back (object (0, 0));
+ v.push_back (object (6, 6));
+ v.push_back (object (2, 2));
+ v.push_back (object (7, 7));
+ v.push_back (object (3, 3));
+ v.push_back (object (8, 8));
+ v.push_back (object (4, 4));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v; // mixture
+ v.push_back (object (0, 0));
+ v.push_back (object (2, 2));
+ v.push_back (object (3, 3));
+ v.push_back (object (6, 6));
+ v.push_back (object (7, 7));
+ v.push_back (object (8, 8));
+ v.push_back (object (4, 4));
+ try_update (db, v.begin (), v.end ());
+ }
+
+ // Test stopping after failure.
+ //
+ {
+ vector<object> v; // batch
+ v.push_back (object (6, 6));
+ v.push_back (object (7, 7));
+ v.push_back (object (0, 0));
+ v.push_back (object (8, 8));
+ v.push_back (object (9, 9));
+ try_update (db, v.begin (), v.end (), false);
+ }
+
+ // Test a database exception (unique constraint violation)
+ //
+ try
+ {
+ v[0].n++;
+ v[2].n++;
+
+ update (db, v.begin (), v.begin () + 3, false);
+ assert (false);
+ }
+ catch (const multiple_exceptions& e)
+ {
+#ifndef DATABASE_PGSQL
+ assert (e.attempted () == 3 && e.failed () == 2);
+ assert (e[0] != 0 && e[1] == 0 && e[2] != 0);
+#else
+ // In PosgreSQL no further statements are attempted after the first
+ // failure.
+ //
+ assert (e.attempted () == 1 && e.failed () == 1);
+ assert (e[0] != 0);
+#endif
+ }
+ }
+
+ // erase
+ //
+ {
+ {
+ unsigned long a[] = {0}; // 1
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {0, 1}; // 2
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {0, 1, 2}; // batch
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {0, 1, 2, 3}; // batch + 1
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {0, 1, 2, 3, 4}; // 2 x batch - 1
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {0, 1, 2, 3, 4, 5}; // 2 x batch
+ try_erase<object> (db, a);
+ }
+
+ // Mixture of success and failure.
+ //
+
+ {
+ unsigned long a[] = {6, 0}; // 2
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {0, 6}; // 2
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {6, 0, 1}; // batch
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {0, 6, 1}; // batch
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {0, 1, 6}; // batch
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {6, 0, 1, 2}; // batch + 1
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {0, 6, 1, 2}; // batch + 1
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {0, 1, 6, 2}; // batch + 1
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {0, 1, 2, 6}; // batch + 1
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {6, 0, 7, 1, 8, 2, 9, 3}; // mixture
+ try_erase<object> (db, a);
+ }
+
+ {
+ unsigned long a[] = {0, 1, 2, 6, 7, 8, 3, 4, 5, 9}; // mixture
+ try_erase<object> (db, a);
+ }
+
+ // Test stopping after failure.
+ //
+ {
+ unsigned long a[] = {6, 7, 0, 8, 9};
+ try_erase<object> (db, a, false);
+ }
+ }
+
+ erase (db, v.begin (), v.end ());
+ }
+
+ // Test a large batch.
+ //
+ {
+ using namespace test4;
+
+ vector<object> v (fill<object> (5000));
+ test (db, v.begin (), v.end ());
+ }
+
+ // Test object without id.
+ //
+ {
+ using namespace test5;
+
+ vector<object> v;
+ v.push_back (object (1, "a"));
+ v.push_back (object (2, "b"));
+
+ {
+ transaction t (db->begin ());
+ db->persist (v.begin (), v.end ());
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<object> ("ORDER BY" + query::n));
+ result::iterator i (r.begin ());
+
+ assert (i != r.end () && i->n == 1 && i->s == "a");
+ assert (++i != r.end () && i->n == 2 && i->s == "b");
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase_query<object> ();
+ t.commit ();
+ }
+ }
+
+ // Test API with persistent class template instantiations (copy of
+ // test #1).
+ {
+ using namespace test6;
+
+ // Make sure we can still call the non-bulk API.
+ //
+ {
+ object o (0, "z");
+ transaction t (db->begin ());
+ db->persist (o);
+ db->update<object> (o);
+ db->reload<object> (o);
+ db->erase<object> (o);
+ t.commit ();
+ }
+
+
+ // The rest is a copy of test #1.
+ //
+ {
+ object a[2];
+ a[0] = object (1, "a");
+ a[1] = object (2, "b");
+ test (db, a, a + sizeof (a) / sizeof (a[0]));
+ }
+
+ {
+ vector<object> v;
+ v.push_back (object (1, "a"));
+ v.push_back (object (2, "b"));
+ test (db, v.begin (), v.end ());
+ }
+
+ {
+ object o1 (1, "a");
+ object o2 (2, "b");
+ object* a[2] = {&o1, &o2};
+ test (db, a, a + sizeof (a) / sizeof (a[0]));
+ }
+
+ {
+ object o1 (1, "a");
+ object o2 (2, "b");
+ vector<object*> v;
+ v.push_back (&o1);
+ v.push_back (&o2);
+ test (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<unique_ptr<unique_object>> v;
+ v.push_back (unique_ptr<unique_object> (new unique_object (1, "a")));
+ v.push_back (unique_ptr<unique_object> (new unique_object (2, "b")));
+ test (db, v.begin (), v.end ());
+ }
+
+ {
+ vector<object> v;
+ v.push_back (object (1, "a"));
+ v.push_back (object (2, "b"));
+ persist (db, v.begin (), v.end ());
+
+ unsigned long id[2] = {v[0].id, v[1].id};
+ erase_id<object> (db, id, id + sizeof (id) / sizeof (id[0]));
+ }
+
+ {
+ vector<object> v;
+ v.push_back (object (1, "a"));
+ v.push_back (object (2, "b"));
+ persist (db, v.begin (), v.end ());
+
+ vector<unsigned long> id;
+ id.push_back (v[0].id);
+ id.push_back (v[1].id);
+ erase_id<object> (db, id.begin (), id.end ());
+ }
+ }
+
+ // Test optimistic concurrency.
+ //
+ {
+ using namespace test7;
+
+ std::vector<object> v (fill<object> (4));
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (v.begin (), v.end ());
+ t.commit ();
+
+ assert (v[0].v != 0 &&
+ v[1].v != 0 &&
+ v[2].v != 0 &&
+ v[3].v != 0);
+ }
+
+ // update
+ //
+ {
+ std::vector<object> c (v);
+
+ transaction t (db->begin ());
+ db->update (v.begin (), v.end ());
+ t.commit ();
+
+ assert (v[0].v > c[0].v &&
+ v[1].v > c[1].v &&
+ v[2].v > c[2].v &&
+ v[3].v > c[3].v);
+ }
+
+ {
+ object o2 (v[1]);
+ object o4 (v[3]);
+
+ o2.n++;
+ o4.n++;
+
+ transaction t (db->begin ());
+ db->update (o2);
+ db->update (o4);
+ t.commit ();
+ }
+
+ try
+ {
+ // Some updates may succeed spoiling the version for erase tests.
+ //
+ std::vector<object> c (v);
+
+ transaction t (db->begin ());
+ db->update (c.begin (), c.end ());
+ assert (false);
+ }
+ catch (const multiple_exceptions& e)
+ {
+ cout << e.what () << endl << endl;
+ }
+
+ // erase
+ //
+ try
+ {
+ transaction t (db->begin ());
+ db->erase (v.begin (), v.end ());
+ assert (false);
+ }
+ catch (const multiple_exceptions& e)
+ {
+ cout << e.what () << endl << endl;
+ }
+
+ {
+ transaction t (db->begin ());
+ db->reload (v[1]);
+ db->reload (v[3]);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (v.begin (), v.end ());
+ t.commit ();
+ }
+ }
+
+ // Test SQL Server optimistic concurrency with ROWVERSION.
+ //
+#ifdef DATABASE_MSSQL
+ {
+ using namespace test8;
+
+ std::vector<object> v (fill<object> (4));
+
+ v[0].id = 1;
+ v[1].id = 2;
+ v[2].id = 3;
+ v[3].id = 4;
+
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (v.begin (), v.end ());
+ t.commit ();
+
+ assert (v[0].v != 0 &&
+ v[1].v != 0 &&
+ v[2].v != 0 &&
+ v[3].v != 0);
+
+ //cerr << v[0].v << endl
+ // << v[1].v << endl
+ // << v[2].v << endl
+ // << v[3].v << endl;
+ }
+
+ // update
+ //
+
+ /*
+ {
+ std::vector<object> c (v);
+
+ transaction t (db->begin ());
+ db->update (v.begin (), v.end ());
+ t.commit ();
+
+ assert (v[0].v > c[0].v &&
+ v[1].v > c[1].v &&
+ v[2].v > c[2].v &&
+ v[3].v > c[3].v);
+ }
+ */
+
+ {
+ object o2 (v[1]);
+ object o4 (v[3]);
+
+ o2.n++;
+ o4.n++;
+
+ transaction t (db->begin ());
+ db->update (o2);
+ db->update (o4);
+ t.commit ();
+ }
+
+ /*
+ try
+ {
+ transaction t (db->begin ());
+ db->update (v.begin (), v.end ());
+ assert (false);
+ }
+ catch (const multiple_exceptions& e)
+ {
+ cout << e.what () << endl << endl;
+ }
+ */
+
+ // erase
+ //
+ try
+ {
+ transaction t (db->begin ());
+ db->erase (v.begin (), v.end ());
+ assert (false);
+ }
+ catch (const multiple_exceptions& e)
+ {
+ assert (e.attempted () == 4 && e.failed () == 4);
+ }
+
+ {
+ transaction t (db->begin ());
+ db->reload (v[1]);
+ db->reload (v[3]);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (v.begin (), v.end ());
+ t.commit ();
+ }
+ }
+#endif
+
+#endif
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/bulk/test.hxx b/odb-tests/common/bulk/test.hxx
new file mode 100644
index 0000000..71755f2
--- /dev/null
+++ b/odb-tests/common/bulk/test.hxx
@@ -0,0 +1,211 @@
+// file : common/bulk/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <memory> // std::unique_ptr
+
+#include <odb/core.hxx>
+
+// Test basic functionality.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ #pragma db object bulk(3) session
+ struct object
+ {
+ object (unsigned int n_ = 0, std::string s_ = "")
+ : id (0), n (n_), s (s_) {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ unsigned int n;
+
+ #pragma db oracle:type("CLOB") mssql:type("VARCHAR(max)") // Long data.
+ std::string s;
+ };
+
+ #pragma db object bulk(3) pointer(std::unique_ptr)
+ struct unique_object
+ {
+ unique_object (unsigned int n_ = 0, std::string s_ = "")
+ : id (0), n (n_), s (s_) {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ unsigned int n;
+ std::string s;
+ };
+}
+
+// Test object with manually assigned id.
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ #pragma db object bulk(3) session
+ struct object
+ {
+ // Can't use empty id because of Oracle.
+ //
+ object (std::string id_ = "!", unsigned int n_ = 0, std::string s_ = "")
+ : id (id_), n (n_), s (s_) {}
+
+ #pragma db id
+ std::string id;
+
+ unsigned int n;
+ std::string s;
+ };
+
+#pragma db object bulk(3) pointer(std::unique_ptr)
+ struct unique_object
+ {
+ unique_object (std::string id_ = "",
+ unsigned int n_ = 0,
+ std::string s_ = "")
+ : id (id_), n (n_), s (s_) {}
+
+ #pragma db id
+ std::string id;
+
+ unsigned int n;
+ std::string s;
+ };
+}
+
+// Test failure.
+//
+#pragma db namespace table("t3_")
+namespace test3
+{
+ #pragma db object bulk(3)
+ struct object
+ {
+ object (unsigned long id_ = 0, unsigned int n_ = 0)
+ : id (id_), n (n_), s ("abc") {}
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db unique
+ unsigned int n;
+ std::string s;
+ };
+}
+
+// Test a large batch.
+//
+#pragma db namespace table("t4_")
+namespace test4
+{
+ #pragma db object bulk(3000)
+ struct object
+ {
+ object (unsigned int n_ = 0, std::string s_ = "")
+ : id (0), n (n_), s (s_) {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ unsigned int n;
+
+ #pragma db oracle:type("CLOB") mssql:type("VARCHAR(max)") // Long data.
+ std::string s;
+ };
+}
+
+// Test object without id.
+//
+#pragma db namespace table("t5_")
+namespace test5
+{
+ #pragma db object no_id bulk(3)
+ struct object
+ {
+ object (unsigned int n_ = 0, std::string s_ = ""): n (n_), s (s_) {}
+
+ unsigned int n;
+ std::string s;
+ };
+}
+
+// Test API with persistent class template instantiations.
+//
+#pragma db namespace table("t6_")
+namespace test6
+{
+ template <int>
+ struct object_template
+ {
+ object_template (unsigned int n_ = 0, std::string s_ = "")
+ : id (0), n (n_), s (s_) {}
+
+ unsigned long id;
+ unsigned int n;
+ std::string s;
+ };
+
+ typedef object_template<1> object;
+
+ #pragma db object(object) bulk(3)
+ #pragma db member(object::id) id auto
+
+ typedef object_template<3> unique_object;
+
+ #pragma db object(unique_object) bulk(3) pointer(std::unique_ptr)
+ #pragma db member(unique_object::id) id auto
+}
+
+// Test optimistic concurrency.
+//
+#pragma db namespace table("t7_")
+namespace test7
+{
+ #pragma db object optimistic bulk(3)
+ struct object
+ {
+ object (unsigned int n_ = 0, std::string s_ = "")
+ : id (0), v (0), n (n_), s (s_) {}
+
+ #pragma db id auto
+ unsigned long long id;
+
+ #pragma db version
+ unsigned long long v;
+
+ unsigned int n;
+ std::string s;
+ };
+}
+
+// Test SQL Server optimistic concurrency with ROWVERSION.
+//
+#if defined(ODB_DATABASE_MSSQL) || defined(DATABASE_MSSQL)
+#pragma db namespace table("t8_")
+namespace test8
+{
+ #pragma db object optimistic bulk(3)
+ struct object
+ {
+ object (unsigned int n_ = 0, std::string s_ = "")
+ : id (0), v (0), n (n_), s (s_) {}
+
+ #pragma db id
+ unsigned long long id;
+
+ #pragma db version type("ROWVERSION")
+ unsigned long long v;
+
+ unsigned int n;
+ std::string s;
+ };
+}
+#endif
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/bulk/testscript b/odb-tests/common/bulk/testscript
new file mode 100644
index 0000000..e7567c9
--- /dev/null
+++ b/odb-tests/common/bulk/testscript
@@ -0,0 +1,503 @@
+# file : common/bulk/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
++cat <<EOI >=output
+ multiple exceptions, 1 element attempted, 1 failed:
+ [0] object already persistent
+
+ multiple exceptions, 2 elements attempted, 2 failed:
+ [0] object already persistent
+ [1] object already persistent
+
+ multiple exceptions, 3 elements attempted, 3 failed:
+ [0] object already persistent
+ [1] object already persistent
+ [2] object already persistent
+
+ multiple exceptions, 4 elements attempted, 4 failed:
+ [0] object already persistent
+ [1] object already persistent
+ [2] object already persistent
+ [3] object already persistent
+
+ multiple exceptions, 5 elements attempted, 5 failed:
+ [0] object already persistent
+ [1] object already persistent
+ [2] object already persistent
+ [3] object already persistent
+ [4] object already persistent
+
+ multiple exceptions, 6 elements attempted, 6 failed:
+ [0] object already persistent
+ [1] object already persistent
+ [2] object already persistent
+ [3] object already persistent
+ [4] object already persistent
+ [5] object already persistent
+
+ multiple exceptions, 2 elements attempted, 1 failed:
+ [1] object already persistent
+
+ multiple exceptions, 2 elements attempted, 1 failed:
+ [0] object already persistent
+
+ multiple exceptions, 3 elements attempted, 2 failed:
+ [1] object already persistent
+ [2] object already persistent
+
+ multiple exceptions, 3 elements attempted, 2 failed:
+ [0] object already persistent
+ [2] object already persistent
+
+ multiple exceptions, 3 elements attempted, 2 failed:
+ [0] object already persistent
+ [1] object already persistent
+
+ multiple exceptions, 4 elements attempted, 3 failed:
+ [0] object already persistent
+ [1] object already persistent
+ [3] object already persistent
+
+ multiple exceptions, 4 elements attempted, 3 failed:
+ [0] object already persistent
+ [1] object already persistent
+ [2] object already persistent
+
+ multiple exceptions, 7 elements attempted, 3 failed:
+ [1] object already persistent
+ [3] object already persistent
+ [5] object already persistent
+
+ multiple exceptions, 3 elements attempted, 1 failed:
+ [2] object already persistent
+
+ multiple exceptions, 1 element attempted, 1 failed:
+ [0] object not persistent
+
+ multiple exceptions, 2 elements attempted, 2 failed:
+ [0] object not persistent
+ [1] object not persistent
+
+ multiple exceptions, 3 elements attempted, 3 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+
+ multiple exceptions, 4 elements attempted, 4 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [3] object not persistent
+
+ multiple exceptions, 5 elements attempted, 5 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [3] object not persistent
+ [4] object not persistent
+
+ multiple exceptions, 6 elements attempted, 6 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [3] object not persistent
+ [4] object not persistent
+ [5] object not persistent
+
+ multiple exceptions, 2 elements attempted, 2 failed:
+ [0-1] (some) object not persistent
+
+ multiple exceptions, 2 elements attempted, 2 failed:
+ [0-1] (some) object not persistent
+
+ multiple exceptions, 3 elements attempted, 3 failed:
+ [0-2] (some) object not persistent
+
+ multiple exceptions, 3 elements attempted, 3 failed:
+ [0-2] (some) object not persistent
+
+ multiple exceptions, 3 elements attempted, 3 failed:
+ [0-2] (some) object not persistent
+
+ multiple exceptions, 4 elements attempted, 4 failed:
+ [0-2] (some) object not persistent
+ [3] object not persistent
+
+ multiple exceptions, 4 elements attempted, 3 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+
+ multiple exceptions, 7 elements attempted, 7 failed:
+ [0-5] (some) object not persistent
+ [6] object not persistent
+
+ multiple exceptions, 7 elements attempted, 4 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [6] object not persistent
+
+ multiple exceptions, 3 elements attempted, 3 failed:
+ [0-2] (some) object not persistent
+
+ multiple exceptions, 1 element attempted, 1 failed:
+ [0] object not persistent
+
+ multiple exceptions, 2 elements attempted, 2 failed:
+ [0] object not persistent
+ [1] object not persistent
+
+ multiple exceptions, 3 elements attempted, 3 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+
+ multiple exceptions, 4 elements attempted, 4 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [3] object not persistent
+
+ multiple exceptions, 5 elements attempted, 5 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [3] object not persistent
+ [4] object not persistent
+
+ multiple exceptions, 6 elements attempted, 6 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [3] object not persistent
+ [4] object not persistent
+ [5] object not persistent
+
+ multiple exceptions, 2 elements attempted, 2 failed:
+ [0-1] (some) object not persistent
+
+ multiple exceptions, 2 elements attempted, 2 failed:
+ [0-1] (some) object not persistent
+
+ multiple exceptions, 3 elements attempted, 3 failed:
+ [0-2] (some) object not persistent
+
+ multiple exceptions, 3 elements attempted, 3 failed:
+ [0-2] (some) object not persistent
+
+ multiple exceptions, 3 elements attempted, 3 failed:
+ [0-2] (some) object not persistent
+
+ multiple exceptions, 4 elements attempted, 4 failed:
+ [0-2] (some) object not persistent
+ [3] object not persistent
+
+ multiple exceptions, 4 elements attempted, 4 failed:
+ [0-2] (some) object not persistent
+ [3] object not persistent
+
+ multiple exceptions, 4 elements attempted, 4 failed:
+ [0-2] (some) object not persistent
+ [3] object not persistent
+
+ multiple exceptions, 4 elements attempted, 3 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+
+ multiple exceptions, 8 elements attempted, 8 failed:
+ [0-7] (some) object not persistent
+
+ multiple exceptions, 10 elements attempted, 6 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [6] object not persistent
+ [7] object not persistent
+ [8] object not persistent
+
+ multiple exceptions, 3 elements attempted, 3 failed:
+ [0-2] (some) object not persistent
+
+ multiple exceptions, 4 elements attempted, 4 failed:
+ [0-2] (some) object changed concurrently
+ [3] object changed concurrently
+
+ multiple exceptions, 4 elements attempted, 4 failed:
+ [0-2] (some) object changed concurrently
+ [3] object changed concurrently
+
+ EOI
+
++cat <<EOI >=pgsql-output
+ multiple exceptions, 1 element attempted, 1 failed:
+ [0] object already persistent
+
+ multiple exceptions, 1 element attempted, 1 failed, fatal:
+ [0] object already persistent
+
+ multiple exceptions, 1 element attempted, 1 failed, fatal:
+ [0] object already persistent
+
+ multiple exceptions, 1 element attempted, 1 failed, fatal:
+ [0] object already persistent
+
+ multiple exceptions, 1 element attempted, 1 failed, fatal:
+ [0] object already persistent
+
+ multiple exceptions, 1 element attempted, 1 failed, fatal:
+ [0] object already persistent
+
+ multiple exceptions, 2 elements attempted, 1 failed:
+ [1] object already persistent
+
+ multiple exceptions, 1 element attempted, 1 failed, fatal:
+ [0] object already persistent
+
+ multiple exceptions, 2 elements attempted, 1 failed, fatal:
+ [1] object already persistent
+
+ multiple exceptions, 1 element attempted, 1 failed, fatal:
+ [0] object already persistent
+
+ multiple exceptions, 1 element attempted, 1 failed, fatal:
+ [0] object already persistent
+
+ multiple exceptions, 1 element attempted, 1 failed, fatal:
+ [0] object already persistent
+
+ multiple exceptions, 1 element attempted, 1 failed, fatal:
+ [0] object already persistent
+
+ multiple exceptions, 2 elements attempted, 1 failed, fatal:
+ [1] object already persistent
+
+ multiple exceptions, 3 elements attempted, 1 failed:
+ [2] object already persistent
+
+ multiple exceptions, 1 element attempted, 1 failed:
+ [0] object not persistent
+
+ multiple exceptions, 2 elements attempted, 2 failed:
+ [0] object not persistent
+ [1] object not persistent
+
+ multiple exceptions, 3 elements attempted, 3 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+
+ multiple exceptions, 4 elements attempted, 4 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [3] object not persistent
+
+ multiple exceptions, 5 elements attempted, 5 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [3] object not persistent
+ [4] object not persistent
+
+ multiple exceptions, 6 elements attempted, 6 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [3] object not persistent
+ [4] object not persistent
+ [5] object not persistent
+
+ multiple exceptions, 2 elements attempted, 1 failed:
+ [1] object not persistent
+
+ multiple exceptions, 2 elements attempted, 1 failed:
+ [0] object not persistent
+
+ multiple exceptions, 3 elements attempted, 2 failed:
+ [1] object not persistent
+ [2] object not persistent
+
+ multiple exceptions, 3 elements attempted, 2 failed:
+ [0] object not persistent
+ [2] object not persistent
+
+ multiple exceptions, 3 elements attempted, 2 failed:
+ [0] object not persistent
+ [1] object not persistent
+
+ multiple exceptions, 4 elements attempted, 3 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [3] object not persistent
+
+ multiple exceptions, 4 elements attempted, 3 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+
+ multiple exceptions, 7 elements attempted, 4 failed:
+ [0] object not persistent
+ [2] object not persistent
+ [4] object not persistent
+ [6] object not persistent
+
+ multiple exceptions, 7 elements attempted, 4 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [6] object not persistent
+
+ multiple exceptions, 3 elements attempted, 1 failed:
+ [2] object not persistent
+
+ multiple exceptions, 1 element attempted, 1 failed:
+ [0] object not persistent
+
+ multiple exceptions, 2 elements attempted, 2 failed:
+ [0] object not persistent
+ [1] object not persistent
+
+ multiple exceptions, 3 elements attempted, 3 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+
+ multiple exceptions, 4 elements attempted, 4 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [3] object not persistent
+
+ multiple exceptions, 5 elements attempted, 5 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [3] object not persistent
+ [4] object not persistent
+
+ multiple exceptions, 6 elements attempted, 6 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [3] object not persistent
+ [4] object not persistent
+ [5] object not persistent
+
+ multiple exceptions, 2 elements attempted, 1 failed:
+ [1] object not persistent
+
+ multiple exceptions, 2 elements attempted, 1 failed:
+ [0] object not persistent
+
+ multiple exceptions, 3 elements attempted, 2 failed:
+ [1] object not persistent
+ [2] object not persistent
+
+ multiple exceptions, 3 elements attempted, 2 failed:
+ [0] object not persistent
+ [2] object not persistent
+
+ multiple exceptions, 3 elements attempted, 2 failed:
+ [0] object not persistent
+ [1] object not persistent
+
+ multiple exceptions, 4 elements attempted, 3 failed:
+ [1] object not persistent
+ [2] object not persistent
+ [3] object not persistent
+
+ multiple exceptions, 4 elements attempted, 3 failed:
+ [0] object not persistent
+ [2] object not persistent
+ [3] object not persistent
+
+ multiple exceptions, 4 elements attempted, 3 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [3] object not persistent
+
+ multiple exceptions, 4 elements attempted, 3 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+
+ multiple exceptions, 8 elements attempted, 4 failed:
+ [1] object not persistent
+ [3] object not persistent
+ [5] object not persistent
+ [7] object not persistent
+
+ multiple exceptions, 10 elements attempted, 6 failed:
+ [0] object not persistent
+ [1] object not persistent
+ [2] object not persistent
+ [6] object not persistent
+ [7] object not persistent
+ [8] object not persistent
+
+ multiple exceptions, 3 elements attempted, 1 failed:
+ [2] object not persistent
+
+ multiple exceptions, 4 elements attempted, 2 failed:
+ [1] object changed concurrently
+ [3] object changed concurrently
+
+ multiple exceptions, 4 elements attempted, 2 failed:
+ [1] object changed concurrently
+ [3] object changed concurrently
+
+ EOI
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if ($pgsql && $pgsql_bulk)
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+
+ # Query the PostgreSQL server version and only run the test if it is 7.4 or
+ # above.
+ #
+ $pgsql_client_cmd --tuples-only -c 'SELECT VERSION()' | \
+ sed -n -e 's/.*PostgreSQL (\d+\.\d+).*/\1/p' | \
+ set version [string];
+
+ if ("$version" == "")
+ exit "unable to obtain PostgreSQL server version"
+ end;
+
+ sed -n -e 's/(.+)\..+/\1/p' <"$version" | set major_version [uint64];
+ sed -n -e 's/.+\.(.+)/\1/p' <"$version" | set minor_version [uint64];
+
+ if (($major_version == 7 && minor_version >= 4) || $major_version > 7)
+ if $multi
+ $* # Noop.
+ else
+ $* >>>../pgsql-output
+ end
+ end
+}
diff --git a/odb-tests/common/callback/buildfile b/odb-tests/common/callback/buildfile
new file mode 100644
index 0000000..2ecc3b8
--- /dev/null
+++ b/odb-tests/common/callback/buildfile
@@ -0,0 +1,41 @@
+# file : common/callback/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 callback_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/callback/driver.cxx b/odb-tests/common/callback/driver.cxx
new file mode 100644
index 0000000..80513c6
--- /dev/null
+++ b/odb-tests/common/callback/driver.cxx
@@ -0,0 +1,184 @@
+// file : common/callback/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test database operation callbacks.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+const char* events[] =
+{
+ "pre_persist",
+ "post_persist",
+ "pre_load",
+ "post_load",
+ "pre_update",
+ "post_update",
+ "pre_erase",
+ "post_erase"
+};
+
+void object::
+db_callback (callback_event e, database& db)
+{
+ cout << " " << events[e] << " " << id_ << endl;
+
+ // Test custom recursive loading.
+ //
+ if (e == callback_event::post_load && ref != 0)
+ {
+ robj = db.load<object> (ref);
+ cout << " " << id_ << ' ' << ref << ' ' << robj->id_ << endl;
+ }
+}
+
+void object::
+db_callback (callback_event e, database&) const
+{
+ cout << " " << events[e] << " " << id_ << " const" << endl;
+}
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv));
+
+ // Persist.
+ //
+ cout << "persist" << endl;
+ {
+ object o1 (1, 1);
+ object const o2 (2, 2);
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (&o2);
+ t.commit ();
+ }
+ cout << "***" << endl;
+
+ // Load.
+ //
+ cout << "load" << endl;
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o1 (db->load<object> (1));
+ object o2;
+ db->load<object> (2, o2);
+ t.commit ();
+ }
+ cout << "***" << endl;
+
+ // Query.
+ //
+ cout << "query" << endl;
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<object> ((query::id < 3) + "ORDER BY" + query::id));
+
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ {
+ if (i->id_ > 3) // Load.
+ break;
+ }
+
+ t.commit ();
+ }
+ cout << "***" << endl;
+
+ // Update.
+ //
+ cout << "update" << endl;
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o1 (db->load<object> (1));
+ unique_ptr<object> o2 (db->load<object> (2));
+ o1->data++;
+ o2->data++;
+ db->update (o1.get ());
+ db->update (static_cast<const object&> (*o2));
+ t.commit ();
+ }
+ cout << "***" << endl;
+
+ // Erase.
+ //
+ cout << "erase" << endl;
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o1 (db->load<object> (1));
+ unique_ptr<object> o2 (db->load<object> (2));
+ db->erase (static_cast<const object*> (o1.get ()));
+ db->erase (*o2);
+ t.commit ();
+ }
+ cout << "***" << endl;
+
+ // Delayed (recursive) load.
+ //
+ cout << "delayed load" << endl;
+ {
+ {
+ object o1 (1, 1);
+ object o2 (2, 2);
+ object o3 (3, 3);
+ object o4 (4, 4);
+
+ o1.pobj = &o2;
+ o1.ref = 4;
+
+ o2.pobj = &o3;
+ o2.ref = 4;
+
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ db->persist (o3);
+ db->persist (o4);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o1 (db->load<object> (1));
+ object* o2 (o1->pobj);
+
+ cout << o1->id_ << ' ' << o1->ref << ' ' << o1->robj->id_ << endl;
+ cout << o2->id_ << ' ' << o2->ref << ' ' << o2->robj->id_ << endl;
+
+ delete o1->robj;
+ delete o2->robj;
+
+ delete o2->pobj;
+ delete o2;
+ t.commit ();
+ }
+ }
+ cout << "***" << endl;
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/callback/test.hxx b/odb-tests/common/callback/test.hxx
new file mode 100644
index 0000000..bd30907
--- /dev/null
+++ b/odb-tests/common/callback/test.hxx
@@ -0,0 +1,43 @@
+// file : common/callback/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <odb/core.hxx>
+#include <odb/callback.hxx>
+
+#pragma db object callback(db_callback)
+struct object
+{
+ object (unsigned long id, unsigned long d)
+ : id_ (id), data (d), pobj (0), robj (0), ref (0)
+ {
+ }
+
+ object ()
+ : id_ (0), pobj (0), robj (0)
+ {
+ }
+
+ #pragma db id
+ unsigned long id_;
+
+ unsigned long data;
+
+ object* pobj;
+
+ // Test custom recursive loading.
+ //
+ #pragma db transient
+ object* robj;
+ unsigned long ref; // Unless 0, reference to another object.
+
+ void
+ db_callback (odb::callback_event, odb::database&);
+
+ void
+ db_callback (odb::callback_event, odb::database&) const;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/callback/testscript b/odb-tests/common/callback/testscript
new file mode 100644
index 0000000..c7d03ee
--- /dev/null
+++ b/odb-tests/common/callback/testscript
@@ -0,0 +1,100 @@
+# file : common/callback/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
++cat <<EOI >=output
+ persist
+ pre_persist 1 const
+ post_persist 1 const
+ pre_persist 2 const
+ post_persist 2 const
+ ***
+ load
+ pre_load 0
+ post_load 1
+ pre_load 0
+ post_load 2
+ ***
+ query
+ pre_load 0
+ post_load 1
+ pre_load 0
+ post_load 2
+ ***
+ update
+ pre_load 0
+ post_load 1
+ pre_load 0
+ post_load 2
+ pre_update 1 const
+ post_update 1 const
+ pre_update 2 const
+ post_update 2 const
+ ***
+ erase
+ pre_load 0
+ post_load 1
+ pre_load 0
+ post_load 2
+ pre_erase 1 const
+ post_erase 1 const
+ pre_erase 2 const
+ post_erase 2 const
+ ***
+ delayed load
+ pre_persist 1 const
+ post_persist 1 const
+ pre_persist 2 const
+ post_persist 2 const
+ pre_persist 3 const
+ post_persist 3 const
+ pre_persist 4 const
+ post_persist 4 const
+ pre_load 0
+ pre_load 0
+ pre_load 0
+ post_load 3
+ post_load 2
+ pre_load 0
+ post_load 4
+ 2 4 4
+ post_load 1
+ pre_load 0
+ post_load 4
+ 1 4 4
+ 1 4 4
+ 2 4 4
+ ***
+ EOI
+
+test.redirects += >>>../output
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/changelog/.gitignore b/odb-tests/common/changelog/.gitignore
new file mode 100644
index 0000000..5352a2b
--- /dev/null
+++ b/odb-tests/common/changelog/.gitignore
@@ -0,0 +1,3 @@
+# Generate ODB options file.
+#
+odb.options
diff --git a/odb-tests/common/changelog/add-column-mssql-diff.xml b/odb-tests/common/changelog/add-column-mssql-diff.xml
new file mode 100644
index 0000000..4f9ba09
--- /dev/null
+++ b/odb-tests/common/changelog/add-column-mssql-diff.xml
@@ -0,0 +1,16 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="num" type="INT" null="false"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-column-mssql-patch.xml b/odb-tests/common/changelog/add-column-mssql-patch.xml
new file mode 100644
index 0000000..4f396d9
--- /dev/null
+++ b/odb-tests/common/changelog/add-column-mssql-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-column-mysql-diff.xml b/odb-tests/common/changelog/add-column-mysql-diff.xml
new file mode 100644
index 0000000..992306d
--- /dev/null
+++ b/odb-tests/common/changelog/add-column-mysql-diff.xml
@@ -0,0 +1,16 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="num" type="INT" null="false"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-column-mysql-patch.xml b/odb-tests/common/changelog/add-column-mysql-patch.xml
new file mode 100644
index 0000000..14f3f01
--- /dev/null
+++ b/odb-tests/common/changelog/add-column-mysql-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-column-oracle-diff.xml b/odb-tests/common/changelog/add-column-oracle-diff.xml
new file mode 100644
index 0000000..fa1dac6
--- /dev/null
+++ b/odb-tests/common/changelog/add-column-oracle-diff.xml
@@ -0,0 +1,16 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="num" type="NUMBER(10)" null="false"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-column-oracle-patch.xml b/odb-tests/common/changelog/add-column-oracle-patch.xml
new file mode 100644
index 0000000..38fb8d6
--- /dev/null
+++ b/odb-tests/common/changelog/add-column-oracle-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="false"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-column-pgsql-diff.xml b/odb-tests/common/changelog/add-column-pgsql-diff.xml
new file mode 100644
index 0000000..9524d9d
--- /dev/null
+++ b/odb-tests/common/changelog/add-column-pgsql-diff.xml
@@ -0,0 +1,16 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="num" type="INTEGER" null="false"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-column-pgsql-patch.xml b/odb-tests/common/changelog/add-column-pgsql-patch.xml
new file mode 100644
index 0000000..7f7d9a0
--- /dev/null
+++ b/odb-tests/common/changelog/add-column-pgsql-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-column-sqlite-diff.xml b/odb-tests/common/changelog/add-column-sqlite-diff.xml
new file mode 100644
index 0000000..b59cc72
--- /dev/null
+++ b/odb-tests/common/changelog/add-column-sqlite-diff.xml
@@ -0,0 +1,16 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="num" type="INTEGER" null="false"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-column-sqlite-patch.xml b/odb-tests/common/changelog/add-column-sqlite-patch.xml
new file mode 100644
index 0000000..fbe4428
--- /dev/null
+++ b/odb-tests/common/changelog/add-column-sqlite-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-column.hxx b/odb-tests/common/changelog/add-column.hxx
new file mode 100644
index 0000000..54eab42
--- /dev/null
+++ b/odb-tests/common/changelog/add-column.hxx
@@ -0,0 +1,20 @@
+// file : common/changelog/add-column.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef ADD_COLUMN_HXX
+#define ADD_COLUMN_HXX
+
+#pragma db model version(BVER, CVER, open)
+
+#pragma db object
+struct object
+{
+ #pragma db id auto
+ int id;
+
+#if CVER > 1
+ int num;
+#endif
+};
+
+#endif // ADD_COLUMN_HXX
diff --git a/odb-tests/common/changelog/add-foreign-key-diff.xml b/odb-tests/common/changelog/add-foreign-key-diff.xml
new file mode 100644
index 0000000..d4f29ad
--- /dev/null
+++ b/odb-tests/common/changelog/add-foreign-key-diff.xml
@@ -0,0 +1,28 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="o1" type="INTEGER" null="true"/>
+ <add-foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </add-foreign-key>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-foreign-key-mssql-diff.xml b/odb-tests/common/changelog/add-foreign-key-mssql-diff.xml
new file mode 100644
index 0000000..76ebce3
--- /dev/null
+++ b/odb-tests/common/changelog/add-foreign-key-mssql-diff.xml
@@ -0,0 +1,28 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="o1" type="INT" null="true"/>
+ <add-foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </add-foreign-key>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-foreign-key-mssql-patch.xml b/odb-tests/common/changelog/add-foreign-key-mssql-patch.xml
new file mode 100644
index 0000000..6d75709
--- /dev/null
+++ b/odb-tests/common/changelog/add-foreign-key-mssql-patch.xml
@@ -0,0 +1,25 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="o1" type="INT" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-foreign-key-mysql-diff.xml b/odb-tests/common/changelog/add-foreign-key-mysql-diff.xml
new file mode 100644
index 0000000..acdfd5b
--- /dev/null
+++ b/odb-tests/common/changelog/add-foreign-key-mysql-diff.xml
@@ -0,0 +1,28 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="o1" type="INT" null="true"/>
+ <add-foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </add-foreign-key>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object1" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-foreign-key-mysql-patch.xml b/odb-tests/common/changelog/add-foreign-key-mysql-patch.xml
new file mode 100644
index 0000000..adc9081
--- /dev/null
+++ b/odb-tests/common/changelog/add-foreign-key-mysql-patch.xml
@@ -0,0 +1,25 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="o1" type="INT" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object1" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-foreign-key-oracle-diff.xml b/odb-tests/common/changelog/add-foreign-key-oracle-diff.xml
new file mode 100644
index 0000000..6bd8bc3
--- /dev/null
+++ b/odb-tests/common/changelog/add-foreign-key-oracle-diff.xml
@@ -0,0 +1,28 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="o1" type="NUMBER(10)" null="true"/>
+ <add-foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </add-foreign-key>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-foreign-key-oracle-patch.xml b/odb-tests/common/changelog/add-foreign-key-oracle-patch.xml
new file mode 100644
index 0000000..7aa01ea
--- /dev/null
+++ b/odb-tests/common/changelog/add-foreign-key-oracle-patch.xml
@@ -0,0 +1,25 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="o1" type="NUMBER(10)" null="true"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-foreign-key-pgsql-diff.xml b/odb-tests/common/changelog/add-foreign-key-pgsql-diff.xml
new file mode 100644
index 0000000..793b009
--- /dev/null
+++ b/odb-tests/common/changelog/add-foreign-key-pgsql-diff.xml
@@ -0,0 +1,28 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="o1" type="INTEGER" null="true"/>
+ <add-foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </add-foreign-key>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-foreign-key-pgsql-patch.xml b/odb-tests/common/changelog/add-foreign-key-pgsql-patch.xml
new file mode 100644
index 0000000..a256831
--- /dev/null
+++ b/odb-tests/common/changelog/add-foreign-key-pgsql-patch.xml
@@ -0,0 +1,25 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="o1" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-foreign-key-sqlite-diff.xml b/odb-tests/common/changelog/add-foreign-key-sqlite-diff.xml
new file mode 100644
index 0000000..1510470
--- /dev/null
+++ b/odb-tests/common/changelog/add-foreign-key-sqlite-diff.xml
@@ -0,0 +1,28 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="o1" type="INTEGER" null="true"/>
+ <add-foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </add-foreign-key>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-foreign-key-sqlite-patch.xml b/odb-tests/common/changelog/add-foreign-key-sqlite-patch.xml
new file mode 100644
index 0000000..1c2d0ea
--- /dev/null
+++ b/odb-tests/common/changelog/add-foreign-key-sqlite-patch.xml
@@ -0,0 +1,25 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="o1" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-foreign-key.hxx b/odb-tests/common/changelog/add-foreign-key.hxx
new file mode 100644
index 0000000..2a43eea
--- /dev/null
+++ b/odb-tests/common/changelog/add-foreign-key.hxx
@@ -0,0 +1,29 @@
+// file : common/changelog/add-foreign-key.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef ADD_FOREIGN_KEY_HXX
+#define ADD_FOREIGN_KEY_HXX
+
+#pragma db model version(BVER, CVER, open)
+
+struct object1;
+
+#pragma db object
+struct object
+{
+ #pragma db id auto
+ int id;
+
+#if CVER > 1
+ object1* o1;
+#endif
+};
+
+#pragma db object
+struct object1
+{
+ #pragma db id
+ int id;
+};
+
+#endif // ADD_FOREIGN_KEY_HXX
diff --git a/odb-tests/common/changelog/add-index-mssql-diff.xml b/odb-tests/common/changelog/add-index-mssql-diff.xml
new file mode 100644
index 0000000..58c623f
--- /dev/null
+++ b/odb-tests/common/changelog/add-index-mssql-diff.xml
@@ -0,0 +1,21 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="y" type="INT" null="false"/>
+ <add-index name="xy_i" type="UNIQUE">
+ <column name="x"/>
+ <column name="y" options="DESC"/>
+ </add-index>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="x" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-index-mssql-patch.xml b/odb-tests/common/changelog/add-index-mssql-patch.xml
new file mode 100644
index 0000000..2b10e65
--- /dev/null
+++ b/odb-tests/common/changelog/add-index-mssql-patch.xml
@@ -0,0 +1,18 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="x" type="INT" null="false"/>
+ <column name="y" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <index name="xy_i" type="UNIQUE">
+ <column name="x"/>
+ <column name="y" options="DESC"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-index-mysql-diff.xml b/odb-tests/common/changelog/add-index-mysql-diff.xml
new file mode 100644
index 0000000..a54a7e3
--- /dev/null
+++ b/odb-tests/common/changelog/add-index-mysql-diff.xml
@@ -0,0 +1,21 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="y" type="INT" null="false"/>
+ <add-index name="xy_i" type="UNIQUE">
+ <column name="x"/>
+ <column name="y" options="DESC"/>
+ </add-index>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="x" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-index-mysql-patch.xml b/odb-tests/common/changelog/add-index-mysql-patch.xml
new file mode 100644
index 0000000..a2454b8
--- /dev/null
+++ b/odb-tests/common/changelog/add-index-mysql-patch.xml
@@ -0,0 +1,18 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="x" type="INT" null="false"/>
+ <column name="y" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <index name="xy_i" type="UNIQUE">
+ <column name="x"/>
+ <column name="y" options="DESC"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-index-oracle-diff.xml b/odb-tests/common/changelog/add-index-oracle-diff.xml
new file mode 100644
index 0000000..80f8ecc
--- /dev/null
+++ b/odb-tests/common/changelog/add-index-oracle-diff.xml
@@ -0,0 +1,21 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="y" type="NUMBER(10)" null="false"/>
+ <add-index name="xy_i" type="UNIQUE">
+ <column name="x"/>
+ <column name="y" options="DESC"/>
+ </add-index>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="x" type="NUMBER(10)" null="false"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-index-oracle-patch.xml b/odb-tests/common/changelog/add-index-oracle-patch.xml
new file mode 100644
index 0000000..a9bafea
--- /dev/null
+++ b/odb-tests/common/changelog/add-index-oracle-patch.xml
@@ -0,0 +1,18 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="x" type="NUMBER(10)" null="false"/>
+ <column name="y" type="NUMBER(10)" null="false"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ <index name="xy_i" type="UNIQUE">
+ <column name="x"/>
+ <column name="y" options="DESC"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-index-pgsql-diff.xml b/odb-tests/common/changelog/add-index-pgsql-diff.xml
new file mode 100644
index 0000000..3988643
--- /dev/null
+++ b/odb-tests/common/changelog/add-index-pgsql-diff.xml
@@ -0,0 +1,21 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="y" type="INTEGER" null="false"/>
+ <add-index name="xy_i" type="UNIQUE">
+ <column name="x"/>
+ <column name="y" options="DESC"/>
+ </add-index>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="x" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-index-pgsql-patch.xml b/odb-tests/common/changelog/add-index-pgsql-patch.xml
new file mode 100644
index 0000000..e9c564c
--- /dev/null
+++ b/odb-tests/common/changelog/add-index-pgsql-patch.xml
@@ -0,0 +1,18 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="x" type="INTEGER" null="false"/>
+ <column name="y" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <index name="xy_i" type="UNIQUE">
+ <column name="x"/>
+ <column name="y" options="DESC"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-index-sqlite-diff.xml b/odb-tests/common/changelog/add-index-sqlite-diff.xml
new file mode 100644
index 0000000..c1f7fdc
--- /dev/null
+++ b/odb-tests/common/changelog/add-index-sqlite-diff.xml
@@ -0,0 +1,21 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <add-column name="y" type="INTEGER" null="false"/>
+ <add-index name="xy_i" type="UNIQUE">
+ <column name="x"/>
+ <column name="y" options="DESC"/>
+ </add-index>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="x" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-index-sqlite-patch.xml b/odb-tests/common/changelog/add-index-sqlite-patch.xml
new file mode 100644
index 0000000..b9512e0
--- /dev/null
+++ b/odb-tests/common/changelog/add-index-sqlite-patch.xml
@@ -0,0 +1,18 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="x" type="INTEGER" null="false"/>
+ <column name="y" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <index name="xy_i" type="UNIQUE">
+ <column name="x"/>
+ <column name="y" options="DESC"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-index.hxx b/odb-tests/common/changelog/add-index.hxx
new file mode 100644
index 0000000..645cee2
--- /dev/null
+++ b/odb-tests/common/changelog/add-index.hxx
@@ -0,0 +1,24 @@
+// file : common/changelog/add-index.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef ADD_INDEX_HXX
+#define ADD_INDEX_HXX
+
+#pragma db model version(BVER, CVER, open)
+
+#pragma db object
+struct object
+{
+ #pragma db id auto
+ int id;
+
+ int x;
+
+#if CVER > 1
+ int y;
+ #pragma db index ("xy_i") unique member(x) member(y, "DESC")
+#endif
+
+};
+
+#endif // ADD_INDEX_HXX
diff --git a/odb-tests/common/changelog/add-table-mssql-diff.xml b/odb-tests/common/changelog/add-table-mssql-diff.xml
new file mode 100644
index 0000000..8bf0e30
--- /dev/null
+++ b/odb-tests/common/changelog/add-table-mssql-diff.xml
@@ -0,0 +1,45 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="2">
+ <add-table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <column name="o1" type="INT" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </add-table>
+ <add-table name="object_nums" kind="container">
+ <column name="object_id" type="INT" null="false"/>
+ <column name="index" type="BIGINT" null="false"/>
+ <column name="value" type="INT" null="false"/>
+ <foreign-key name="object_nums_object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="index_i">
+ <column name="index"/>
+ </index>
+ </add-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object1" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-table-mssql-patch.xml b/odb-tests/common/changelog/add-table-mssql-patch.xml
new file mode 100644
index 0000000..9dd41f3
--- /dev/null
+++ b/odb-tests/common/changelog/add-table-mssql-patch.xml
@@ -0,0 +1,44 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object1" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <column name="o1" type="INT" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object_nums" kind="container">
+ <column name="object_id" type="INT" null="false"/>
+ <column name="index" type="BIGINT" null="false"/>
+ <column name="value" type="INT" null="false"/>
+ <foreign-key name="object_nums_object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-table-mysql-diff.xml b/odb-tests/common/changelog/add-table-mysql-diff.xml
new file mode 100644
index 0000000..57f741b
--- /dev/null
+++ b/odb-tests/common/changelog/add-table-mysql-diff.xml
@@ -0,0 +1,45 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="2">
+ <add-table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <column name="o1" type="INT" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </add-table>
+ <add-table name="object_nums" options="ENGINE=InnoDB" kind="container">
+ <column name="object_id" type="INT" null="false"/>
+ <column name="index" type="BIGINT UNSIGNED" null="false"/>
+ <column name="value" type="INT" null="false"/>
+ <foreign-key name="object_nums_object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="index_i">
+ <column name="index"/>
+ </index>
+ </add-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object1" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-table-mysql-patch.xml b/odb-tests/common/changelog/add-table-mysql-patch.xml
new file mode 100644
index 0000000..0db1e9f
--- /dev/null
+++ b/odb-tests/common/changelog/add-table-mysql-patch.xml
@@ -0,0 +1,44 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object1" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <column name="o1" type="INT" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object_nums" options="ENGINE=InnoDB" kind="container">
+ <column name="object_id" type="INT" null="false"/>
+ <column name="index" type="BIGINT UNSIGNED" null="false"/>
+ <column name="value" type="INT" null="false"/>
+ <foreign-key name="object_nums_object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-table-oracle-diff.xml b/odb-tests/common/changelog/add-table-oracle-diff.xml
new file mode 100644
index 0000000..70ec7c6
--- /dev/null
+++ b/odb-tests/common/changelog/add-table-oracle-diff.xml
@@ -0,0 +1,45 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="2">
+ <add-table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="false"/>
+ <column name="o1" type="NUMBER(10)" null="true"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </add-table>
+ <add-table name="object_nums" kind="container">
+ <column name="object_id" type="NUMBER(10)" null="false"/>
+ <column name="index" type="NUMBER(20)" null="false"/>
+ <column name="value" type="NUMBER(10)" null="false"/>
+ <foreign-key name="object_nums_object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_nums_object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="object_nums_index_i">
+ <column name="index"/>
+ </index>
+ </add-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object1" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-table-oracle-patch.xml b/odb-tests/common/changelog/add-table-oracle-patch.xml
new file mode 100644
index 0000000..969c2e0
--- /dev/null
+++ b/odb-tests/common/changelog/add-table-oracle-patch.xml
@@ -0,0 +1,44 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object1" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="false"/>
+ <column name="o1" type="NUMBER(10)" null="true"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object_nums" kind="container">
+ <column name="object_id" type="NUMBER(10)" null="false"/>
+ <column name="index" type="NUMBER(20)" null="false"/>
+ <column name="value" type="NUMBER(10)" null="false"/>
+ <foreign-key name="object_nums_object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_nums_object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="object_nums_index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-table-pgsql-diff.xml b/odb-tests/common/changelog/add-table-pgsql-diff.xml
new file mode 100644
index 0000000..9b48062
--- /dev/null
+++ b/odb-tests/common/changelog/add-table-pgsql-diff.xml
@@ -0,0 +1,45 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="2">
+ <add-table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <column name="o1" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </add-table>
+ <add-table name="object_nums" kind="container">
+ <column name="object_id" type="INTEGER" null="false"/>
+ <column name="index" type="BIGINT" null="false"/>
+ <column name="value" type="INTEGER" null="false"/>
+ <foreign-key name="object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_nums_object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="object_nums_index_i">
+ <column name="index"/>
+ </index>
+ </add-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-table-pgsql-patch.xml b/odb-tests/common/changelog/add-table-pgsql-patch.xml
new file mode 100644
index 0000000..b04a933
--- /dev/null
+++ b/odb-tests/common/changelog/add-table-pgsql-patch.xml
@@ -0,0 +1,44 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <column name="o1" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object_nums" kind="container">
+ <column name="object_id" type="INTEGER" null="false"/>
+ <column name="index" type="BIGINT" null="false"/>
+ <column name="value" type="INTEGER" null="false"/>
+ <foreign-key name="object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_nums_object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="object_nums_index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-table-sqlite-diff.xml b/odb-tests/common/changelog/add-table-sqlite-diff.xml
new file mode 100644
index 0000000..573bc69
--- /dev/null
+++ b/odb-tests/common/changelog/add-table-sqlite-diff.xml
@@ -0,0 +1,45 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="2">
+ <add-table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <column name="o1" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </add-table>
+ <add-table name="object_nums" kind="container">
+ <column name="object_id" type="INTEGER" null="false"/>
+ <column name="index" type="INTEGER" null="false"/>
+ <column name="value" type="INTEGER" null="false"/>
+ <foreign-key name="object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_nums_object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="object_nums_index_i">
+ <column name="index"/>
+ </index>
+ </add-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-table-sqlite-patch.xml b/odb-tests/common/changelog/add-table-sqlite-patch.xml
new file mode 100644
index 0000000..3506410
--- /dev/null
+++ b/odb-tests/common/changelog/add-table-sqlite-patch.xml
@@ -0,0 +1,44 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <column name="o1" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object_nums" kind="container">
+ <column name="object_id" type="INTEGER" null="false"/>
+ <column name="index" type="INTEGER" null="false"/>
+ <column name="value" type="INTEGER" null="false"/>
+ <foreign-key name="object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_nums_object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="object_nums_index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/add-table.hxx b/odb-tests/common/changelog/add-table.hxx
new file mode 100644
index 0000000..a22e206
--- /dev/null
+++ b/odb-tests/common/changelog/add-table.hxx
@@ -0,0 +1,34 @@
+// file : common/changelog/add-table.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef ADD_TABLE_HXX
+#define ADD_TABLE_HXX
+
+#include <vector>
+
+#pragma db model version(BVER, CVER, open)
+
+struct object1;
+
+#if CVER > 1
+#pragma db object
+struct object
+{
+ #pragma db id auto
+ int id;
+ int num;
+
+ std::vector<int> nums;
+ object1* o1;
+};
+#endif
+
+#pragma db object
+struct object1
+{
+ #pragma db id
+ int id;
+ int num;
+};
+
+#endif // ADD_TABLE_HXX
diff --git a/odb-tests/common/changelog/alter-column-mssql-diff.xml b/odb-tests/common/changelog/alter-column-mssql-diff.xml
new file mode 100644
index 0000000..6c1fb6a
--- /dev/null
+++ b/odb-tests/common/changelog/alter-column-mssql-diff.xml
@@ -0,0 +1,17 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <alter-column name="num" null="true"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/alter-column-mssql-patch.xml b/odb-tests/common/changelog/alter-column-mssql-patch.xml
new file mode 100644
index 0000000..15db347
--- /dev/null
+++ b/odb-tests/common/changelog/alter-column-mssql-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/alter-column-mysql-diff.xml b/odb-tests/common/changelog/alter-column-mysql-diff.xml
new file mode 100644
index 0000000..39ad6ef
--- /dev/null
+++ b/odb-tests/common/changelog/alter-column-mysql-diff.xml
@@ -0,0 +1,17 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <alter-column name="num" null="true"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/alter-column-mysql-patch.xml b/odb-tests/common/changelog/alter-column-mysql-patch.xml
new file mode 100644
index 0000000..0131466
--- /dev/null
+++ b/odb-tests/common/changelog/alter-column-mysql-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/alter-column-oracle-diff.xml b/odb-tests/common/changelog/alter-column-oracle-diff.xml
new file mode 100644
index 0000000..d41d333
--- /dev/null
+++ b/odb-tests/common/changelog/alter-column-oracle-diff.xml
@@ -0,0 +1,17 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <alter-column name="num" null="true"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="false"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/alter-column-oracle-patch.xml b/odb-tests/common/changelog/alter-column-oracle-patch.xml
new file mode 100644
index 0000000..0e0794d
--- /dev/null
+++ b/odb-tests/common/changelog/alter-column-oracle-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="true"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/alter-column-pgsql-diff.xml b/odb-tests/common/changelog/alter-column-pgsql-diff.xml
new file mode 100644
index 0000000..fd97fa0
--- /dev/null
+++ b/odb-tests/common/changelog/alter-column-pgsql-diff.xml
@@ -0,0 +1,17 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <alter-column name="num" null="true"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/alter-column-pgsql-patch.xml b/odb-tests/common/changelog/alter-column-pgsql-patch.xml
new file mode 100644
index 0000000..dade1a3
--- /dev/null
+++ b/odb-tests/common/changelog/alter-column-pgsql-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/alter-column-sqlite-diff.xml b/odb-tests/common/changelog/alter-column-sqlite-diff.xml
new file mode 100644
index 0000000..7ecea06
--- /dev/null
+++ b/odb-tests/common/changelog/alter-column-sqlite-diff.xml
@@ -0,0 +1,17 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <alter-column name="num" null="true"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/alter-column-sqlite-patch.xml b/odb-tests/common/changelog/alter-column-sqlite-patch.xml
new file mode 100644
index 0000000..de2762e
--- /dev/null
+++ b/odb-tests/common/changelog/alter-column-sqlite-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/alter-column.hxx b/odb-tests/common/changelog/alter-column.hxx
new file mode 100644
index 0000000..02f091d
--- /dev/null
+++ b/odb-tests/common/changelog/alter-column.hxx
@@ -0,0 +1,21 @@
+// file : common/changelog/alter-column.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef ALTER_COLUMN_HXX
+#define ALTER_COLUMN_HXX
+
+#pragma db model version(BVER, CVER, open)
+
+#pragma db object
+struct object
+{
+ #pragma db id auto
+ int id;
+
+#if CVER > 1
+ #pragma db null
+#endif
+ int num;
+};
+
+#endif // ALTER_COLUMN_HXX
diff --git a/odb-tests/common/changelog/buildfile b/odb-tests/common/changelog/buildfile
new file mode 100644
index 0000000..04e0685
--- /dev/null
+++ b/odb-tests/common/changelog/buildfile
@@ -0,0 +1,30 @@
+# file : common/changelog/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+./: file{odb.options} xml{*} testscript
+
+# Introduce the metadata library target to make sure the libodb library is
+# resolved for the below ad hoc recipe.
+#
+libue{test-meta}: $libodb
+
+file{odb.options}: libue{test-meta}
+{{
+ pops = $cxx.lib_poptions($<[0])
+ depdb hash $pops
+
+ f = $path($>[0])
+ rm -f $f
+
+ for o: $pops
+ echo $o >+$f
+ end
+}}
+
+# Testscript's run-time prerequisites.
+#
+testscript@./: test = $odb
+
+./: $odb: clean = false
diff --git a/odb-tests/common/changelog/drop-column-mssql-diff.xml b/odb-tests/common/changelog/drop-column-mssql-diff.xml
new file mode 100644
index 0000000..f2bab96
--- /dev/null
+++ b/odb-tests/common/changelog/drop-column-mssql-diff.xml
@@ -0,0 +1,17 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-column name="num"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-column-mssql-patch.xml b/odb-tests/common/changelog/drop-column-mssql-patch.xml
new file mode 100644
index 0000000..32402a3
--- /dev/null
+++ b/odb-tests/common/changelog/drop-column-mssql-patch.xml
@@ -0,0 +1,12 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-column-mysql-diff.xml b/odb-tests/common/changelog/drop-column-mysql-diff.xml
new file mode 100644
index 0000000..2bb321d
--- /dev/null
+++ b/odb-tests/common/changelog/drop-column-mysql-diff.xml
@@ -0,0 +1,17 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-column name="num"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-column-mysql-patch.xml b/odb-tests/common/changelog/drop-column-mysql-patch.xml
new file mode 100644
index 0000000..6572ebe
--- /dev/null
+++ b/odb-tests/common/changelog/drop-column-mysql-patch.xml
@@ -0,0 +1,12 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-column-oracle-diff.xml b/odb-tests/common/changelog/drop-column-oracle-diff.xml
new file mode 100644
index 0000000..e920a12
--- /dev/null
+++ b/odb-tests/common/changelog/drop-column-oracle-diff.xml
@@ -0,0 +1,17 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-column name="num"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="false"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-column-oracle-patch.xml b/odb-tests/common/changelog/drop-column-oracle-patch.xml
new file mode 100644
index 0000000..b113664
--- /dev/null
+++ b/odb-tests/common/changelog/drop-column-oracle-patch.xml
@@ -0,0 +1,12 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-column-pgsql-diff.xml b/odb-tests/common/changelog/drop-column-pgsql-diff.xml
new file mode 100644
index 0000000..d2e91ba
--- /dev/null
+++ b/odb-tests/common/changelog/drop-column-pgsql-diff.xml
@@ -0,0 +1,17 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-column name="num"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-column-pgsql-patch.xml b/odb-tests/common/changelog/drop-column-pgsql-patch.xml
new file mode 100644
index 0000000..06cc73d
--- /dev/null
+++ b/odb-tests/common/changelog/drop-column-pgsql-patch.xml
@@ -0,0 +1,12 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-column-sqlite-diff.xml b/odb-tests/common/changelog/drop-column-sqlite-diff.xml
new file mode 100644
index 0000000..1c9d138
--- /dev/null
+++ b/odb-tests/common/changelog/drop-column-sqlite-diff.xml
@@ -0,0 +1,17 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-column name="num"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-column-sqlite-patch.xml b/odb-tests/common/changelog/drop-column-sqlite-patch.xml
new file mode 100644
index 0000000..6887530
--- /dev/null
+++ b/odb-tests/common/changelog/drop-column-sqlite-patch.xml
@@ -0,0 +1,12 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-column.hxx b/odb-tests/common/changelog/drop-column.hxx
new file mode 100644
index 0000000..3de237d
--- /dev/null
+++ b/odb-tests/common/changelog/drop-column.hxx
@@ -0,0 +1,20 @@
+// file : common/changelog/drop-column.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef DROP_COLUMN_HXX
+#define DROP_COLUMN_HXX
+
+#pragma db model version(BVER, CVER, open)
+
+#pragma db object
+struct object
+{
+ #pragma db id auto
+ int id;
+
+#if CVER == 1
+ int num;
+#endif
+};
+
+#endif // DROP_COLUMN_HXX
diff --git a/odb-tests/common/changelog/drop-foreign-key-mssql-diff.xml b/odb-tests/common/changelog/drop-foreign-key-mssql-diff.xml
new file mode 100644
index 0000000..da3ce73
--- /dev/null
+++ b/odb-tests/common/changelog/drop-foreign-key-mssql-diff.xml
@@ -0,0 +1,30 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-foreign-key name="object_o1_fk"/>
+ <drop-column name="o1"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="o1" type="INT" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-foreign-key-mssql-patch.xml b/odb-tests/common/changelog/drop-foreign-key-mssql-patch.xml
new file mode 100644
index 0000000..cd1a372
--- /dev/null
+++ b/odb-tests/common/changelog/drop-foreign-key-mssql-patch.xml
@@ -0,0 +1,18 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-foreign-key-mysql-diff.xml b/odb-tests/common/changelog/drop-foreign-key-mysql-diff.xml
new file mode 100644
index 0000000..aa179f2
--- /dev/null
+++ b/odb-tests/common/changelog/drop-foreign-key-mysql-diff.xml
@@ -0,0 +1,30 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-foreign-key name="object_o1_fk"/>
+ <drop-column name="o1"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="o1" type="INT" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object1" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-foreign-key-mysql-patch.xml b/odb-tests/common/changelog/drop-foreign-key-mysql-patch.xml
new file mode 100644
index 0000000..67f026e
--- /dev/null
+++ b/odb-tests/common/changelog/drop-foreign-key-mysql-patch.xml
@@ -0,0 +1,18 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object1" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-foreign-key-oracle-diff.xml b/odb-tests/common/changelog/drop-foreign-key-oracle-diff.xml
new file mode 100644
index 0000000..aa407d3
--- /dev/null
+++ b/odb-tests/common/changelog/drop-foreign-key-oracle-diff.xml
@@ -0,0 +1,30 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-foreign-key name="object_o1_fk"/>
+ <drop-column name="o1"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="o1" type="NUMBER(10)" null="true"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-foreign-key-oracle-patch.xml b/odb-tests/common/changelog/drop-foreign-key-oracle-patch.xml
new file mode 100644
index 0000000..56253f0
--- /dev/null
+++ b/odb-tests/common/changelog/drop-foreign-key-oracle-patch.xml
@@ -0,0 +1,18 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-foreign-key-pgsql-diff.xml b/odb-tests/common/changelog/drop-foreign-key-pgsql-diff.xml
new file mode 100644
index 0000000..9a6259a
--- /dev/null
+++ b/odb-tests/common/changelog/drop-foreign-key-pgsql-diff.xml
@@ -0,0 +1,30 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-foreign-key name="o1_fk"/>
+ <drop-column name="o1"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="o1" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-foreign-key-pgsql-patch.xml b/odb-tests/common/changelog/drop-foreign-key-pgsql-patch.xml
new file mode 100644
index 0000000..df024b4
--- /dev/null
+++ b/odb-tests/common/changelog/drop-foreign-key-pgsql-patch.xml
@@ -0,0 +1,18 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-foreign-key-sqlite-diff.xml b/odb-tests/common/changelog/drop-foreign-key-sqlite-diff.xml
new file mode 100644
index 0000000..6f9f994
--- /dev/null
+++ b/odb-tests/common/changelog/drop-foreign-key-sqlite-diff.xml
@@ -0,0 +1,30 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-foreign-key name="o1_fk"/>
+ <drop-column name="o1"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="o1" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-foreign-key-sqlite-patch.xml b/odb-tests/common/changelog/drop-foreign-key-sqlite-patch.xml
new file mode 100644
index 0000000..6e63218
--- /dev/null
+++ b/odb-tests/common/changelog/drop-foreign-key-sqlite-patch.xml
@@ -0,0 +1,18 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-foreign-key.hxx b/odb-tests/common/changelog/drop-foreign-key.hxx
new file mode 100644
index 0000000..ba3005f
--- /dev/null
+++ b/odb-tests/common/changelog/drop-foreign-key.hxx
@@ -0,0 +1,29 @@
+// file : common/changelog/drop-foreign-key.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef DROP_FOREIGN_KEY_HXX
+#define DROP_FOREIGN_KEY_HXX
+
+#pragma db model version(BVER, CVER, open)
+
+struct object1;
+
+#pragma db object
+struct object
+{
+ #pragma db id auto
+ int id;
+
+#if CVER == 1
+ object1* o1;
+#endif
+};
+
+#pragma db object
+struct object1
+{
+ #pragma db id
+ int id;
+};
+
+#endif // DROP_FOREIGN_KEY_HXX
diff --git a/odb-tests/common/changelog/drop-index-mssql-diff.xml b/odb-tests/common/changelog/drop-index-mssql-diff.xml
new file mode 100644
index 0000000..ac95db5
--- /dev/null
+++ b/odb-tests/common/changelog/drop-index-mssql-diff.xml
@@ -0,0 +1,20 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-index name="num_i"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <index name="num_i">
+ <column name="num"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-index-mssql-patch.xml b/odb-tests/common/changelog/drop-index-mssql-patch.xml
new file mode 100644
index 0000000..4f396d9
--- /dev/null
+++ b/odb-tests/common/changelog/drop-index-mssql-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-index-mysql-diff.xml b/odb-tests/common/changelog/drop-index-mysql-diff.xml
new file mode 100644
index 0000000..f8c95ef
--- /dev/null
+++ b/odb-tests/common/changelog/drop-index-mysql-diff.xml
@@ -0,0 +1,20 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-index name="num_i"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <index name="num_i">
+ <column name="num"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-index-mysql-patch.xml b/odb-tests/common/changelog/drop-index-mysql-patch.xml
new file mode 100644
index 0000000..14f3f01
--- /dev/null
+++ b/odb-tests/common/changelog/drop-index-mysql-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-index-oracle-diff.xml b/odb-tests/common/changelog/drop-index-oracle-diff.xml
new file mode 100644
index 0000000..d174802
--- /dev/null
+++ b/odb-tests/common/changelog/drop-index-oracle-diff.xml
@@ -0,0 +1,20 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-index name="object_num_i"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="false"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ <index name="object_num_i">
+ <column name="num"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-index-oracle-patch.xml b/odb-tests/common/changelog/drop-index-oracle-patch.xml
new file mode 100644
index 0000000..38fb8d6
--- /dev/null
+++ b/odb-tests/common/changelog/drop-index-oracle-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="false"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-index-pgsql-diff.xml b/odb-tests/common/changelog/drop-index-pgsql-diff.xml
new file mode 100644
index 0000000..375a3d8
--- /dev/null
+++ b/odb-tests/common/changelog/drop-index-pgsql-diff.xml
@@ -0,0 +1,20 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-index name="object_num_i"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <index name="object_num_i">
+ <column name="num"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-index-pgsql-patch.xml b/odb-tests/common/changelog/drop-index-pgsql-patch.xml
new file mode 100644
index 0000000..7f7d9a0
--- /dev/null
+++ b/odb-tests/common/changelog/drop-index-pgsql-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-index-sqlite-diff.xml b/odb-tests/common/changelog/drop-index-sqlite-diff.xml
new file mode 100644
index 0000000..bf54f9d
--- /dev/null
+++ b/odb-tests/common/changelog/drop-index-sqlite-diff.xml
@@ -0,0 +1,20 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="2">
+ <alter-table name="object">
+ <drop-index name="object_num_i"/>
+ </alter-table>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <index name="object_num_i">
+ <column name="num"/>
+ </index>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-index-sqlite-patch.xml b/odb-tests/common/changelog/drop-index-sqlite-patch.xml
new file mode 100644
index 0000000..fbe4428
--- /dev/null
+++ b/odb-tests/common/changelog/drop-index-sqlite-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-index.hxx b/odb-tests/common/changelog/drop-index.hxx
new file mode 100644
index 0000000..08fecba
--- /dev/null
+++ b/odb-tests/common/changelog/drop-index.hxx
@@ -0,0 +1,21 @@
+// file : common/changelog/drop-index.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef DROP_INDEX_HXX
+#define DROP_INDEX_HXX
+
+#pragma db model version(BVER, CVER, open)
+
+#pragma db object
+struct object
+{
+ #pragma db id auto
+ int id;
+
+#if CVER == 1
+ #pragma db index
+#endif
+ int num;
+};
+
+#endif // DROP_INDEX_HXX
diff --git a/odb-tests/common/changelog/drop-table-mssql-diff.xml b/odb-tests/common/changelog/drop-table-mssql-diff.xml
new file mode 100644
index 0000000..399d8bc
--- /dev/null
+++ b/odb-tests/common/changelog/drop-table-mssql-diff.xml
@@ -0,0 +1,47 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="2">
+ <drop-table name="object"/>
+ <drop-table name="object_nums"/>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <column name="o1" type="INT" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object_nums" kind="container">
+ <column name="object_id" type="INT" null="false"/>
+ <column name="index" type="BIGINT" null="false"/>
+ <column name="value" type="INT" null="false"/>
+ <foreign-key name="object_nums_object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-table-mssql-patch.xml b/odb-tests/common/changelog/drop-table-mssql-patch.xml
new file mode 100644
index 0000000..c5dda75
--- /dev/null
+++ b/odb-tests/common/changelog/drop-table-mssql-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object1" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-table-mysql-diff.xml b/odb-tests/common/changelog/drop-table-mysql-diff.xml
new file mode 100644
index 0000000..3ccd553
--- /dev/null
+++ b/odb-tests/common/changelog/drop-table-mysql-diff.xml
@@ -0,0 +1,47 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="2">
+ <drop-table name="object"/>
+ <drop-table name="object_nums"/>
+ </changeset>
+
+ <model version="1">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <column name="o1" type="INT" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object_nums" options="ENGINE=InnoDB" kind="container">
+ <column name="object_id" type="INT" null="false"/>
+ <column name="index" type="BIGINT UNSIGNED" null="false"/>
+ <column name="value" type="INT" null="false"/>
+ <foreign-key name="object_nums_object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ <table name="object1" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-table-mysql-patch.xml b/odb-tests/common/changelog/drop-table-mysql-patch.xml
new file mode 100644
index 0000000..250bd20
--- /dev/null
+++ b/odb-tests/common/changelog/drop-table-mysql-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object1" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-table-oracle-diff.xml b/odb-tests/common/changelog/drop-table-oracle-diff.xml
new file mode 100644
index 0000000..589d64b
--- /dev/null
+++ b/odb-tests/common/changelog/drop-table-oracle-diff.xml
@@ -0,0 +1,47 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="2">
+ <drop-table name="object"/>
+ <drop-table name="object_nums"/>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="false"/>
+ <column name="o1" type="NUMBER(10)" null="true"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object_nums" kind="container">
+ <column name="object_id" type="NUMBER(10)" null="false"/>
+ <column name="index" type="NUMBER(20)" null="false"/>
+ <column name="value" type="NUMBER(10)" null="false"/>
+ <foreign-key name="object_nums_object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_nums_object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="object_nums_index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-table-oracle-patch.xml b/odb-tests/common/changelog/drop-table-oracle-patch.xml
new file mode 100644
index 0000000..5f222dc
--- /dev/null
+++ b/odb-tests/common/changelog/drop-table-oracle-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object1" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-table-pgsql-diff.xml b/odb-tests/common/changelog/drop-table-pgsql-diff.xml
new file mode 100644
index 0000000..168ec46
--- /dev/null
+++ b/odb-tests/common/changelog/drop-table-pgsql-diff.xml
@@ -0,0 +1,47 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="2">
+ <drop-table name="object"/>
+ <drop-table name="object_nums"/>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <column name="o1" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object_nums" kind="container">
+ <column name="object_id" type="INTEGER" null="false"/>
+ <column name="index" type="BIGINT" null="false"/>
+ <column name="value" type="INTEGER" null="false"/>
+ <foreign-key name="object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_nums_object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="object_nums_index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-table-pgsql-patch.xml b/odb-tests/common/changelog/drop-table-pgsql-patch.xml
new file mode 100644
index 0000000..ebb7000
--- /dev/null
+++ b/odb-tests/common/changelog/drop-table-pgsql-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-table-sqlite-diff.xml b/odb-tests/common/changelog/drop-table-sqlite-diff.xml
new file mode 100644
index 0000000..6a258c4
--- /dev/null
+++ b/odb-tests/common/changelog/drop-table-sqlite-diff.xml
@@ -0,0 +1,47 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="2">
+ <drop-table name="object"/>
+ <drop-table name="object_nums"/>
+ </changeset>
+
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <column name="o1" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1"/>
+ <references table="object1">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ </table>
+ <table name="object_nums" kind="container">
+ <column name="object_id" type="INTEGER" null="false"/>
+ <column name="index" type="INTEGER" null="false"/>
+ <column name="value" type="INTEGER" null="false"/>
+ <foreign-key name="object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_nums_object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="object_nums_index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-table-sqlite-patch.xml b/odb-tests/common/changelog/drop-table-sqlite-patch.xml
new file mode 100644
index 0000000..45c6f00
--- /dev/null
+++ b/odb-tests/common/changelog/drop-table-sqlite-patch.xml
@@ -0,0 +1,13 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <changeset version="3"/>
+
+ <model version="2">
+ <table name="object1" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/drop-table.hxx b/odb-tests/common/changelog/drop-table.hxx
new file mode 100644
index 0000000..2919e52
--- /dev/null
+++ b/odb-tests/common/changelog/drop-table.hxx
@@ -0,0 +1,34 @@
+// file : common/changelog/drop-table.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef DROP_TABLE_HXX
+#define DROP_TABLE_HXX
+
+#include <vector>
+
+#pragma db model version(BVER, CVER, open)
+
+struct object1;
+
+#if CVER == 1
+#pragma db object
+struct object
+{
+ #pragma db id auto
+ int id;
+ int num;
+
+ std::vector<int> nums;
+ object1* o1;
+};
+#endif
+
+#pragma db object
+struct object1
+{
+ #pragma db id
+ int id;
+ int num;
+};
+
+#endif // DROP_TABLE_HXX
diff --git a/odb-tests/common/changelog/model-mssql-diff.xml b/odb-tests/common/changelog/model-mssql-diff.xml
new file mode 120000
index 0000000..e1f812d
--- /dev/null
+++ b/odb-tests/common/changelog/model-mssql-diff.xml
@@ -0,0 +1 @@
+model-mssql.xml \ No newline at end of file
diff --git a/odb-tests/common/changelog/model-mssql-patch.xml b/odb-tests/common/changelog/model-mssql-patch.xml
new file mode 120000
index 0000000..e1f812d
--- /dev/null
+++ b/odb-tests/common/changelog/model-mssql-patch.xml
@@ -0,0 +1 @@
+model-mssql.xml \ No newline at end of file
diff --git a/odb-tests/common/changelog/model-mssql.xml b/odb-tests/common/changelog/model-mssql.xml
new file mode 100644
index 0000000..509a210
--- /dev/null
+++ b/odb-tests/common/changelog/model-mssql.xml
@@ -0,0 +1,56 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mssql" version="1">
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="true" default="0" options="DUMMY=1"/>
+ <column name="v_x" type="INT" null="false"/>
+ <column name="v_y" type="INT" null="false"/>
+ <column name="o1_x" type="INT" null="true"/>
+ <column name="o1_y" type="INT" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1_x"/>
+ <column name="o1_y"/>
+ <references table="object1">
+ <column name="id_x"/>
+ <column name="id_y"/>
+ </references>
+ </foreign-key>
+ <index name="num_i" type="UNIQUE" method="BTREE" options="DUMMY=1">
+ <column name="num" options="DESC"/>
+ </index>
+ <index name="v_i">
+ <column name="v_x"/>
+ <column name="v_y"/>
+ </index>
+ </table>
+ <table name="object_nums" kind="container">
+ <column name="object_id" type="INT" null="false"/>
+ <column name="index" type="BIGINT" null="false"/>
+ <column name="value" type="INT" null="false"/>
+ <foreign-key name="object_nums_object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id_x" type="INT" null="false"/>
+ <column name="id_y" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key>
+ <column name="id_x"/>
+ <column name="id_y"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/model-mysql-diff.xml b/odb-tests/common/changelog/model-mysql-diff.xml
new file mode 120000
index 0000000..9100280
--- /dev/null
+++ b/odb-tests/common/changelog/model-mysql-diff.xml
@@ -0,0 +1 @@
+model-mysql.xml \ No newline at end of file
diff --git a/odb-tests/common/changelog/model-mysql-patch.xml b/odb-tests/common/changelog/model-mysql-patch.xml
new file mode 120000
index 0000000..9100280
--- /dev/null
+++ b/odb-tests/common/changelog/model-mysql-patch.xml
@@ -0,0 +1 @@
+model-mysql.xml \ No newline at end of file
diff --git a/odb-tests/common/changelog/model-mysql.xml b/odb-tests/common/changelog/model-mysql.xml
new file mode 100644
index 0000000..ffbcf8d
--- /dev/null
+++ b/odb-tests/common/changelog/model-mysql.xml
@@ -0,0 +1,56 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="mysql" version="1">
+ <model version="1">
+ <table name="object" options="ENGINE=InnoDB" kind="object">
+ <column name="id" type="INT" null="false"/>
+ <column name="num" type="INT" null="true" default="0" options="DUMMY=1"/>
+ <column name="v_x" type="INT" null="false"/>
+ <column name="v_y" type="INT" null="false"/>
+ <column name="o1_x" type="INT" null="true"/>
+ <column name="o1_y" type="INT" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1_x"/>
+ <column name="o1_y"/>
+ <references table="object1">
+ <column name="id_x"/>
+ <column name="id_y"/>
+ </references>
+ </foreign-key>
+ <index name="num_i" type="UNIQUE" method="BTREE" options="DUMMY=1">
+ <column name="num" options="DESC"/>
+ </index>
+ <index name="v_i">
+ <column name="v_x"/>
+ <column name="v_y"/>
+ </index>
+ </table>
+ <table name="object_nums" options="ENGINE=InnoDB" kind="container">
+ <column name="object_id" type="INT" null="false"/>
+ <column name="index" type="BIGINT UNSIGNED" null="false"/>
+ <column name="value" type="INT" null="false"/>
+ <foreign-key name="object_nums_object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ <table name="object1" options="ENGINE=InnoDB" kind="object">
+ <column name="id_x" type="INT" null="false"/>
+ <column name="id_y" type="INT" null="false"/>
+ <column name="num" type="INT" null="false"/>
+ <primary-key>
+ <column name="id_x"/>
+ <column name="id_y"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/model-oracle-diff.xml b/odb-tests/common/changelog/model-oracle-diff.xml
new file mode 120000
index 0000000..36e4479
--- /dev/null
+++ b/odb-tests/common/changelog/model-oracle-diff.xml
@@ -0,0 +1 @@
+model-oracle.xml \ No newline at end of file
diff --git a/odb-tests/common/changelog/model-oracle-patch.xml b/odb-tests/common/changelog/model-oracle-patch.xml
new file mode 120000
index 0000000..36e4479
--- /dev/null
+++ b/odb-tests/common/changelog/model-oracle-patch.xml
@@ -0,0 +1 @@
+model-oracle.xml \ No newline at end of file
diff --git a/odb-tests/common/changelog/model-oracle.xml b/odb-tests/common/changelog/model-oracle.xml
new file mode 100644
index 0000000..1824690
--- /dev/null
+++ b/odb-tests/common/changelog/model-oracle.xml
@@ -0,0 +1,56 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="oracle" version="1">
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="true" default="0" options="DUMMY=1"/>
+ <column name="v_x" type="NUMBER(10)" null="false"/>
+ <column name="v_y" type="NUMBER(10)" null="false"/>
+ <column name="o1_x" type="NUMBER(10)" null="true"/>
+ <column name="o1_y" type="NUMBER(10)" null="true"/>
+ <primary-key auto="true" sequence="object_seq">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="object_o1_fk" deferrable="DEFERRED">
+ <column name="o1_x"/>
+ <column name="o1_y"/>
+ <references table="object1">
+ <column name="id_x"/>
+ <column name="id_y"/>
+ </references>
+ </foreign-key>
+ <index name="object_num_i" type="UNIQUE" method="BTREE" options="DUMMY=1">
+ <column name="num" options="DESC"/>
+ </index>
+ <index name="object_v_i">
+ <column name="v_x"/>
+ <column name="v_y"/>
+ </index>
+ </table>
+ <table name="object_nums" kind="container">
+ <column name="object_id" type="NUMBER(10)" null="false"/>
+ <column name="index" type="NUMBER(20)" null="false"/>
+ <column name="value" type="NUMBER(10)" null="false"/>
+ <foreign-key name="object_nums_object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_nums_object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="object_nums_index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id_x" type="NUMBER(10)" null="false"/>
+ <column name="id_y" type="NUMBER(10)" null="false"/>
+ <column name="num" type="NUMBER(10)" null="false"/>
+ <primary-key>
+ <column name="id_x"/>
+ <column name="id_y"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/model-pgsql-diff.xml b/odb-tests/common/changelog/model-pgsql-diff.xml
new file mode 120000
index 0000000..b39ce26
--- /dev/null
+++ b/odb-tests/common/changelog/model-pgsql-diff.xml
@@ -0,0 +1 @@
+model-pgsql.xml \ No newline at end of file
diff --git a/odb-tests/common/changelog/model-pgsql-patch.xml b/odb-tests/common/changelog/model-pgsql-patch.xml
new file mode 120000
index 0000000..b39ce26
--- /dev/null
+++ b/odb-tests/common/changelog/model-pgsql-patch.xml
@@ -0,0 +1 @@
+model-pgsql.xml \ No newline at end of file
diff --git a/odb-tests/common/changelog/model-pgsql.xml b/odb-tests/common/changelog/model-pgsql.xml
new file mode 100644
index 0000000..13aee6b
--- /dev/null
+++ b/odb-tests/common/changelog/model-pgsql.xml
@@ -0,0 +1,56 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" version="1">
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="true" default="0" options="DUMMY=1"/>
+ <column name="v_x" type="INTEGER" null="false"/>
+ <column name="v_y" type="INTEGER" null="false"/>
+ <column name="o1_x" type="INTEGER" null="true"/>
+ <column name="o1_y" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1_x"/>
+ <column name="o1_y"/>
+ <references table="object1">
+ <column name="id_x"/>
+ <column name="id_y"/>
+ </references>
+ </foreign-key>
+ <index name="object_num_i" type="UNIQUE" method="BTREE" options="DUMMY=1">
+ <column name="num" options="DESC"/>
+ </index>
+ <index name="object_v_i">
+ <column name="v_x"/>
+ <column name="v_y"/>
+ </index>
+ </table>
+ <table name="object_nums" kind="container">
+ <column name="object_id" type="INTEGER" null="false"/>
+ <column name="index" type="BIGINT" null="false"/>
+ <column name="value" type="INTEGER" null="false"/>
+ <foreign-key name="object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_nums_object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="object_nums_index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id_x" type="INTEGER" null="false"/>
+ <column name="id_y" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id_x"/>
+ <column name="id_y"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/model-sqlite-diff.xml b/odb-tests/common/changelog/model-sqlite-diff.xml
new file mode 120000
index 0000000..3454f51
--- /dev/null
+++ b/odb-tests/common/changelog/model-sqlite-diff.xml
@@ -0,0 +1 @@
+model-sqlite.xml \ No newline at end of file
diff --git a/odb-tests/common/changelog/model-sqlite-patch.xml b/odb-tests/common/changelog/model-sqlite-patch.xml
new file mode 120000
index 0000000..3454f51
--- /dev/null
+++ b/odb-tests/common/changelog/model-sqlite-patch.xml
@@ -0,0 +1 @@
+model-sqlite.xml \ No newline at end of file
diff --git a/odb-tests/common/changelog/model-sqlite.xml b/odb-tests/common/changelog/model-sqlite.xml
new file mode 100644
index 0000000..d0199ed
--- /dev/null
+++ b/odb-tests/common/changelog/model-sqlite.xml
@@ -0,0 +1,56 @@
+<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
+ <model version="1">
+ <table name="object" kind="object">
+ <column name="id" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="true" default="0" options="DUMMY=1"/>
+ <column name="v_x" type="INTEGER" null="false"/>
+ <column name="v_y" type="INTEGER" null="false"/>
+ <column name="o1_x" type="INTEGER" null="true"/>
+ <column name="o1_y" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
+ </primary-key>
+ <foreign-key name="o1_fk" deferrable="DEFERRED">
+ <column name="o1_x"/>
+ <column name="o1_y"/>
+ <references table="object1">
+ <column name="id_x"/>
+ <column name="id_y"/>
+ </references>
+ </foreign-key>
+ <index name="object_num_i" type="UNIQUE" method="BTREE" options="DUMMY=1">
+ <column name="num" options="DESC"/>
+ </index>
+ <index name="object_v_i">
+ <column name="v_x"/>
+ <column name="v_y"/>
+ </index>
+ </table>
+ <table name="object_nums" kind="container">
+ <column name="object_id" type="INTEGER" null="false"/>
+ <column name="index" type="INTEGER" null="false"/>
+ <column name="value" type="INTEGER" null="false"/>
+ <foreign-key name="object_id_fk" on-delete="CASCADE">
+ <column name="object_id"/>
+ <references table="object">
+ <column name="id"/>
+ </references>
+ </foreign-key>
+ <index name="object_nums_object_id_i">
+ <column name="object_id"/>
+ </index>
+ <index name="object_nums_index_i">
+ <column name="index"/>
+ </index>
+ </table>
+ <table name="object1" kind="object">
+ <column name="id_x" type="INTEGER" null="false"/>
+ <column name="id_y" type="INTEGER" null="false"/>
+ <column name="num" type="INTEGER" null="false"/>
+ <primary-key>
+ <column name="id_x"/>
+ <column name="id_y"/>
+ </primary-key>
+ </table>
+ </model>
+</changelog>
diff --git a/odb-tests/common/changelog/model.hxx b/odb-tests/common/changelog/model.hxx
new file mode 100644
index 0000000..aa8891a
--- /dev/null
+++ b/odb-tests/common/changelog/model.hxx
@@ -0,0 +1,46 @@
+// file : common/changelog/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_HXX
+#define MODEL_HXX
+
+#include <vector>
+
+#pragma db model version(1, 1, open)
+
+#pragma db value
+struct value
+{
+ int x;
+ int y;
+};
+
+struct object1;
+
+#pragma db object
+struct object
+{
+ #pragma db id auto
+ int id;
+
+ #pragma db null default(0) options("DUMMY=1")
+ int num;
+
+ #pragma db index unique member(num, "DESC") method("BTREE") options("DUMMY=1")
+
+ #pragma db index
+ value v; // Multi-column.
+
+ std::vector<int> nums;
+ object1* o1;
+};
+
+#pragma db object
+struct object1
+{
+ #pragma db id
+ value id; // Multi-column.
+ int num;
+};
+
+#endif // MODEL_HXX
diff --git a/odb-tests/common/changelog/testscript b/odb-tests/common/changelog/testscript
new file mode 100644
index 0000000..9368938
--- /dev/null
+++ b/odb-tests/common/changelog/testscript
@@ -0,0 +1,66 @@
+# file : common/changelog/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+headers = [paths] $path_search($src_base/*.hxx)
+
+odb_options = --generate-schema-only \
+ --schema-format sql \
+ --suppress-migration \
+ --options-file $out_base/odb.options
+
+: mysql
+:
+if $mysql
+{
+ odb_options += --database 'mysql' --changelog-dir $~
+
+ for h: $headers
+ n = $base($leaf($h))
+
+ $* $odb_options -DBVER=1 -DCVER=1 --init-changelog $h &$(n).xml &$(n).sql
+
+ $* $odb_options -DBVER=1 -DCVER=2 $h
+ diff $src_base/$n-mysql-diff.xml $(n).xml
+
+ $* $odb_options -DBVER=2 -DCVER=3 $h
+ diff $src_base/$n-mysql-patch.xml $(n).xml
+ end
+}
+
+: sqlite
+:
+if $sqlite
+{
+ odb_options += --database 'sqlite' --changelog-dir $~
+
+ for h: $headers
+ n = $base($leaf($h))
+
+ $* $odb_options -DBVER=1 -DCVER=1 --init-changelog $h &$(n).xml &$(n).sql
+
+ $* $odb_options -DBVER=1 -DCVER=2 $h
+ diff $src_base/$n-sqlite-diff.xml $(n).xml
+
+ $* $odb_options -DBVER=2 -DCVER=3 $h
+ diff $src_base/$n-sqlite-patch.xml $(n).xml
+ end
+}
+
+: pgsql
+:
+if $pgsql
+{
+ odb_options += --database 'pgsql' --changelog-dir $~
+
+ for h: $headers
+ n = $base($leaf($h))
+
+ $* $odb_options -DBVER=1 -DCVER=1 --init-changelog $h &$(n).xml &$(n).sql
+
+ $* $odb_options -DBVER=1 -DCVER=2 $h
+ diff $src_base/$n-pgsql-diff.xml $(n).xml
+
+ $* $odb_options -DBVER=2 -DCVER=3 $h
+ diff $src_base/$n-pgsql-patch.xml $(n).xml
+ end
+}
diff --git a/odb-tests/common/circular/multiple/.gitignore b/odb-tests/common/circular/multiple/.gitignore
new file mode 100644
index 0000000..5d39d39
--- /dev/null
+++ b/odb-tests/common/circular/multiple/.gitignore
@@ -0,0 +1,6 @@
+# ODB-generated files.
+#
+test1-odb.?xx
+test1-odb-*.?xx
+test2-odb.?xx
+test2-odb-*.?xx
diff --git a/odb-tests/common/circular/multiple/buildfile b/odb-tests/common/circular/multiple/buildfile
new file mode 100644
index 0000000..b060cb5
--- /dev/null
+++ b/odb-tests/common/circular/multiple/buildfile
@@ -0,0 +1,49 @@
+# file : common/circular/multiple/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+hs = test1 test2
+
+exe{driver}: {hxx cxx}{* -*-odb -*-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: $hs
+{
+ exe{driver}: {hxx ixx cxx}{$h-odb}
+
+ <{hxx ixx cxx}{$h-odb}>: hxx{$h} libue{test-meta}
+
+ for db: $databases
+ {
+ exe{driver}: {hxx ixx cxx}{$h-odb-$db}: include = $multi
+ <{hxx ixx cxx}{$h-odb-$db}>: hxx{$h} libue{test-meta}
+ }
+}
+
+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 circular_m_ \
+ --generate-schema \
+ --generate-query \
+ --schema-format embedded
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/circular/multiple/driver.cxx b/odb-tests/common/circular/multiple/driver.cxx
new file mode 100644
index 0000000..4887ac2
--- /dev/null
+++ b/odb-tests/common/circular/multiple/driver.cxx
@@ -0,0 +1,69 @@
+// file : common/circular/multiple/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test cases of circular dependencies between persistent classes, multiple
+// files version.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/connection.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test1.hxx"
+#include "test2.hxx"
+
+#include "test2-odb.hxx"
+#include "test1-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));
+
+ // Create the database schema.
+ //
+ {
+ connection_ptr c (db->connection ());
+
+ // Temporarily disable foreign key constraints for MySQL and SQLite.
+ // For these databases this is the only way to drop circularly-
+ // dependant tables.
+ //
+ if (db->id () == odb::id_mysql)
+ c->execute ("SET FOREIGN_KEY_CHECKS=0");
+ else if (db->id () == odb::id_sqlite)
+ c->execute ("PRAGMA foreign_keys=OFF");
+
+ transaction t (c->begin ());
+ schema_catalog::create_schema (*db);
+ t.commit ();
+
+ if (db->id () == odb::id_mysql)
+ c->execute ("SET FOREIGN_KEY_CHECKS=1");
+ else if (db->id () == odb::id_sqlite)
+ c->execute ("PRAGMA foreign_keys=ON");
+ }
+
+ query<base> bq (query<base>::d->id != 0);
+ query<derived> dq (query<derived>::b->id != 0);
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/circular/multiple/test1.hxx b/odb-tests/common/circular/multiple/test1.hxx
new file mode 100644
index 0000000..36df963
--- /dev/null
+++ b/odb-tests/common/circular/multiple/test1.hxx
@@ -0,0 +1,27 @@
+// file : common/circular/multiple/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#include <odb/core.hxx>
+
+#pragma db object
+struct derived;
+
+#pragma db object polymorphic
+struct base
+{
+ virtual ~base () {}
+
+ #pragma db id
+ unsigned long id_;
+
+ derived* d_;
+};
+
+#ifdef ODB_COMPILER
+# include "test2.hxx"
+#endif
+
+#endif // TEST1_HXX
diff --git a/odb-tests/common/circular/multiple/test2.hxx b/odb-tests/common/circular/multiple/test2.hxx
new file mode 100644
index 0000000..49e9ed2
--- /dev/null
+++ b/odb-tests/common/circular/multiple/test2.hxx
@@ -0,0 +1,17 @@
+// file : common/circular/multiple/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#include <odb/core.hxx>
+
+#include "test1.hxx"
+
+#pragma db object
+struct derived: base
+{
+ base* b_;
+};
+
+#endif // TEST2_HXX
diff --git a/odb-tests/common/circular/multiple/testscript b/odb-tests/common/circular/multiple/testscript
new file mode 100644
index 0000000..6a05dc6
--- /dev/null
+++ b/odb-tests/common/circular/multiple/testscript
@@ -0,0 +1,31 @@
+# file : common/circular/multiple/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $*
+}
diff --git a/odb-tests/common/circular/single/buildfile b/odb-tests/common/circular/single/buildfile
new file mode 100644
index 0000000..740ce91
--- /dev/null
+++ b/odb-tests/common/circular/single/buildfile
@@ -0,0 +1,41 @@
+# file : common/circular/single/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 circular_s_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/circular/single/driver.cxx b/odb-tests/common/circular/single/driver.cxx
new file mode 100644
index 0000000..9bcb135
--- /dev/null
+++ b/odb-tests/common/circular/single/driver.cxx
@@ -0,0 +1,40 @@
+// file : common/circular/single/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test cases of circular dependencies between persistent classes, single
+// file version.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ query<base> bq (query<base>::d->id != 0);
+ query<derived> dq (query<derived>::b->id != 0);
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/circular/single/test.hxx b/odb-tests/common/circular/single/test.hxx
new file mode 100644
index 0000000..7f95dea
--- /dev/null
+++ b/odb-tests/common/circular/single/test.hxx
@@ -0,0 +1,28 @@
+// file : common/circular/single/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <odb/core.hxx>
+
+struct derived;
+
+#pragma db object polymorphic
+struct base
+{
+ virtual ~base () {}
+
+ #pragma db id
+ unsigned long id_;
+
+ derived* d_;
+};
+
+#pragma db object
+struct derived: base
+{
+ base* b_;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/circular/single/testscript b/odb-tests/common/circular/single/testscript
new file mode 100644
index 0000000..b870306
--- /dev/null
+++ b/odb-tests/common/circular/single/testscript
@@ -0,0 +1,33 @@
+# file : common/circular/single/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/composite/buildfile b/odb-tests/common/composite/buildfile
new file mode 100644
index 0000000..0a60638
--- /dev/null
+++ b/odb-tests/common/composite/buildfile
@@ -0,0 +1,41 @@
+# file : common/composite/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_comp_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/composite/driver.cxx b/odb-tests/common/composite/driver.cxx
new file mode 100644
index 0000000..06b24b2
--- /dev/null
+++ b/odb-tests/common/composite/driver.cxx
@@ -0,0 +1,229 @@
+// file : common/composite/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test composite value types.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+using namespace std;
+using namespace odb::core;
+
+#undef NDEBUG
+#include <cassert>
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv));
+
+ // Test basic composite functionality.
+ //
+ for (unsigned short i (0); i < 2; ++i)
+ {
+ using namespace test1;
+
+ person p (1);
+ p.name_.first = "Joe";
+ p.name_.last = "Dirt";
+ p.name_.title = "Mr";
+ p.name_.alias.first = "Anthony";
+ p.name_.alias.last = "Clean";
+ p.name_.nick = "Squeaky";
+ p.name_.flags.nick = true;
+ p.name_.flags.alias = false;
+ p.age_ = 32;
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (p);
+ t.commit ();
+ }
+
+ // load & check
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<person> p1 (db->load<person> (1));
+ t.commit ();
+
+ assert (p == *p1);
+ }
+
+ p.name_.title = "Mrs";
+ p.name_.alias.first = "Anthonia";
+ p.name_.flags.nick = false;
+ p.name_.flags.alias = true;
+
+ // update
+ //
+ {
+ transaction t (db->begin ());
+ db->update (p);
+ t.commit ();
+ }
+
+ // load & check
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<person> p1 (db->load<person> (1));
+ t.commit ();
+
+ assert (p == *p1);
+ }
+
+ typedef odb::query<person> query;
+ typedef odb::result<person> result;
+
+ // query
+ //
+ {
+ transaction t (db->begin ());
+
+ result r (db->query<person> (query::name.first == "Joe"));
+
+ assert (!r.empty ());
+ assert (*r.begin () == p);
+ assert (size (r) == 1);
+
+ t.commit ();
+ }
+
+ // query
+ //
+ {
+ transaction t (db->begin ());
+
+ result r (db->query<person> (query::name.flags.alias));
+
+ assert (!r.empty ());
+ assert (*r.begin () == p);
+ assert (size (r) == 1);
+
+ t.commit ();
+ }
+
+ // erase
+ //
+ if (i == 0)
+ {
+ transaction t (db->begin ());
+ db->erase<person> (1);
+ t.commit ();
+ }
+ }
+
+ // Test composite class template instantiation.
+ //
+ {
+ using namespace test2;
+
+ object o (1);
+
+ o.comp_.num = 123;
+ o.comp_.str = "abc";
+ o.comp_.vec.push_back (int_str_pair (123, "abc"));
+ o.comp_.vec.push_back (int_str_pair (234, "bcd"));
+ o.comp_.vec.push_back (int_str_pair (345, "cde"));
+
+ o.pair_.first = 123;
+ o.pair_.second = "abc";
+
+ o.vec_.push_back (int_str_pair (123, "abc"));
+ o.vec_.push_back (int_str_pair (234, "bcd"));
+ o.vec_.push_back (int_str_pair (345, "cde"));
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ // load & check
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o1 (db->load<object> (1));
+ t.commit ();
+
+ assert (o == *o1);
+ }
+ }
+
+ // Test empty column name.
+ //
+ {
+ using namespace test3;
+
+ object o (1);
+ o.c_.str = "abc";
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ // load & check
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o1 (db->load<object> (1));
+ t.commit ();
+
+ assert (o == *o1);
+ }
+ }
+
+ // Test composite definition inside object.
+ {
+ using namespace test4;
+
+ object o (1);
+ o.str ("abc");
+ o.x (123);
+ o.y (234);
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ // load & check
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o1 (db->load<object> (1));
+ t.commit ();
+
+ assert (o == *o1);
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/composite/test.hxx b/odb-tests/common/composite/test.hxx
new file mode 100644
index 0000000..13b2025
--- /dev/null
+++ b/odb-tests/common/composite/test.hxx
@@ -0,0 +1,250 @@
+// file : common/composite/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <vector>
+#include <utility> // std::pair
+
+#include <odb/core.hxx>
+
+// Test basic composite functionality.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ #pragma db value
+ struct name
+ {
+ std::string first;
+ std::string last;
+ };
+
+ #pragma db value
+ struct name_title
+ {
+ std::string title;
+ };
+
+ #pragma db value
+ struct name_title_ex: name_title
+ {
+ // Test value types without data members.
+ };
+
+ #pragma db value
+ struct name_flags
+ {
+ bool nick;
+ bool alias;
+ };
+
+ #pragma db value
+ struct name_ex: name, name_title_ex
+ {
+ name alias;
+ std::string nick;
+
+ #pragma db column("show_")
+ name_flags flags;
+ };
+
+ #pragma db object
+ struct person
+ {
+ person () {}
+ person (unsigned long id): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db column("")
+ name_ex name_;
+
+ unsigned short age_;
+ };
+
+ inline bool
+ operator== (const person& x, const person& y)
+ {
+ return x.id_ == y.id_ &&
+ x.name_.first == y.name_.first&&
+ x.name_.last == y.name_.last &&
+ x.name_.title == y.name_.title &&
+ x.name_.alias.first == y.name_.alias.first &&
+ x.name_.alias.last == y.name_.alias.last &&
+ x.name_.nick == y.name_.nick &&
+ x.name_.flags.nick == y.name_.flags.nick &&
+ x.name_.flags.alias == y.name_.flags.alias &&
+ x.age_ == y.age_;
+ }
+}
+
+// Test composite class template instantiation.
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ template <typename I, typename S>
+ struct comp
+ {
+ I num;
+ S str;
+ std::vector<std::pair<I, S> > vec;
+ };
+
+ template <typename I, typename S>
+ inline bool
+ operator== (const comp<I, S>& x, const comp<I, S>& y)
+ {
+ return x.num == y.num && x.str == y.str && x.vec == y.vec;
+ }
+
+ typedef std::pair<int, std::string> int_str_pair;
+ #pragma db value(int_str_pair)
+
+ // Make sure we use the name that was specified in the pragma.
+ //
+#ifdef ODB_COMPILER
+ typedef comp<int, std::string> int_str_comp1;
+#endif
+
+ typedef comp<int, std::string> int_str_comp;
+ #pragma db value(int_str_comp)
+
+ #pragma db object
+ struct object
+ {
+ object () {}
+ object (unsigned long id): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ comp<int, std::string> comp_;
+ std::pair<int, std::string> pair_;
+ std::vector<int_str_pair> vec_;
+ };
+
+ inline bool
+ operator== (const object& x, const object& y)
+ {
+ return x.id_ == y.id_ &&
+ x.comp_ == y.comp_ &&
+ x.pair_ == y.pair_ &&
+ x.vec_ == y.vec_;
+ }
+}
+
+// Test empty column name.
+//
+#pragma db namespace table("t3_")
+namespace test3
+{
+ #pragma db value
+ struct comp
+ {
+ #pragma db column("")
+ std::string str;
+ };
+
+ #pragma db object
+ struct object
+ {
+ object () {}
+ object (unsigned long id): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ comp c_;
+ };
+
+ inline bool
+ operator== (const object& x, const object& y)
+ {
+ return x.id_ == y.id_ && x.c_.str == y.c_.str;
+ }
+}
+
+// Test composite definition inside object.
+//
+#pragma db namespace table("t4_")
+namespace test4
+{
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ unsigned long id () const {return id_;}
+
+ void str (const std::string& s) {c_.str = s;}
+ const std::string& str () const {return c_.str;}
+
+ void x (int i) {p_.first = i;}
+ int x () const {return p_.first;}
+
+ void y (int i) {p_.second = i;}
+ int y () const {return p_.second;}
+
+ private:
+ friend class odb::access;
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db value
+ struct comp
+ {
+ std::string str;
+ };
+
+ comp c_;
+
+ typedef std::pair<int, int> int_pair;
+ #pragma db value(int_pair)
+
+ int_pair p_;
+ };
+
+ inline bool
+ operator== (const object& x, const object& y)
+ {
+ return x.id () == y.id () && x.str () == y.str () &&
+ x.x () == y.x () && x.y () == y.y ();
+ }
+}
+
+// Test composite name clashes in query columns (compilation test)
+//
+#pragma db namespace table("t5_")
+namespace test5
+{
+ // Class-member conflict.
+ //
+ #pragma db value
+ struct value {int value_;};
+
+ // Class-class conflict.
+ //
+ #pragma db value
+ struct inner {int value;};
+
+ #pragma db value
+ struct outer {inner value;};
+
+ #pragma db object
+ struct object
+ {
+ #pragma db id
+ int id;
+
+ outer value;
+ test5::value v;
+ };
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/composite/testscript b/odb-tests/common/composite/testscript
new file mode 100644
index 0000000..0747507
--- /dev/null
+++ b/odb-tests/common/composite/testscript
@@ -0,0 +1,33 @@
+# file : common/composite/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/const-member/buildfile b/odb-tests/common/const-member/buildfile
new file mode 100644
index 0000000..868f7fd
--- /dev/null
+++ b/odb-tests/common/const-member/buildfile
@@ -0,0 +1,40 @@
+# file : common/const-member/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 constm_ \
+ --generate-schema
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/const-member/driver.cxx b/odb-tests/common/const-member/driver.cxx
new file mode 100644
index 0000000..0c71dfa
--- /dev/null
+++ b/odb-tests/common/const-member/driver.cxx
@@ -0,0 +1,119 @@
+// file : common/const-member/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test const data members. The readonly test tests that const
+// members are automatically treated as read-only.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ // Const ids.
+ //
+ {
+ const_id o (1);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->load<const_id> (1, o);
+ t.commit ();
+ assert (o.id == 1);
+ }
+ }
+
+ {
+ {
+ const_auto_id o;
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ assert (o.id == 1);
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<const_auto_id> o (db->load<const_auto_id> (1));
+ t.commit ();
+ assert (o->id == 1);
+ }
+ }
+
+ // Container.
+ //
+ {
+ container o (1, 1);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<container> o (db->load<container> (1));
+ t.commit ();
+
+ assert (o->ccom.vec.size () == 1 && o->ccom.vec[0] == 1 &&
+ o->ccom.cvec.size () == 1 && o->ccom.cvec[0] == 1 &&
+ o->cvec.size () == 1 && o->cvec[0] == 1);
+ }
+ }
+
+ // Wrapper.
+ //
+ {
+ wrapper o (1, "abc", 1);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<wrapper> o (db->load<wrapper> (1));
+ t.commit ();
+
+ assert (*o->str == "abc" &&
+ o->com->str == "abc" && o->com->num == 1 &&
+ o->com->vec.size () == 1 && o->com->vec[0] == 1 &&
+ o->vec->size () == 1 && (*o->vec)[0] == 1);
+ }
+ }
+
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/const-member/test.hxx b/odb-tests/common/const-member/test.hxx
new file mode 100644
index 0000000..ab75c55
--- /dev/null
+++ b/odb-tests/common/const-member/test.hxx
@@ -0,0 +1,109 @@
+// file : common/const-member/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <vector>
+#include <string>
+#include <memory> // std::auto_ptr
+
+#include <odb/core.hxx>
+
+// Const ids.
+//
+#pragma db object
+struct const_id
+{
+ const_id (unsigned long i): id (i) {}
+ const_id (): id (0) {}
+
+ #pragma db id
+ const unsigned long id;
+};
+
+#pragma db object
+struct const_auto_id
+{
+ const_auto_id (): id (0) {}
+
+ #pragma db id auto
+ const unsigned long id;
+};
+
+// Container.
+//
+#pragma db value
+struct container_value
+{
+ container_value (unsigned long x)
+ {
+ vec.push_back (x);
+ const_cast<std::vector<unsigned long>&> (cvec).push_back (x);
+ }
+
+ container_value () {}
+
+ std::vector<unsigned long> vec;
+ const std::vector<unsigned long> cvec;
+};
+
+#pragma db object
+struct container
+{
+ container (unsigned long i, unsigned long x)
+ : id (i), ccom (x)
+ {
+ const_cast<std::vector<unsigned long>&> (cvec).push_back (x);
+ }
+
+ container () {}
+
+ #pragma db id
+ unsigned long id;
+
+ const container_value ccom;
+ const std::vector<unsigned long> cvec;
+};
+
+// Wrapper.
+//
+#pragma db value
+struct wrapped_value
+{
+ wrapped_value (const std::string& s, unsigned long n)
+ : str (s), num (n)
+ {
+ vec.push_back (n);
+ }
+
+ wrapped_value () {}
+
+ const std::string str;
+ unsigned long num;
+ std::vector<unsigned long> vec;
+};
+
+#pragma db object
+struct wrapper
+{
+ wrapper (unsigned long i, const std::string& s, unsigned long n)
+ : id (i),
+ str (new std::string (s)),
+ com (new wrapped_value (s, n)),
+ vec (new std::vector<unsigned long>)
+ {
+ const_cast<std::vector<unsigned long>&> (*vec).push_back (n);
+ }
+
+ wrapper () {}
+
+ #pragma db id
+ unsigned long id;
+
+ const std::unique_ptr<const std::string> str;
+ const std::unique_ptr<const wrapped_value> com;
+ const std::unique_ptr<const std::vector<unsigned long>> vec;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/const-member/testscript b/odb-tests/common/const-member/testscript
new file mode 100644
index 0000000..c81d856
--- /dev/null
+++ b/odb-tests/common/const-member/testscript
@@ -0,0 +1,33 @@
+# file : common/const-member/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/const-object/buildfile b/odb-tests/common/const-object/buildfile
new file mode 100644
index 0000000..853c831
--- /dev/null
+++ b/odb-tests/common/const-object/buildfile
@@ -0,0 +1,41 @@
+# file : common/const-object/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 consto_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/const-object/driver.cxx b/odb-tests/common/const-object/driver.cxx
new file mode 100644
index 0000000..7ef48ee
--- /dev/null
+++ b/odb-tests/common/const-object/driver.cxx
@@ -0,0 +1,216 @@
+// file : common/const-object/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test database operations with const objects.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/session.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ aggr a (1);
+ aggr ca_ (2); // o1 and o2 are NULL
+ const aggr& ca (ca_);
+
+ obj1* o1 (new obj1 (1));
+ obj1* co1_ (new obj1 (2));
+ const obj1* co1 (co1_);
+ a.o1 = co1;
+
+ unique_ptr<obj2> o2 (new obj2 (1));
+ obj2* co2_ (new obj2 (2));
+ a.o2.reset (co2_);
+ unique_ptr<const obj2>& co2 (a.o2);
+
+ // persist via references
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (*o1);
+ db->persist (*co1);
+ db->persist (*o2);
+ db->persist (*co2);
+ db->persist (a);
+ db->persist (ca);
+ t.commit ();
+ }
+
+ // persist via pointers
+ //
+ o1->id += 2;
+ co1_->id += 2;
+ o2->id += 2;
+ co2_->id += 2;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (co1);
+ db->persist (o2);
+ db->persist (co2);
+ t.commit ();
+ }
+
+ // load & compare
+ //
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<aggr> a (db->load<aggr> (1));
+ unique_ptr<const aggr> ca (db->load<aggr> (2));
+
+ t.commit ();
+
+ assert (a->o1->id == 2);
+ assert (a->o2->id == 2);
+
+ assert (ca->o1 == 0);
+ assert (ca->o2.get () == 0);
+ }
+
+ // update via references
+ //
+ {
+ transaction t (db->begin ());
+ db->update (*o1);
+ db->update (*co1);
+ db->update (*o2);
+ db->update (*co2);
+ db->update (a);
+ db->update (ca);
+ t.commit ();
+ }
+
+ // update via pointers
+ //
+ {
+ transaction t (db->begin ());
+ db->update (o1);
+ db->update (co1);
+ db->update (o2);
+ db->update (co2);
+ t.commit ();
+ }
+
+ // query
+ //
+ typedef odb::query<obj1> query1;
+ typedef odb::query<obj2> query2;
+
+ typedef odb::result<const obj1> result1;
+ typedef odb::result<const obj2> result2;
+
+ {
+ transaction t (db->begin ());
+ result1 r1 (db->query<obj1> (query1::id < 3));
+ // odb::result<obj1> ur (r1); // error
+ size_t n1 (0);
+
+ for (result1::iterator i (r1.begin ()); i != r1.end (); ++i)
+ {
+ // i->f (); // error
+ i->cf ();
+ // obj1* p (i.load ()); // error
+ const obj1* p (i.load ());
+ obj1 o (0);
+ i.load (o);
+ assert (p->id == o.id);
+ delete p;
+ n1++;
+ }
+
+ assert (n1 == 2);
+
+ result2 r2 (db->query<obj2> (query2::id < 3));
+ size_t n2 (0);
+
+ for (result2::iterator i (r2.begin ()); i != r2.end (); ++i)
+ {
+ // i->f (); // error
+ i->cf ();
+ //unique_ptr<obj2> p (i.load ()); // error
+ unique_ptr<const obj2> p (i.load ());
+ obj2 o (0);
+ i.load (o);
+ assert (p->id == o.id);
+ n2++;
+ }
+
+ assert (n2 == 2);
+
+ t.commit ();
+ }
+
+ // erase via references
+ //
+ {
+ transaction t (db->begin ());
+ db->erase (*o1);
+ db->erase (*co1);
+ db->erase (*o2);
+ db->erase (*co2);
+ db->erase (a);
+ db->erase (ca);
+ t.commit ();
+ }
+
+ // erase via pointers
+ //
+ o1->id -= 2;
+ co1_->id -= 2;
+ o2->id -= 2;
+ co2_->id -= 2;
+
+ {
+ transaction t (db->begin ());
+ db->erase (o1);
+ db->erase (co1);
+ db->erase (o2);
+ db->erase (co2);
+ t.commit ();
+ }
+
+ // Test session and const/non-const object handling
+ //
+ {
+ session s;
+ transaction t (db->begin ());
+
+ obj1 o1 (1);
+ const obj1& co1 (o1);
+ db->persist (co1);
+
+ assert (db->load<obj1> (1) == &o1);
+
+ t.commit ();
+ }
+
+ delete o1;
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/const-object/test.hxx b/odb-tests/common/const-object/test.hxx
new file mode 100644
index 0000000..4e66231
--- /dev/null
+++ b/odb-tests/common/const-object/test.hxx
@@ -0,0 +1,51 @@
+// file : common/const-object/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <memory>
+#include <odb/core.hxx>
+
+#pragma db object pointer (obj1*) session
+struct obj1
+{
+ obj1 () {}
+ obj1 (int i): id (i) {}
+
+ #pragma db id
+ int id;
+
+ void f () {}
+ void cf () const {}
+};
+
+#pragma db object pointer (std::unique_ptr<obj2>)
+struct obj2
+{
+ obj2 () {}
+ obj2 (int i): id (i) {}
+
+ #pragma db id
+ int id;
+
+ void f () {}
+ void cf () const {}
+};
+
+#pragma db object
+struct aggr
+{
+ aggr (int i): id (i), o1 (0) {}
+ aggr (): o1 (0) {}
+ ~aggr () {delete o1;}
+
+ #pragma db id
+ int id;
+
+ const obj1* o1;
+
+ std::unique_ptr<const obj2> o2;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/const-object/testscript b/odb-tests/common/const-object/testscript
new file mode 100644
index 0000000..3885e96
--- /dev/null
+++ b/odb-tests/common/const-object/testscript
@@ -0,0 +1,33 @@
+# file : common/const-object/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/container/basics/buildfile b/odb-tests/common/container/basics/buildfile
new file mode 100644
index 0000000..f83444e
--- /dev/null
+++ b/odb-tests/common/container/basics/buildfile
@@ -0,0 +1,40 @@
+# file : common/container/basics/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_cont_bs_ \
+ --generate-schema
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/container/basics/driver.cxx b/odb-tests/common/container/basics/driver.cxx
new file mode 100644
index 0000000..14e1984
--- /dev/null
+++ b/odb-tests/common/container/basics/driver.cxx
@@ -0,0 +1,523 @@
+// file : common/container/basics/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test basic container persistence.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ for (unsigned short i (0); i < 2; ++i)
+ {
+ object empty ("empty"), med ("medium"), full ("full");
+
+ //
+ // empty
+ //
+
+ empty.num = 0;
+ empty.str = "";
+
+ // array
+ //
+ empty.na[0] = 123;
+ empty.na[1] = 234;
+ empty.na[2] = 345;
+
+ empty.sa[0] = "aaa";
+ empty.sa[1] = "bbbb";
+ empty.sa[2] = "ccccc";
+
+ empty.ca[0] = comp (123, "aaa");
+ empty.ca[1] = comp (234, "bbbb");
+ empty.ca[2] = comp (345, "ccccc");
+
+
+ //
+ // med
+ //
+
+ med.num = 999;
+ med.str = "xxx";
+
+ // vector
+ //
+ med.nv.push_back (123);
+ med.nv.push_back (234);
+
+ med.sv.push_back ("aaa");
+ med.sv.push_back ("bbbb");
+
+ med.cv.push_back (comp (123, "aaa"));
+ med.cv.push_back (comp (234, "bbbb"));
+
+ med.uv.push_back (123);
+ med.uv.push_back (234);
+
+ // list
+ //
+ med.sl.push_back ("aaa");
+ med.sl.push_back ("bbbb");
+
+ // deque
+ //
+ med.nd.push_back (123);
+ med.nd.push_back (234);
+
+ // set
+ //
+ med.ns.insert (123);
+ med.ns.insert (234);
+
+ med.ss.insert ("aaa");
+ med.ss.insert ("bbbb");
+
+ med.cs.insert (comp (123, "aaa"));
+ med.cs.insert (comp (234, "bbbb"));
+
+ // map
+ //
+ med.nsm[123] = "aaa";
+ med.nsm[234] = "bbbb";
+
+ med.snm["aaa"] = 123;
+ med.snm["bbbb"] = 234;
+
+ med.ncm[123] = comp (123, "aaa");
+ med.ncm[234] = comp (234, "bbbb");
+
+ med.csm[comp (123, "aaa")] = "aaa";
+ med.csm[comp (234, "bbbb")] = "bbbb";
+
+ // array
+ //
+ med.na[0] = 123;
+ med.na[1] = 234;
+ med.na[2] = 345;
+
+ med.sa[0] = "aaa";
+ med.sa[1] = "bbbb";
+ med.sa[2] = "ccccc";
+
+ med.ca[0] = comp (123, "aaa");
+ med.ca[1] = comp (234, "bbbb");
+ med.ca[2] = comp (345, "ccccc");
+
+ // forward_list
+ //
+ med.nfl.push_front (234);
+ med.nfl.push_front (123);
+
+ med.sfl.push_front ("bbbb");
+ med.sfl.push_front ("aaa");
+
+ med.cfl.push_front (comp (234, "bbbb"));
+ med.cfl.push_front (comp (123, "aaa"));
+
+ // unordered_set
+ //
+ med.nus.insert (123);
+ med.nus.insert (234);
+
+ med.sus.insert ("aaa");
+ med.sus.insert ("bbbb");
+
+ med.cus.insert (comp (123, "aaa"));
+ med.cus.insert (comp (234, "bbbb"));
+
+ // unordered_map
+ //
+ med.nsum[123] = "aaa";
+ med.nsum[234] = "bbbb";
+
+ med.snum["aaa"] = 123;
+ med.snum["bbbb"] = 234;
+
+ med.ncum[123] = comp (123, "aaa");
+ med.ncum[234] = comp (234, "bbbb");
+
+ med.csum[comp (123, "aaa")] = "aaa";
+ med.csum[comp (234, "bbbb")] = "bbbb";
+
+ //
+ // full
+ //
+
+ full.num = 9999;
+ full.str = "xxxx";
+
+ // vector
+ //
+ full.nv.push_back (1234);
+ full.nv.push_back (2345);
+ full.nv.push_back (3456);
+
+ full.sv.push_back ("aaaa");
+ full.sv.push_back ("bbbbb");
+ full.sv.push_back ("cccccc");
+
+ full.cv.push_back (comp (1234, "aaaa"));
+ full.cv.push_back (comp (2345, "bbbbb"));
+ full.cv.push_back (comp (3456, "cccccc"));
+
+ full.uv.push_back (1234);
+ full.uv.push_back (2345);
+ full.uv.push_back (3456);
+
+ // list
+ //
+ full.sl.push_back ("aaaa");
+ full.sl.push_back ("bbbbb");
+ full.sl.push_back ("cccccc");
+
+ // deque
+ //
+ full.nd.push_back (1234);
+ full.nd.push_back (2345);
+ full.nd.push_back (3456);
+
+ // set
+ //
+ full.ns.insert (1234);
+ full.ns.insert (2345);
+ full.ns.insert (3456);
+
+ full.ss.insert ("aaaa");
+ full.ss.insert ("bbbbb");
+ full.ss.insert ("cccccc");
+
+ full.cs.insert (comp (1234, "aaaa"));
+ full.cs.insert (comp (2345, "bbbbb"));
+ full.cs.insert (comp (3456, "cccccc"));
+
+ // map
+ //
+ full.nsm[1234] = "aaaa";
+ full.nsm[2345] = "bbbbb";
+ full.nsm[3456] = "cccccc";
+
+ full.snm["aaaa"] = 1234;
+ full.snm["bbbbb"] = 2345;
+ full.snm["cccccc"] = 3456;
+
+ full.ncm[1234] = comp (1234, "aaaa");
+ full.ncm[2345] = comp (2345, "bbbbb");
+ full.ncm[3456] = comp (3456, "cccccc");
+
+ full.csm[comp (1234, "aaaa")] = "aaaa";
+ full.csm[comp (2345, "bbbbb")] = "bbbbb";
+ full.csm[comp (3456, "cccccc")] = "cccccc";
+
+ // array
+ //
+ full.na[0] = 123;
+ full.na[1] = 234;
+ full.na[2] = 345;
+
+ full.sa[0] = "aaa";
+ full.sa[1] = "bbbb";
+ full.sa[2] = "ccccc";
+
+ full.ca[0] = comp (123, "aaa");
+ full.ca[1] = comp (234, "bbbb");
+ full.ca[2] = comp (345, "ccccc");
+
+ // forward_list
+ //
+ full.nfl.push_front (345);
+ full.nfl.push_front (234);
+ full.nfl.push_front (123);
+
+ full.sfl.push_front ("ccccc");
+ full.sfl.push_front ("bbbb");
+ full.sfl.push_front ("aaa");
+
+ full.cfl.push_front (comp (345, "ccccc"));
+ full.cfl.push_front (comp (234, "bbbb"));
+ full.cfl.push_front (comp (123, "aaa"));
+
+ // unordered_set
+ //
+ full.nus.insert (1234);
+ full.nus.insert (2345);
+ full.nus.insert (3456);
+
+ full.sus.insert ("aaaa");
+ full.sus.insert ("bbbbb");
+ full.sus.insert ("cccccc");
+
+ full.cus.insert (comp (1234, "aaaa"));
+ full.cus.insert (comp (2345, "bbbbb"));
+ full.cus.insert (comp (3456, "cccccc"));
+
+ // unordered_map
+ //
+ full.nsum[1234] = "aaaa";
+ full.nsum[2345] = "bbbbb";
+ full.nsum[3456] = "cccccc";
+
+ full.snum["aaaa"] = 1234;
+ full.snum["bbbbb"] = 2345;
+ full.snum["cccccc"] = 3456;
+
+ full.ncum[1234] = comp (1234, "aaaa");
+ full.ncum[2345] = comp (2345, "bbbbb");
+ full.ncum[3456] = comp (3456, "cccccc");
+
+ full.csum[comp (1234, "aaaa")] = "aaaa";
+ full.csum[comp (2345, "bbbbb")] = "bbbbb";
+ full.csum[comp (3456, "cccccc")] = "cccccc";
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (empty);
+ db->persist (med);
+ db->persist (full);
+ t.commit ();
+ }
+
+ // load & check
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> e (db->load<object> ("empty"));
+ unique_ptr<object> m (db->load<object> ("medium"));
+ unique_ptr<object> f (db->load<object> ("full"));
+ t.commit ();
+
+ assert (empty == *e);
+ assert (med == *m);
+ assert (full == *f);
+ }
+
+ //
+ // empty
+ //
+
+ empty.num = 99;
+ empty.str = "xx";
+
+ empty.nv.push_back (12);
+ empty.sv.push_back ("aa");
+ empty.cv.push_back (comp (12, "aa"));
+ empty.uv.push_back (12);
+ empty.sl.push_back ("aa");
+ empty.nd.push_back (12);
+
+ empty.ns.insert (12);
+ empty.ss.insert ("aa");
+ empty.cs.insert (comp (12, "aa"));
+
+ empty.nsm[12] = "aa";
+ empty.snm["aa"] = 12;
+ empty.ncm[12] = comp (12, "aa");
+ empty.csm[comp (12, "aa")] = "aa";
+
+ empty.nfl.push_front (12);
+ empty.sfl.push_front ("aa");
+ empty.cfl.push_front (comp (12, "aa"));
+
+ empty.nus.insert (12);
+ empty.sus.insert ("aa");
+ empty.cus.insert (comp (12, "aa"));
+
+ empty.nsum[12] = "aa";
+ empty.snum["aa"] = 12;
+ empty.ncum[12] = comp (12, "aa");
+ empty.csum[comp (12, "aa")] = "aa";
+
+ //
+ // med
+ //
+
+ med.num = 0;
+ med.str = "";
+
+ med.nv.clear ();
+ med.sv.clear ();
+ med.cv.clear ();
+ med.uv.clear ();
+
+ med.sl.clear ();
+
+ med.nd.clear ();
+
+ med.ns.clear ();
+ med.ss.clear ();
+ med.cs.clear ();
+
+ med.nsm.clear ();
+ med.snm.clear ();
+ med.ncm.clear ();
+ med.csm.clear ();
+
+ med.nfl.clear ();
+ med.sfl.clear ();
+ med.cfl.clear ();
+
+ med.nus.clear ();
+ med.sus.clear ();
+ med.cus.clear ();
+
+ med.nsum.clear ();
+ med.snum.clear ();
+ med.ncum.clear ();
+ med.csum.clear ();
+
+ //
+ // full
+ //
+
+ full.num++;
+ full.str += "x";
+
+ // vector
+ //
+ full.nv.back ()++;
+ full.nv.push_back (4567);
+
+ full.sv.back () += "c";
+ full.sv.push_back ("ddddddd");
+
+ full.cv.back ().num++;
+ full.cv.back ().str += "c";
+ full.cv.push_back (comp (4567, "ddddddd"));
+
+ full.uv.back ()++;
+ full.uv.push_back (4567);
+
+ // list
+ //
+ full.sl.back () += "c";
+ full.sl.push_back ("ddddddd");
+
+ // deque
+ //
+ full.nd.push_front (456);
+
+ // set
+ //
+ full.ns.insert (4567);
+ full.ss.insert ("ddddddd");
+ full.cs.insert (comp (4567, "ddddddd"));
+
+ // map
+ //
+ full.nsm[3456] += 'c';
+ full.nsm[4567] = "ddddddd";
+
+ full.snm["cccccc"]++;
+ full.snm["ddddddd"] = 4567;
+
+ full.ncm[3456].num++;
+ full.ncm[3456].str += 'c';
+ full.ncm[4567] = comp (4567, "ddddddd");
+
+ full.csm[comp (3456, "cccccc")] += "c";
+ full.csm[comp (4567, "ddddddd")] = "ddddddd";
+
+ // array
+ //
+ full.na[0]++;
+ full.sa[0] += 'a';
+ full.ca[0].num++;
+ full.ca[0].str += 'a';
+
+ // forward_list
+ //
+ full.nfl.front ()++;
+ full.nfl.push_front (4567);
+
+ full.sfl.front () += 'a';
+ full.sfl.push_front ("ddddddd");
+
+ full.cfl.front ().num++;
+ full.cfl.front ().str += 'a';
+ full.cfl.push_front (comp (4567, "ddddddd"));
+
+ // unordered_set
+ //
+ full.nus.insert (4567);
+ full.sus.insert ("ddddddd1"); // 1 is to preserve order in VC++ 10.
+ full.cus.insert (comp (4567, "ddddddd1"));
+
+ // unordered_map
+ //
+ full.nsum[3456] += 'c';
+ full.nsum[4567] = "ddddddd";
+
+ full.snum["cccccc"]++;
+ full.snum["ddddddd1"] = 4567;
+
+ full.ncum[3456].num++;
+ full.ncum[3456].str += 'c';
+ full.ncum[4567] = comp (4567, "ddddddd");
+
+ full.csum[comp (3456, "cccccc")] += "c";
+ full.csum[comp (4567, "ddddddd1")] = "ddddddd";
+
+ // update
+ //
+ {
+ transaction t (db->begin ());
+ db->update (empty);
+ db->update (med);
+ db->update (full);
+ t.commit ();
+ }
+
+ // load & check
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> e (db->load<object> ("empty"));
+ unique_ptr<object> m (db->load<object> ("medium"));
+ unique_ptr<object> f (db->load<object> ("full"));
+ t.commit ();
+
+ assert (empty == *e);
+ assert (med == *m);
+ assert (full == *f);
+ }
+
+ // erase
+ //
+ if (i == 0)
+ {
+ transaction t (db->begin ());
+ db->erase<object> ("empty");
+ db->erase<object> ("medium");
+ db->erase<object> ("full");
+ t.commit ();
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/container/basics/test.hxx b/odb-tests/common/container/basics/test.hxx
new file mode 100644
index 0000000..e8e329e
--- /dev/null
+++ b/odb-tests/common/container/basics/test.hxx
@@ -0,0 +1,245 @@
+// file : common/container/basics/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <map>
+#include <set>
+#include <list>
+#include <vector>
+#include <deque>
+#include <array>
+#include <string>
+#include <forward_list>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <odb/core.hxx>
+
+#pragma db value
+struct comp
+{
+ comp () {}
+ comp (int n, const std::string& s) : num (n), str (s) {}
+
+ #pragma db column("number")
+ int num = 0;
+ std::string str;
+};
+
+inline bool
+operator== (const comp& x, const comp& y)
+{
+ return x.num == y.num && x.str == y.str;
+}
+
+inline bool
+operator!= (const comp& x, const comp& y)
+{
+ return !(x == y);
+}
+
+inline bool
+operator< (const comp& x, const comp& y)
+{
+ return x.num != y.num ? x.num < y.num : x.str < y.str;
+}
+
+typedef std::list<std::string> str_list;
+typedef std::deque<int> num_deque;
+
+typedef std::vector<int> num_vector;
+typedef std::vector<std::string> str_vector;
+
+typedef std::set<int> num_set;
+typedef std::set<std::string> str_set;
+typedef std::set<comp> comp_set;
+
+typedef std::map<int, std::string> num_str_map;
+typedef std::map<std::string, int> str_num_map;
+typedef std::map<int, comp> num_comp_map;
+typedef std::map<comp, std::string> comp_str_map;
+
+struct comp_hash
+{
+ std::size_t
+ operator() (const comp& x) const {return nh (x.num) + sh (x.str);}
+
+ std::hash<int> nh;
+ std::hash<std::string> sh;
+};
+
+typedef std::array<int, 3> num_array;
+typedef std::array<std::string, 3> str_array;
+typedef std::array<comp, 3> comp_array;
+
+typedef std::forward_list<int> num_flist;
+typedef std::forward_list<std::string> str_flist;
+typedef std::forward_list<comp> comp_flist;
+
+typedef std::unordered_set<int> num_uset;
+typedef std::unordered_set<std::string> str_uset;
+typedef std::unordered_set<comp, comp_hash> comp_uset;
+
+typedef std::unordered_map<int, std::string> num_str_umap;
+typedef std::unordered_map<std::string, int> str_num_umap;
+typedef std::unordered_map<int, comp> num_comp_umap;
+typedef std::unordered_map<comp, std::string, comp_hash> comp_str_umap;
+
+#pragma db value
+struct cont_comp1
+{
+ // This composite value does not have any columns.
+ //
+ num_vector sv; // Have the name "conflic" with the one in the object.
+};
+
+#pragma db value
+struct cont_comp2
+{
+ cont_comp2 (): num (777), str ("ggg") {}
+
+ int num;
+ str_list sl;
+ std::string str;
+};
+
+#pragma db object
+struct object
+{
+ object (): nv (comp1_.sv), sl (comp2_.sl) {}
+ object (const std::string& id) : id_ (id), nv (comp1_.sv), sl (comp2_.sl) {}
+
+ #pragma db id
+ std::string id_;
+
+ int num;
+
+ cont_comp1 comp1_;
+ cont_comp2 comp2_;
+
+ // vector
+ //
+ #pragma db transient
+ num_vector& nv;
+
+ #pragma db table("object_strings") id_column ("obj_id")
+ str_vector sv;
+
+ #pragma db value_column("")
+ std::vector<comp> cv;
+
+ #pragma db unordered
+ num_vector uv;
+
+ // list
+ //
+ #pragma db transient
+ str_list& sl;
+
+ // deque
+ //
+ num_deque nd;
+
+ // set
+ //
+ num_set ns;
+ str_set ss;
+ comp_set cs;
+
+ // map
+ //
+ num_str_map nsm;
+ str_num_map snm;
+ num_comp_map ncm;
+ comp_str_map csm;
+
+ // array
+ //
+ num_array na;
+ str_array sa;
+ comp_array ca;
+
+ // forward_list
+ //
+ num_flist nfl;
+ str_flist sfl;
+ comp_flist cfl;
+
+ // unordered_set
+ //
+ num_uset nus;
+ str_uset sus;
+ comp_uset cus;
+
+ // unordered_map
+ //
+ num_str_umap nsum;
+ str_num_umap snum;
+ num_comp_umap ncum;
+ comp_str_umap csum;
+
+ std::string str;
+};
+
+inline bool
+operator== (const object& x, const object& y)
+{
+ if (x.uv.size () != y.uv.size ())
+ return false;
+
+ int xs (0), ys (0);
+
+ for (num_vector::size_type i (0); i < x.uv.size (); ++i)
+ {
+ xs += x.uv[i];
+ ys += y.uv[i];
+ }
+
+ return
+ x.id_ == y.id_ &&
+ x.num == y.num &&
+
+ x.comp2_.num == y.comp2_.num &&
+ x.comp2_.str == y.comp2_.str &&
+
+ x.nv == y.nv &&
+ x.sv == y.sv &&
+ x.cv == y.cv &&
+ xs == ys &&
+
+ x.sl == y.sl &&
+
+ x.nd == y.nd &&
+
+ x.ns == y.ns &&
+ x.ss == y.ss &&
+ x.cs == y.cs &&
+
+ x.nsm == y.nsm &&
+ x.snm == y.snm &&
+ x.ncm == y.ncm &&
+ x.csm == y.csm &&
+
+ x.na == y.na &&
+ x.sa == y.sa &&
+ x.ca == y.ca &&
+
+ x.nfl == y.nfl &&
+ x.sfl == y.sfl &&
+ x.cfl == y.cfl &&
+
+ x.nus == y.nus &&
+ x.sus == y.sus &&
+ x.cus == y.cus &&
+
+ x.nsum == y.nsum &&
+ x.snum == y.snum &&
+ x.ncum == y.ncum &&
+ x.csum == y.csum &&
+
+ x.str == y.str;
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/container/basics/testscript b/odb-tests/common/container/basics/testscript
new file mode 100644
index 0000000..ea99498
--- /dev/null
+++ b/odb-tests/common/container/basics/testscript
@@ -0,0 +1,33 @@
+# file : common/container/basics/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/container/change-tracking/buildfile b/odb-tests/common/container/change-tracking/buildfile
new file mode 100644
index 0000000..1dda818
--- /dev/null
+++ b/odb-tests/common/container/change-tracking/buildfile
@@ -0,0 +1,40 @@
+# file : common/container/change-tracking/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_cont_changet_ \
+ --generate-schema
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/container/change-tracking/driver.cxx b/odb-tests/common/container/change-tracking/driver.cxx
new file mode 100644
index 0000000..4894ed9
--- /dev/null
+++ b/odb-tests/common/container/change-tracking/driver.cxx
@@ -0,0 +1,729 @@
+// file : common/container/change-tracking/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test change-tracking containers.
+//
+
+#include <memory> // std::unique_ptr
+#include <utility> // std::move
+#include <iostream>
+
+#include <odb/tracer.hxx>
+#include <odb/session.hxx>
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+struct counting_tracer: odb::tracer
+{
+ void
+ reset (transaction& tr) {u = i = d = s = t = 0; tr.tracer (*this);}
+
+ virtual void
+ execute (odb::connection&, const char* stmt)
+ {
+ string p (stmt, 6);
+ if (p == "UPDATE")
+ u++;
+ else if (p == "INSERT")
+ i++;
+ else if (p == "DELETE")
+ d++;
+ else if (p == "SELECT")
+ s++;
+ t++;
+ }
+
+ size_t u, i, d, s, t;
+};
+
+static counting_tracer tr;
+
+// Compilation test: instantiate all the functions. In C++11 mode only
+// do this if we have a fairly conforming compiler that implements the
+// complete std::vector interface.
+//
+
+#ifndef _RWSTD_NO_CLASS_PARTIAL_SPEC
+#if defined (__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7
+struct item {};
+template class odb::vector<item>;
+template class odb::vector_iterator<odb::vector<item>,
+ std::vector<item>::iterator>;
+template class odb::vector_iterator<odb::vector<item>,
+ std::vector<item>::reverse_iterator>;
+#endif
+#endif
+
+void
+f (const std::vector<int>&) {}
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ // Test extended interface.
+ //
+ {
+ typedef odb::vector<int> vector;
+
+ vector ov;
+ std::vector<int> sv;
+ f (ov); // Implicit conversion to std::vector.
+ vector ov1 (sv); // Initialization from std::vector.
+ ov = sv; // Assignement from std::vector.
+
+ // Container comparison.
+ //
+ if (ov != ov1 ||
+ ov != sv ||
+ sv != ov1)
+ ov.clear ();
+
+ // Iterator comparison/conversion.
+ //
+ vector::const_iterator i (ov.begin ());
+ if (i != ov.end ())
+ i = ov.end ();
+
+ // Things are just really borken in Sun CC, no matter which STL
+ // you use.
+ //
+#ifndef __SUNPRO_CC
+ vector::const_reverse_iterator j (ov.rbegin ());
+ if (j != ov.rend ())
+ j = ov.rend ();
+#endif
+ }
+
+ unique_ptr<database> db (create_database (argc, argv));
+
+ // Test traits logic.
+ //
+ {
+ object o ("1");
+ o.i = 123;
+ o.s.push_back ("a");
+
+ assert (!o.s._tracking ());
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ assert (o.s._tracking ());
+
+ // load
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> ("1"));
+ assert (p->s._tracking ());
+ t.commit ();
+ }
+
+ // update
+ //
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ assert (o.s._tracking ());
+
+ // erase
+ //
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+
+ assert (!o.s._tracking ());
+ }
+
+ // Test change tracking.
+ //
+ object o ("1");
+ o.i = 123;
+ o.s.push_back ("a");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ // push_back/pop_back
+ //
+ {
+ o.s.push_back ("b"); // insert
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 1 && tr.i == 1 && tr.t == 2);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.pop_back ();
+ o.s.push_back ("c"); // update
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 2 && tr.t == 2);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.pop_back ();
+ for (int i (0); i != 1024; ++i)
+ o.s.push_back ("x"); // realloc
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 2 && tr.i == 1023 && tr.t == 1025);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ for (int i (0); i != 1024; ++i)
+ o.s.pop_back (); // delete
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 1 && tr.d == 1 && tr.t == 2);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.push_back ("b");
+ o.s.pop_back (); // no-op
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 1 && tr.t == 1);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ // insert
+ //
+ {
+ o.s.clear ();
+ o.s.push_back ("a");
+ o.s.push_back ("b");
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.insert (o.s.begin (), "a1"); // insert front
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 3 && tr.i == 1 && tr.t == 4);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.insert (o.s.begin () + 1, "a2"); // insert middle
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 3 && tr.i == 1 && tr.t == 4);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.insert (o.s.end (), "b1"); // insert back
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 1 && tr.i == 1 && tr.t == 2);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ // erase
+ //
+ {
+ o.s.clear ();
+ o.s.push_back ("a");
+ o.s.push_back ("b");
+ o.s.push_back ("c");
+ o.s.push_back ("d");
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.erase (o.s.begin ()); // erase front
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 4 && tr.d == 1 && tr.t == 5);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.erase (o.s.begin () + 1); // erase middle
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 2 && tr.d == 1 && tr.t == 3);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.erase (o.s.end () - 1); // erase back
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 1 && tr.d == 1 && tr.t == 2);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ // modify
+ //
+ {
+ o.s.clear ();
+ o.s.push_back ("a");
+ o.s.push_back ("b");
+ o.s.push_back ("c");
+ o.s.push_back ("d");
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.modify (1) += 'b';
+ o.s.modify_at (2) += 'c';
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 3 && tr.t == 3);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.modify_front () += 'a';
+ o.s.modify_back () += 'd';
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 3 && tr.t == 3);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.begin ().modify () += 'a';
+#ifndef _RWSTD_NO_CLASS_PARTIAL_SPEC
+ o.s.rbegin ().modify () += 'c';
+#endif
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+#ifndef _RWSTD_NO_CLASS_PARTIAL_SPEC
+ assert (tr.u == 3 && tr.t == 3);
+#else
+ assert (tr.u == 2 && tr.t == 2);
+#endif
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+#ifndef _RWSTD_NO_CLASS_PARTIAL_SPEC
+ {
+ (o.s.rbegin () + 1).modify (1) += 'a';
+ (o.s.rbegin () + 1).modify (-1) += 'c';
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 3 && tr.t == 3);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+#endif
+
+ {
+ o.s.mbegin ();
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 5 && tr.t == 5);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ // clear
+ //
+ {
+ o.s.clear ();
+ o.s.push_back ("a");
+ o.s.push_back ("b");
+ o.s.push_back ("c");
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.clear ();
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 1 && tr.d == 1 && tr.t == 2);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ // assign
+ //
+ {
+ o.s.clear ();
+ o.s.push_back ("a");
+ o.s.push_back ("b");
+ o.s.push_back ("c");
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.assign (4, "x");
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 4 && tr.i == 1 && tr.t == 5);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ // resize
+ //
+ {
+ o.s.clear ();
+ o.s.push_back ("a");
+ o.s.push_back ("b");
+ o.s.push_back ("c");
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.pop_back ();
+ o.s.resize (4, "x"); // expand
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 2 && tr.i == 1 && tr.t == 3);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ o.s.push_back ("y");
+ o.s.resize (3); // shrink
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 1 && tr.d == 1 && tr.t == 2);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ // Transaction rollback.
+ //
+ {
+ o.s.clear ();
+ o.s.push_back ("a");
+ o.s.push_back ("b");
+ o.s.push_back ("c");
+
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (*db->load<object> ("1") == o);
+ t.commit ();
+ }
+
+ {
+ {
+ o.s.push_back ("d");
+
+ transaction t (db->begin ());
+ db->update (o);
+ t.rollback ();
+ }
+
+ {
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 1 && tr.i == 4 && tr.d == 1 && tr.t == 6);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (o);
+ assert (tr.u == 1 && tr.t == 1);
+ t.commit ();
+ }
+ }
+
+ // Armed copy.
+ //
+ {
+ unique_ptr<object> c;
+
+ {
+ o.s.pop_back ();
+
+ transaction t (db->begin ());
+ db->update (o);
+ c.reset (new object (o));
+ t.rollback ();
+ }
+
+ {
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (c);
+ assert (tr.u == 1 && tr.i == 3 && tr.d == 1 && tr.t == 5);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (c);
+ assert (tr.u == 1 && tr.t == 1);
+ t.commit ();
+ }
+ }
+
+ // Armed swap.
+ //
+ {
+ object c (o);
+
+ {
+ o.s.push_back ("d");
+
+ transaction t (db->begin ());
+ db->update (o);
+ assert (o.s._tracking () && !c.s._tracking ());
+ c.s.swap (o.s);
+ assert (!o.s._tracking () && c.s._tracking ());
+ t.rollback ();
+ }
+
+ {
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (c);
+ assert (tr.u == 1 && tr.i == 4 && tr.d == 1 && tr.t == 6);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (c);
+ assert (tr.u == 1 && tr.t == 1);
+ t.commit ();
+ }
+ }
+
+ // Armed move.
+ //
+ {
+ unique_ptr<object> c;
+
+ {
+ o.s.pop_back ();
+
+ transaction t (db->begin ());
+ db->update (o);
+ assert (o.s._tracking ());
+ c.reset (new object (std::move (o)));
+ assert (!o.s._tracking () && c->s._tracking ());
+ t.rollback ();
+ }
+
+ {
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (c);
+ assert (tr.u == 1 && tr.i == 2 && tr.d == 1 && tr.t == 4);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ tr.reset (t);
+ db->update (c);
+ assert (tr.u == 1 && tr.t == 1);
+ t.commit ();
+ }
+ }
+
+ // Test mixing "smart" and "dumb" container (specifically, erase(obj)).
+ //
+ {
+ mix_object o (1);
+ o.ov.assign (3, 123);
+ o.sv.assign (3, 123);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ // Test using change tracking container as inverse member.
+ //
+ {
+ inv_object1 o1;
+ inv_object2 o2;
+ o1.o2 = &o2;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o2);
+ db->persist (o1);
+ t.commit ();
+ }
+
+ assert (!o2.o1._tracking ());
+
+ {
+ session s;
+ transaction t (db->begin ());
+ unique_ptr<inv_object1> p1 (db->load<inv_object1> (o1.id_));
+ unique_ptr<inv_object2> p2 (db->load<inv_object2> (o2.id_));
+ assert (p2->o1[0] == p1.get ());
+ assert (!p2->o1._tracking ());
+ t.commit ();
+ }
+ }
+
+ // Test read-only values.
+ {
+ ro_object o (1);
+ o.v.push_back (ro_value (1, 1));
+ o.v.push_back (ro_value (2, 2));
+ o.v.push_back (ro_value (3, 3));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ o.v.erase (o.v.begin ());
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ assert (db->load<ro_object> (1)->v == o.v);
+ t.commit ();
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/container/change-tracking/test.hxx b/odb-tests/common/container/change-tracking/test.hxx
new file mode 100644
index 0000000..8e06f4a
--- /dev/null
+++ b/odb-tests/common/container/change-tracking/test.hxx
@@ -0,0 +1,106 @@
+// file : common/container/change-tracking/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <memory>
+#include <vector>
+#include <utility> // std::move
+
+#include <odb/core.hxx>
+#include <odb/vector.hxx>
+
+#pragma db object pointer(std::unique_ptr)
+struct object
+{
+ object () {}
+ object (const std::string& id): id_ (id) {}
+
+ object (const object& x): id_ (x.id_), i (x.i), s (x.s) {}
+ object (object&& x): id_ (std::move (x.id_)), i (x.i), s (std::move (x.s)) {}
+
+ #pragma db id
+ std::string id_;
+
+ unsigned int i;
+
+ odb::vector<std::string> s;
+
+ inline bool
+ operator== (const object& o) const {return id_ == o.id_ && i == o.i && s == o.s;}
+};
+
+// Test mixing "smart" and "dumb" container (specifically, erase(obj)).
+//
+#pragma db object
+struct mix_object
+{
+ mix_object () {}
+ mix_object (unsigned long id): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ odb::vector<int> ov;
+ std::vector<int> sv;
+};
+
+// Test using change tracking container as inverse member.
+//
+struct inv_object2;
+
+#pragma db object session
+struct inv_object1
+{
+ #pragma db id auto
+ unsigned long id_;
+
+ inv_object2* o2;
+};
+
+#pragma db object session
+struct inv_object2
+{
+ #pragma db id auto
+ unsigned long id_;
+
+ #pragma db inverse(o2)
+ odb::vector<inv_object1*> o1;
+};
+
+// Test read-only values (we still need to include them in the UPDATE
+// statement).
+//
+#pragma db value
+struct ro_value
+{
+ ro_value (int i_ = 0, int j_ = 0): i (i_), j (j_) {}
+
+ #pragma db readonly
+ int i;
+
+ #pragma db readonly
+ int j;
+};
+
+inline bool
+operator== (const ro_value& x, const ro_value& y)
+{
+ return x.i == y.i && x.j == y.j;
+}
+
+#pragma db object
+struct ro_object
+{
+ ro_object () {}
+ ro_object (unsigned long id): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ odb::vector<ro_value> v;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/container/change-tracking/testscript b/odb-tests/common/container/change-tracking/testscript
new file mode 100644
index 0000000..2169869
--- /dev/null
+++ b/odb-tests/common/container/change-tracking/testscript
@@ -0,0 +1,33 @@
+# file : common/container/change-tracking/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/ctor/buildfile b/odb-tests/common/ctor/buildfile
new file mode 100644
index 0000000..a9892bc
--- /dev/null
+++ b/odb-tests/common/ctor/buildfile
@@ -0,0 +1,41 @@
+# file : common/ctor/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 ctor_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/ctor/driver.cxx b/odb-tests/common/ctor/driver.cxx
new file mode 100644
index 0000000..c9b445d
--- /dev/null
+++ b/odb-tests/common/ctor/driver.cxx
@@ -0,0 +1,79 @@
+// file : common/ctor/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test support for persistent objects without default constructors.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ typedef odb::query<person> query;
+ typedef odb::result<person> result;
+
+ unique_ptr<database> db (create_database (argc, argv));
+
+ person p1 ("John", "Doe", 30);
+ person p2 ("Jane", "Doe", 29);
+ person p3 ("Joe", "Dirt", 31);
+
+ {
+ transaction t (db->begin ());
+
+ db->persist (p1);
+ db->persist (p2);
+ db->persist (p3);
+
+ t.commit ();
+ }
+
+ {
+ person p ("", "", 0);
+
+ transaction t (db->begin ());
+
+ db->load (p1.id_, p);
+
+ assert (p.first_ == p1.first_);
+ assert (p.last_ == p1.last_);
+ assert (p.age_ == p1.age_);
+
+ result r (db->query<person> (query::age < 30));
+
+ assert (!r.empty ());
+
+ result::iterator i (r.begin ());
+ i.load (p);
+ assert (p.first_ == "Jane");
+ assert (p.last_ == "Doe");
+ assert (p.age_ == 29);
+
+ assert (size (r) == 1);
+
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/ctor/test.hxx b/odb-tests/common/ctor/test.hxx
new file mode 100644
index 0000000..2a2becd
--- /dev/null
+++ b/odb-tests/common/ctor/test.hxx
@@ -0,0 +1,29 @@
+// file : common/ctor/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+
+#include <odb/core.hxx>
+
+#pragma db object
+struct person
+{
+ person (const std::string& first,
+ const std::string& last,
+ unsigned short age)
+ : first_ (first), last_ (last), age_ (age)
+ {
+ }
+
+ #pragma db id auto
+ unsigned long id_;
+
+ std::string first_;
+ std::string last_;
+ unsigned short age_;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/ctor/testscript b/odb-tests/common/ctor/testscript
new file mode 100644
index 0000000..8946ddb
--- /dev/null
+++ b/odb-tests/common/ctor/testscript
@@ -0,0 +1,33 @@
+# file : common/ctor/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/default/buildfile b/odb-tests/common/default/buildfile
new file mode 100644
index 0000000..e25bd08
--- /dev/null
+++ b/odb-tests/common/default/buildfile
@@ -0,0 +1,41 @@
+# file : common/default/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 default_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/default/driver.cxx b/odb-tests/common/default/driver.cxx
new file mode 100644
index 0000000..2d3ef01
--- /dev/null
+++ b/odb-tests/common/default/driver.cxx
@@ -0,0 +1,81 @@
+// file : common/default/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test default values.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ // Insert an object using an ad-hoc SQL statement. This way
+ // we get all the default values.
+ //
+ {
+ transaction t (db->begin ());
+
+ if (db->id () != odb::id_oracle)
+ db->execute ("INSERT INTO default_object (obj_id) VALUES (1)");
+ else
+ db->execute ("INSERT INTO \"default_object\" (\"obj_id\") VALUES (1)");
+
+ t.commit ();
+ }
+
+ // Now load the object and check all the values.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o (db->load<object> (1));
+ t.commit ();
+
+ assert (o->b);
+ assert (o->pi == 1234);
+ assert (o->ni == -1234);
+ assert (o->zi == 0);
+ assert (o->pf == 1.234);
+ assert (o->nf == -1.234);
+ assert (o->zf == 0.0);
+ assert (o->sf == 1.123e+10);
+ assert (o->str == "Someone's string");
+ assert (o->e == green);
+ }
+
+ // Check the NULL default value using a query.
+ //
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::null.is_null ()));
+ assert (!r.empty ());
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/default/test.hxx b/odb-tests/common/default/test.hxx
new file mode 100644
index 0000000..7f35ed4
--- /dev/null
+++ b/odb-tests/common/default/test.hxx
@@ -0,0 +1,67 @@
+// file : common/default/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <odb/core.hxx>
+
+enum color {red, green, blue};
+
+#pragma db value(unsigned long) default(0)
+
+#pragma db object
+struct object
+{
+ #pragma db id
+ unsigned long obj_id;
+
+ // NULL.
+ //
+ #pragma db null default(null)
+ unsigned long null;
+
+ // Boolean.
+ //
+ #pragma db default(true)
+ bool b;
+
+ // Integers.
+ //
+ #pragma db default(1234)
+ unsigned long pi;
+
+ #pragma db default(-1234)
+ long ni;
+
+ // 0 default taken from the type.
+ unsigned long zi;
+
+ // Floats.
+ //
+ #pragma db default(1.234)
+ double pf;
+
+ #pragma db default(-1.234)
+ double nf;
+
+ #pragma db default(0.0)
+ double zf;
+
+ #pragma db default(1.123e+10)
+ double sf;
+
+ // Strings. MySQL doesn't support default values on TEXT
+ // columns, so make the type VARCHAR.
+ //
+ #pragma db type("VARCHAR(64)") default("Someone's string")
+ std::string str;
+
+ // Enums.
+ //
+ #pragma db default(green)
+ color e;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/default/testscript b/odb-tests/common/default/testscript
new file mode 100644
index 0000000..f29cef4
--- /dev/null
+++ b/odb-tests/common/default/testscript
@@ -0,0 +1,33 @@
+# file : common/default/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/definition/.gitignore b/odb-tests/common/definition/.gitignore
new file mode 100644
index 0000000..5838670
--- /dev/null
+++ b/odb-tests/common/definition/.gitignore
@@ -0,0 +1,6 @@
+# ODB-generated files.
+#
+time-mapping-odb.?xx
+time-mapping-odb-*.?xx
+time-mapping.sql
+time-mapping-*.sql
diff --git a/odb-tests/common/definition/buildfile b/odb-tests/common/definition/buildfile
new file mode 100644
index 0000000..09ad1db
--- /dev/null
+++ b/odb-tests/common/definition/buildfile
@@ -0,0 +1,52 @@
+# file : common/definition/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+hs = test time-mapping
+
+exe{driver}: {hxx cxx}{* -*-odb -*-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: $hs
+{
+ exe{driver}: {hxx ixx cxx}{$h-odb}
+
+ <{hxx ixx cxx}{$h-odb}>: hxx{$h} libue{test-meta}
+
+ for db: $databases
+ {
+ exe{driver}: {hxx ixx cxx}{$h-odb-$db}: include = $multi
+ <{hxx ixx cxx}{$h-odb-$db}>: hxx{$h} libue{test-meta}
+ }
+}
+
+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 definition_ \
+ --generate-schema
+
+<{hxx ixx cxx}{time-mapping-odb}>: odb_options =
+
+for db: $databases
+ {hxx ixx cxx}{time-mapping-odb-$db}: odb_options =
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/definition/driver.cxx b/odb-tests/common/definition/driver.cxx
new file mode 100644
index 0000000..223eeaf
--- /dev/null
+++ b/odb-tests/common/definition/driver.cxx
@@ -0,0 +1,56 @@
+// file : common/definition/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test overriding composite value definition point. This is primarily
+// useful to make composite values out of third-party types.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ object o;
+ o.time.tv_sec = 1;
+ o.time.tv_usec = 1000;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ t.commit ();
+
+ assert (p->time.tv_sec == o.time.tv_sec &&
+ p->time.tv_usec == o.time.tv_usec);
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/definition/test.hxx b/odb-tests/common/definition/test.hxx
new file mode 100644
index 0000000..38fc02a
--- /dev/null
+++ b/odb-tests/common/definition/test.hxx
@@ -0,0 +1,26 @@
+// file : common/definition/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#ifdef _WIN32
+# include <winsock2.h> // timeval
+#else
+# include <sys/time.h> // timeval
+#endif
+
+#include <odb/core.hxx>
+
+#include "time-mapping.hxx"
+
+#pragma db object
+struct object
+{
+ #pragma db id auto
+ unsigned long id;
+
+ timeval time;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/definition/testscript b/odb-tests/common/definition/testscript
new file mode 100644
index 0000000..c9dea6d
--- /dev/null
+++ b/odb-tests/common/definition/testscript
@@ -0,0 +1,33 @@
+# file : common/definition/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/definition/time-mapping.hxx b/odb-tests/common/definition/time-mapping.hxx
new file mode 100644
index 0000000..469cfb7
--- /dev/null
+++ b/odb-tests/common/definition/time-mapping.hxx
@@ -0,0 +1,17 @@
+// file : common/definition/time-mapping.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TIME_MAPPING_HXX
+#define TIME_MAPPING_HXX
+
+#ifdef _WIN32
+# include <winsock2.h> // timeval
+#else
+# include <sys/time.h> // timeval
+#endif
+
+#pragma db value(timeval) definition
+#pragma db member(timeval::tv_sec) column("sec")
+#pragma db member(timeval::tv_usec) column("usec")
+
+#endif // TIME_MAPPING_HXX
diff --git a/odb-tests/common/enum/buildfile b/odb-tests/common/enum/buildfile
new file mode 100644
index 0000000..eb3a29a
--- /dev/null
+++ b/odb-tests/common/enum/buildfile
@@ -0,0 +1,41 @@
+# file : common/enum/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 enum_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/enum/driver.cxx b/odb-tests/common/enum/driver.cxx
new file mode 100644
index 0000000..ed3eb59
--- /dev/null
+++ b/odb-tests/common/enum/driver.cxx
@@ -0,0 +1,84 @@
+// file : common/enum/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test automatic C++ enum mapping.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ unique_ptr<database> db (create_database (argc, argv));
+
+ object o;
+ o.color_ = green;
+ o.taste_ = object::sweet;
+ o.position_ = object::left;
+
+ o.gender_ = object::gender::female;
+ o.scale_ = object::scale::ten;
+ o.yesno_ = object::yesno::yes;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o1 (db->load<object> (o.id_));
+ t.commit ();
+
+ assert (o == *o1);
+ }
+
+ {
+ transaction t (db->begin ());
+
+ result r1 (db->query<object> (query::color == blue));
+ result r2 (db->query<object> (query::taste == object::sweet));
+ result r3 (db->query<object> (query::position == object::left));
+
+ assert (r1.empty ());
+ assert (!r2.empty ());
+ assert (!r3.empty ());
+
+ result r4 (db->query<object> (query::gender == object::gender::female));
+ result r5 (db->query<object> (query::scale == object::scale::ten));
+ result r6 (db->query<object> (query::yesno == object::yesno::yes));
+
+ assert (!r4.empty ());
+ assert (!r5.empty ());
+ assert (!r6.empty ());
+
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/enum/test.hxx b/odb-tests/common/enum/test.hxx
new file mode 100644
index 0000000..a279112
--- /dev/null
+++ b/odb-tests/common/enum/test.hxx
@@ -0,0 +1,47 @@
+// file : common/enum/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <odb/core.hxx>
+
+enum color {red, green, blue};
+
+#pragma db object
+struct object
+{
+ #pragma db id auto
+ unsigned long id_;
+
+ color color_;
+ enum taste {bitter, sweet, sour};
+ taste taste_;
+
+ enum position {left = -1, center = 0, right = 1};
+ position position_;
+
+
+ enum class gender {male, female};
+ enum class scale: unsigned char {one = 1, ten = 10, hundred = 100};
+ enum class yesno: bool {no, yes};
+
+ gender gender_;
+ scale scale_;
+ yesno yesno_;
+};
+
+inline bool
+operator == (const object& x, const object& y)
+{
+ return
+ x.id_ == y.id_
+ && x.color_ == y.color_
+ && x.taste_ == y.taste_
+ && x.position_ == y.position_
+ && x.gender_ == y.gender_
+ && x.scale_ == y.scale_
+ && x.yesno_ == y.yesno_;
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/enum/testscript b/odb-tests/common/enum/testscript
new file mode 100644
index 0000000..d2ca28c
--- /dev/null
+++ b/odb-tests/common/enum/testscript
@@ -0,0 +1,33 @@
+# file : common/enum/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/erase-query/buildfile b/odb-tests/common/erase-query/buildfile
new file mode 100644
index 0000000..d833b6e
--- /dev/null
+++ b/odb-tests/common/erase-query/buildfile
@@ -0,0 +1,41 @@
+# file : common/erase-query/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 erase_query_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/erase-query/driver.cxx b/odb-tests/common/erase-query/driver.cxx
new file mode 100644
index 0000000..6c11957
--- /dev/null
+++ b/odb-tests/common/erase-query/driver.cxx
@@ -0,0 +1,181 @@
+// file : common/erase-query/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test query-based erase.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+void
+persist (database& db)
+{
+ object o1 (1);
+ object o2 (2);
+ object o3 (3);
+ object o4 (4);
+
+ transaction t (db.begin ());
+ db.persist (o1);
+ db.persist (o2);
+ db.persist (o3);
+ db.persist (o4);
+ t.commit ();
+}
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ typedef odb::query<object> query;
+
+ unique_ptr<database> db (create_database (argc, argv));
+
+ // erase_query()
+ //
+ persist (*db);
+
+ {
+ transaction t (db->begin ());
+ assert (db->erase_query<object> () == 4);
+ t.commit ();
+ }
+
+ // erase_query(const char*)
+ //
+ persist (*db);
+
+ {
+ transaction t (db->begin ());
+
+ if (db->id () != odb::id_oracle)
+ assert (db->erase_query<object> (
+ "erase_query_object.id < 3") == 2);
+ else
+ assert (db->erase_query<object> (
+ "\"erase_query_object\".\"id\" < 3") == 2);
+
+ db->erase_query<object> ();
+ t.commit ();
+ }
+
+ // erase_query(query)
+ //
+ persist (*db);
+
+ {
+ transaction t (db->begin ());
+ assert (db->erase_query<object> (query::id == 2 || query::id == 4) == 2);
+ db->erase_query<object> ();
+ t.commit ();
+ }
+
+ // Test predicates involving object pointers (DELETE JOIN).
+ //
+ /*
+ {
+ object o11 (1);
+ object o12 (2);
+ object o13 (3);
+ object2 o2;
+
+ o11.o2 = &o2;
+ o2.num = 123;
+
+ o12.o1 = &o13;
+ o13.num = 123;
+
+ transaction t (db->begin ());
+ db->persist (o2);
+ db->persist (o13);
+ db->persist (o12);
+ db->persist (o11);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ assert (db->erase_query<object> (query::o1::num == 123) == 1);
+ assert (db->erase_query<object> (query::o2::num == 123) == 1);
+ db->erase_query<object> ();
+ t.commit ();
+ }
+ */
+
+ // For now we can only do column-based tests, like is_null().
+ //
+ {
+ object o11 (1);
+ object o12 (2);
+ object o13 (3);
+ object2 o2;
+
+ o12.o2 = &o2;
+
+ transaction t (db->begin ());
+ db->persist (o2);
+ db->persist (o13);
+ db->persist (o12);
+ db->persist (o11);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ assert (db->erase_query<object> (query::o2.is_null ()) == 2);
+ db->erase_query<object> ();
+ t.commit ();
+ }
+
+ // Make sure container data is deleted.
+ //
+ {
+ object o (1);
+ o.v.push_back (1);
+ o.v.push_back (2);
+ o.v.push_back (3);
+
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ assert (db->erase_query<object> () == 1);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ if (db->id () != odb::id_oracle)
+ assert (db->execute ("SELECT * FROM erase_query_object_v "
+ "WHERE object_id = 1") == 0);
+ else
+ assert (db->execute ("SELECT * FROM \"erase_query_object_v\" "
+ "WHERE \"object_id\" = 1") == 0);
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/erase-query/test.hxx b/odb-tests/common/erase-query/test.hxx
new file mode 100644
index 0000000..9e73f12
--- /dev/null
+++ b/odb-tests/common/erase-query/test.hxx
@@ -0,0 +1,46 @@
+// file : common/erase-query/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <vector>
+
+#include <odb/core.hxx>
+
+struct object2;
+
+#pragma db object
+struct object
+{
+ object (unsigned long id)
+ : id_ (id), o1 (0), o2 (0)
+ {
+ }
+
+ object ()
+ : o1 (0), o2 (0)
+ {
+ }
+
+ #pragma db id
+ unsigned long id_;
+
+ std::vector<int> v;
+
+ int num;
+
+ object* o1;
+ object2* o2;
+};
+
+#pragma db object
+struct object2
+{
+ #pragma db id auto
+ unsigned long id_;
+
+ int num;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/erase-query/testscript b/odb-tests/common/erase-query/testscript
new file mode 100644
index 0000000..90862ab
--- /dev/null
+++ b/odb-tests/common/erase-query/testscript
@@ -0,0 +1,33 @@
+# file : common/erase-query/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/id/auto/buildfile b/odb-tests/common/id/auto/buildfile
new file mode 100644
index 0000000..c340200
--- /dev/null
+++ b/odb-tests/common/id/auto/buildfile
@@ -0,0 +1,40 @@
+# file : common/id/auto/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_id_auto_ \
+ --generate-schema
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/id/auto/driver.cxx b/odb-tests/common/id/auto/driver.cxx
new file mode 100644
index 0000000..d294e69
--- /dev/null
+++ b/odb-tests/common/id/auto/driver.cxx
@@ -0,0 +1,96 @@
+// file : common/id/auto/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test automatic id assignment.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ // object
+ //
+ {
+ unsigned long id1, id2, id3;
+ {
+ object o1 ("one");
+ object o2 ("two");
+ object o3 ("three");
+
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ db->persist (o3);
+ t.commit ();
+
+ id1 = o1.id_;
+ id2 = o2.id_;
+ id3 = o3.id_;
+
+ assert (id1 != id2);
+ assert (id1 != id3);
+ assert (id2 != id3);
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o1 (db->load<object> (id1));
+ unique_ptr<object> o2 (db->load<object> (id2));
+ unique_ptr<object> o3 (db->load<object> (id3));
+ t.commit ();
+
+ assert (o1->id_ == id1 && o1->str_ == "one");
+ assert (o2->id_ == id2 && o2->str_ == "two");
+ assert (o3->id_ == id3 && o3->str_ == "three");
+ }
+ }
+
+ // auto_only
+ //
+ {
+ unsigned short id;
+ {
+ auto_only o;
+
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ id = o.id_;
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<auto_only> o (db->load<auto_only> (id));
+ t.commit ();
+
+ assert (o->id_ == id);
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/id/auto/test.hxx b/odb-tests/common/id/auto/test.hxx
new file mode 100644
index 0000000..233c79f
--- /dev/null
+++ b/odb-tests/common/id/auto/test.hxx
@@ -0,0 +1,40 @@
+// file : common/id/auto/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+
+#include <odb/core.hxx>
+
+#pragma db object
+struct object
+{
+ object (const std::string& str)
+ : id_ (1), str_ (str)
+ {
+ }
+
+ #pragma db auto id
+ unsigned long id_;
+ std::string str_;
+
+private:
+ object ()
+ {
+ }
+
+ friend class odb::access;
+};
+
+// Test the case where the object has just the auto id.
+//
+#pragma db object
+struct auto_only
+{
+ #pragma db auto id pgsql:type("BIGINT")
+ unsigned short id_;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/id/auto/testscript b/odb-tests/common/id/auto/testscript
new file mode 100644
index 0000000..bb2a3a4
--- /dev/null
+++ b/odb-tests/common/id/auto/testscript
@@ -0,0 +1,33 @@
+# file : common/id/auto/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/id/composite/buildfile b/odb-tests/common/id/composite/buildfile
new file mode 100644
index 0000000..4bc9f9a
--- /dev/null
+++ b/odb-tests/common/id/composite/buildfile
@@ -0,0 +1,42 @@
+# file : common/id/composite/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_id_comp_ \
+ --generate-schema \
+ --generate-query \
+ --generate-session
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/id/composite/driver.cxx b/odb-tests/common/id/composite/driver.cxx
new file mode 100644
index 0000000..3d66101
--- /dev/null
+++ b/odb-tests/common/id/composite/driver.cxx
@@ -0,0 +1,731 @@
+// file : common/id/composite/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test composite object ids.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/session.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ // Test 1.
+ //
+ {
+ using namespace test1;
+
+ object o1 (scomp ("aaa", "bbb", "ccc"), 123);
+ o1.vec.push_back (scomp ("xxx", "xxx", "xxx"));
+ o1.vec.push_back (scomp ("yyy", "yyy", "yyy"));
+
+ object o2 (scomp ("aaa", "bbb", "ccd"), 234);
+ o2.vec.push_back (scomp ("zzz", "", "zzz"));
+
+ object o3 (scomp ("baa", "bbb", "ccc"), 345);
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ db->persist (o3);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id));
+ unique_ptr<object> p2 (db->load<object> (o2.id));
+ unique_ptr<object> p3 (db->load<object> (o3.id));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ assert (*p3 == o3);
+ }
+
+ // Update.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o1.id));
+ p->num++;
+ db->update (*p);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o1.id));
+ t.commit ();
+
+ assert (p->num == o1.num + 1);
+ }
+
+ // Erase.
+ //
+ {
+ transaction t (db->begin ());
+ db->erase<object> (o1.id);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->find<object> (o1.id));
+ assert (p.get () == 0);
+ t.commit ();
+ }
+ }
+
+ // Test 2.
+ //
+ {
+ using namespace test2;
+
+ object2 o2 (ncomp (2, 0, 1));
+ o2.o1 = new object1 (scomp ("o1", "o2", "aaa"));
+
+ object3 o3 (ncomp (3, 0, 1));
+ o3.o1.push_back (new object1 (scomp ("o1", "o3", "aaa")));
+ o3.o1.push_back (new object1 (scomp ("o1", "o3", "bbb")));
+
+ object4 o4 (ncomp (4, 0, 1));
+ o4.c.o2 = new object2 (ncomp (2, 4, 1));
+ o4.c.o2->o1 = new object1 (scomp ("o1", "o2", "ccc"));
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o2.o1);
+ db->persist (o2);
+ db->persist (o3.o1[0]);
+ db->persist (o3.o1[1]);
+ db->persist (o3);
+ db->persist (o4.c.o2->o1);
+ db->persist (o4.c.o2);
+ db->persist (o4);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object2> p2 (db->load<object2> (o2.id));
+ unique_ptr<object3> p3 (db->load<object3> (o3.id));
+ unique_ptr<object4> p4 (db->load<object4> (o4.id));
+ t.commit ();
+
+ assert (p2->o1->id == o2.o1->id);
+ assert (p3->o1.size () == o3.o1.size ());
+ assert (p3->o1[0]->id == o3.o1[0]->id);
+ assert (p3->o1[1]->id == o3.o1[1]->id);
+ assert (p4->c.o2->id == o4.c.o2->id);
+ assert (p4->c.o2->o1->id == o4.c.o2->o1->id);
+ }
+
+ // Update.
+ //
+ {
+ scomp id2, id3;
+
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<object2> p2 (db->load<object2> (o2.id));
+ delete p2->o1;
+ p2->o1 = new object1 (scomp ("o1", "o2", "bbb"));
+ id2 = db->persist (p2->o1);
+ db->update (*p2);
+
+ unique_ptr<object3> p3 (db->load<object3> (o3.id));
+ delete p3->o1.back ();
+ p3->o1.pop_back ();
+ p3->o1.push_back (new object1 (scomp ("o1", "o3", "ccc")));
+ id3 = db->persist (p3->o1.back ());
+ db->update (*p3);
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object2> p2 (db->load<object2> (o2.id));
+ unique_ptr<object3> p3 (db->load<object3> (o3.id));
+ t.commit ();
+
+ assert (p2->o1->id == id2);
+ assert (p3->o1.back ()->id == id3);
+ }
+ }
+
+ // Query.
+ //
+ {
+ {
+ typedef odb::query<object2> query;
+ typedef odb::result<object2> result;
+
+ transaction t (db->begin ());
+
+ {
+ result r (db->query<object2> (query::o1->id.str3 == "bbb"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->id == o2.id);
+ assert (++i == r.end ());
+ }
+
+ {
+ // As id (dual interface).
+ //
+ result r (db->query<object2> (query::o1.str3 == "bbb"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->id == o2.id);
+ assert (++i == r.end ());
+ }
+
+ t.commit ();
+ }
+
+ // Second level composite object pointer.
+ //
+ {
+ typedef odb::query<object4> query;
+ typedef odb::result<object4> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<object4> (query::c.o2->o1.str3 == "ccc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->id == o4.id);
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+ }
+
+ // View.
+ //
+ {
+ transaction t (db->begin ());
+
+ {
+ typedef odb::query<view2> query;
+ typedef odb::result<view2> result;
+
+ result r (db->query<view2> (query::object2::id.num2 == 0));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->num == 1 && i->str == "bbb");
+ assert (++i == r.end ());
+ }
+
+ {
+ typedef odb::query<view3> query;
+ typedef odb::result<view3> result;
+
+ result r (db->query<view3> ((query::object3::id.num2 == 0) +
+ "ORDER BY" + query::object1::id.str3));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->num == 1 && i->str == "aaa");
+ assert (++i != r.end ());
+ assert (i->num == 1 && i->str == "ccc");
+ assert (++i == r.end ());
+ }
+
+ {
+ typedef odb::query<view4> query;
+ typedef odb::result<view4> result;
+
+ result r (db->query<view4> (query::object4::id.num2 == 0));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->num4 == 1 && i->num2 == 1 && i->str == "ccc");
+ assert (++i == r.end ());
+ }
+
+ t.commit ();
+ }
+ }
+
+ // Test 3.
+ //
+ {
+ using namespace test3;
+
+ object2 o2 (ncomp (2, 0, 1));
+ o2.o1 = new object1 (scomp ("o1", "o2", "aaa"));
+ o2.o1->o2 = &o2;
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o2.o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ session s;
+ transaction t (db->begin ());
+ unique_ptr<object2> p2 (db->load<object2> (o2.id));
+ t.commit ();
+
+ assert (p2->o1->o2->id == o2.id);
+ }
+
+ // Query.
+ //
+ {
+ typedef odb::query<object1> query;
+ typedef odb::result<object1> result;
+
+ transaction t (db->begin ());
+
+ {
+ session s;
+
+ result r (db->query<object1> (query::o2->id.num2 == 0));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->id == o2.o1->id);
+
+ i->o2->o1 = 0;
+ delete i->o2;
+
+ assert (++i == r.end ());
+ }
+
+ t.commit ();
+ }
+
+ // View.
+ //
+ {
+ typedef odb::query<view> query;
+ typedef odb::result<view> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<view> (query::object1::id.str2 == "o2"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->num == 1 && i->str == "aaa");
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+ }
+
+ // Test 4.
+ //
+ {
+ using namespace test4;
+
+ object2 o2 (ncomp (2, 0, 1));
+
+ o2.o1.push_back (new object1 (scomp ("o1", "o2", "aaa")));
+ o2.o1.back ()->o2 = &o2;
+
+ o2.o1.push_back (new object1 (scomp ("o1", "o2", "bbb")));
+ o2.o1.back ()->o2 = &o2;
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o2.o1[0]);
+ db->persist (o2.o1[1]);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ session s;
+ transaction t (db->begin ());
+ unique_ptr<object2> p2 (db->load<object2> (o2.id));
+ t.commit ();
+
+ assert (p2->o1.size () == 2);
+ assert (p2->o1[0]->o2->id == o2.id);
+ assert (p2->o1[1]->o2->id == o2.id);
+ }
+
+ // Query.
+ //
+ {
+ typedef odb::query<object1> query;
+ typedef odb::result<object1> result;
+
+ transaction t (db->begin ());
+
+ {
+ session s;
+
+ result r (db->query<object1> (query::o2->id.num2 == 0));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->id == o2.o1[0]->id);
+ i->o2->o1.clear ();
+
+ assert (++i != r.end ());
+ assert (i->id == o2.o1[1]->id);
+
+ i->o2->o1.clear ();
+ delete i->o2;
+
+ assert (++i == r.end ());
+ }
+
+ t.commit ();
+ }
+
+ // View.
+ //
+ {
+ typedef odb::query<view> query;
+ typedef odb::result<view> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<view> (query::object1::id.str3 == "bbb"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->num == 1 && i->str == "bbb");
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+ }
+
+ // Test 5.
+ //
+ {
+ using namespace test5;
+
+ object2 o2 (ncomp (2, 0, 1));
+
+ o2.o1.push_back (new object1 (scomp ("o1", "o2", "aaa")));
+ o2.o1.back ()->o2 = &o2;
+
+ o2.o1.push_back (new object1 (scomp ("o1", "o2", "bbb")));
+ o2.o1.back ()->o2 = &o2;
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o2.o1[0]);
+ db->persist (o2.o1[1]);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ session s;
+ transaction t (db->begin ());
+ unique_ptr<object2> p2 (db->load<object2> (o2.id));
+ t.commit ();
+
+ assert (p2->o1.size () == 2);
+
+ assert (p2->o1[0]->id == o2.o1[0]->id);
+ assert (p2->o1[0]->o2->id == o2.id);
+
+ assert (p2->o1[1]->id == o2.o1[1]->id);
+ assert (p2->o1[1]->o2->id == o2.id);
+ }
+
+ // View.
+ //
+ {
+ typedef odb::query<view> query;
+ typedef odb::result<view> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<view> ((query::object2::id.num2 == 0) +
+ "ORDER BY" + query::object1::id.str3));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->num == 1 && i->str == "aaa");
+ assert (++i != r.end ());
+ assert (i->num == 1 && i->str == "bbb");
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+ }
+
+ // Test 6.
+ //
+ {
+ using namespace test6;
+
+ object2 o2 (ncomp (2, 0, 1));
+
+ o2.o1.push_back (new object1 (scomp ("o1", "o2", "aaa")));
+ o2.o1.back ()->o2.push_back (&o2);
+
+ o2.o1.push_back (new object1 (scomp ("o1", "o2", "bbb")));
+ o2.o1.back ()->o2.push_back (&o2);
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o2.o1[0]);
+ db->persist (o2.o1[1]);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ session s;
+ transaction t (db->begin ());
+ unique_ptr<object2> p2 (db->load<object2> (o2.id));
+ t.commit ();
+
+ assert (p2->o1.size () == 2);
+
+ assert (p2->o1[0]->id == o2.o1[0]->id);
+ assert (p2->o1[0]->o2[0]->id == o2.id);
+
+ assert (p2->o1[1]->id == o2.o1[1]->id);
+ assert (p2->o1[1]->o2[0]->id == o2.id);
+ }
+
+ // View.
+ //
+ {
+ typedef odb::query<view> query;
+ typedef odb::result<view> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<view> ((query::object2::id.num2 == 0) +
+ "ORDER BY" + query::object1::id.str3));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->num == 1 && i->str == "aaa");
+ assert (++i != r.end ());
+ assert (i->num == 1 && i->str == "bbb");
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+ }
+
+ // Test 7.
+ //
+ {
+ using namespace test7;
+
+ object o (scomp ("aaa", "bbb", "ccc"), 123);
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ t.commit ();
+
+ assert (*p == o);
+ }
+
+ // Update.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ p->num++;
+ db->update (*p);
+
+ try
+ {
+ db->update (o);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ }
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ t.commit ();
+
+ assert (p->num == o.num + 1);
+ }
+
+ // Erase.
+ //
+ {
+ transaction t (db->begin ());
+
+ try
+ {
+ db->update (o);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ }
+
+ t.commit ();
+ }
+ }
+
+ // Test 8.
+ //
+ {
+ using namespace test8;
+
+ object2 o2a, o2b;
+ object3 o3;
+
+ o2b.o1 = new object1 (scomp ("222", "aaa", "bbb"), 123);
+ o3.o1.push_back (0);
+ o3.o1.push_back (new object1 (scomp ("333", "aaa", "bbb"), 234));
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o2a);
+ db->persist (o2b);
+ db->persist (o2b.o1);
+ db->persist (o3);
+ db->persist (o3.o1[1]);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object2> p2a (db->load<object2> (o2a.id));
+ unique_ptr<object2> p2b (db->load<object2> (o2b.id));
+ unique_ptr<object3> p3 (db->load<object3> (o3.id));
+ t.commit ();
+
+ assert (p2a->o1 == 0);
+ assert (p2b->o1 != 0 && *p2b->o1 == *o2b.o1);
+ assert (p3->o1[0] == 0);
+ assert (p3->o1[1] != 0 && *p3->o1[1] == *o3.o1[1]);
+ }
+
+ // Update.
+ //
+ {
+ object1* o1 (o3.o1[1]);
+
+ o3.o1.clear ();
+ o3.o1.push_back (o2b.o1);
+ o3.o1.push_back (0);
+
+ o2a.o1 = o1;
+ o2b.o1 = 0;
+
+ transaction t (db->begin ());
+ db->update (o2a);
+ db->update (o2b);
+ db->update (o3);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object2> p2a (db->load<object2> (o2a.id));
+ unique_ptr<object2> p2b (db->load<object2> (o2b.id));
+ unique_ptr<object3> p3 (db->load<object3> (o3.id));
+ t.commit ();
+
+ assert (p2a->o1 != 0 && *p2a->o1 == *o2a.o1);
+ assert (p2b->o1 == 0);
+ assert (p3->o1[0] != 0 && *p3->o1[0] == *o3.o1[0]);
+ assert (p3->o1[1] == 0);
+ }
+ }
+
+ // Test 9.
+ {
+ using namespace test9;
+
+ object o (123, "abc");
+ o.v.push_back (123);
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ // load & check
+ //
+ {
+ transaction t (db->begin ());
+ result<object> r (db->query<object> ());
+ result<object>::iterator i (r.begin ());
+ assert (i != r.end () && o == *i && ++i == r.end ());
+ t.commit ();
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/id/composite/test.hxx b/odb-tests/common/id/composite/test.hxx
new file mode 100644
index 0000000..70856a6
--- /dev/null
+++ b/odb-tests/common/id/composite/test.hxx
@@ -0,0 +1,519 @@
+// file : common/id/composite/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <vector>
+
+#include <odb/core.hxx>
+
+#pragma db value
+struct scomp
+{
+ scomp () {}
+ scomp (const std::string& s1, const std::string& s2, const std::string& s3)
+ : str1 (s1), str2 (s2), str3 (s3)
+ {
+ }
+
+ std::string str1;
+ std::string str2;
+ std::string str3;
+};
+
+inline bool
+operator== (const scomp& x, const scomp& y)
+{
+ return x.str1 == y.str1 && x.str2 == y.str2 && x.str3 == y.str3;
+}
+
+inline bool
+operator< (const scomp& x, const scomp& y)
+{
+ return x.str1 < y.str1 ||
+ (x.str1 == y.str1 && x.str2 < y.str2) ||
+ (x.str1 == y.str1 && x.str2 == y.str2 && x.str3 < y.str3);
+}
+
+#pragma db value
+struct ncomp
+{
+ ncomp () {}
+ ncomp (unsigned short n1, unsigned short n2, unsigned short n3)
+ : num1 (n1), num2 (n2), num3 (n3)
+ {
+ }
+
+ unsigned short num1;
+ unsigned short num2;
+ unsigned short num3;
+};
+
+inline bool
+operator== (const ncomp& x, const ncomp& y)
+{
+ return x.num1 == y.num1 && x.num2 == y.num2 && x.num3 == y.num3;
+}
+
+inline bool
+operator< (const ncomp& x, const ncomp& y)
+{
+ return x.num1 < y.num1 ||
+ (x.num1 == y.num1 && x.num2 < y.num2) ||
+ (x.num1 == y.num1 && x.num2 == y.num2 && x.num3 < y.num3);
+}
+
+// Test object with composite id, container.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ #pragma db object
+ struct object
+ {
+ object () {}
+ object (const scomp& i, unsigned long n): id (i), num (n) {}
+
+ #pragma db id
+ scomp id;
+
+ unsigned long num;
+ std::vector<scomp> vec;
+ };
+
+ inline bool
+ operator== (const object& x, const object& y)
+ {
+ return x.id == y.id && x.num == y.num && x.vec == y.vec;
+ }
+}
+
+// Test to-one and to-many relationships with composite id as well as
+// queries and views.
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ #pragma db object
+ struct object1
+ {
+ object1 () {}
+ object1 (const scomp& i): id (i) {}
+
+ #pragma db id
+ scomp id;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 (): o1 (0) {}
+ object2 (const ncomp& i): id (i), o1 (0) {}
+ ~object2 () {delete o1;}
+
+ #pragma db id
+ ncomp id;
+
+ object1* o1;
+ };
+
+ #pragma db object
+ struct object3
+ {
+ object3 () {}
+ object3 (const ncomp& i): id (i) {}
+
+ ~object3 ()
+ {
+ for (std::vector<object1*>::iterator i (o1.begin ());
+ i != o1.end (); ++i)
+ delete *i;
+ }
+
+ #pragma db id
+ ncomp id;
+
+ std::vector<object1*> o1;
+ };
+
+ // Test second-level query pointer test as well as pointers in
+ // composite types.
+ //
+ #pragma db value
+ struct comp
+ {
+ comp (): o2 (0) {}
+ ~comp () {delete o2;}
+
+ object2* o2;
+ };
+
+ #pragma db object
+ struct object4
+ {
+ object4 () {}
+ object4 (const ncomp& i): id (i) {}
+
+ #pragma db id
+ ncomp id;
+
+ comp c;
+ };
+
+ #pragma db view object(object2) object(object1)
+ struct view2
+ {
+ #pragma db column (object2::id.num3)
+ unsigned short num;
+
+ #pragma db column (object1::id.str3)
+ std::string str;
+ };
+
+ #pragma db view object(object3) object(object1)
+ struct view3
+ {
+ #pragma db column (object3::id.num3)
+ unsigned short num;
+
+ #pragma db column (object1::id.str3)
+ std::string str;
+ };
+
+ #pragma db view object(object4) object(object2) object(object1)
+ struct view4
+ {
+ #pragma db column (object4::id.num3)
+ unsigned short num4;
+
+ #pragma db column (object2::id.num3)
+ unsigned short num2;
+
+ #pragma db column (object1::id.str3)
+ std::string str;
+ };
+}
+
+// Test one-to-one(i) relationship with composite id.
+//
+#pragma db namespace table("t3_")
+namespace test3
+{
+ struct object2;
+
+ #pragma db object
+ struct object1
+ {
+ object1 () {}
+ object1 (const scomp& i): id (i) {}
+
+ #pragma db id
+ scomp id;
+
+ #pragma db inverse(o1)
+ object2* o2;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 (): o1 (0) {}
+ object2 (const ncomp& i): id (i), o1 (0) {}
+ ~object2 () {delete o1;}
+
+ #pragma db id
+ ncomp id;
+
+ object1* o1;
+ };
+
+ #pragma db view object(object2) object(object1)
+ struct view
+ {
+ #pragma db column (object2::id.num3)
+ unsigned short num;
+
+ #pragma db column (object1::id.str3)
+ std::string str;
+ };
+}
+
+// Test many-to-one(i) relationship with composite id.
+//
+#pragma db namespace table("t4_")
+namespace test4
+{
+ struct object2;
+
+ #pragma db object
+ struct object1
+ {
+ object1 () {}
+ object1 (const scomp& i): id (i) {}
+
+ #pragma db id
+ scomp id;
+
+ #pragma db inverse(o1)
+ object2* o2;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 () {}
+ object2 (const ncomp& i): id (i) {}
+
+ ~object2 ()
+ {
+ for (std::vector<object1*>::iterator i (o1.begin ());
+ i != o1.end (); ++i)
+ delete *i;
+ }
+
+ #pragma db id
+ ncomp id;
+
+ std::vector<object1*> o1;
+ };
+
+ #pragma db view object(object2) object(object1)
+ struct view
+ {
+ #pragma db column (object2::id.num3)
+ unsigned short num;
+
+ #pragma db column (object1::id.str3)
+ std::string str;
+ };
+}
+
+// Test one-to-many(i) relationship with composite id.
+//
+#pragma db namespace table("t5_")
+namespace test5
+{
+ struct object2;
+
+ #pragma db object
+ struct object1
+ {
+ object1 () {}
+ object1 (const scomp& i): id (i) {}
+
+ #pragma db id
+ scomp id;
+
+ object2* o2;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 () {}
+ object2 (const ncomp& i): id (i) {}
+
+ ~object2 ()
+ {
+ for (std::vector<object1*>::iterator i (o1.begin ());
+ i != o1.end (); ++i)
+ delete *i;
+ }
+
+ #pragma db id
+ ncomp id;
+
+ #pragma db inverse(o2)
+ std::vector<object1*> o1;
+ };
+
+ #pragma db view object(object2) object(object1)
+ struct view
+ {
+ #pragma db column (object2::id.num3)
+ unsigned short num;
+
+ #pragma db column (object1::id.str3)
+ std::string str;
+ };
+}
+
+// Test many-to-many(i) relationship with composite id.
+//
+#pragma db namespace table("t6_")
+namespace test6
+{
+ struct object2;
+
+ #pragma db object
+ struct object1
+ {
+ object1 () {}
+ object1 (const scomp& i): id (i) {}
+
+ #pragma db id
+ scomp id;
+
+ std::vector<object2*> o2;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 () {}
+ object2 (const ncomp& i): id (i) {}
+
+ ~object2 ()
+ {
+ for (std::vector<object1*>::iterator i (o1.begin ());
+ i != o1.end (); ++i)
+ delete *i;
+ }
+
+ #pragma db id
+ ncomp id;
+
+ #pragma db inverse(o2)
+ std::vector<object1*> o1;
+ };
+
+ #pragma db view object(object2) object(object1)
+ struct view
+ {
+ #pragma db column (object2::id.num3)
+ unsigned short num;
+
+ #pragma db column (object1::id.str3)
+ std::string str;
+ };
+}
+
+// Test object with composite id and version (optimistic concurrency).
+//
+#pragma db namespace table("t7_")
+namespace test7
+{
+ #pragma db object optimistic
+ struct object
+ {
+ object () {}
+ object (const scomp& i, unsigned long n): id (i), num (n) {}
+
+ #pragma db id
+ scomp id;
+
+ #pragma db version
+ unsigned long ver;
+
+ unsigned long num;
+ };
+
+ inline bool
+ operator== (const object& x, const object& y)
+ {
+ return x.id == y.id && x.ver == y.ver && x.num == y.num;
+ }
+}
+
+// Test composite NULL pointers.
+//
+#pragma db namespace table("t8_")
+namespace test8
+{
+ #pragma db object
+ struct object1
+ {
+ object1 () {}
+ object1 (const scomp& i, unsigned long n): id (i), num (n) {}
+
+ #pragma db id
+ scomp id;
+
+ unsigned long num;
+ };
+
+ inline bool
+ operator== (const object1& x, const object1& y)
+ {
+ return x.id == y.id && x.num == y.num;
+ }
+
+ #pragma db object
+ struct object2
+ {
+ object2 (): o1 (0) {}
+ ~object2 () {delete o1;}
+
+ #pragma db id auto
+ unsigned long id;
+
+ object1* o1;
+ };
+
+ #pragma db object
+ struct object3
+ {
+ ~object3 ()
+ {
+ for (std::vector<object1*>::iterator i (o1.begin ());
+ i != o1.end (); ++i)
+ delete *i;
+ }
+
+ #pragma db id auto
+ unsigned long id;
+
+ std::vector<object1*> o1;
+ };
+}
+
+// Test composite id definition inside object.
+//
+#pragma db namespace table("t9_")
+namespace test9
+{
+ #pragma db object
+ struct object
+ {
+ object (unsigned long n = 0, const std::string& s = "")
+ {
+ id_.num = n;
+ id_.str = s;
+ }
+
+ unsigned long num () const {return id_.num;}
+ const std::string& str () const {return id_.str;}
+
+ std::vector<int> v;
+
+ private:
+ friend class odb::access;
+
+ #pragma db value
+ struct comp
+ {
+ unsigned long num;
+ std::string str;
+
+ bool
+ operator< (const comp& x) const
+ {
+ return num < x.num || (num == x.num && str < x.str);
+ }
+ };
+
+ #pragma db id
+ comp id_;
+ };
+
+ inline bool
+ operator== (const object& x, const object& y)
+ {
+ return x.num () == y.num () && x.str () == y.str () && x.v == y.v;
+ }
+}
+
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/id/composite/testscript b/odb-tests/common/id/composite/testscript
new file mode 100644
index 0000000..f87d4bc
--- /dev/null
+++ b/odb-tests/common/id/composite/testscript
@@ -0,0 +1,33 @@
+# file : common/id/composite/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/id/nested/buildfile b/odb-tests/common/id/nested/buildfile
new file mode 100644
index 0000000..777cb65
--- /dev/null
+++ b/odb-tests/common/id/nested/buildfile
@@ -0,0 +1,41 @@
+# file : common/nested/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_id_nested_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/id/nested/driver.cxx b/odb-tests/common/id/nested/driver.cxx
new file mode 100644
index 0000000..92a80f6
--- /dev/null
+++ b/odb-tests/common/id/nested/driver.cxx
@@ -0,0 +1,266 @@
+// file : common/id/nested/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test nested ids.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/session.hxx>
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+struct failed {};
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv));
+
+
+ // Simple nested id.
+ //
+ {
+ using namespace test1;
+
+ object o1 (1, "a", 3);
+ o1.v.push_back (123);
+
+ object o2 (4, "b", 6);
+ o2.v.push_back (234);
+
+ object1 o (new object (10, "abc", 11));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ db->persist (o.p);
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id.y));
+ unique_ptr<object> p2 (db->load<object> (o2.id.y));
+ unique_ptr<object1> p (db->load<object1> (o.id));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ assert (*p == o);
+ }
+
+ o1.z++;
+ o1.v.pop_back ();
+ o1.v.push_back (234);
+
+ o2.z--;
+ o2.v.back ()++;
+ o2.v.push_back (123);
+
+ delete o.p;
+ o.p = new object (20, "xyz", 11);
+
+ {
+ transaction t (db->begin ());
+ db->update (o1);
+ db->update (o2);
+ db->persist (o.p);
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id.y));
+ unique_ptr<object> p2 (db->load<object> (o2.id.y));
+ unique_ptr<object1> p (db->load<object1> (o.id));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ assert (*p == o);
+ }
+ }
+
+ // Composite nested id.
+ //
+ {
+ using namespace test2;
+
+ object o1 (1, 2, "a", 123);
+ o1.v.push_back (123);
+
+ object o2 (1, 3, "b", 234);
+ o2.v.push_back (234);
+
+ object1 o (new object (2, 2, "abc", 123));
+ o.p->v.push_back (345);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ db->persist (o.p);
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id.c));
+ unique_ptr<object> p2 (db->load<object> (o2.id.c));
+ unique_ptr<object1> p (db->load<object1> (o.id));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ assert (*p == o);
+ }
+
+ o1.z++;
+ o1.v.pop_back ();
+ o1.v.push_back (234);
+
+ o2.z--;
+ o2.v.modify_back ()++;
+ o2.v.push_back (123);
+
+ delete o.p;
+ o.p = new object (2, 3, "xyz", 234);
+
+ {
+ transaction t (db->begin ());
+ db->update (o1);
+ db->update (o2);
+ db->persist (o.p);
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id.c));
+ unique_ptr<object> p2 (db->load<object> (o2.id.c));
+ unique_ptr<object1> p (db->load<object1> (o.id));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ assert (*p == o);
+ }
+ }
+
+ // Custom/by-value access.
+ //
+ {
+ using namespace test3;
+
+ object o1 (1, "a", 3);
+ object o2 (4, "b", 6);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id.y));
+ unique_ptr<object> p2 (db->load<object> (o2.id.y));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ }
+
+ o1.z++;
+ o2.z--;
+
+ {
+ transaction t (db->begin ());
+ db->update (o1);
+ db->update (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id.y));
+ unique_ptr<object> p2 (db->load<object> (o2.id.y));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ }
+ }
+
+ // Polymorphic.
+ //
+ {
+ using namespace test4;
+
+ base o1 (1, "a");
+ object o2 (2, "b", 1);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> p1 (db->load<base> (o1.id.y));
+ unique_ptr<object> p2 (db->load<object> (o2.id.y));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ }
+
+ o2.z--;
+
+ {
+ transaction t (db->begin ());
+ db->update (o1);
+ db->update (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> p1 (db->load<base> (o1.id.y));
+ unique_ptr<object> p2 (db->load<object> (o2.id.y));
+ t.commit ();
+
+ assert (*p1 == o1);
+ assert (*p2 == o2);
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/id/nested/test.hxx b/odb-tests/common/id/nested/test.hxx
new file mode 100644
index 0000000..06ee6b8
--- /dev/null
+++ b/odb-tests/common/id/nested/test.hxx
@@ -0,0 +1,217 @@
+// file : common/id/nested/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <vector>
+
+#include <odb/core.hxx>
+#include <odb/vector.hxx>
+
+// Simple nested id.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ #pragma db value
+ struct comp
+ {
+ int x;
+ std::string y;
+ };
+
+ #pragma db object
+ struct object
+ {
+ #pragma db id(y)
+ comp id;
+
+ int z;
+ std::vector<int> v;
+
+ object () {}
+ object (int x, std::string y, int z_): z (z_) {id.x = x; id.y = y;}
+ };
+
+ inline bool
+ operator== (object a, object b)
+ {
+ return a.id.x == b.id.x && a.id.y == b.id.y && a.z == b.z && a.v == b.v;
+ }
+
+ #pragma db object
+ struct object1
+ {
+ #pragma db id auto
+ int id;
+
+ object* p;
+
+ object1 (object* p_ = 0): p (p_) {}
+ ~object1 () {delete p;}
+ };
+
+ inline bool
+ operator== (const object1& a, const object1& b)
+ {
+ return a.id == b.id && *a.p == *b.p;
+ }
+}
+
+// Composite nested id.
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ #pragma db value
+ struct comp1
+ {
+ int x;
+ int y;
+ };
+
+ #pragma db value
+ struct comp2
+ {
+ comp1 c;
+ std::string s;
+ };
+
+ #pragma db object
+ struct object
+ {
+ #pragma db id(c)
+ comp2 id;
+
+ int z;
+ odb::vector<int> v;
+
+ object () {}
+ object (int x, int y, std::string s, int z_)
+ : z (z_) {id.c.x = x; id.c.y = y; id.s = s;}
+ };
+
+ inline bool
+ operator== (object a, object b)
+ {
+ return a.id.c.x == b.id.c.x && a.id.c.y == b.id.c.y &&
+ a.id.s == b.id.s && a.z == b.z && a.v == b.v;
+ }
+
+ #pragma db object
+ struct object1
+ {
+ #pragma db id auto
+ int id;
+
+ object* p;
+
+ object1 (object* p_ = 0): p (p_) {}
+ ~object1 () {delete p;}
+ };
+
+ inline bool
+ operator== (const object1& a, const object1& b)
+ {
+ return a.id == b.id && *a.p == *b.p;
+ }
+
+ // Multiple levels of nesting, just a compile test.
+ //
+ #pragma db object
+ struct object2
+ {
+ #pragma db id(c.x)
+ comp2 id;
+
+ int z;
+ };
+}
+
+// Custom/by-value access.
+//
+#pragma db namespace table("t3_")
+namespace test3
+{
+ #pragma db value
+ struct comp
+ {
+ int x;
+
+ std::string get_y () const {return y;}
+ void set_y (std::string v) {y = v;}
+
+ #pragma db get(get_y) set(set_y)
+ std::string y;
+ };
+
+ #pragma db object
+ struct object
+ {
+ comp get_id () const {return id;}
+ void set_id (comp v) {id = v;}
+
+ #pragma db id(y) get(get_id) set(set_id)
+ comp id;
+
+ int z;
+
+ object () {}
+ object (int x, std::string y, int z_): z (z_) {id.x = x; id.y = y;}
+ };
+
+ inline bool
+ operator== (object a, object b)
+ {
+ return a.id.x == b.id.x && a.id.y == b.id.y && a.z == b.z;
+ }
+}
+
+// Polymorphic.
+//
+#pragma db namespace table("t4_")
+namespace test4
+{
+ #pragma db value
+ struct comp
+ {
+ int x;
+ std::string y;
+ };
+
+ #pragma db object polymorphic
+ struct base
+ {
+ #pragma db id(y)
+ comp id;
+
+ virtual ~base () {}
+ base () {}
+ base (int x, std::string y) {id.x = x; id.y = y;}
+ };
+
+ inline bool
+ operator== (const base& a, const base& b)
+ {
+ return a.id.x == b.id.x && a.id.y == b.id.y;
+ }
+
+ #pragma db object
+ struct object: base
+ {
+ int z;
+
+ object () {}
+ object (int x, std::string y, int z_): base (x, y), z (z_) {}
+ };
+
+ inline bool
+ operator== (const object& a, const object& b)
+ {
+ return a.id.x == b.id.x && a.id.y == b.id.y && a.z == b.z;
+ }
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/id/nested/testscript b/odb-tests/common/id/nested/testscript
new file mode 100644
index 0000000..89e8d7a
--- /dev/null
+++ b/odb-tests/common/id/nested/testscript
@@ -0,0 +1,33 @@
+# file : common/nested/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/include/.gitignore b/odb-tests/common/include/.gitignore
new file mode 100644
index 0000000..d52f166
--- /dev/null
+++ b/odb-tests/common/include/.gitignore
@@ -0,0 +1,17 @@
+# ODB-generated files.
+#
+obj1-odb.?xx
+obj1-odb-*.?xx
+obj2-odb.?xx
+obj2-odb-*.?xx
+obj3-odb.?xx
+obj3-odb-*.?xx
+
+test1-odb.?xx
+test1-odb-*.?xx
+test2-odb.?xx
+test2-odb-*.?xx
+test3-odb.?xx
+test3-odb-*.?xx
+test4-odb.?xx
+test4-odb-*.?xx
diff --git a/odb-tests/common/include/buildfile b/odb-tests/common/include/buildfile
new file mode 100644
index 0000000..6db878c
--- /dev/null
+++ b/odb-tests/common/include/buildfile
@@ -0,0 +1,51 @@
+# file : common/include/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+hs = obj1 obj2 obj3 test1 test2 test3 test4
+
+exe{driver}: {hxx cxx}{* -*-odb -*-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: $hs
+{
+ exe{driver}: {hxx ixx cxx}{$h-odb}
+
+ <{hxx ixx cxx}{$h-odb}>: hxx{$h} libue{test-meta}
+
+ for db: $databases
+ {
+ exe{driver}: {hxx ixx cxx}{$h-odb-$db}: include = $multi
+ <{hxx ixx cxx}{$h-odb-$db}>: hxx{$h} libue{test-meta}
+ }
+}
+
+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). Also see driver.cxx for the details on
+# the -I options usage.
+#
+odb_options = --table-prefix include_ \
+ "-I$out_base" \
+ "-I$src_base/.." \
+ "-I$src_base/../.."
+
+cxx.poptions =+ "-I$out_base" "-I$src_base" \
+ "-I$out_base/../.." "-I$src_base/../.."
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/include/driver.cxx b/odb-tests/common/include/driver.cxx
new file mode 100644
index 0000000..561746a
--- /dev/null
+++ b/odb-tests/common/include/driver.cxx
@@ -0,0 +1,42 @@
+// file : common/include/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test inclusion of -odb files (compilation test).
+//
+// The setup of this test is as follows: the ODB compiler has two
+// additional include directories in its search path: .. and ../..
+// while the C++ compiler has only ../.. . This way, if a ..-based
+// path is used in the generated code, the C++ compilation will
+// fail.
+//
+
+#include <memory>
+#include <iostream>
+
+#include <odb/exceptions.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test1.hxx"
+#include "test1-odb.hxx"
+
+#include "test2.hxx"
+#include "test2-odb.hxx"
+
+#include "test3.hxx"
+#include "test3-odb.hxx"
+
+#include "test4.hxx"
+#include "test4-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main ()
+{
+}
diff --git a/odb-tests/common/include/obj1.hxx b/odb-tests/common/include/obj1.hxx
new file mode 100644
index 0000000..33ae0d6
--- /dev/null
+++ b/odb-tests/common/include/obj1.hxx
@@ -0,0 +1,25 @@
+// file : common/include/obj1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef OBJ1_HXX
+#define OBJ1_HXX
+
+#include <odb/core.hxx>
+
+#pragma db object
+struct object1
+{
+ object1 (unsigned long id)
+ : id_ (id)
+ {
+ }
+
+ object1 ()
+ {
+ }
+
+ #pragma db id
+ unsigned long id_;
+};
+
+#endif // OBJ1_HXX
diff --git a/odb-tests/common/include/obj2.hxx b/odb-tests/common/include/obj2.hxx
new file mode 100644
index 0000000..2f20f58
--- /dev/null
+++ b/odb-tests/common/include/obj2.hxx
@@ -0,0 +1,25 @@
+// file : common/include/obj2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef OBJ2_HXX
+#define OBJ2_HXX
+
+#include <odb/core.hxx>
+
+#pragma db object
+struct object2
+{
+ object2 (unsigned long id)
+ : id_ (id)
+ {
+ }
+
+ object2 ()
+ {
+ }
+
+ #pragma db id
+ unsigned long id_;
+};
+
+#endif // OBJ2_HXX
diff --git a/odb-tests/common/include/obj3.hxx b/odb-tests/common/include/obj3.hxx
new file mode 100644
index 0000000..432145b
--- /dev/null
+++ b/odb-tests/common/include/obj3.hxx
@@ -0,0 +1,25 @@
+// file : common/include/obj3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef OBJ3_HXX
+#define OBJ3_HXX
+
+#include <odb/core.hxx>
+
+#pragma db object
+struct object3
+{
+ object3 (unsigned long id)
+ : id_ (id)
+ {
+ }
+
+ object3 ()
+ {
+ }
+
+ #pragma db id
+ unsigned long id_;
+};
+
+#endif // OBJ3_HXX
diff --git a/odb-tests/common/include/objs1.hxx b/odb-tests/common/include/objs1.hxx
new file mode 100644
index 0000000..6e949e2
--- /dev/null
+++ b/odb-tests/common/include/objs1.hxx
@@ -0,0 +1,13 @@
+// file : common/include/objs1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef OBJS1_HXX
+#define OBJS1_HXX
+
+#ifdef ODB_COMPILER
+# include <include/obj1.hxx>
+# include <include/obj2.hxx>
+# include <include/obj3.hxx>
+#endif
+
+#endif // OBJS1_HXX
diff --git a/odb-tests/common/include/objs2.hxx b/odb-tests/common/include/objs2.hxx
new file mode 100644
index 0000000..4f8133b
--- /dev/null
+++ b/odb-tests/common/include/objs2.hxx
@@ -0,0 +1,13 @@
+// file : common/include/objs2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef OBJS2_HXX
+#define OBJS2_HXX
+
+#ifdef ODB_COMPILER
+# include "include/obj1.hxx"
+# include "include/obj2.hxx"
+# include "include/obj3.hxx"
+#endif
+
+#endif // OBJS2_HXX
diff --git a/odb-tests/common/include/objs3.hxx b/odb-tests/common/include/objs3.hxx
new file mode 100644
index 0000000..2f7aaff
--- /dev/null
+++ b/odb-tests/common/include/objs3.hxx
@@ -0,0 +1,11 @@
+// file : common/include/objs3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef OBJS3_HXX
+#define OBJS3_HXX
+
+#include "../include/obj1.hxx"
+#include "../include/obj2.hxx"
+#include "../include/obj3.hxx"
+
+#endif // OBJS3_HXX
diff --git a/odb-tests/common/include/objs4.hxx b/odb-tests/common/include/objs4.hxx
new file mode 100644
index 0000000..d766fe6
--- /dev/null
+++ b/odb-tests/common/include/objs4.hxx
@@ -0,0 +1,11 @@
+// file : common/include/objs1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef OBJS4_HXX
+#define OBJS4_HXX
+
+#include <common/include/obj1.hxx>
+#include <common/include/obj2.hxx>
+#include <common/include/obj3.hxx>
+
+#endif // OBJS4_HXX
diff --git a/odb-tests/common/include/test1.hxx b/odb-tests/common/include/test1.hxx
new file mode 100644
index 0000000..1914ddb
--- /dev/null
+++ b/odb-tests/common/include/test1.hxx
@@ -0,0 +1,16 @@
+// file : common/include/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+// Test include directive parsing.
+//
+#include"obj1.hxx"
+
+ # include \
+ <common/include/obj2.hxx>
+
+/*comment*/ # /*comment*/ include /* comment */ "obj3.hxx" // comment
+
+#endif // TEST1_HXX
diff --git a/odb-tests/common/include/test2.hxx b/odb-tests/common/include/test2.hxx
new file mode 100644
index 0000000..6017ac4
--- /dev/null
+++ b/odb-tests/common/include/test2.hxx
@@ -0,0 +1,15 @@
+// file : common/include/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+// Test preference of includes from the main file.
+//
+#include "objs1.hxx"
+
+#include "obj1.hxx"
+#include "obj2.hxx"
+#include "obj3.hxx"
+
+#endif // TEST2_HXX
diff --git a/odb-tests/common/include/test3.hxx b/odb-tests/common/include/test3.hxx
new file mode 100644
index 0000000..e55ecdb
--- /dev/null
+++ b/odb-tests/common/include/test3.hxx
@@ -0,0 +1,12 @@
+// file : common/include/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+// Test preference of longer (more qualified) paths.
+//
+#include "objs2.hxx"
+#include "objs3.hxx"
+
+#endif // TEST3_HXX
diff --git a/odb-tests/common/include/test4.hxx b/odb-tests/common/include/test4.hxx
new file mode 100644
index 0000000..b58e679
--- /dev/null
+++ b/odb-tests/common/include/test4.hxx
@@ -0,0 +1,12 @@
+// file : common/include/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+// Test preference of <> over "".
+//
+#include "objs2.hxx"
+#include "objs4.hxx"
+
+#endif // TEST3_HXX
diff --git a/odb-tests/common/include/testscript b/odb-tests/common/include/testscript
new file mode 100644
index 0000000..089f7a1
--- /dev/null
+++ b/odb-tests/common/include/testscript
@@ -0,0 +1,31 @@
+# file : common/include/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $* &!odb-test.db
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $*
+}
diff --git a/odb-tests/common/index/buildfile b/odb-tests/common/index/buildfile
new file mode 100644
index 0000000..535bd26
--- /dev/null
+++ b/odb-tests/common/index/buildfile
@@ -0,0 +1,40 @@
+# file : common/index/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 index_ \
+ --generate-schema
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/index/driver.cxx b/odb-tests/common/index/driver.cxx
new file mode 100644
index 0000000..7a22a7c
--- /dev/null
+++ b/odb-tests/common/index/driver.cxx
@@ -0,0 +1,44 @@
+// file : common/index/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test index creation with db pragma index. See also database-specific
+// tests.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ // This is just a schema creation test.
+ //
+ unique_ptr<database> db (create_database (argc, argv));
+
+ {
+ transaction t (db->begin ());
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/index/test.hxx b/odb-tests/common/index/test.hxx
new file mode 100644
index 0000000..f27783f
--- /dev/null
+++ b/odb-tests/common/index/test.hxx
@@ -0,0 +1,142 @@
+// file : common/index/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <vector>
+
+#include <odb/core.hxx>
+
+// Test basic functionality.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ #pragma db object
+ struct object
+ {
+ #pragma db id auto
+ unsigned long id_;
+
+ #pragma db index
+ int i1;
+
+ #pragma db unique
+ int i2;
+
+ #pragma db unique index
+ int i3;
+
+ int i4;
+ #pragma db index unique member(i4)
+
+ int i5;
+ #pragma db index type("UNIQUE") member(i5)
+
+ int i6;
+ #pragma db index("object_i6_index") member(i6)
+
+ int i7;
+ int i8;
+ int i9;
+
+ int i10;
+ #pragma db index member(i10, "ASC")
+ };
+
+ #pragma db index(object) member(i7)
+ #pragma db index(object::"object_i8_index") member(i8)
+}
+
+#pragma db index(test1::object::"object_i9_index") member(i9)
+
+// Test composite indexes.
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ #pragma db value
+ struct nested
+ {
+ int x;
+ int y;
+ };
+
+ #pragma db value
+ struct comp
+ {
+ int x;
+ int y;
+ nested n;
+ };
+
+ #pragma db object
+ struct object
+ {
+ #pragma db id auto
+ unsigned long id_;
+
+ int i1a;
+ int i1b;
+ #pragma db index("object_i1_i") member(i1a) member(i1b)
+
+ int i2a;
+ int i2b;
+ #pragma db index("object_i2_i") members(i2a, i2b)
+
+ #pragma db index
+ comp c1;
+
+ #pragma db index column("")
+ comp c2;
+
+ comp c3;
+ #pragma db index member(c3.x)
+ #pragma db index member(c3.y)
+
+ comp c4;
+ #pragma db index("object_c4_i") members(c4.x, c4.y, c4.n.x)
+
+ comp c5;
+ int i5;
+ #pragma db index("object_ci5_i") member(c5) member(i5)
+ };
+}
+
+// Test container indexes.
+//
+#pragma db namespace table("t3_")
+namespace test3
+{
+ #pragma db value
+ struct id
+ {
+ int x;
+ int y;
+ };
+
+ #pragma db value
+ struct comp
+ {
+ int x;
+ std::vector<int> v;
+ };
+
+ #pragma db object
+ struct object
+ {
+ #pragma db id
+ id id_;
+
+ std::vector<int> v;
+ #pragma db index unique member(v.id)
+ #pragma db index("object_v_index_index") member(v.index)
+
+ comp c;
+ #pragma db index("object_c_v_id_index") member(c.v.id)
+ #pragma db index unique member(c.v.index)
+ };
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/index/testscript b/odb-tests/common/index/testscript
new file mode 100644
index 0000000..cbce341
--- /dev/null
+++ b/odb-tests/common/index/testscript
@@ -0,0 +1,33 @@
+# file : common/index/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/inheritance/polymorphism/.gitignore b/odb-tests/common/inheritance/polymorphism/.gitignore
new file mode 100644
index 0000000..f183a6f
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/.gitignore
@@ -0,0 +1,76 @@
+# ODB-generated files.
+#
+test1-odb.?xx
+test1-odb-*.?xx
+test1.sql
+test1-*.sql
+
+test2-odb.?xx
+test2-odb-*.?xx
+test2.sql
+test2-*.sql
+
+test3-odb.?xx
+test3-odb-*.?xx
+test3.sql
+test3-*.sql
+
+test4-odb.?xx
+test4-odb-*.?xx
+test4.sql
+test4-*.sql
+
+test5-odb.?xx
+test5-odb-*.?xx
+test5.sql
+test5-*.sql
+
+test6-odb.?xx
+test6-odb-*.?xx
+test6.sql
+test6-*.sql
+
+test7-odb.?xx
+test7-odb-*.?xx
+test7.sql
+test7-*.sql
+
+test8-odb.?xx
+test8-odb-*.?xx
+test8.sql
+test8-*.sql
+
+test9-odb.?xx
+test9-odb-*.?xx
+test9.sql
+test9-*.sql
+
+test10-odb.?xx
+test10-odb-*.?xx
+test10.sql
+test10-*.sql
+
+test11-odb.?xx
+test11-odb-*.?xx
+test11.sql
+test11-*.sql
+
+test12-odb.?xx
+test12-odb-*.?xx
+test12.sql
+test12-*.sql
+
+test13-odb.?xx
+test13-odb-*.?xx
+test13.sql
+test13-*.sql
+
+test14-odb.?xx
+test14-odb-*.?xx
+test14.sql
+test14-*.sql
+
+test15-odb.?xx
+test15-odb-*.?xx
+test15.sql
+test15-*.sql
diff --git a/odb-tests/common/inheritance/polymorphism/buildfile b/odb-tests/common/inheritance/polymorphism/buildfile
new file mode 100644
index 0000000..846eb12
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/buildfile
@@ -0,0 +1,52 @@
+# file : common/inheritance/polymorphism/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+hs = test1 test2 test3 test4 test5 test6 test7 test8 test9 test10 test11 \
+ test12 test13 test14 test15
+
+exe{driver}: {hxx cxx}{* -*-odb -*-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: $hs
+{
+ exe{driver}: {hxx ixx cxx}{$h-odb}
+
+ <{hxx ixx cxx}{$h-odb}>: hxx{$h} libue{test-meta}
+
+ for db: $databases
+ {
+ exe{driver}: {hxx ixx cxx}{$h-odb-$db}: include = $multi
+ <{hxx ixx cxx}{$h-odb-$db}>: hxx{$h} libue{test-meta}
+ }
+}
+
+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 inhrt_p_ \
+ --generate-schema \
+ --generate-query \
+ --generate-prepared
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
+
+testscript@./: schemas = $hs
diff --git a/odb-tests/common/inheritance/polymorphism/driver.cxx b/odb-tests/common/inheritance/polymorphism/driver.cxx
new file mode 100644
index 0000000..12f4666
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/driver.cxx
@@ -0,0 +1,2093 @@
+// file : common/inheritance/polymorphism/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test polymorphic object inheritance.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/session.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test1.hxx"
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test4.hxx"
+#include "test5.hxx"
+#include "test6.hxx"
+#include "test7.hxx"
+#include "test8.hxx"
+#include "test9.hxx"
+#include "test10.hxx"
+#include "test11.hxx"
+#include "test12.hxx"
+#include "test13.hxx"
+#include "test14.hxx"
+#include "test15.hxx"
+
+#include "test1-odb.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+#include "test4-odb.hxx"
+#include "test5-odb.hxx"
+#include "test6-odb.hxx"
+#include "test7-odb.hxx"
+#include "test8-odb.hxx"
+#include "test9-odb.hxx"
+#include "test10-odb.hxx"
+#include "test11-odb.hxx"
+#include "test12-odb.hxx"
+#include "test13-odb.hxx"
+#include "test14-odb.hxx"
+#include "test15-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+const char* events[] =
+{
+ "pre_persist",
+ "post_persist",
+ "pre_load",
+ "post_load",
+ "pre_update",
+ "post_update",
+ "pre_erase",
+ "post_erase"
+};
+
+namespace test6
+{
+ void base::
+ db_callback (callback_event e, database&)
+ {
+ cout << "base " << events[e] << " " << id << endl;
+ }
+
+ void base::
+ db_callback (callback_event e, database&) const
+ {
+ cout << "base " << events[e] << " " << id << " const" << endl;
+ }
+
+ void derived::
+ db_callback (callback_event e, database&) const
+ {
+ cout << "derived " << events[e] << " " << id << " const" << endl;
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv));
+
+ // Test 1: basic polymorphism functionality.
+ //
+ {
+ using namespace test1;
+
+ root r (1, 1);
+ base b (2, 2, "bbb");
+ derived d (3, 3, "ddd");
+
+ r.strs.push_back ("a");
+ r.strs.push_back ("aa");
+ r.strs.push_back ("aaa");
+
+ b.nums.push_back (21);
+ b.nums.push_back (22);
+ b.nums.push_back (23);
+ b.strs.push_back ("b");
+ b.strs.push_back ("bb");
+ b.strs.push_back ("bbb");
+
+ d.nums.push_back (31);
+ d.nums.push_back (32);
+ d.nums.push_back (33);
+ d.strs.push_back ("d");
+ d.strs.push_back ("dd");
+ d.strs.push_back ("ddd");
+
+ {
+ transaction t (db->begin ());
+
+ // Static persist.
+ //
+ db->persist (r);
+ db->persist (b);
+
+ // Dynamic persist.
+ //
+ root& r (d);
+ db->persist (r);
+
+ t.commit ();
+ }
+
+ // Static load.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<root> pr (db->load<root> (r.id));
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+ t.commit ();
+
+ assert (*pr == r);
+ assert (*pb == b);
+ assert (*pd == d);
+ }
+
+ // Dynamic load.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<root> pb (db->load<root> (b.id));
+ unique_ptr<root> pd1 (db->load<root> (d.id));
+ unique_ptr<base> pd2 (db->load<base> (d.id));
+ t.commit ();
+
+ assert (*pb == b);
+ assert (*pd1 == d);
+ assert (*pd2 == d);
+ }
+
+ // Invalid load.
+ //
+ {
+ transaction t (db->begin ());
+
+ try
+ {
+ unique_ptr<base> p (db->load<base> (r.id));
+ assert (false);
+ }
+ catch (const object_not_persistent&) {}
+
+ try
+ {
+ unique_ptr<derived> p (db->load<derived> (b.id));
+ assert (false);
+ }
+ catch (const object_not_persistent&) {}
+
+ t.commit ();
+ }
+
+ // Static load into existing instance.
+ //
+ {
+ transaction t (db->begin ());
+ root r1;
+ db->load (r.id, r1);
+ base b1;
+ db->load (b.id, b1);
+ derived d1;
+ db->load (d.id, d1);
+ t.commit ();
+
+ assert (r1 == r);
+ assert (b1 == b);
+ assert (d1 == d);
+ }
+
+ // Dynamic load into existing instance.
+ //
+ {
+ transaction t (db->begin ());
+ base b1;
+ db->load (b.id, static_cast<root&> (b1));
+ derived d1;
+ db->load (d.id, static_cast<base&> (d1));
+ t.commit ();
+
+ assert (b1 == b);
+ assert (d1 == d);
+ }
+
+ // Invalid load into existing instance.
+ //
+ {
+ transaction t (db->begin ());
+
+ try
+ {
+ base b;
+ db->load (r.id, static_cast<root&> (b));
+ assert (false);
+ }
+ catch (const object_not_persistent&) {}
+
+ try
+ {
+ derived d;
+ db->load (b.id, static_cast<base&> (d));
+ assert (false);
+ }
+ catch (const object_not_persistent&) {}
+
+ t.commit ();
+ }
+
+ // Slicing load.
+ //
+ {
+ transaction t (db->begin ());
+ root b1;
+ db->load (b.id, b1);
+ base d1;
+ db->load (d.id, d1);
+ t.commit ();
+
+ assert (b1 == static_cast<root> (b));
+ assert (d1 == static_cast<base> (d));
+ }
+
+ // Static reload.
+ //
+ {
+ transaction t (db->begin ());
+ root r1;
+ r1.id = r.id;
+ db->reload (r1);
+ base b1;
+ b1.id = b.id;
+ db->reload (b1);
+ derived d1;
+ d1.id = d.id;
+ db->reload (d1);
+ t.commit ();
+
+ assert (r1 == r);
+ assert (b1 == b);
+ assert (d1 == d);
+ }
+
+ // Dynamic reload.
+ //
+ {
+ transaction t (db->begin ());
+ base b1;
+ b1.id = b.id;
+ db->reload (static_cast<root&> (b1));
+ derived d1;
+ d1.id = d.id;
+ db->reload (static_cast<base&> (d1));
+ t.commit ();
+
+ assert (b1 == b);
+ assert (d1 == d);
+ }
+
+ // Invalid reload.
+ //
+ {
+ transaction t (db->begin ());
+
+ try
+ {
+ base b;
+ b.id = r.id;
+ db->reload (static_cast<root&> (b));
+ assert (false);
+ }
+ catch (const object_not_persistent&) {}
+
+ try
+ {
+ derived d;
+ d.id = b.id;
+ db->reload (static_cast<base&> (d));
+ assert (false);
+ }
+ catch (const object_not_persistent&) {}
+
+ t.commit ();
+ }
+
+ // Slicing reload.
+ //
+ {
+ transaction t (db->begin ());
+ root b1;
+ b1.id = b.id;
+ db->reload (b1);
+ base d1;
+ d1.id = d.id;
+ db->reload (d1);
+ t.commit ();
+
+ assert (b1 == static_cast<root> (b));
+ assert (d1 == static_cast<base> (d));
+ }
+
+ // Query.
+ //
+ {
+ typedef odb::query<root> root_query;
+ typedef odb::result<root> root_result;
+
+ typedef odb::query<base> base_query;
+ typedef odb::result<base> base_result;
+
+ typedef odb::result<derived> derived_result;
+
+ transaction t (db->begin ());
+
+ // Test loading via root.
+ //
+ {
+ root_result qr (db->query<root> ("ORDER BY" + root_query::id));
+ root_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e && *i == r);
+ assert (++i != e && *i == b);
+ assert (++i != e && *i == d);
+ assert (++i == e);
+ }
+
+ // Test loading via base.
+ //
+ {
+ base_result qr (db->query<base> ("ORDER BY" + base_query::id));
+ base_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e && *i == b);
+ assert (++i != e && *i == d);
+ assert (++i == e);
+ }
+
+ // Test loading via derived.
+ //
+ {
+ derived_result qr (db->query<derived> ());
+ derived_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e && *i == d);
+ assert (++i == e);
+ }
+
+ // Test loading into an existing instance.
+ //
+ {
+ root_result qr (db->query<root> ());
+
+ unsigned short mask (0);
+
+ for (root_result::iterator i (qr.begin ()); i != qr.end (); ++i)
+ {
+ string ds (i.discriminator ());
+
+ if (ds == "test1::root")
+ {
+ root r1;
+ i.load (r1);
+ assert (r1 == r);
+ mask |= 1;
+ }
+ else if (ds == "test1::base")
+ {
+ base b1;
+ i.load (b1);
+ assert (b1 == b);
+ mask |= 2;
+ }
+ else if (ds == "test1::derived")
+ {
+ derived d1;
+ i.load (d1);
+ assert (d1 == d);
+ mask |= 4;
+ }
+ else
+ assert (false);
+ }
+
+ assert (mask == 7);
+ }
+
+ // Test query conditions with columns from multiple tables.
+ //
+ {
+ base_result qr (
+ db->query<base> (
+ base_query::num == 3 && base_query::str == "ddd"));
+
+ base_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e && *i == d);
+ assert (++i == e);
+ }
+
+ // Test discriminator access.
+ //
+ {
+ base_result qr (db->query<base> (base_query::id == 3));
+ base_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e && i.discriminator () == "test1::derived");
+ assert (++i == e);
+ }
+
+ // Test loading of an object from the same hierarchy during
+ // query result iteration (tests image copying via change
+ // callbacks in some databases).
+ //
+ {
+ base_result qr (db->query<base> ());
+
+ unsigned short mask (0);
+
+ for (base_result::iterator i (qr.begin ()); i != qr.end (); ++i)
+ {
+ string ds (i.discriminator ());
+
+ if (ds == "test1::base")
+ {
+ unique_ptr<derived> d1 (db->load<derived> (d.id));
+ assert (*d1 == d);
+ assert (*i == b);
+ mask |= 1;
+ }
+ else if (ds == "test1::derived")
+ {
+ unique_ptr<base> b1 (db->load<base> (b.id));
+ assert (*b1 == b);
+ assert (*i == d);
+ mask |= 2;
+ }
+ }
+
+ assert (mask == 3);
+ }
+
+ t.commit ();
+ }
+
+ // Views.
+ //
+ {
+ typedef odb::query<root_view> root_query;
+ typedef odb::result<root_view> root_result;
+
+ typedef odb::query<base_view> base_query;
+ typedef odb::result<base_view> base_result;
+
+ typedef odb::result<derived_view> derived_result;
+
+ transaction t (db->begin ());
+
+ // root
+ //
+ {
+ root_result qr (db->query<root_view> ("ORDER BY" + root_query::id));
+ root_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e && i->typeid_ == "test1::root" && i->num == r.num);
+ assert (++i != e && i->typeid_ == "test1::base" && i->num == b.num);
+ assert (++i != e && i->typeid_ == "test1::derived" && i->num == d.num);
+ assert (++i == e);
+ }
+
+ // base
+ //
+ {
+ base_result qr (db->query<base_view> ("ORDER BY" + base_query::id));
+ base_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e &&
+ i->id == b.id && i->num == b.num && i->str == b.str);
+ assert (++i != e &&
+ i->id == d.id && i->num == d.num && i->str == d.str);
+ assert (++i == e);
+ }
+
+ // derived
+ //
+ {
+ derived_result qr (db->query<derived_view> ());
+ derived_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e &&
+ i->num == d.num && i->str == d.str &&
+ i->dnum == d.dnum && i->dstr == d.dstr);
+ assert (++i == e);
+ }
+
+ t.commit ();
+ }
+
+ // Update.
+ //
+ r.num++;
+ r.strs.push_back ("aaaa");
+
+ b.num++;
+ b.str += "b";
+ b.nums.push_back (24);
+ b.strs.push_back ("bbbb");
+
+ d.num++;
+ d.str += "d";
+ d.dnum++;
+ d.dstr += "d";
+ d.nums.push_back (34);
+ d.strs.push_back ("dddd");
+
+ {
+ transaction t (db->begin ());
+
+ // Static update.
+ //
+ db->update (r);
+ db->update (b);
+
+ // Dynamic update.
+ //
+ root& r (d);
+ db->update (r);
+
+ t.commit ();
+ }
+
+ // Verify update.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<root> pr (db->load<root> (r.id));
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+ t.commit ();
+
+ assert (*pr == r);
+ assert (*pb == b);
+ assert (*pd == d);
+ }
+
+ // Invalid erase via id.
+ //
+ {
+ transaction t (db->begin ());
+
+ try
+ {
+ db->erase<base> (r.id);
+ assert (false);
+ }
+ catch (const object_not_persistent&) {}
+
+ try
+ {
+ db->erase<derived> (b.id);
+ assert (false);
+ }
+ catch (const object_not_persistent&) {}
+
+ t.commit ();
+ }
+
+ // Static erase via id.
+ //
+ {
+ transaction t (db->begin ());
+ db->erase<root> (r.id);
+ db->erase<base> (b.id);
+ t.commit ();
+ }
+
+ // Dynamic erase via id.
+ //
+ {
+ transaction t (db->begin ());
+ db->erase<root> (d.id);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->persist (r);
+ db->persist (b);
+ db->persist (d);
+ t.commit ();
+ }
+
+ // Static erase via object.
+ //
+ {
+ transaction t (db->begin ());
+ db->erase (r);
+ db->erase (b);
+ t.commit ();
+ }
+
+ // Dynamic erase via object.
+ //
+ {
+ const root& r (d);
+ transaction t (db->begin ());
+ db->erase (r);
+ t.commit ();
+ }
+ }
+
+ // Test 2: inverse object pointers in polymorhic bases.
+ //
+ {
+ using namespace test2;
+
+ derived d (1, "d", 1);
+ root_pointer rp (&d);
+ base_pointer bp (&d);
+
+ {
+ transaction t (db->begin ());
+ db->persist (rp);
+ db->persist (bp);
+
+ d.rp.reset (*db, &rp);
+ d.bp.reset (*db, &bp);
+
+ db->persist (d);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+ unique_ptr<base> pb (db->load<base> (d.id));
+ unique_ptr<root> pr (db->load<root> (d.id));
+ t.commit ();
+
+ assert (pd->rp.object_id<root_pointer> () == rp.id &&
+ pd->bp.object_id<base_pointer> () == bp.id);
+
+ derived* p (dynamic_cast<derived*> (pb.get ()));
+ assert (p != 0 &&
+ p->rp.object_id<root_pointer> () == rp.id &&
+ p->bp.object_id<base_pointer> () == bp.id);
+
+ p = dynamic_cast<derived*> (pr.get ());
+ assert (p != 0 &&
+ p->rp.object_id<root_pointer> () == rp.id &&
+ p->bp.object_id<base_pointer> () == bp.id);
+ }
+
+ // Query.
+ //
+ {
+ typedef odb::query<base> base_query;
+ typedef odb::result<base> base_result;
+
+ transaction t (db->begin ());
+
+ // Test query conditions with columns in pointed-to objects from
+ // multiple tables.
+ //
+ {
+ base_result qr (
+ db->query<base> (
+ base_query::rp->id == rp.id &&
+ base_query::bp->id == bp.id));
+
+ base_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e && i.discriminator () == "test2::derived");
+ assert (++i == e);
+ }
+
+ t.commit ();
+ }
+
+ // Views.
+ //
+ {
+ typedef odb::result<root_view> root_result;
+ typedef odb::result<base_view> base_result;
+
+ transaction t (db->begin ());
+
+ // root
+ //
+ {
+ root_result qr (db->query<root_view> ());
+ root_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e && i->rp_id == rp.id && i->r_id == d.id);
+ assert (++i == e);
+ }
+
+ // base
+ //
+ {
+ base_result qr (db->query<base_view> ());
+ base_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e &&
+ i->bp_id == bp.id && i->b_id == d.id && i->str == d.str);
+ assert (++i == e);
+ }
+
+ t.commit ();
+ }
+ }
+
+ // Test 3: delayed loading.
+ //
+ {
+ using namespace test3;
+
+ base b1 (21, 21);
+ base b2 (22, 22);
+ base b3 (23, 23);
+
+ derived d1 (31, 31, "d");
+ derived d2 (32, 32, "dd");
+ derived d3 (33, 33, "ddd");
+
+ b1.rptr = new root (1);
+ b2.rptr = new base (2, 2);
+ b3.rptr = new derived (3, 3, "b3");
+
+ d1.rptr = new root (4);
+ d2.rptr = new base (5, 5);
+ d3.rptr = new derived (6, 6, "d3");
+
+ d2.bptr = new base (7, 7);
+ d3.bptr = new derived (8, 8, "d3b");
+
+ {
+ transaction t (db->begin ());
+ db->persist (b1);
+ db->persist (b2);
+ db->persist (b3);
+
+ db->persist (d1);
+ db->persist (d2);
+ db->persist (d3);
+
+ db->persist (b1.rptr);
+ db->persist (b2.rptr);
+ db->persist (b3.rptr);
+
+ db->persist (d1.rptr);
+ db->persist (d2.rptr);
+ db->persist (d3.rptr);
+
+ db->persist (d2.bptr);
+ db->persist (d3.bptr);
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ {
+ unique_ptr<base> p1 (db->load<base> (b1.id));
+ unique_ptr<base> p2 (db->load<base> (b2.id));
+ unique_ptr<root> p3 (db->load<root> (b3.id));
+ assert (*p1 == b1);
+ assert (*p2 == b2);
+ assert (*p3 == b3);
+ }
+
+ {
+ unique_ptr<derived> p1 (db->load<derived> (d1.id));
+ unique_ptr<base> p2 (db->load<base> (d2.id));
+ unique_ptr<root> p3 (db->load<root> (d3.id));
+ assert (*p1 == d1);
+ assert (*p2 == d2);
+ assert (*p3 == d3);
+ }
+
+ t.commit ();
+ }
+
+ // Query.
+ //
+ {
+ typedef odb::query<derived> derived_query;
+ typedef odb::result<derived> derived_result;
+
+ transaction t (db->begin ());
+
+ // Test query conditions with columns in pointed-to objects from
+ // multiple tables.
+ //
+ {
+ derived_result qr (
+ db->query<derived> (
+ derived_query::rptr->id == 6 &&
+ derived_query::bptr->id == 8 &&
+ derived_query::bptr->num == 8));
+
+ derived_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e && *i == d3);
+ assert (++i == e);
+ }
+
+ t.commit ();
+ }
+
+ // Views.
+ //
+ {
+ typedef odb::query<base_view> base_query;
+ typedef odb::result<base_view> base_result;
+
+ typedef odb::query<derived_view> derived_query;
+ typedef odb::result<derived_view> derived_result;
+
+ typedef odb::query<root_view> root_query;
+ typedef odb::result<root_view> root_result;
+
+ transaction t (db->begin ());
+
+ // base
+ //
+ {
+ base_result qr (
+ db->query<base_view> (
+ base_query::base::num == b2.num &&
+ base_query::base::id == b2.id &&
+ base_query::r::id == b2.rptr->id));
+
+ base_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e &&
+ i->b_id == b2.id &&
+ i->r_id == b2.rptr->id &&
+ i->num == b2.num);
+ assert (++i == e);
+ }
+
+ // derived
+ //
+ {
+ derived_result qr (
+ db->query<derived_view> (
+ derived_query::d::str == d3.str &&
+ derived_query::d::num == d3.num &&
+ derived_query::b::num == d3.bptr->num &&
+ derived_query::d::id == d3.id &&
+ derived_query::b::id == d3.bptr->id &&
+ derived_query::r::id == d3.rptr->id));
+
+ derived_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e &&
+ i->d_id == d3.id &&
+ i->b_id == d3.bptr->id &&
+ i->r_id == d3.rptr->id &&
+ i->d_num == d3.num &&
+ i->b_num == d3.bptr->num &&
+ i->str == d3.str);
+ assert (++i == e);
+ }
+
+ // root
+ //
+ {
+ root_result qr (
+ db->query<root_view> (
+ root_query::r::id.in (b2.rptr->id, d2.rptr->id)));
+
+ root_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e &&
+ i->r_id == d2.rptr->id &&
+ i->d_id == d2.id &&
+ i->str == d2.str);
+ assert (++i == e);
+ }
+
+ t.commit ();
+ }
+ }
+
+ // Test 4: views.
+ //
+ {
+ using namespace test4;
+
+ base1 b1 (21, 1);
+
+ root2 r2 (11, 0);
+ base2 b2 (21, 1, "abc");
+
+ {
+ transaction t (db->begin ());
+ db->persist (b1);
+ db->persist (r2);
+ db->persist (b2);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<view1> query;
+ typedef odb::result<view1> result;
+
+ transaction t (db->begin ());
+
+ {
+ result qr (
+ db->query<view1> (
+ query::base1::num == b1.num));
+
+ result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e && i->str == "abc");
+ assert (++i == e);
+ }
+
+ t.commit ();
+ }
+
+ {
+ typedef odb::result<view2> result;
+
+ transaction t (db->begin ());
+
+ {
+ result qr (db->query<view2> ());
+ result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e && i->min_num == 1);
+ assert (++i == e);
+ }
+
+ t.commit ();
+ }
+
+ {
+ typedef odb::result<view3> result;
+
+ transaction t (db->begin ());
+
+ {
+ result qr (db->query<view3> ());
+ result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e && i->str == "abc");
+ assert (++i == e);
+ }
+
+ t.commit ();
+ }
+ }
+
+ // Test 5: polymorphism and optimistic concurrency.
+ //
+ {
+ using namespace test5;
+
+ root r (1, 1);
+ base b (2, 2, "bbb");
+ derived d (3, 3, "ddd");
+
+ r.strs.push_back ("a");
+ r.strs.push_back ("aa");
+ r.strs.push_back ("aaa");
+
+ b.nums.push_back (21);
+ b.nums.push_back (22);
+ b.nums.push_back (23);
+ b.strs.push_back ("b");
+ b.strs.push_back ("bb");
+ b.strs.push_back ("bbb");
+
+ d.nums.push_back (31);
+ d.nums.push_back (32);
+ d.nums.push_back (33);
+ d.strs.push_back ("d");
+ d.strs.push_back ("dd");
+ d.strs.push_back ("ddd");
+
+ {
+ transaction t (db->begin ());
+ db->persist (r);
+ db->persist (b);
+ db->persist (d);
+ t.commit ();
+ }
+
+ // Update.
+ //
+ {
+ transaction t (db->begin ());
+
+ // Root.
+ //
+ {
+ unique_ptr<root> p (db->load<root> (r.id));
+
+ r.num++;
+ r.strs.push_back ("aaaa");
+ db->update (r);
+
+ p->num--;
+ p->strs.pop_back ();
+ try
+ {
+ db->update (p);
+ assert (false);
+ }
+ catch (const odb::object_changed&) {}
+
+ // Make sure the object is intact.
+ //
+ db->reload (p);
+ assert (r == *p);
+ }
+
+ // Base.
+ //
+ {
+ unique_ptr<base> p (db->load<base> (b.id));
+
+ b.num++;
+ b.str += "b";
+ b.strs.push_back ("bbbb");
+ b.nums.push_back (24);
+ db->update (b);
+
+ p->num--;
+ p->str += "B";
+ p->strs.pop_back ();
+ p->nums.pop_back ();
+ try
+ {
+ db->update (p);
+ assert (false);
+ }
+ catch (const odb::object_changed&) {}
+
+ // Make sure the object is intact.
+ //
+ db->reload (p);
+ assert (b == *p);
+ }
+
+ // Derived.
+ //
+ {
+ unique_ptr<root> p (db->load<root> (d.id)); // Via root.
+
+ d.num++;
+ d.str += "d";
+ d.strs.push_back ("dddd");
+ d.nums.push_back (24);
+ d.dnum++;
+ d.dstr += "d";
+ db->update (d);
+
+ derived& d1 (static_cast<derived&> (*p));
+ d1.num--;
+ d1.str += "D";
+ d1.strs.pop_back ();
+ d1.nums.pop_back ();
+ d1.dnum--;
+ d1.dstr += "D";
+ try
+ {
+ db->update (p);
+ assert (false);
+ }
+ catch (const odb::object_changed&) {}
+
+ // Make sure the object is intact.
+ //
+ db->reload (p);
+ assert (d == *p);
+ }
+
+ t.commit ();
+ }
+
+ // Reload.
+ //
+ {
+ transaction t (db->begin ());
+
+ // Make sure reload doesn't modify the object if the versions
+ // match.
+ //
+ derived d1 (d);
+ d1.num++;
+ d1.str += "d";
+ d1.strs.push_back ("dddd");
+ d1.nums.push_back (24);
+ d1.dnum++;
+ d1.dstr += "d";
+ derived d2 (d1);
+
+ db->reload (d1);
+ assert (d1 == d2);
+
+ t.commit ();
+ }
+
+ // Erase.
+ //
+ {
+ transaction t (db->begin ());
+
+ // Root.
+ //
+ {
+ unique_ptr<root> p (db->load<root> (r.id));
+
+ r.num++;
+ r.strs.push_back ("aaaaa");
+ db->update (r);
+
+ try
+ {
+ db->erase (p);
+ assert (false);
+ }
+ catch (const odb::object_changed&) {}
+
+ db->reload (p);
+ db->erase (p);
+ }
+
+ // Base.
+ //
+ {
+ unique_ptr<base> p (db->load<base> (b.id));
+
+ b.num++;
+ b.str += "b";
+ b.strs.push_back ("bbbb");
+ b.nums.push_back (24);
+ db->update (b);
+
+ try
+ {
+ db->erase (p);
+ assert (false);
+ }
+ catch (const odb::object_changed&) {}
+
+ db->reload (p);
+ db->erase (p);
+ }
+
+ // Derived.
+ //
+ {
+ unique_ptr<root> p (db->load<root> (d.id)); // Via root.
+
+ d.num++;
+ d.str += "d";
+ d.strs.push_back ("dddd");
+ d.nums.push_back (24);
+ d.dnum++;
+ d.dstr += "d";
+ db->update (d);
+
+ try
+ {
+ db->erase (p);
+ assert (false);
+ }
+ catch (const odb::object_changed&) {}
+
+ db->reload (p);
+ db->erase (p);
+ }
+
+ // Try to update non-existent object.
+ //
+ {
+ try
+ {
+ db->update (d);
+ assert (false);
+ }
+ catch (const odb::object_changed&) {}
+ }
+
+ // Try to erase non-existent object.
+ //
+ {
+ try
+ {
+ db->erase (d);
+ assert (false);
+ }
+ catch (const odb::object_changed&) {}
+ }
+
+ t.commit ();
+ }
+ }
+
+ // Test 6: polymorphism and callbacks.
+ //
+ {
+ using namespace test6;
+
+ base b (1, 1, "bbb");
+
+ unique_ptr<base> d (new derived (2, 2, "ddd"));
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (b);
+ db->persist (d);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<root> pd (db->load<root> (d->id));
+
+ db->load (b.id, *pb);
+ db->load (d->id, *pd);
+
+ db->reload (*pb);
+ db->reload (*pd);
+
+ t.commit ();
+ }
+
+ // Update.
+ //
+ {
+ b.num++;
+ d->num++;
+
+ transaction t (db->begin ());
+ db->update (b);
+ db->update (d);
+ t.commit ();
+ }
+
+ // Query.
+ //
+ {
+ typedef odb::query<base> query;
+ typedef odb::result<base> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<base> ("ORDER BY" + query::id));
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ *i;
+
+ t.commit ();
+ }
+
+ // Erase.
+ //
+ {
+ transaction t (db->begin ());
+ db->erase (b);
+ db->erase (d);
+ t.commit ();
+ }
+
+ // Recursive (delayed) loading.
+ //
+ {
+ derived d (3, 3, "dddd");
+ d.ptr.reset (new derived (4, 4, "ddddd"));
+
+ {
+ transaction t (db->begin ());
+ db->persist (d);
+ db->persist (d.ptr);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<root> p (db->load<root> (d.id));
+ t.commit ();
+ }
+ }
+ }
+
+ // Test 7: polymorphism and object cache (session).
+ //
+ {
+ using namespace test7;
+
+ shared_ptr<root> r (new root (1, 1));
+ shared_ptr<base> b (new base (2, 2, "b"));
+ shared_ptr<root> d (new derived (3, 3, "d"));
+
+ // Persist.
+ //
+ {
+ session s;
+
+ {
+ transaction t (db->begin ());
+ db->persist (r);
+ db->persist (b);
+ db->persist (d);
+ t.commit ();
+ }
+
+ assert (db->load<root> (r->id) == r);
+ assert (db->load<base> (b->id) == b);
+ assert (db->load<root> (d->id) == d);
+ }
+
+ // Load.
+ //
+ {
+ session s;
+
+ transaction t (db->begin ());
+ shared_ptr<root> r1 (db->load<root> (r->id));
+ shared_ptr<base> b1 (db->load<base> (b->id));
+ shared_ptr<derived> d1 (db->load<derived> (d->id));
+ t.commit ();
+
+ assert (db->load<root> (r->id) == r1);
+ assert (db->load<base> (b->id) == b1);
+ assert (db->load<root> (d->id) == d1);
+
+ assert (!db->find<derived> (b->id));
+ }
+
+ // Query.
+ //
+ {
+ typedef odb::query<root> query;
+ typedef odb::result<root> result;
+
+ session s;
+
+ transaction t (db->begin ());
+ shared_ptr<root> r1 (db->load<root> (r->id));
+ shared_ptr<base> b1 (db->load<base> (b->id));
+ shared_ptr<derived> d1 (db->load<derived> (d->id));
+ t.commit ();
+
+ {
+ transaction t (db->begin ());
+
+ result r (db->query<root> ("ORDER BY" + query::id));
+ result::iterator i (r.begin ()), e (r.end ());
+
+ assert (i != e && i.load () == r1);
+ assert (++i != e && i.load () == b1);
+ assert (++i != e && i.load () == d1);
+ assert (++i == e);
+
+ t.commit ();
+ }
+ }
+
+ // Erase.
+ //
+ {
+ session s;
+
+ {
+ transaction t (db->begin ());
+ db->load<root> (r->id);
+ db->load<root> (b->id);
+ db->load<root> (d->id);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (r);
+ db->erase (b);
+ db->erase (d);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ assert (!db->find<root> (r->id));
+ assert (!db->find<base> (b->id));
+ assert (!db->find<root> (d->id));
+ t.commit ();
+ }
+ }
+ }
+
+ // Test 8: polymorphism and abstract bases.
+ //
+ {
+ using namespace test8;
+
+ base b (1, 1, "b");
+ interm i (2, 2, "i", true);
+ derived1 d1 (3, 3, "d1", true);
+ derived2 d2 (4, 4, "d2", false);
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (b);
+ db->persist (static_cast<root&> (d1));
+ db->persist (static_cast<interm&> (d2));
+
+ try
+ {
+ db->persist (i);
+ assert (false);
+ }
+ catch (const odb::abstract_class&) {}
+
+ try
+ {
+ db->persist (static_cast<base&> (i));
+ assert (false);
+ }
+ catch (const odb::no_type_info&) {}
+
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ base vb;
+ interm vi;
+ derived1 vd1;
+ derived2 vd2;
+
+ transaction t (db->begin ());
+
+ // load (id)
+ //
+ unique_ptr<root> pb (db->load<root> (b.id));
+ unique_ptr<interm> pd1 (db->load<interm> (d1.id));
+ unique_ptr<derived2> pd2 (db->load<derived2> (d2.id));
+
+ assert (*pb == b);
+ assert (*pd1 == d1);
+ assert (*pd2 == d2);
+
+ // load (id, obj)
+ //
+ db->load (b.id, static_cast<root&> (vb));
+ db->load (d1.id, static_cast<base&> (vd1));
+ db->load (d2.id, static_cast<interm&> (vd2));
+
+ assert (vb == b);
+ assert (vd1 == d1);
+ assert (vd2 == d2);
+
+ try
+ {
+ db->load (i.id, static_cast<root&> (vi));
+ assert (false);
+ }
+ catch (const odb::no_type_info&) {}
+
+ // reload (obj)
+ //
+ vb.num = 0;
+ vd1.num = 0;
+ vd2.num = 0;
+
+ db->reload (static_cast<root&> (vb));
+ db->reload (static_cast<base&> (vd1));
+ db->reload (static_cast<interm&> (vd2));
+
+ assert (vb == b);
+ assert (vd1 == d1);
+ assert (vd2 == d2);
+
+ try
+ {
+ db->reload (static_cast<root&> (vi));
+ assert (false);
+ }
+ catch (const odb::no_type_info&) {}
+
+ t.commit ();
+ }
+
+ // Update.
+ //
+ {
+ b.num++;
+ b.str += 'b';
+ d1.num++;
+ d1.str += "d1";
+ d2.num++;
+ d2.str += "d1";
+
+ {
+ transaction t (db->begin ());
+ db->update (static_cast<root&> (b));
+ db->update (d1);
+ db->update (static_cast<interm&> (d2));
+
+ try
+ {
+ db->update (i);
+ assert (false);
+ }
+ catch (const odb::abstract_class&) {}
+
+ try
+ {
+ db->update (static_cast<base&> (i));
+ assert (false);
+ }
+ catch (const odb::no_type_info&) {}
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<root> pd1 (db->load<root> (d1.id));
+ unique_ptr<base> pd2 (db->load<base> (d2.id));
+
+ t.commit ();
+
+ assert (*pb == b);
+ assert (*pd1 == d1);
+ assert (*pd2 == d2);
+ }
+ }
+
+ // Erase.
+ //
+ {
+ transaction t (db->begin ());
+ db->erase (b);
+ db->erase<interm> (d1.id);
+ db->erase (static_cast<root&> (d2));
+
+ try
+ {
+ db->erase (i);
+ assert (false);
+ }
+ catch (const odb::abstract_class&) {}
+
+ try
+ {
+ db->erase (static_cast<base&> (i));
+ assert (false);
+ }
+ catch (const odb::no_type_info&) {}
+
+ t.commit ();
+ }
+ }
+
+ // Test 9: polymorphism and readonly classes.
+ //
+ {
+ using namespace test9;
+
+ ro_root ro_r (1, 1);
+ rw_base rw_b (2, 2, "b");
+ ro_derived ro_d (3, 3, "d");
+
+ rw_root rw_r (1, 1);
+ ro_base ro_b (2, 2, "b");
+ rw_derived rw_d (3, 3, "d");
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (ro_r);
+ db->persist (rw_b);
+ db->persist (ro_d);
+
+ db->persist (rw_r);
+ db->persist (ro_b);
+ db->persist (rw_d);
+ t.commit ();
+ }
+
+ // Update.
+ //
+ {
+ ro_root ro_r1 (ro_r);
+ rw_base rw_b1 (rw_b);
+ ro_derived ro_d1 (ro_d);
+
+ ro_base ro_b1 (ro_b);
+ rw_derived rw_d1 (rw_d);
+
+ ro_r1.num++;
+ ro_r1.strs.push_back ("b");
+
+ rw_b1.num++;
+ rw_b1.strs.push_back ("b");
+ rw_b1.str += "b";
+ rw_b1.nums.push_back (2);
+ rw_b.str += "b";
+ rw_b.nums.push_back (2);
+
+ ro_d1.num++;
+ ro_d1.strs.push_back ("d");
+ ro_d1.str += "d";
+ ro_d1.nums.push_back (3);
+ ro_d1.dnum++;
+ ro_d1.dstr += "d";
+
+ rw_r.num++;
+ rw_r.strs.push_back ("b");
+
+ ro_b1.num++;
+ ro_b1.strs.push_back ("b");
+ ro_b1.str += "b";
+ ro_b1.nums.push_back (2);
+
+ rw_d1.num++;
+ rw_d1.strs.push_back ("d");
+ rw_d1.str += "d";
+ rw_d1.nums.push_back (3);
+ rw_d1.dnum++;
+ rw_d1.dstr += "d";
+ rw_d.dnum++;
+ rw_d.dstr += "d";
+
+ {
+ // These should be no-ops.
+ //
+ db->update (ro_r1);
+ db->update (static_cast<ro_root&> (ro_d1));
+ db->update (ro_b1);
+
+ transaction t (db->begin ());
+ db->update (static_cast<ro_root&> (rw_b1));
+ db->update (rw_r);
+ db->update (static_cast<ro_base&> (rw_d1));
+ t.commit ();
+ }
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<ro_root> p_ro_r (db->load<ro_root> (ro_r.id));
+ unique_ptr<ro_root> p_rw_b (db->load<ro_root> (rw_b.id));
+ unique_ptr<ro_root> p_ro_d (db->load<ro_root> (ro_d.id));
+
+ unique_ptr<rw_root> p_rw_r (db->load<rw_root> (rw_r.id));
+ unique_ptr<rw_root> p_ro_b (db->load<rw_root> (ro_b.id));
+ unique_ptr<rw_root> p_rw_d (db->load<rw_root> (rw_d.id));
+
+ t.commit ();
+
+ assert (*p_ro_r == ro_r);
+ assert (*p_rw_b == rw_b);
+ assert (*p_ro_d == ro_d);
+
+ assert (*p_rw_r == rw_r);
+ assert (*p_ro_b == ro_b);
+ assert (*p_rw_d == rw_d);
+ }
+ }
+
+ // Test 10: empty polymorphic classes.
+ //
+ {
+ using namespace test10;
+
+ base b (1, 1);
+ derived d (2, 2);
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (b);
+ db->persist (static_cast<root&> (d));
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<root> pb (db->load<root> (b.id));
+ unique_ptr<root> pd (db->load<root> (d.id));
+ t.commit ();
+
+ assert (*pb == b);
+ assert (*pd == d);
+ }
+
+ // Update.
+ //
+ {
+ b.num++;
+ d.num++;
+
+ transaction t (db->begin ());
+ db->update (static_cast<root&> (b));
+ db->update (d);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<root> pb (db->load<root> (b.id));
+ unique_ptr<root> pd (db->load<root> (d.id));
+ t.commit ();
+
+ assert (*pb == b);
+ assert (*pd == d);
+ }
+ }
+
+ // Test 11: reuse and polymorphic inheritance.
+ //
+ {
+ using namespace test11;
+
+ base b (1, 1, "b");
+ derived d (2, 2, "d");
+
+ b.strs.push_back ("b");
+ b.nums.push_back (1);
+
+ d.strs.push_back ("d");
+ d.nums.push_back (1);
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (b);
+ db->persist (static_cast<base&> (d));
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<base> pd (db->load<base> (d.id));
+ t.commit ();
+
+ assert (*pb == b);
+ assert (*pd == d);
+ }
+
+ // Update.
+ //
+ {
+ b.num++;
+ b.str += "b";
+ b.strs.push_back ("bb");
+ b.nums.push_back (2);
+
+ d.num++;
+ d.str += "d";
+ d.strs.push_back ("dd");
+ d.nums.push_back (2);
+ d.dnum++;
+ d.dstr += "d";
+
+ transaction t (db->begin ());
+ db->update (b);
+ db->update (d);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<base> pd (db->load<base> (d.id));
+
+ t.commit ();
+
+ assert (*pb == b);
+ assert (*pd == d);
+ }
+
+ // Query.
+ //
+ {
+ typedef odb::query<base> base_query;
+ typedef odb::result<base> base_result;
+
+ typedef odb::query<derived> derived_query;
+ typedef odb::result<derived> derived_result;
+
+ transaction t (db->begin ());
+
+ {
+ base_result qr (db->query<base> (base_query::num == 2));
+ base_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e && *i == b);
+ assert (++i == e);
+ }
+
+ {
+ derived_result qr (db->query<derived> (derived_query::num == 3));
+ derived_result::iterator i (qr.begin ()), e (qr.end ());
+
+ assert (i != e && *i == d);
+ assert (++i == e);
+ }
+
+ t.commit ();
+ }
+ }
+
+ // Test 12: polymorphic objects with auto id.
+ //
+ {
+ using namespace test12;
+
+ base b (1);
+ derived d (2);
+
+ unsigned long id1, id2;
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ id1 = db->persist (b);
+ id2 = db->persist (static_cast<root&> (d));
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<root> pb (db->load<root> (id1));
+ unique_ptr<root> pd (db->load<root> (id2));
+
+ t.commit ();
+
+ assert (*pb == b);
+ assert (*pd == d);
+ }
+ }
+
+ // Test 13: polymorphic derived without any non-container data members
+ // (which results in an empty SELECT statement).
+ //
+ {
+ using namespace test13;
+
+ base b;
+ b.nums.push_back (123);
+ derived d;
+ d.nums.push_back (123);
+ d.strs.push_back ("abc");
+
+ base1 b1;
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (b);
+ db->persist (d);
+ db->persist (b1);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<root> pbr (db->load<root> (b.id));
+ unique_ptr<root> pdr (db->load<root> (d.id));
+ unique_ptr<base> pdb (db->load<base> (d.id));
+ unique_ptr<root> pb1r (db->load<root> (b1.id));
+ t.commit ();
+
+ base& rb (static_cast<base&> (*pbr));
+ derived& rd1 (static_cast<derived&> (*pdr));
+ derived& rd2 (static_cast<derived&> (*pdb));
+ base1 rb1 (static_cast<base1&> (*pb1r));
+
+ assert (rb.id == b.id && rb.nums == b.nums);
+ assert (rd1.id == d.id && rd1.nums == rd1.nums &&
+ rd1.strs == rd1.strs);
+ assert (rd2.id == d.id && rd2.nums == rd2.nums &&
+ rd2.strs == rd2.strs);
+ assert (rb1.id == b1.id);
+ }
+ }
+
+ // Test 14: inverse pointer in polymorphic base.
+ //
+ {
+ using namespace test14;
+
+ derived d;
+ d.num = 123;
+
+ d.o1 = new object1;
+ d.o2 = new object2;
+ d.o3.push_back (new object3);
+ d.o4.push_back (new object4);
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (d.o1);
+ db->persist (d.o2);
+ db->persist (d.o3[0]);
+ db->persist (d.o4[0]);
+ db->persist (d);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ session s;
+
+ transaction t (db->begin ());
+ object1* p1 (db->load<object1> (d.o1->id));
+ object2* p2 (db->load<object2> (d.o2->id));
+ object3* p3 (db->load<object3> (d.o3[0]->id));
+ object4* p4 (db->load<object4> (d.o4[0]->id));
+ t.commit ();
+
+ assert (p1->d->num == d.num);
+ assert (p2->d[0]->num == d.num);
+ assert (p3->d[0]->num == d.num);
+ assert (p4->d->num == d.num);
+ delete p1->d;
+ }
+
+ // Query.
+ //
+ {
+ typedef odb::query<object1> query;
+ typedef odb::result<object1> result;
+
+ session s;
+ transaction t (db->begin ());
+
+ result r (db->query<object1> (query::d->num == d.num));
+ result::iterator i (r.begin ()), e (r.end ());
+
+ assert (i != e && i->d->num == d.num);
+ delete i.load ()->d;
+ assert (++i == e);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object4> query;
+ typedef odb::result<object4> result;
+
+ session s;
+ transaction t (db->begin ());
+
+ result r (db->query<object4> (query::d->num == d.num));
+ result::iterator i (r.begin ()), e (r.end ());
+
+ assert (i != e && i->d->num == d.num);
+ delete i.load ()->d;
+ assert (++i == e);
+ t.commit ();
+ }
+ }
+
+ // Test 15: LOB/long data and polymorphism.
+ //
+ {
+ using namespace test15;
+
+ const char data[] = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B";
+
+ derived d;
+ d.blob.assign (data, data + sizeof (data));
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ base* b (&d);
+ db->persist (b);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<base> pb (db->load<base> (d.id));
+ t.commit ();
+
+ derived* pd (dynamic_cast<derived*> (pb.get ()));
+ assert (pd != 0 && pd->blob == d.blob);
+ }
+
+ // Query.
+ //
+ {
+ typedef odb::result<base> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<base> ());
+ result::iterator i (r.begin ()), e (r.end ());
+
+ assert (i != e);
+
+ derived* pd (dynamic_cast<derived*> (&*i));
+ assert (pd != 0 && pd->blob == d.blob);
+
+ assert (++i == e);
+ t.commit ();
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/inheritance/polymorphism/test1.hxx b/odb-tests/common/inheritance/polymorphism/test1.hxx
new file mode 100644
index 0000000..7f598de
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test1.hxx
@@ -0,0 +1,115 @@
+// file : common/inheritance/polymorphism/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#include <string>
+#include <vector>
+#include <typeinfo>
+
+#include <odb/core.hxx>
+
+// Test basic polymorphism functionality.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ #pragma db object polymorphic
+ struct root
+ {
+ virtual ~root () {}
+ root () {}
+ root (unsigned long i, unsigned long n): id (i), num (n) {}
+
+ #pragma db id column("object_id")
+ unsigned long id;
+
+ unsigned long num;
+ std::vector<std::string> strs;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (root))
+ return false;
+
+ return id == r.id && num == r.num && strs == r.strs;
+ }
+ };
+
+ inline bool
+ operator== (const root& x, const root& y) {return x.compare (y);}
+
+ #pragma db object
+ struct base: root
+ {
+ base () {}
+ base (unsigned long i, unsigned long n, const std::string& s)
+ : root (i, n), str (s) {}
+
+ std::string str;
+ std::vector<unsigned long> nums;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (base))
+ return false;
+
+ const base& b (static_cast<const base&> (r));
+ return root::compare (r, false) && str == b.str && nums == b.nums;
+ }
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived () {}
+ derived (unsigned long i, unsigned long n, const std::string& s)
+ : base (i, n, s), dnum (n + 1), dstr (s + 'd') {}
+
+ unsigned long dnum;
+ std::string dstr;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (derived))
+ return false;
+
+ const derived& d (static_cast<const derived&> (r));
+ return base::compare (r, false) && dnum == d.dnum && dstr == d.dstr;
+ }
+ };
+
+ // Views.
+ //
+ #pragma db view object(root)
+ struct root_view
+ {
+ //#pragma db column(root::typeid_)
+ std::string typeid_; // @@ tmp
+
+ unsigned long num;
+ };
+
+ #pragma db view object(base = b)
+ struct base_view
+ {
+ unsigned long id;
+ unsigned long num;
+ std::string str;
+ };
+
+ #pragma db view object(derived)
+ struct derived_view
+ {
+ unsigned long num;
+ std::string str;
+ unsigned long dnum;
+ std::string dstr;
+ };
+}
+
+#endif // TEST1_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/test10.hxx b/odb-tests/common/inheritance/polymorphism/test10.hxx
new file mode 100644
index 0000000..63673a1
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test10.hxx
@@ -0,0 +1,78 @@
+// file : common/inheritance/polymorphism/test10.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST10_HXX
+#define TEST10_HXX
+
+#include <typeinfo>
+
+#include <odb/core.hxx>
+
+// Test empty polymorphic classes.
+//
+#pragma db namespace table("t10_")
+namespace test10
+{
+ #pragma db object polymorphic
+ struct root
+ {
+ virtual ~root () = 0; // Auto-abstract.
+ root () {}
+ root (unsigned long i): id (i) {}
+
+ #pragma db id
+ unsigned long id;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (root))
+ return false;
+
+ return id == r.id;
+ }
+ };
+
+ inline root::
+ ~root () {}
+
+ inline bool
+ operator== (const root& x, const root& y) {return x.compare (y);}
+
+ #pragma db object
+ struct base: root
+ {
+ base () {}
+ base (unsigned long i, unsigned long n): root (i), num (n) {}
+
+ unsigned long num;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (base))
+ return false;
+
+ const base& b (static_cast<const base&> (r));
+ return root::compare (r, false) && num == b.num;
+ }
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived () {}
+ derived (unsigned long i, unsigned long n): base (i, n) {}
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (derived))
+ return false;
+
+ return base::compare (r, false);
+ }
+ };
+}
+
+#endif // TEST10_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/test11.hxx b/odb-tests/common/inheritance/polymorphism/test11.hxx
new file mode 100644
index 0000000..2d38a6c
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test11.hxx
@@ -0,0 +1,78 @@
+// file : common/inheritance/polymorphism/test11.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST11_HXX
+#define TEST11_HXX
+
+#include <string>
+#include <vector>
+#include <typeinfo>
+
+#include <odb/core.hxx>
+
+// Test mixing reuse and polymorphic inheritance.
+//
+#pragma db namespace table("t11_")
+namespace test11
+{
+ #pragma db object abstract
+ struct root
+ {
+ root () {}
+ root (unsigned long i, unsigned long n): id (i), num (n) {}
+
+ #pragma db id
+ unsigned long id;
+
+ unsigned long num;
+ std::vector<std::string> strs;
+ };
+
+ #pragma db object polymorphic
+ struct base: root
+ {
+ virtual ~base () {}
+ base () {}
+ base (unsigned long i, unsigned long n, const std::string& s)
+ : root (i, n), str (s) {}
+
+ std::string str;
+ std::vector<unsigned long> nums;
+
+ virtual bool
+ compare (const base& b, bool tc = true) const
+ {
+ if (tc && typeid (b) != typeid (base))
+ return false;
+
+ return id == b.id && num == b.num && strs == b.strs &&
+ str == b.str && nums == b.nums;
+ }
+ };
+
+ inline bool
+ operator== (const base& x, const base& y) {return x.compare (y);}
+
+ #pragma db object
+ struct derived: base
+ {
+ derived () {}
+ derived (unsigned long i, unsigned long n, const std::string& s)
+ : base (i, n, s), dnum (n + 1), dstr (s + 'd') {}
+
+ unsigned long dnum;
+ std::string dstr;
+
+ virtual bool
+ compare (const base& b, bool tc = true) const
+ {
+ if (tc && typeid (b) != typeid (derived))
+ return false;
+
+ const derived& d (static_cast<const derived&> (b));
+ return base::compare (b, false) && dnum == d.dnum && dstr == d.dstr;
+ }
+ };
+}
+
+#endif // TEST11_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/test12.hxx b/odb-tests/common/inheritance/polymorphism/test12.hxx
new file mode 100644
index 0000000..85000ac
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test12.hxx
@@ -0,0 +1,79 @@
+// file : common/inheritance/polymorphism/test12.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST12_HXX
+#define TEST12_HXX
+
+#include <typeinfo>
+
+#include <odb/core.hxx>
+
+// Test polymorphic classes with private auto id.
+//
+#pragma db namespace table("t12_")
+namespace test12
+{
+ #pragma db object polymorphic
+ struct root
+ {
+ virtual ~root () = 0; // Auto-abstract.
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (root))
+ return false;
+
+ return id_ == r.id_;
+ }
+
+ unsigned long id () const {return id_;}
+ void id (unsigned long id) {id_ = id;}
+ private:
+ #pragma db id auto access(id)
+ unsigned long id_;
+ };
+
+ inline root::
+ ~root () {}
+
+ inline bool
+ operator== (const root& x, const root& y) {return x.compare (y);}
+
+ #pragma db object
+ struct base: root
+ {
+ base () {}
+ base (unsigned long n): num (n) {}
+
+ unsigned long num;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (base))
+ return false;
+
+ const base& b (static_cast<const base&> (r));
+ return root::compare (r, false) && num == b.num;
+ }
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived () {}
+ derived (unsigned long n): base (n) {}
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (derived))
+ return false;
+
+ return base::compare (r, false);
+ }
+ };
+}
+
+#endif // TEST12_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/test13.hxx b/odb-tests/common/inheritance/polymorphism/test13.hxx
new file mode 100644
index 0000000..3240a9a
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test13.hxx
@@ -0,0 +1,46 @@
+// file : common/inheritance/polymorphism/test13.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST13_HXX
+#define TEST13_HXX
+
+#include <string>
+#include <vector>
+
+#include <odb/core.hxx>
+
+// Test polymorphic derived without any non-container data members (which
+// results in an empty SELECT statement).
+//
+#pragma db namespace table("t13_")
+namespace test13
+{
+ #pragma db object polymorphic
+ struct root
+ {
+ virtual ~root () {}
+
+ #pragma db id auto
+ unsigned long id;
+ };
+
+ #pragma db object
+ struct base: root
+ {
+ std::vector<int> nums;
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ std::vector<std::string> strs;
+ };
+
+ #pragma db object
+ struct base1: root
+ {
+ // Nothing.
+ };
+}
+
+#endif // TEST13_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/test14.hxx b/odb-tests/common/inheritance/polymorphism/test14.hxx
new file mode 100644
index 0000000..1050861
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test14.hxx
@@ -0,0 +1,99 @@
+// file : common/inheritance/polymorphism/test14.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST14_HXX
+#define TEST14_HXX
+
+#include <vector>
+
+#include <odb/core.hxx>
+
+// Test inverse pointer in polymorphic base.
+//
+#pragma db namespace table("t14_")
+namespace test14
+{
+ struct object1;
+ struct object2;
+ struct object3;
+ struct object4;
+
+ #pragma db object polymorphic session
+ struct base
+ {
+ virtual ~base ();
+
+ #pragma db id auto
+ unsigned long id;
+
+ object1* o1;
+ object2* o2;
+ std::vector<object3*> o3;
+ std::vector<object4*> o4;
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ unsigned long num;
+ };
+
+ // one-to-one(i)
+ //
+ #pragma db object session
+ struct object1
+ {
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db inverse(o1)
+ derived* d;
+ };
+
+ // one-to-many(i)
+ //
+ #pragma db object session
+ struct object2
+ {
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db inverse(o2)
+ std::vector<derived*> d;
+ };
+
+ // many-to-many(i)
+ //
+ #pragma db object session
+ struct object3
+ {
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db inverse(o3)
+ std::vector<derived*> d;
+ };
+
+ // many-to-one(i)
+ //
+ #pragma db object session
+ struct object4
+ {
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db inverse(o4)
+ derived* d;
+ };
+
+ inline base::
+ ~base ()
+ {
+ delete o1;
+ delete o2;
+ delete o3[0];
+ delete o4[0];
+ }
+}
+
+#endif // TEST14_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/test15.hxx b/odb-tests/common/inheritance/polymorphism/test15.hxx
new file mode 100644
index 0000000..5799ace
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test15.hxx
@@ -0,0 +1,44 @@
+// file : common/inheritance/polymorphism/test15.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST15_HXX
+#define TEST15_HXX
+
+#include <vector>
+
+#include <odb/core.hxx>
+
+#ifdef ODB_COMPILER
+# if defined(ODB_DATABASE_PGSQL)
+# define BLOB_TYPE "BYTEA"
+# elif defined(ODB_DATABASE_MSSQL)
+# define BLOB_TYPE "VARBINARY(max)"
+# else
+# define BLOB_TYPE "BLOB"
+# endif
+#endif
+
+
+// Test LOB/long data and polymorphism.
+//
+#pragma db namespace table("t15_")
+namespace test15
+{
+ #pragma db object polymorphic
+ struct base
+ {
+ virtual ~base () {}
+
+ #pragma db id auto
+ unsigned long id;
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ #pragma db type(BLOB_TYPE)
+ std::vector<char> blob;
+ };
+}
+
+#endif // TEST15_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/test2.hxx b/odb-tests/common/inheritance/polymorphism/test2.hxx
new file mode 100644
index 0000000..9890e02
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test2.hxx
@@ -0,0 +1,105 @@
+// file : common/inheritance/polymorphism/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#include <string>
+#include <vector>
+
+#include <odb/core.hxx>
+#include <odb/lazy-ptr.hxx>
+
+// Test inverse object pointers in polymorhic bases.
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ struct root;
+
+ #pragma db object
+ struct root_pointer
+ {
+ root_pointer (root* r = 0): p (r) {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ root* p;
+ };
+
+ #pragma db object polymorphic
+ struct root
+ {
+ virtual ~root () {}
+ root () {}
+ root (unsigned long i): id (i) {}
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db inverse(p)
+ odb::lazy_ptr<root_pointer> rp;
+ };
+
+ struct base;
+
+ #pragma db object
+ struct base_pointer
+ {
+ base_pointer (base* b = 0) {if (b != 0) vp.push_back (b);}
+
+ #pragma db id auto
+ unsigned long id;
+
+ std::vector<base*> vp;
+ };
+
+ #pragma db object
+ struct base: root
+ {
+ base () {}
+ base (unsigned long i, const std::string& s): root (i), str (s) {}
+
+ std::string str;
+
+ #pragma db inverse(vp)
+ odb::lazy_ptr<base_pointer> bp;
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived () {}
+ derived (unsigned long i, const std::string& s, unsigned long n)
+ : base (i, s), num (n) {}
+
+ unsigned long num;
+ };
+
+ // Views.
+ //
+ #pragma db view object(root_pointer = rp) object(root)
+ struct root_view
+ {
+ #pragma db column(rp::id)
+ unsigned long rp_id;
+
+ #pragma db column(root::id)
+ unsigned long r_id;
+ };
+
+ #pragma db view object(base_pointer) object(base = b)
+ struct base_view
+ {
+ #pragma db column(base_pointer::id)
+ unsigned long bp_id;
+
+ #pragma db column(b::id)
+ unsigned long b_id;
+
+ std::string str;
+ };
+}
+
+#endif // TEST2_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/test3.hxx b/odb-tests/common/inheritance/polymorphism/test3.hxx
new file mode 100644
index 0000000..fd68f24
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test3.hxx
@@ -0,0 +1,146 @@
+// file : common/inheritance/polymorphism/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#include <string>
+#include <typeinfo>
+
+#include <odb/core.hxx>
+
+// Test delayed loading.
+//
+#pragma db namespace table("t3_")
+namespace test3
+{
+ #pragma db object polymorphic
+ struct root
+ {
+ virtual ~root () {}
+ root () {}
+ root (unsigned long i): id (i) {}
+
+ #pragma db id
+ unsigned long id;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (root))
+ return false;
+
+ return id == r.id;
+ }
+ };
+
+ inline bool
+ operator== (const root& x, const root& y) {return x.compare (y);}
+
+ #pragma db object
+ struct base: root
+ {
+ virtual ~base () {delete rptr;}
+ base (): rptr (0) {}
+ base (unsigned long i, unsigned long n): root (i), num (n), rptr (0) {}
+
+ unsigned long num;
+ root* rptr;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (base))
+ return false;
+
+ const base& b (static_cast<const base&> (r));
+ return
+ root::compare (r, false) &&
+ num == b.num &&
+ ((rptr == 0 && b.rptr == 0) || rptr->compare (*b.rptr));
+ }
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ virtual ~derived () {delete bptr;}
+ derived (): bptr (0) {}
+ derived (unsigned long i, unsigned long n, const std::string& s)
+ : base (i, n), str (s), bptr (0) {}
+
+ std::string str;
+ base* bptr;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (derived))
+ return false;
+
+ const derived& d (static_cast<const derived&> (r));
+ return
+ base::compare (r, false) &&
+ str == d.str &&
+ ((bptr == 0 && d.bptr == 0) || bptr->compare (*d.bptr));
+ }
+ };
+
+ // Views.
+ //
+ #pragma db view object(base) object(root = r)
+ struct base_view
+ {
+ #pragma db column(base::id)
+ unsigned long b_id;
+
+ #pragma db column(r::id)
+ unsigned long r_id;
+
+ unsigned long num;
+ };
+
+ #pragma db view \
+ object(derived = d) \
+ object(base = b) \
+ object(root = r: d::rptr)
+ struct derived_view
+ {
+ #pragma db column(d::id)
+ unsigned long d_id;
+
+ #pragma db column(b::id)
+ unsigned long b_id;
+
+ #pragma db column(r::id)
+ unsigned long r_id;
+
+ #pragma db column(d::num)
+ unsigned long d_num;
+
+ #pragma db column(b::num)
+ unsigned long b_num;
+
+ std::string str;
+ };
+
+ // This is an example of a pathological case, where the right-hand-side
+ // of the join condition comes from one of the bases. As a result, we
+ // join the base table first, which means we will get both bases and
+ // derived objects instead of just derived.
+ //
+ //#pragma db view object(root = r) object(derived = d)
+ #pragma db view object(derived = d) object(root = r)
+ struct root_view
+ {
+ #pragma db column(r::id)
+ unsigned long r_id;
+
+ #pragma db column(d::id)
+ unsigned long d_id;
+
+ std::string str;
+ };
+}
+
+#endif // TEST3_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/test4.hxx b/odb-tests/common/inheritance/polymorphism/test4.hxx
new file mode 100644
index 0000000..148c53c
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test4.hxx
@@ -0,0 +1,84 @@
+// file : common/inheritance/polymorphism/test4.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST4_HXX
+#define TEST4_HXX
+
+#include <string>
+
+#include <odb/core.hxx>
+
+// Test views.
+//
+#pragma db namespace table("t4_")
+namespace test4
+{
+ #pragma db object polymorphic
+ struct root1
+ {
+ virtual ~root1 () {}
+ root1 () {}
+ root1 (unsigned long i): id (i) {}
+
+ #pragma db id
+ unsigned long id;
+ };
+
+ #pragma db object
+ struct base1: root1
+ {
+ base1 () {}
+ base1 (unsigned long i, unsigned long n): root1 (i), num (n) {}
+
+ unsigned long num;
+ };
+
+ #pragma db object polymorphic
+ struct root2
+ {
+ virtual ~root2 () {}
+ root2 () {}
+ root2 (unsigned long i, unsigned long n): id (i), num (n) {}
+
+ #pragma db id
+ unsigned long id;
+
+ unsigned long num;
+ };
+
+ #pragma db object
+ struct base2: root2
+ {
+ base2 () {}
+ base2 (unsigned long i, unsigned long n, const std::string& s)
+ : root2 (i, n), str (s) {}
+
+ std::string str;
+ };
+
+ // Test custom join condition.
+ //
+ #pragma db view object(base2) object(base1: base2::num == base1::num)
+ struct view1
+ {
+ std::string str;
+ };
+
+ #pragma db view object(base2)
+ struct view2
+ {
+ #pragma db column("min(" + base2::num + ")")
+ unsigned long min_num;
+ };
+
+ // Test custom join condition that uses object id. It cannot come
+ // from the base since the base table hasn't been join'ed yet.
+ //
+ #pragma db view object(base1) object(base2: base2::id == base1::id)
+ struct view3
+ {
+ std::string str;
+ };
+}
+
+#endif // TEST4_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/test5.hxx b/odb-tests/common/inheritance/polymorphism/test5.hxx
new file mode 100644
index 0000000..172e7e8
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test5.hxx
@@ -0,0 +1,92 @@
+// file : common/inheritance/polymorphism/test5.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST5_HXX
+#define TEST5_HXX
+
+#include <string>
+#include <vector>
+#include <memory>
+#include <typeinfo>
+
+#include <odb/core.hxx>
+
+// Test polymorphism and optimistic concurrency.
+//
+#pragma db namespace table("t5_")
+namespace test5
+{
+ #pragma db object polymorphic optimistic pointer(std::unique_ptr)
+ struct root
+ {
+ virtual ~root () {}
+ root () {}
+ root (unsigned long i, unsigned long n): id (i), num (n) {}
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db version
+ unsigned long version;
+
+ unsigned long num;
+ std::vector<std::string> strs;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (root))
+ return false;
+
+ return id == r.id && version == r.version &&
+ num == r.num && strs == r.strs;
+ }
+ };
+
+ inline bool
+ operator== (const root& x, const root& y) {return x.compare (y);}
+
+ #pragma db object
+ struct base: root
+ {
+ base () {}
+ base (unsigned long i, unsigned long n, const std::string& s)
+ : root (i, n), str (s) {}
+
+ std::string str;
+ std::vector<unsigned long> nums;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (base))
+ return false;
+
+ const base& b (static_cast<const base&> (r));
+ return root::compare (r, false) && str == b.str && nums == b.nums;
+ }
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived () {}
+ derived (unsigned long i, unsigned long n, const std::string& s)
+ : base (i, n, s), dnum (n + 1), dstr (s + 'd') {}
+
+ unsigned long dnum;
+ std::string dstr;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (derived))
+ return false;
+
+ const derived& d (static_cast<const derived&> (r));
+ return base::compare (r, false) && dnum == d.dnum && dstr == d.dstr;
+ }
+ };
+}
+
+#endif // TEST5_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/test6.hxx b/odb-tests/common/inheritance/polymorphism/test6.hxx
new file mode 100644
index 0000000..b0f9a16
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test6.hxx
@@ -0,0 +1,64 @@
+// file : common/inheritance/polymorphism/test6.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST6_HXX
+#define TEST6_HXX
+
+#include <string>
+#include <memory>
+
+#include <odb/core.hxx>
+#include <odb/callback.hxx>
+
+// Test polymorphism and callbacks.
+//
+#pragma db namespace table("t6_")
+namespace test6
+{
+ #pragma db object polymorphic pointer(std::unique_ptr)
+ struct root
+ {
+ virtual ~root () {}
+ root (): id (0) {}
+ root (unsigned long i, unsigned long n): id (i), num (n) {}
+
+ #pragma db id
+ unsigned long id;
+
+ unsigned long num;
+ };
+
+ #pragma db object callback(db_callback)
+ struct base: root
+ {
+ base () {}
+ base (unsigned long i, unsigned long n, const std::string& s)
+ : root (i, n), str (s) {}
+
+ std::string str;
+
+ void
+ db_callback (odb::callback_event, odb::database&);
+
+ void
+ db_callback (odb::callback_event, odb::database&) const;
+ };
+
+ #pragma db object callback(db_callback)
+ struct derived: base
+ {
+ derived () {}
+ derived (unsigned long i, unsigned long n, const std::string& s)
+ : base (i, n, s), dnum (n + 1), dstr (s + 'd') {}
+
+ unsigned long dnum;
+ std::string dstr;
+
+ std::unique_ptr<root> ptr;
+
+ void
+ db_callback (odb::callback_event, odb::database&) const;
+ };
+}
+
+#endif // TEST6_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/test7.hxx b/odb-tests/common/inheritance/polymorphism/test7.hxx
new file mode 100644
index 0000000..60da98e
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test7.hxx
@@ -0,0 +1,54 @@
+// file : common/inheritance/polymorphism/test7.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST7_HXX
+#define TEST7_HXX
+
+#include <string>
+#include <memory>
+
+#include <odb/core.hxx>
+
+// Test polymorphism and object cache (session).
+//
+#pragma db namespace table("t7_")
+namespace test7
+{
+ using std::shared_ptr;
+
+ #pragma db object polymorphic pointer(shared_ptr) session
+ struct root
+ {
+ virtual ~root () {}
+ root (): id (0) {}
+ root (unsigned long i, unsigned long n): id (i), num (n) {}
+
+ #pragma db id
+ unsigned long id;
+
+ unsigned long num;
+ };
+
+ #pragma db object
+ struct base: root
+ {
+ base () {}
+ base (unsigned long i, unsigned long n, const std::string& s)
+ : root (i, n), str (s) {}
+
+ std::string str;
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived () {}
+ derived (unsigned long i, unsigned long n, const std::string& s)
+ : base (i, n, s), dnum (n + 1), dstr (s + 'd') {}
+
+ unsigned long dnum;
+ std::string dstr;
+ };
+}
+
+#endif // TEST7_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/test8.hxx b/odb-tests/common/inheritance/polymorphism/test8.hxx
new file mode 100644
index 0000000..84b6688
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test8.hxx
@@ -0,0 +1,129 @@
+// file : common/inheritance/polymorphism/test8.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST8_HXX
+#define TEST8_HXX
+
+#include <string>
+#include <vector>
+#include <typeinfo>
+
+#include <odb/core.hxx>
+
+// Test polymorphism and abstract bases.
+//
+#pragma db namespace table("t8_")
+namespace test8
+{
+ #pragma db object polymorphic
+ struct root
+ {
+ virtual ~root () = 0; // Auto-abstract.
+ root () {}
+ root (unsigned long i, unsigned long n): id (i), num (n) {}
+
+ #pragma db id
+ unsigned long id;
+
+ unsigned long num;
+ std::vector<std::string> strs;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (root))
+ return false;
+
+ return id == r.id && num == r.num && strs == r.strs;
+ }
+ };
+
+ inline root::
+ ~root () {}
+
+ inline bool
+ operator== (const root& x, const root& y) {return x.compare (y);}
+
+ #pragma db object
+ struct base: root
+ {
+ base () {}
+ base (unsigned long i, unsigned long n, const std::string& s)
+ : root (i, n), str (s) {}
+
+ std::string str;
+ std::vector<unsigned long> nums;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (base))
+ return false;
+
+ const base& b (static_cast<const base&> (r));
+ return root::compare (r, false) && str == b.str && nums == b.nums;
+ }
+ };
+
+ #pragma db object abstract
+ struct interm: base
+ {
+ interm () {}
+ interm (unsigned long i, unsigned long n, const std::string& s, bool b)
+ : base (i, n, s), bln (b) {}
+
+ bool bln;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (interm))
+ return false;
+
+ const interm& i (static_cast<const interm&> (r));
+ return base::compare (r, false) && bln == i.bln;
+ }
+ };
+
+ #pragma db object
+ struct derived1: interm
+ {
+ derived1 () {}
+ derived1 (unsigned long i, unsigned long n, const std::string& s, bool b)
+ : interm (i, n, s, b), dnum (n + 1) {}
+
+ unsigned long dnum;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (derived1))
+ return false;
+
+ const derived1& d (static_cast<const derived1&> (r));
+ return interm::compare (r, false) && dnum == d.dnum;
+ }
+ };
+
+ #pragma db object
+ struct derived2: interm
+ {
+ derived2 () {}
+ derived2 (unsigned long i, unsigned long n, const std::string& s, bool b)
+ : interm (i, n, s, b), dstr (s + 'd') {}
+
+ std::string dstr;
+
+ virtual bool
+ compare (const root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (derived2))
+ return false;
+
+ const derived2& d (static_cast<const derived2&> (r));
+ return interm::compare (r, false) && dstr == d.dstr;
+ }
+ };
+}
+
+#endif // TEST8_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/test9.hxx b/odb-tests/common/inheritance/polymorphism/test9.hxx
new file mode 100644
index 0000000..cdc97ae
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/test9.hxx
@@ -0,0 +1,161 @@
+// file : common/inheritance/polymorphism/test9.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST9_HXX
+#define TEST9_HXX
+
+#include <string>
+#include <vector>
+#include <typeinfo>
+
+#include <odb/core.hxx>
+
+// Test polymorphism and readonly classes.
+//
+#pragma db namespace table("t9_")
+namespace test9
+{
+ //
+ // ro_root, rw_base, ro_derived
+ //
+ #pragma db object polymorphic readonly
+ struct ro_root
+ {
+ virtual ~ro_root () {}
+ ro_root () {}
+ ro_root (unsigned long i, unsigned long n): id (i), num (n) {}
+
+ #pragma db id
+ unsigned long id;
+
+ unsigned long num;
+ std::vector<std::string> strs;
+
+ virtual bool
+ compare (const ro_root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (ro_root))
+ return false;
+
+ return id == r.id && num == r.num && strs == r.strs;
+ }
+ };
+
+ inline bool
+ operator== (const ro_root& x, const ro_root& y) {return x.compare (y);}
+
+ #pragma db object
+ struct rw_base: ro_root
+ {
+ rw_base () {}
+ rw_base (unsigned long i, unsigned long n, const std::string& s)
+ : ro_root (i, n), str (s) {}
+
+ std::string str;
+ std::vector<unsigned long> nums;
+
+ virtual bool
+ compare (const ro_root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (rw_base))
+ return false;
+
+ const rw_base& b (static_cast<const rw_base&> (r));
+ return ro_root::compare (r, false) && str == b.str && nums == b.nums;
+ }
+ };
+
+ #pragma db object readonly
+ struct ro_derived: rw_base
+ {
+ ro_derived () {}
+ ro_derived (unsigned long i, unsigned long n, const std::string& s)
+ : rw_base (i, n, s), dnum (n + 1), dstr (s + 'd') {}
+
+ unsigned long dnum;
+ std::string dstr;
+
+ virtual bool
+ compare (const ro_root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (ro_derived))
+ return false;
+
+ const ro_derived& d (static_cast<const ro_derived&> (r));
+ return rw_base::compare (r, false) && dnum == d.dnum && dstr == d.dstr;
+ }
+ };
+
+ //
+ // rw_root, ro_base, rw_derived
+ //
+ #pragma db object polymorphic
+ struct rw_root
+ {
+ virtual ~rw_root () {}
+ rw_root () {}
+ rw_root (unsigned long i, unsigned long n): id (i), num (n) {}
+
+ #pragma db id
+ unsigned long id;
+
+ unsigned long num;
+ std::vector<std::string> strs;
+
+ virtual bool
+ compare (const rw_root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (rw_root))
+ return false;
+
+ return id == r.id && num == r.num && strs == r.strs;
+ }
+ };
+
+ inline bool
+ operator== (const rw_root& x, const rw_root& y) {return x.compare (y);}
+
+ #pragma db object readonly
+ struct ro_base: rw_root
+ {
+ ro_base () {}
+ ro_base (unsigned long i, unsigned long n, const std::string& s)
+ : rw_root (i, n), str (s) {}
+
+ std::string str;
+ std::vector<unsigned long> nums;
+
+ virtual bool
+ compare (const rw_root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (ro_base))
+ return false;
+
+ const ro_base& b (static_cast<const ro_base&> (r));
+ return rw_root::compare (r, false) && str == b.str && nums == b.nums;
+ }
+ };
+
+ #pragma db object
+ struct rw_derived: ro_base
+ {
+ rw_derived () {}
+ rw_derived (unsigned long i, unsigned long n, const std::string& s)
+ : ro_base (i, n, s), dnum (n + 1), dstr (s + 'd') {}
+
+ unsigned long dnum;
+ std::string dstr;
+
+ virtual bool
+ compare (const rw_root& r, bool tc = true) const
+ {
+ if (tc && typeid (r) != typeid (rw_derived))
+ return false;
+
+ const rw_derived& d (static_cast<const rw_derived&> (r));
+ return ro_base::compare (r, false) && dnum == d.dnum && dstr == d.dstr;
+ }
+ };
+}
+
+#endif // TEST9_HXX
diff --git a/odb-tests/common/inheritance/polymorphism/testscript b/odb-tests/common/inheritance/polymorphism/testscript
new file mode 100644
index 0000000..89e5726
--- /dev/null
+++ b/odb-tests/common/inheritance/polymorphism/testscript
@@ -0,0 +1,80 @@
+# file : common/inheritance/polymorphism/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
++cat <<EOI >=output
+ base pre_persist 1 const
+ base post_persist 1 const
+ derived pre_persist 2 const
+ derived post_persist 2 const
+ base pre_load 0
+ base post_load 1
+ derived pre_load 0 const
+ derived post_load 2 const
+ base pre_load 1
+ base post_load 1
+ derived pre_load 2 const
+ derived post_load 2 const
+ base pre_load 1
+ base post_load 1
+ derived pre_load 2 const
+ derived post_load 2 const
+ base pre_update 1 const
+ base post_update 1 const
+ derived pre_update 2 const
+ derived post_update 2 const
+ base pre_load 0
+ base post_load 1
+ derived pre_load 0 const
+ derived post_load 2 const
+ base pre_erase 1 const
+ base post_erase 1 const
+ derived pre_erase 2 const
+ derived post_erase 2 const
+ derived pre_persist 3 const
+ derived post_persist 3 const
+ derived pre_persist 4 const
+ derived post_persist 4 const
+ derived pre_load 0 const
+ derived pre_load 0 const
+ derived post_load 4 const
+ derived post_load 3 const
+ EOI
+
+test.redirects += >>>../output
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql-schema.testscript
+
+ for s: $schemas
+ cat $out_base/"$s"($multi ? '-mysql' : '').sql | $create_schema_cmd
+ end;
+
+ $* ($multi ? 'mysql' : ) $mysql_options
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql-schema.testscript
+
+ for s: $schemas
+ $create_schema_cmd -f $out_base/"$s"($multi ? '-pgsql' : '').sql
+ end;
+
+ $* ($multi ? 'pgsql' : ) $pgsql_options
+}
diff --git a/odb-tests/common/inheritance/reuse/buildfile b/odb-tests/common/inheritance/reuse/buildfile
new file mode 100644
index 0000000..b82439a
--- /dev/null
+++ b/odb-tests/common/inheritance/reuse/buildfile
@@ -0,0 +1,41 @@
+# file : common/inheritance/reuse/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 inhrt_r_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/inheritance/reuse/driver.cxx b/odb-tests/common/inheritance/reuse/driver.cxx
new file mode 100644
index 0000000..e6122bb
--- /dev/null
+++ b/odb-tests/common/inheritance/reuse/driver.cxx
@@ -0,0 +1,237 @@
+// file : common/inheritance/reuse/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test reuse object inheritance.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ base b;
+ b.comp_.bools.push_back (true);
+ b.comp_.obools.push_back (true);
+ b.comp_.num = 10;
+ b.comp_.str = "comp bbb";
+ b.comp_.nums.push_back (101);
+ b.comp_.nums.push_back (102);
+ b.comp_.onums.push_back (101);
+ b.comp_.onums.push_back (102);
+ b.num_ = 0;
+ b.str_ = "bbb";
+ b.strs_.push_back ("bbb one");
+ b.strs_.push_back ("bbb two");
+ b.ostrs_.push_back ("bbb one");
+ b.ostrs_.push_back ("bbb two");
+
+ object1 o1;
+ o1.comp_.bools.push_back (false);
+ o1.comp_.obools.push_back (false);
+ o1.comp_.num = 11;
+ o1.comp_.str = "comp o1o1o1";
+ o1.comp_.nums.push_back (111);
+ o1.comp_.nums.push_back (112);
+ o1.comp_.onums.push_back (111);
+ o1.comp_.onums.push_back (112);
+ static_cast<base&> (o1).num_ = 1;
+ o1.num1_ = 21;
+ o1.str_ = "base o1o1o1";
+ o1.strs_.push_back ("base o1o1o1 one");
+ o1.strs_.push_back ("base o1o1o1 two");
+ o1.ostrs_.push_back ("base o1o1o1 one");
+ o1.ostrs_.push_back ("base o1o1o1 two");
+
+ object2 o2;
+ o2.comp_.bools.push_back (true);
+ o2.comp_.bools.push_back (false);
+ o2.comp_.obools.push_back (true);
+ o2.comp_.obools.push_back (false);
+ o2.comp_.num = 12;
+ o2.comp_.str = "comp o2o2o2";
+ o2.comp_.nums.push_back (121);
+ o2.comp_.nums.push_back (122);
+ o2.comp_.onums.push_back (121);
+ o2.comp_.onums.push_back (122);
+ o2.num_ = 2;
+ static_cast<base&> (o2).str_ = "base o2o2o2";
+ o2.str_ = "o2o2o2";
+ o2.strs_.push_back ("base o2o2o2 one");
+ o2.strs_.push_back ("base o2o2o2 two");
+ o2.ostrs_.push_back ("base o2o2o2 one");
+ o2.ostrs_.push_back ("base o2o2o2 two");
+
+ object3 o3;
+ o3.comp_.bools.push_back (false);
+ o3.comp_.bools.push_back (false);
+ o3.comp_.obools.push_back (false);
+ o3.comp_.obools.push_back (false);
+ o3.comp_.num = 13;
+ o3.comp_.str = "comp o3o3o3";
+ o3.comp_.nums.push_back (131);
+ o3.comp_.nums.push_back (132);
+ o3.comp_.onums.push_back (131);
+ o3.comp_.onums.push_back (132);
+ o3.num_ = 3;
+ o3.str_ = "base o3o3o3";
+ o3.strs_.push_back ("base o3o3o3 one");
+ o3.strs_.push_back ("base o3o3o3 two");
+ o3.ostrs_.push_back ("base o3o3o3 one");
+ o3.ostrs_.push_back ("base o3o3o3 two");
+
+ reference r;
+ r.o1_ = &o1;
+
+ empty_object e;
+ e.comp_.bools.push_back (true);
+ e.comp_.bools.push_back (true);
+ e.comp_.obools.push_back (true);
+ e.comp_.obools.push_back (true);
+ e.comp_.num = 14;
+ e.comp_.str = "comp eee";
+ e.comp_.nums.push_back (141);
+ e.comp_.nums.push_back (142);
+ e.comp_.onums.push_back (141);
+ e.comp_.onums.push_back (142);
+ e.num_ = 4;
+ e.str_ = "base eee";
+ e.strs_.push_back ("base eee one");
+ e.strs_.push_back ("base eee two");
+ e.ostrs_.push_back ("base eee one");
+ e.ostrs_.push_back ("base eee two");
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (b);
+ db->persist (o1);
+ db->persist (o2);
+ db->persist (o3);
+ db->persist (r);
+ db->persist (e);
+ t.commit ();
+ }
+
+ // load & check
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> lb (db->load<base> (b.id_));
+ unique_ptr<object1> lo1 (db->load<object1> (o1.id_));
+ unique_ptr<object2> lo2 (db->load<object2> (o2.id_));
+ unique_ptr<object3> lo3 (db->load<object3> (o3.id_));
+ unique_ptr<empty_object> le (db->load<empty_object> (e.id_));
+ unique_ptr<reference> lr (db->load<reference> (r.id_));
+ t.commit ();
+
+ assert (b == *lb);
+ assert (o1 == *lo1);
+ assert (o2 == *lo2);
+ assert (o3 == *lo3);
+ assert (lr->o1_->id_ == r.o1_->id_);
+ assert (e == *le);
+
+ delete lr->o1_;
+ }
+
+ // update
+ //
+ {
+ transaction t (db->begin ());
+ db->update (b);
+ db->update (o1);
+ db->update (o2);
+ db->update (o3);
+ db->update (r);
+ db->update (e);
+ t.commit ();
+ }
+
+ // query
+ //
+ {
+ typedef odb::query<base> b_query;
+ typedef odb::query<object1> o1_query;
+ typedef odb::query<object2> o2_query;
+ typedef odb::query<reference> r_query;
+
+ typedef odb::result<reference> r_result;
+
+ transaction t (db->begin ());
+
+ assert (!db->query<base> (b_query::comp.num == 10).empty ());
+ assert (!db->query<object1> (o1_query::num1 == 21).empty ());
+ assert (!db->query<object2> (o2_query::num == 2).empty ());
+
+ // Query condition with hidden members.
+ //
+ assert (
+ !db->query<object2> (o2_query::base::str == "base o2o2o2").empty ());
+
+ // Query condition with referenced composite member in base class.
+ //
+ {
+ r_result r (db->query<reference> (r_query::o1->comp.num == 11));
+ assert (!r.empty ());
+ delete r.begin ()->o1_;
+ }
+
+ t.commit ();
+ }
+
+ // views
+ //
+ {
+ typedef odb::query<object2_view> query;
+ typedef odb::result<object2_view> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<object2_view> (query::num == o2.num_));
+ result::iterator i (r.begin ());
+ assert (i != r.end () &&
+ i->num == o2.num_ && i->id == o2.id_ && i->str == o2.str_);
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+
+ // erase
+ //
+ {
+ transaction t (db->begin ());
+ db->erase (b);
+ db->erase (o1);
+ db->erase (o2);
+ db->erase (o3);
+ db->erase (r);
+ db->erase (e);
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/inheritance/reuse/test.hxx b/odb-tests/common/inheritance/reuse/test.hxx
new file mode 100644
index 0000000..48f474f
--- /dev/null
+++ b/odb-tests/common/inheritance/reuse/test.hxx
@@ -0,0 +1,163 @@
+// file : common/inheritance/reuse/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <vector>
+
+#include <odb/core.hxx>
+#include <odb/vector.hxx>
+
+#pragma db value
+struct comp_base
+{
+ std::vector<unsigned char> bools;
+ odb::vector<unsigned char> obools;
+
+ bool
+ operator== (const comp_base& y) const
+ {
+ return bools == y.bools && obools == y.obools;
+ }
+};
+
+#pragma db value
+struct comp: comp_base
+{
+ unsigned int num;
+ std::string str;
+
+ std::vector<unsigned int> nums;
+ odb::vector<unsigned int> onums;
+
+ bool
+ operator== (const comp& y) const
+ {
+ return
+ static_cast<const comp_base&> (*this) == y &&
+ num == y.num &&
+ str == y.str &&
+ nums == y.nums &&
+ onums == y.onums;
+ }
+};
+
+#pragma db object abstract
+struct abstract_base
+{
+ comp comp_;
+
+ unsigned int num_;
+ std::string str_;
+
+ std::vector<std::string> strs_;
+ odb::vector<std::string> ostrs_;
+
+ bool
+ operator== (const abstract_base& y) const
+ {
+ return
+ comp_ == y.comp_ &&
+ num_ == y.num_ &&
+ str_ == y.str_ &&
+ strs_ == y.strs_ &&
+ ostrs_ == y.ostrs_;
+ }
+};
+
+#pragma db object
+struct base: abstract_base
+{
+ #pragma db id auto
+ unsigned long id_;
+
+ bool
+ operator== (const base& y) const
+ {
+ return id_ == y.id_ && static_cast<const abstract_base&> (*this) == y;
+ }
+};
+
+#pragma db object
+struct object1: base
+{
+ unsigned int num1_;
+
+ bool
+ operator== (const object1& y) const
+ {
+ return static_cast<const base&> (*this) == y && num1_ == y.num1_;
+ }
+};
+
+#pragma db object
+struct object2: base
+{
+ #pragma db column("derived_str")
+ std::string str_;
+
+ bool
+ operator== (const object2& y) const
+ {
+ return static_cast<const base&> (*this) == y && str_ == y.str_;
+ }
+};
+
+// Reference to derived object.
+//
+#pragma db object
+struct reference
+{
+ #pragma db id auto
+ unsigned long id_;
+
+ object1* o1_;
+};
+
+// Multiple inheritance.
+//
+#pragma db object abstract
+struct id_base
+{
+ #pragma db id auto
+ unsigned long id_;
+
+ bool
+ operator== (const id_base& y) const
+ {
+ return id_ == y.id_;
+ }
+};
+
+#pragma db object
+struct object3: abstract_base, id_base
+{
+ bool
+ operator== (const object3& y) const
+ {
+ return
+ static_cast<const abstract_base&> (*this) == y &&
+ static_cast<const id_base&> (*this) == y;
+ }
+};
+
+// Empty derived object.
+//
+#pragma db object
+struct empty_object: base
+{
+};
+
+// View based on the derived object.
+//
+#pragma db view object(object2)
+struct object2_view
+{
+ unsigned int num; // from abstract_base
+ unsigned long id; // from base
+ std::string str; // from object2, hides one from abstract_base
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/inheritance/reuse/testscript b/odb-tests/common/inheritance/reuse/testscript
new file mode 100644
index 0000000..995b3f5
--- /dev/null
+++ b/odb-tests/common/inheritance/reuse/testscript
@@ -0,0 +1,33 @@
+# file : common/inheritance/reuse/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/inheritance/transient/buildfile b/odb-tests/common/inheritance/transient/buildfile
new file mode 100644
index 0000000..1961abc
--- /dev/null
+++ b/odb-tests/common/inheritance/transient/buildfile
@@ -0,0 +1,41 @@
+# file : common/inheritance/transient/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 inhrt_t_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/inheritance/transient/driver.cxx b/odb-tests/common/inheritance/transient/driver.cxx
new file mode 100644
index 0000000..1caae6c
--- /dev/null
+++ b/odb-tests/common/inheritance/transient/driver.cxx
@@ -0,0 +1,80 @@
+// file : common/inheritance/transient/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test transient inheritance of objects, composite value types, and views.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ object o;
+ o.num = 1;
+ o.str = "abc";
+ o.strs.push_back ("abc 1");
+ o.strs.push_back ("abc 2");
+ o.c.num = 11;
+ o.c.str = "comp abc";
+ o.c.nums.push_back (111);
+ o.c.nums.push_back (112);
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ // load & check
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id_));
+ t.commit ();
+
+ assert (*p == o);
+ }
+
+ // view
+ //
+ {
+ typedef odb::query<view> query;
+ typedef odb::result<view> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<view> (query::id == o.id_));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->num == o.num && i->str == o.str);
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/inheritance/transient/test.hxx b/odb-tests/common/inheritance/transient/test.hxx
new file mode 100644
index 0000000..394ee8f
--- /dev/null
+++ b/odb-tests/common/inheritance/transient/test.hxx
@@ -0,0 +1,60 @@
+// file : common/inheritance/transient/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <vector>
+
+#include <odb/core.hxx>
+
+struct object;
+
+struct base
+{
+ int n;
+ std::vector<std::string> v;
+ object* p;
+};
+
+#pragma db value
+struct comp: base
+{
+ unsigned int num;
+ std::string str;
+ std::vector<unsigned int> nums;
+
+ bool
+ operator== (const comp& y) const
+ {
+ return num == y.num && str == y.str && nums == y.nums;
+ }
+};
+
+#pragma db object
+struct object: base
+{
+ #pragma db id auto
+ unsigned int id_;
+
+ unsigned int num;
+ std::string str;
+ std::vector<std::string> strs;
+ comp c;
+
+ bool
+ operator== (const object& y) const
+ {
+ return num == y.num && str == y.str && strs == y.strs && c == y.c;
+ }
+};
+
+#pragma db view object(object)
+struct view: base
+{
+ unsigned int num;
+ std::string str;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/inheritance/transient/testscript b/odb-tests/common/inheritance/transient/testscript
new file mode 100644
index 0000000..bce91de
--- /dev/null
+++ b/odb-tests/common/inheritance/transient/testscript
@@ -0,0 +1,33 @@
+# file : common/inheritance/transient/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/inverse/buildfile b/odb-tests/common/inverse/buildfile
new file mode 100644
index 0000000..63fa1cb
--- /dev/null
+++ b/odb-tests/common/inverse/buildfile
@@ -0,0 +1,42 @@
+# file : common/inverse/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_inverse_ \
+ --generate-schema \
+ --generate-query \
+ --generate-session
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/inverse/driver.cxx b/odb-tests/common/inverse/driver.cxx
new file mode 100644
index 0000000..842438e
--- /dev/null
+++ b/odb-tests/common/inverse/driver.cxx
@@ -0,0 +1,502 @@
+// file : common/inverse/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test bidirectional relationships with inverse sides.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/session.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ // Test raw pointers.
+ //
+ {
+ using namespace test1;
+
+ obj1_ptr o1_1 (new obj1);
+ obj1_ptr o1_2 (new obj1);
+ obj2_ptr o2 (new obj2);
+ obj3_ptr o3_1 (new obj3);
+ obj3_ptr o3_2 (new obj3);
+ obj4_ptr o4_1 (new obj4);
+ obj4_ptr o4_2 (new obj4);
+ obj5_ptr o5_1 (new obj5);
+ obj5_ptr o5_2 (new obj5);
+ obj5_ptr o5_3 (new obj5);
+ obj5_ptr o5_4 (new obj5);
+
+ o1_1->id = "obj1 1";
+ o1_1->o2 = o2;
+ o1_1->o3.insert (o3_1);
+ o1_1->o3.insert (o3_2);
+ o1_1->o4 = o4_1;
+ o1_1->o5.insert (o5_1);
+ o1_1->o5.insert (o5_2);
+
+ o1_2->id = "obj1 2";
+ o1_2->o2 = 0;
+ o1_2->o3.clear ();
+ o1_2->o4 = o4_2;
+ o1_2->o5.insert (o5_3);
+ o1_2->o5.insert (o5_4);
+
+ o2->str = "obj2";
+ o2->o1 = o1_1;
+
+ o3_1->str = "obj3 1";
+ o3_1->o1 = o1_1;
+
+ o3_2->str = "obj3 2";
+ o3_2->o1 = o1_1;
+
+ o4_1->str = "obj4 1";
+ o4_1->o1.insert (o1_1);
+
+ o4_2->str = "obj4 2";
+ o4_2->o1.insert (o1_2);
+
+ o5_1->str = "obj5 1";
+ o5_1->o1.insert (o1_1);
+
+ o5_2->str = "obj5 2";
+ o5_2->o1.insert (o1_1);
+
+ o5_3->str = "obj5 3";
+ o5_3->o1.insert (o1_2);
+
+ o5_4->str = "obj5 4";
+ o5_4->o1.insert (o1_2);
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+
+ // objN come before obj1 to get object id assigned.
+ //
+ db->persist (o5_1);
+ db->persist (o5_2);
+ db->persist (o5_3);
+ db->persist (o5_4);
+ db->persist (o4_1);
+ db->persist (o4_2);
+ db->persist (o3_1);
+ db->persist (o3_2);
+ db->persist (o2);
+ db->persist (o1_1);
+ db->persist (o1_2);
+
+ t.commit ();
+ }
+
+ // load
+ //
+ {
+ session s;
+ transaction t (db->begin ());
+ obj2_ptr x2 (db->load<obj2> (o2->id));
+ obj3_ptr x3_1 (db->load<obj3> (o3_1->id));
+ obj3_ptr x3_2 (db->load<obj3> (o3_2->id));
+ obj4_ptr x4_1 (db->load<obj4> (o4_1->id));
+ obj4_ptr x4_2 (db->load<obj4> (o4_2->id));
+ obj5_ptr x5_1 (db->load<obj5> (o5_1->id));
+ obj5_ptr x5_2 (db->load<obj5> (o5_2->id));
+ obj5_ptr x5_3 (db->load<obj5> (o5_3->id));
+ obj5_ptr x5_4 (db->load<obj5> (o5_4->id));
+ t.commit ();
+
+ assert (x2->str == o2->str);
+ assert (x2->o1->id == o1_1->id);
+ assert (x2->o1->o2 == x2);
+
+ assert (x3_1->str == o3_1->str);
+ assert (x3_2->str == o3_2->str);
+ assert (x3_1->o1 == x3_2->o1);
+ assert (x3_1->o1->id == o1_1->id);
+ assert (x3_1->o1->o3.find (x3_1) != x3_1->o1->o3.end ());
+ assert (x3_1->o1->o3.find (x3_2) != x3_1->o1->o3.end ());
+
+ assert (x4_1->str == o4_1->str);
+ assert (x4_2->str == o4_2->str);
+ assert ((*x4_1->o1.begin ())->id == o1_1->id);
+ assert ((*x4_2->o1.begin ())->id == o1_2->id);
+ assert ((*x4_1->o1.begin ())->o4 == x4_1);
+ assert ((*x4_2->o1.begin ())->o4 == x4_2);
+
+ assert (x5_1->str == o5_1->str);
+ assert (x5_2->str == o5_2->str);
+ assert ((*x5_1->o1.begin ())->id == o1_1->id);
+ assert ((*x5_2->o1.begin ())->id == o1_1->id);
+ assert ((*x5_3->o1.begin ())->id == o1_2->id);
+ assert ((*x5_4->o1.begin ())->id == o1_2->id);
+ assert ((*x5_1->o1.begin ())->o5.find (x5_1) !=
+ (*x5_1->o1.begin ())->o5.end ());
+ assert ((*x5_2->o1.begin ())->o5.find (x5_2) !=
+ (*x5_2->o1.begin ())->o5.end ());
+ assert ((*x5_3->o1.begin ())->o5.find (x5_3) !=
+ (*x5_3->o1.begin ())->o5.end ());
+ assert ((*x5_4->o1.begin ())->o5.find (x5_4) !=
+ (*x5_4->o1.begin ())->o5.end ());
+
+ delete *x4_1->o1.begin ();
+ delete *x4_2->o1.begin ();
+ }
+
+ // query
+ //
+ {
+ // one(i)-to-one
+ //
+ typedef odb::query<obj2> query;
+ typedef odb::result<obj2> result;
+
+ session s;
+ transaction t (db->begin ());
+
+ result r (db->query<obj2> (query::o1->id == "obj1 1"));
+ assert (!r.empty ());
+ assert (r.begin ()->id == o2->id);
+ assert (r.begin ()->o1->id == o1_1->id);
+ assert (size (r) == 1);
+
+ t.commit ();
+ }
+
+ {
+ // one(i)-to-many
+ //
+ typedef odb::query<obj3> query;
+ typedef odb::result<obj3> result;
+
+ session s;
+ transaction t (db->begin ());
+
+ result r (db->query<obj3> (query::o1->id == "obj1 1"));
+ size_t n (0);
+
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ {
+ assert (i->id == o3_1->id || i->id == o3_2->id);
+ assert (i->o1->id == o1_1->id);
+ n++;
+ }
+
+ assert (n == 2);
+
+ t.commit ();
+ }
+
+ delete o1_1;
+ delete o1_2;
+ }
+
+ // Test shared_ptr/weak_ptr.
+ //
+ {
+ using namespace test2;
+
+ obj1_ptr o1_1 (new obj1);
+ obj1_ptr o1_2 (new obj1);
+ obj2_ptr o2 (new obj2);
+ obj3_ptr o3_1 (new obj3);
+ obj3_ptr o3_2 (new obj3);
+ obj4_ptr o4 (new obj4);
+ obj5_ptr o5_1 (new obj5);
+ obj5_ptr o5_2 (new obj5);
+
+ o1_1->id = "obj1 1";
+ o1_1->o2 = o2;
+ o1_1->o3.push_back (o3_1);
+ o1_1->o3.push_back (o3_2);
+ o1_1->o4 = o4;
+ o1_1->o5.push_back (o5_1);
+ o1_1->o5.push_back (o5_2);
+
+ o1_2->id = "obj1 2";
+ o1_2->o2 = obj2_ptr ();
+ o1_2->o3.clear ();
+ o1_2->o4 = o4;
+ o1_2->o5.push_back (o5_1);
+
+ o2->str = "obj2";
+ o2->o1 = o1_1;
+
+ o3_1->str = "obj3 1";
+ o3_1->o1 = o1_1;
+
+ o3_2->str = "obj3 3";
+ o3_2->o1 = o1_1;
+
+ o4->str = "obj4";
+ o4->o1.push_back (o1_1);
+ o4->o1.push_back (o1_2);
+
+ o5_1->str = "obj5 1";
+ o5_1->o1.push_back (o1_1);
+ o5_1->o1.push_back (o1_2);
+
+ o5_2->str = "obj5 2";
+ o5_2->o1.push_back (o1_1);
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+
+ // objN come before obj1 to get object id assigned.
+ //
+ db->persist (o5_1);
+ db->persist (o5_2);
+ db->persist (o4);
+ db->persist (o3_1);
+ db->persist (o3_2);
+ db->persist (o2);
+ db->persist (o1_1);
+ db->persist (o1_2);
+
+ t.commit ();
+ }
+
+ // load
+ //
+ {
+ session s;
+ transaction t (db->begin ());
+ obj2_ptr x2 (db->load<obj2> (o2->id));
+ obj3_ptr x3_1 (db->load<obj3> (o3_1->id));
+ obj3_ptr x3_2 (db->load<obj3> (o3_2->id));
+ obj4_ptr x4 (db->load<obj4> (o4->id));
+ obj5_ptr x5_1 (db->load<obj5> (o5_1->id));
+ obj5_ptr x5_2 (db->load<obj5> (o5_2->id));
+ t.commit ();
+
+ assert (x2->str == o2->str);
+ assert (x2->o1.lock ()->id == o1_1->id);
+ assert (x2->o1.lock ()->o2 == x2);
+
+ assert (x3_1->str == o3_1->str);
+ assert (x3_2->str == o3_2->str);
+ assert (x3_1->o1.lock () == x3_2->o1.lock ());
+ assert (x3_1->o1.lock ()->id == o1_1->id);
+ assert (x3_1->o1.lock ()->o3[0] == x3_1);
+ assert (x3_1->o1.lock ()->o3[1] == x3_2);
+
+ {
+ assert (x4->str == o4->str);
+
+ obj1_ptr t1 (x4->o1[0].lock ()), t2 (x4->o1[1].lock ());
+
+ assert (t1->id == o1_1->id || t2->id == o1_1->id);
+ assert (t1->id == o1_2->id || t2->id == o1_2->id);
+ }
+
+ {
+ assert (x5_1->str == o5_1->str);
+ assert (x5_2->str == o5_2->str);
+
+ obj1_ptr t1 (x5_1->o1[0].lock ()), t2 (x5_1->o1[1].lock ()),
+ t3 (x5_2->o1[0].lock ());
+
+ assert (t1->id == o1_1->id || t2->id == o1_1->id);
+ assert (t1->id == o1_2->id || t2->id == o1_2->id);
+ assert (t3->id == o1_1->id);
+ }
+ }
+ }
+
+ // Test inverse based on points_to.
+ //
+ {
+ using namespace test3;
+
+ {
+ obj1 o1 (1, 2);
+ o1.o2 = new obj2;
+
+ {
+ transaction t (db->begin ());
+
+ o1.o2->o1 = db->persist (o1);
+ db->persist (o1.o2);
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<obj1> p (db->load<obj1> (o1.id));
+ assert (p->o2->id == o1.o2->id);
+
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<obj1> query;
+
+ transaction t (db->begin ());
+
+ unique_ptr<obj1> p (db->query_one<obj1> (query::o2->o1.i == o1.id.i &&
+ query::o2->o1.j == o1.id.j));
+ assert (p->o2->id == o1.o2->id);
+
+ t.commit ();
+ }
+ }
+
+ {
+ obj3 o3;
+ o3.o4.push_back (new obj4);
+ o3.o4.push_back (new obj4);
+
+ {
+ transaction t (db->begin ());
+
+ o3.o4[0]->o3 = o3.o4[1]->o3 = db->persist (o3);
+ db->persist (o3.o4[0]);
+ db->persist (o3.o4[1]);
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<obj3> p (db->load<obj3> (o3.id));
+ assert (p->o4[0]->id == o3.o4[0]->id);
+ assert (p->o4[1]->id == o3.o4[1]->id);
+
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<obj3> query;
+
+ transaction t (db->begin ());
+
+ unique_ptr<obj3> p (db->query_one<obj3> (query::id == o3.id));
+ assert (p->o4[0]->id == o3.o4[0]->id);
+ assert (p->o4[1]->id == o3.o4[1]->id);
+
+ t.commit ();
+ }
+ }
+ }
+
+ // Test inverse with nested data members.
+ //
+ {
+ using namespace test4;
+
+ {
+ obj1 o1;
+ o1.o2 = new obj2;
+
+ {
+ transaction t (db->begin ());
+
+ o1.o2->id.i = db->persist (o1);
+ o1.o2->id.j = 123;
+ db->persist (o1.o2);
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<obj1> p (db->load<obj1> (o1.id));
+ assert (p->o2->id.i == o1.o2->id.i && p->o2->id.j == o1.o2->id.j);
+
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<obj1> query;
+
+ transaction t (db->begin ());
+
+ unique_ptr<obj1> p (db->query_one<obj1> (
+ query::o2->id.i == o1.o2->id.i &&
+ query::o2->id.j == o1.o2->id.j));
+ assert (p->o2->id.i == o1.o2->id.i && p->o2->id.j == o1.o2->id.j);
+
+ t.commit ();
+ }
+ }
+
+ {
+ obj3 o3;
+ o3.o4.push_back (new obj4);
+ o3.o4.push_back (new obj4);
+
+ {
+ transaction t (db->begin ());
+
+ o3.o4[0]->id.i = o3.o4[1]->id.i = db->persist (o3);
+ o3.o4[0]->id.j = 123;
+ o3.o4[1]->id.j = 234;
+ db->persist (o3.o4[0]);
+ db->persist (o3.o4[1]);
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<obj3> p (db->load<obj3> (o3.id));
+ assert (p->o4[0]->id.i == o3.o4[0]->id.i &&
+ p->o4[0]->id.j == o3.o4[0]->id.j);
+
+ assert (p->o4[1]->id.i == o3.o4[1]->id.i &&
+ p->o4[1]->id.j == o3.o4[1]->id.j);
+
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<obj3> query;
+
+ transaction t (db->begin ());
+
+ unique_ptr<obj3> p (db->query_one<obj3> (query::id == o3.id));
+
+ assert (p->o4[0]->id.i == o3.o4[0]->id.i &&
+ p->o4[0]->id.j == o3.o4[0]->id.j);
+
+ assert (p->o4[1]->id.i == o3.o4[1]->id.i &&
+ p->o4[1]->id.j == o3.o4[1]->id.j);
+
+ t.commit ();
+ }
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/inverse/test.hxx b/odb-tests/common/inverse/test.hxx
new file mode 100644
index 0000000..a7b8678
--- /dev/null
+++ b/odb-tests/common/inverse/test.hxx
@@ -0,0 +1,391 @@
+// file : common/inverse/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <set>
+#include <vector>
+#include <string>
+#include <memory>
+
+#include <odb/core.hxx>
+
+// Test raw pointers.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ struct obj1;
+ struct obj2;
+ struct obj3;
+ struct obj4;
+ struct obj5;
+
+ typedef obj1* obj1_ptr;
+ typedef obj2* obj2_ptr;
+ typedef obj3* obj3_ptr;
+ typedef obj4* obj4_ptr;
+ typedef obj5* obj5_ptr;
+
+ typedef std::set<obj1_ptr> obj1_ptr_set;
+ typedef std::set<obj3_ptr> obj3_ptr_set;
+ typedef std::set<obj5_ptr> obj5_ptr_set;
+
+ #pragma db object
+ struct obj1
+ {
+ obj1 (): o2 (0), o4 (0) {}
+ ~obj1 ();
+
+ #pragma db id
+ std::string id;
+
+ obj2_ptr o2;
+
+ #pragma db id_column("obj1_id") value_column("obj3_id")
+ obj3_ptr_set o3;
+
+ obj4_ptr o4;
+
+ obj5_ptr_set o5;
+ };
+
+ #pragma db object
+ struct obj2
+ {
+ #pragma db id auto
+ int id;
+
+ // one-to-one
+ //
+ #pragma db inverse(o2)
+ obj1_ptr o1;
+
+ std::string str;
+ };
+
+ #pragma db object
+ struct obj3
+ {
+ std::string str;
+
+ // one(i)-to-many
+ //
+ #pragma db inverse (o3)
+ obj1_ptr o1;
+
+ #pragma db id auto
+ int id;
+ };
+
+ #pragma db object
+ struct obj4
+ {
+ #pragma db id auto
+ int id;
+
+ std::string str;
+
+ // many(i)-to-one
+ //
+ #pragma db inverse (o4)
+ obj1_ptr_set o1;
+ };
+
+ #pragma db object
+ struct obj5
+ {
+ #pragma db id auto
+ int id;
+
+ std::string str;
+
+ // many(i)-to-many
+ //
+ #pragma db inverse (o5)
+ obj1_ptr_set o1;
+ };
+
+ inline obj1::
+ ~obj1 ()
+ {
+ delete o2;
+ for (obj3_ptr_set::iterator i (o3.begin ()); i != o3.end (); ++i)
+ delete *i;
+ delete o4;
+ for (obj5_ptr_set::iterator i (o5.begin ()); i != o5.end (); ++i)
+ delete *i;
+ }
+}
+
+// Test shared_ptr/weak_ptr.
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ using std::shared_ptr;
+ using std::weak_ptr;
+
+ struct obj1;
+ struct obj2;
+ struct obj3;
+ struct obj4;
+ struct obj5;
+
+ typedef shared_ptr<obj1> obj1_ptr;
+ typedef shared_ptr<obj2> obj2_ptr;
+ typedef shared_ptr<obj3> obj3_ptr;
+ typedef shared_ptr<obj4> obj4_ptr;
+ typedef shared_ptr<obj5> obj5_ptr;
+
+ typedef weak_ptr<obj1> obj1_wptr;
+
+ typedef std::vector<obj1_wptr> obj1_wptr_vec;
+ typedef std::vector<obj3_ptr> obj3_ptr_vec;
+ typedef std::vector<obj5_ptr> obj5_ptr_vec;
+
+ #pragma db object pointer(obj1_ptr)
+ struct obj1
+ {
+ #pragma db id
+ std::string id;
+
+ obj2_ptr o2;
+
+ #pragma db id_column("obj1_id") value_column("obj3_id")
+ obj3_ptr_vec o3;
+
+ obj4_ptr o4;
+ obj5_ptr_vec o5;
+ };
+
+ #pragma db object pointer(obj2_ptr)
+ struct obj2
+ {
+ #pragma db id auto
+ int id;
+
+ std::string str;
+
+ // one(i)-to-one
+ //
+ #pragma db inverse(o2)
+ obj1_wptr o1;
+ };
+
+ #pragma db object pointer(obj3_ptr)
+ struct obj3
+ {
+ #pragma db id auto
+ int id;
+
+ std::string str;
+
+ // one(i)-to-many
+ //
+ #pragma db inverse (o3)
+ obj1_wptr o1;
+ };
+
+ #pragma db object pointer(obj4_ptr)
+ struct obj4
+ {
+ #pragma db id auto
+ int id;
+
+ std::string str;
+
+ // many(i)-to-one
+ //
+ #pragma db inverse (o4)
+ obj1_wptr_vec o1;
+ };
+
+ #pragma db object pointer(obj5_ptr)
+ struct obj5
+ {
+ #pragma db id auto
+ int id;
+
+ std::string str;
+
+ // many(i)-to-many
+ //
+ #pragma db inverse (o5)
+ obj1_wptr_vec o1;
+ };
+}
+
+// Test inverse based on points_to.
+//
+#pragma db namespace table("t3_")
+namespace test3
+{
+ // Inverse pointer.
+ //
+ #pragma db value
+ struct comp
+ {
+ int i;
+ int j;
+ };
+
+ inline bool
+ operator< (comp x, comp y) {return x.i < y.i || (x.i == y.i && x.j < y.j);}
+
+ struct obj2;
+
+ #pragma db object
+ struct obj1
+ {
+ #pragma db id
+ comp id;
+
+ #pragma db inverse(o1)
+ obj2* o2;
+
+ obj1 (int i = 0, int j = 0): o2 (0) {id.i = i; id.j = j;}
+ ~obj1 ();
+ };
+
+ #pragma db object
+ struct obj2
+ {
+ #pragma db id auto
+ int id;
+
+ #pragma db points_to(obj1)
+ comp o1;
+
+#ifndef ODB_DATABASE_MYSQL
+ #pragma db member(o1) on_delete(cascade)
+#endif
+ };
+
+ inline obj1::
+ ~obj1 () {delete o2;}
+
+ // Inverse container of pointers.
+ //
+ struct obj4;
+
+ #pragma db object
+ struct obj3
+ {
+ #pragma db id auto
+ int id;
+
+ #pragma db inverse(o3)
+ std::vector<obj4*> o4;
+
+ ~obj3 ();
+ };
+
+ #pragma db object
+ struct obj4
+ {
+ #pragma db id auto
+ int id;
+
+ #pragma db points_to(obj3)
+ int o3;
+ };
+
+ inline obj3::
+ ~obj3 ()
+ {
+ for (std::vector<obj4*>::iterator i (o4.begin ()); i != o4.end (); ++i)
+ delete *i;
+ }
+};
+
+// Test inverse with nested data members.
+//
+#pragma db namespace table("t4_")
+namespace test4
+{
+ // Inverse pointer.
+ //
+ struct obj1;
+ struct obj2;
+
+ #pragma db value
+ struct obj2_id
+ {
+ #pragma db points_to(obj1)
+ int i;
+ int j;
+ };
+
+ inline bool
+ operator< (obj2_id x, obj2_id y)
+ {return x.i < y.i || (x.i == y.i && x.j < y.j);}
+
+ #pragma db object
+ struct obj1
+ {
+ #pragma db id auto
+ int id;
+
+ #pragma db inverse(id.i)
+ obj2* o2;
+
+ obj1 (): o2 (0) {}
+ ~obj1 ();
+ };
+
+ #pragma db object
+ struct obj2
+ {
+ #pragma db id
+ obj2_id id;
+ };
+
+ inline obj1::
+ ~obj1 () {delete o2;}
+
+ // Inverse container of pointers.
+ //
+ struct obj3;
+ struct obj4;
+
+ #pragma db value
+ struct obj4_id
+ {
+ #pragma db points_to(obj3)
+ int i;
+ int j;
+ };
+
+ inline bool
+ operator< (obj4_id x, obj4_id y)
+ {return x.i < y.i || (x.i == y.i && x.j < y.j);}
+
+ #pragma db object
+ struct obj3
+ {
+ #pragma db id auto
+ int id;
+
+ #pragma db inverse(id.i)
+ std::vector<obj4*> o4;
+
+ ~obj3 ();
+ };
+
+ #pragma db object
+ struct obj4
+ {
+ #pragma db id
+ obj4_id id;
+ };
+
+ inline obj3::
+ ~obj3 ()
+ {
+ for (std::vector<obj4*>::iterator i (o4.begin ()); i != o4.end (); ++i)
+ delete *i;
+ }
+};
+#endif // TEST_HXX
diff --git a/odb-tests/common/inverse/testscript b/odb-tests/common/inverse/testscript
new file mode 100644
index 0000000..c2a4e3e
--- /dev/null
+++ b/odb-tests/common/inverse/testscript
@@ -0,0 +1,33 @@
+# file : common/inverse/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/lazy-ptr/buildfile b/odb-tests/common/lazy-ptr/buildfile
new file mode 100644
index 0000000..d495d2f
--- /dev/null
+++ b/odb-tests/common/lazy-ptr/buildfile
@@ -0,0 +1,41 @@
+# file : common/lazy-ptr/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 lazy_ptr_ \
+ --generate-schema \
+ --generate-session
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/lazy-ptr/driver.cxx b/odb-tests/common/lazy-ptr/driver.cxx
new file mode 100644
index 0000000..9a3b324
--- /dev/null
+++ b/odb-tests/common/lazy-ptr/driver.cxx
@@ -0,0 +1,360 @@
+// file : common/lazy-ptr/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test lazy object pointers.
+//
+
+#include <memory> // std::unique_ptr
+#include <utility> // std::move
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/session.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+namespace test2
+{
+ cont::cont (unsigned long i)
+ : id (i)
+ {
+ }
+
+ obj_ptr
+ create (unsigned int id)
+ {
+ obj_ptr r (new obj (id));
+ return r;
+ }
+
+ lazy_obj_ptr
+ create (database& db, unsigned int id)
+ {
+ lazy_obj_ptr r (db, id);
+ return r;
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv));
+
+ // Raw.
+ //
+ {
+ using namespace test1;
+
+ // persist
+ //
+ obj* o1 (new obj (1));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ t.commit ();
+ }
+
+ unique_ptr<cont> c1 (new cont (1));
+ unique_ptr<cont> c2 (new cont (2));
+
+ lazy_ptr<obj> lo1 (*db, 1);
+ obj* o2 (new obj (2));
+
+ obj* o3 (new obj (3));
+ obj* o4 (new obj (4));
+
+ c1->o.push_back (lo1);
+ c1->o.push_back (o2);
+ c2->o.push_back (o3);
+ c2->o.push_back (o4);
+
+ // Test pointer comparison.
+ //
+ assert (lazy_ptr<obj> () == lazy_ptr<obj> ());
+ assert (lazy_ptr<obj> (o1) != lazy_ptr<obj> ());
+ assert (lo1 != lazy_ptr<obj> ());
+ assert (lazy_ptr<obj> (o1) == lazy_ptr<obj> (o1));
+ assert (lo1 == lazy_ptr<obj> (*db, o1));
+ assert (lo1 != lazy_ptr<obj> (*db, o2));
+
+ delete o1;
+
+ {
+ transaction t (db->begin ());
+
+ db->persist (o2);
+ db->persist (o3);
+ db->persist (o4);
+
+ db->persist (*c1);
+ db->persist (*c2);
+
+ t.commit ();
+ }
+
+ // load
+ //
+ {
+ session s;
+ transaction t (db->begin ());
+ unique_ptr<cont> c (db->load<cont> (1));
+ obj* o (db->load<obj> (1));
+
+ // Not loaded.
+ //
+ assert (c->o.size () == 2);
+ assert (!c->o[0].loaded ());
+ assert (!c->o[1].loaded ());
+
+ assert (!o->c.loaded ());
+
+ // Correct object ids.
+ //
+ assert (c->o[0].object_id () == o->id);
+ assert (o->c.object_id () == c->id);
+
+ // Load.
+ //
+ cont* cl (o->c.load ());
+ obj* ol (c->o[0].load ());
+
+ assert (cl == c.get ());
+ assert (ol == o);
+
+ // Test unload/reload.
+ //
+ o->c.unload ();
+ assert (!o->c.loaded ());
+ o->c.load ();
+ assert (o->c.loaded ());
+
+ t.commit ();
+ }
+ }
+
+ // std::unique_ptr
+ //
+ {
+ using namespace test2;
+
+ // persist
+ //
+ {
+ obj_ptr o1 (new obj (1));
+ transaction t (db->begin ());
+ db->persist (*o1);
+ t.commit ();
+ }
+
+ cont_ptr c1 (new cont (1));
+ cont_ptr c2 (new cont (2));
+
+ lazy_obj_ptr lo1 = create (*db, 1);
+ lo1 = create (*db, 1);
+
+ c1->o = std::move (lo1);
+ c2->o = create (2);
+
+ {
+ transaction t (db->begin ());
+
+ db->persist (*c2->o);
+
+ db->persist (*c1);
+ db->persist (*c2);
+
+ t.commit ();
+ }
+
+ // load
+ //
+ {
+ session s;
+ transaction t (db->begin ());
+ cont_ptr c (db->load<cont> (1));
+ obj* o (db->load<obj> (1));
+
+ // Not loaded.
+ //
+ assert (!c->o.loaded ());
+ assert (!o->c.loaded ());
+
+ // Correct object ids.
+ //
+ assert (c->o.object_id () == o->id);
+ assert (o->c.object_id () == c->id);
+
+ // Load.
+ //
+ cont* cl (o->c.load ());
+ const obj_ptr& ol (c->o.load ());
+
+ assert (cl == c.get ());
+ assert (ol.get () == o);
+
+ t.commit ();
+ }
+
+ // unload/reload
+ //
+ {
+ // No session.
+ transaction t (db->begin ());
+ cont_ptr c (db->load<cont> (1));
+
+ assert (!c->o.loaded ());
+ c->o.load ();
+ assert (c->o.loaded ());
+ c->o.unload ();
+ assert (!c->o.loaded ());
+ c->o.load ();
+ assert (c->o.loaded ());
+
+ t.commit ();
+ }
+ }
+
+ // Shared pointer from C++11 or TR1.
+ //
+ {
+ using namespace test3;
+
+ // persist
+ //
+ shared_ptr<cont> c1 (new cont (1));
+
+ {
+ transaction t (db->begin ());
+ db->persist (c1);
+ t.commit ();
+ }
+
+ lazy_shared_ptr<cont> lc1 (*db, 1);
+ shared_ptr<cont> c2 (new cont (2));
+
+ shared_ptr<obj> o1 (new obj (1));
+ shared_ptr<obj> o2 (new obj (2));
+
+ shared_ptr<obj> o3 (new obj (3));
+ shared_ptr<obj> o4 (new obj (4));
+
+ o1->c = lc1;
+ o2->c = lc1;
+ o3->c = c2;
+ o4->c = c2;
+
+ // Test pointer comparison.
+ //
+ assert (lazy_shared_ptr<cont> () == lazy_shared_ptr<cont> ());
+ assert (lazy_shared_ptr<cont> (c1) != lazy_shared_ptr<cont> ());
+ assert (lc1 != lazy_shared_ptr<cont> ());
+ assert (lazy_shared_ptr<cont> (c1) == lazy_shared_ptr<cont> (c1));
+ assert (lc1 == lazy_shared_ptr<cont> (*db, c1));
+ assert (lc1 != lazy_shared_ptr<cont> (*db, c2));
+
+ // Test move constructors.
+ //
+ {
+ lazy_shared_ptr<cont> tmp (*db, 1);
+ lazy_shared_ptr<cont> l (std::move (tmp));
+ assert (lc1 == l);
+ }
+
+ {
+ shared_ptr<cont> tmp (c1);
+ lazy_shared_ptr<cont> l (*db, std::move (tmp));
+ assert (lc1 == l);
+ }
+
+ {
+ transaction t (db->begin ());
+
+ db->persist (o1);
+ db->persist (o2);
+ db->persist (o3);
+ db->persist (o4);
+
+ db->persist (c2);
+
+ t.commit ();
+ }
+
+ // load
+ //
+ {
+ session s;
+ transaction t (db->begin ());
+ shared_ptr<cont> c (db->load<cont> (1));
+ shared_ptr<obj> o (db->load<obj> (1));
+
+ // Not loaded.
+ //
+ assert (c->o.size () == 2);
+ assert (!c->o[0].loaded ());
+ assert (!c->o[1].loaded ());
+
+ assert (!o->c.loaded ());
+
+ // Correct object ids.
+ //
+ assert (c->o[0].object_id () == o->id);
+ assert (o->c.object_id () == c->id);
+
+ // Load.
+ //
+ shared_ptr<cont> cl (o->c.load ());
+ shared_ptr<obj> ol (c->o[0].load ());
+
+ assert (cl == c);
+ assert (ol == o);
+
+ t.commit ();
+ }
+
+ // Test lazy weak locking and reloading.
+ //
+ {
+ // No session.
+ transaction t (db->begin ());
+ shared_ptr<cont> c (db->load<cont> (1));
+
+ // Lock.
+ //
+ assert (!c->o[1].loaded ());
+ lazy_shared_ptr<obj> l (c->o[1].lock ());
+ assert (!l.loaded ());
+ assert (l.object_id () == c->o[1].object_id ());
+
+ // Reload.
+ //
+ assert (!c->o[1].loaded ());
+ shared_ptr<obj> ol (c->o[1].load ());
+ assert (c->o[1].loaded ());
+ ol.reset ();
+ assert (!c->o[1].loaded ());
+ ol = c->o[1].load ();
+ assert (c->o[1].loaded ());
+
+ t.commit ();
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/lazy-ptr/test.hxx b/odb-tests/common/lazy-ptr/test.hxx
new file mode 100644
index 0000000..f946029
--- /dev/null
+++ b/odb-tests/common/lazy-ptr/test.hxx
@@ -0,0 +1,147 @@
+// file : common/lazy-ptr/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <vector>
+#include <string>
+#include <memory>
+
+#include <odb/core.hxx>
+#include <odb/lazy-ptr.hxx>
+
+// Raw pointer.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ using odb::lazy_ptr;
+ class obj;
+
+ #pragma db object
+ class cont
+ {
+ public:
+ cont () {}
+ cont (unsigned long i): id (i) {}
+ ~cont ();
+
+ #pragma db id
+ unsigned long id;
+
+ typedef std::vector<lazy_ptr<obj> > obj_list;
+
+ #pragma db value_not_null
+ obj_list o;
+ };
+
+ #pragma db object
+ class obj
+ {
+ public:
+ obj () {}
+ obj (unsigned long i): id (i) {}
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db inverse(o) not_null
+ lazy_ptr<cont> c; // weak
+ };
+
+ inline cont::
+ ~cont ()
+ {
+ for (obj_list::iterator i (o.begin ()); i != o.end (); ++i)
+ if (obj* p = i->get ())
+ delete p;
+ }
+}
+
+// std::auto_ptr/std::unique_ptr
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ using odb::lazy_ptr;
+
+ class obj;
+ class cont;
+
+ typedef std::unique_ptr<obj> obj_ptr;
+ typedef std::unique_ptr<cont> cont_ptr;
+ typedef odb::lazy_unique_ptr<obj> lazy_obj_ptr;
+
+ #pragma db object
+ class cont
+ {
+ public:
+ cont () = default;
+ cont (unsigned long id);
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db not_null
+ lazy_obj_ptr o;
+ };
+
+ #pragma db object
+ class obj
+ {
+ public:
+ obj () = default;
+ obj (unsigned long i): id (i) {}
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db inverse(o) not_null
+ lazy_ptr<cont> c; // weak
+ };
+}
+
+// shared_ptr
+//
+#pragma db namespace table("t3_")
+namespace test3
+{
+ using std::shared_ptr;
+ using odb::lazy_shared_ptr;
+ using odb::lazy_weak_ptr;
+
+ class obj;
+
+ #pragma db object pointer(shared_ptr<cont>)
+ class cont
+ {
+ public:
+ cont () {}
+ cont (unsigned long i): id (i) {}
+
+ #pragma db id
+ unsigned long id;
+
+ typedef std::vector<lazy_weak_ptr<obj> > obj_list;
+
+ #pragma db inverse(c) value_not_null
+ obj_list o;
+ };
+
+ #pragma db object pointer(shared_ptr<obj>)
+ class obj
+ {
+ public:
+ obj () {}
+ obj (unsigned long i): id (i) {}
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db not_null
+ lazy_shared_ptr<cont> c;
+ };
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/lazy-ptr/testscript b/odb-tests/common/lazy-ptr/testscript
new file mode 100644
index 0000000..736fa4c
--- /dev/null
+++ b/odb-tests/common/lazy-ptr/testscript
@@ -0,0 +1,33 @@
+# file : common/lazy-ptr/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/lifecycle/buildfile b/odb-tests/common/lifecycle/buildfile
new file mode 100644
index 0000000..b5b2b00
--- /dev/null
+++ b/odb-tests/common/lifecycle/buildfile
@@ -0,0 +1,40 @@
+# file : common/lifecycle/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 lifecycle_ \
+ --generate-schema
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/lifecycle/driver.cxx b/odb-tests/common/lifecycle/driver.cxx
new file mode 100644
index 0000000..a01d5bd
--- /dev/null
+++ b/odb-tests/common/lifecycle/driver.cxx
@@ -0,0 +1,248 @@
+// file : common/lifecycle/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test object state transistions.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ // Database operation out of transaction.
+ //
+ try
+ {
+ object o (1);
+ db->persist (o);
+ assert (false);
+ }
+ catch (const not_in_transaction&)
+ {
+ }
+
+ // Transient.
+ //
+ try
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o (db->load<object> (1));
+ assert (false);
+ t.commit ();
+ }
+ catch (const object_not_persistent&)
+ {
+ }
+
+ // Persistent.
+ //
+ {
+ object o (1);
+ o.str_ = "value 1";
+
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ try
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ assert (false);
+ t.commit ();
+ }
+ catch (const object_already_persistent&)
+ {
+ }
+ }
+
+ // Find.
+ //
+ {
+ transaction t (db->begin ());
+
+ unique_ptr<object> o1 (db->find<object> (1));
+ assert (o1.get () != 0 && o1->str_ == "value 1");
+
+ unique_ptr<object> o2 (db->find<object> (2));
+ assert (o2.get () == 0);
+
+ t.commit ();
+ }
+
+ // Find (into existing).
+ //
+ {
+ object o;
+
+ transaction t (db->begin ());
+
+ assert (db->find (1, o));
+ assert (o.str_ == "value 1");
+
+ assert (!db->find (2, o));
+
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o (db->load<object> (1));
+ assert (o->str_ == "value 1");
+ t.commit ();
+
+ try
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o (db->load<object> (2));
+ assert (false);
+ t.commit ();
+ }
+ catch (const object_not_persistent&)
+ {
+ }
+ }
+
+ // Load (into existing).
+ //
+ {
+ object o;
+
+ transaction t (db->begin ());
+ db->load (1, o);
+ assert (o.str_ == "value 1");
+ t.commit ();
+
+ try
+ {
+ transaction t (db->begin ());
+ db->load (2, o);
+ assert (false);
+ t.commit ();
+ }
+ catch (const object_not_persistent&)
+ {
+ }
+ }
+
+ // Reload.
+ //
+ {
+ object o;
+
+ transaction t (db->begin ());
+ db->load (1, o);
+ o.str_ = "junk";
+ db->reload (o);
+ assert (o.str_ == "value 1");
+ t.commit ();
+
+ try
+ {
+ transaction t (db->begin ());
+ o.id_ = 2;
+ db->reload (o);
+ assert (false);
+ t.commit ();
+ }
+ catch (const object_not_persistent&)
+ {
+ }
+ }
+
+ // Modified.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o (db->load<object> (1));
+ o->str_ = "value 2";
+ db->update (*o);
+ t.commit ();
+
+ try
+ {
+ transaction t (db->begin ());
+ o->id_ = 2;
+ db->update (*o);
+ assert (false);
+ t.commit ();
+ }
+ catch (const object_not_persistent&)
+ {
+ }
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o (db->load<object> (1));
+ assert (o->str_ == "value 2");
+ t.commit ();
+ }
+
+ // Update of unmodified object.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o (db->load<object> (1));
+ db->update (*o);
+ t.commit ();
+ }
+
+ // Transient.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o (db->load<object> (1));
+ db->erase (*o);
+ t.commit ();
+
+ try
+ {
+ transaction t (db->begin ());
+ db->erase<object> (1);
+ assert (false);
+ t.commit ();
+ }
+ catch (const object_not_persistent&)
+ {
+ }
+ }
+
+ try
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o (db->load<object> (1));
+ assert (false);
+ t.commit ();
+ }
+ catch (const object_not_persistent&)
+ {
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/lifecycle/test.hxx b/odb-tests/common/lifecycle/test.hxx
new file mode 100644
index 0000000..8d260d2
--- /dev/null
+++ b/odb-tests/common/lifecycle/test.hxx
@@ -0,0 +1,27 @@
+// file : common/lifecycle/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <odb/core.hxx>
+
+#pragma db object
+struct object
+{
+ object (unsigned long id)
+ : id_ (id)
+ {
+ }
+
+ object ()
+ {
+ }
+
+ #pragma db id
+ unsigned long id_;
+ std::string str_;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/lifecycle/testscript b/odb-tests/common/lifecycle/testscript
new file mode 100644
index 0000000..0337bba
--- /dev/null
+++ b/odb-tests/common/lifecycle/testscript
@@ -0,0 +1,33 @@
+# file : common/lifecycle/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/no-id/buildfile b/odb-tests/common/no-id/buildfile
new file mode 100644
index 0000000..1a64401
--- /dev/null
+++ b/odb-tests/common/no-id/buildfile
@@ -0,0 +1,41 @@
+# file : common/no-id/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 no_id_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/no-id/driver.cxx b/odb-tests/common/no-id/driver.cxx
new file mode 100644
index 0000000..eee69a5
--- /dev/null
+++ b/odb-tests/common/no-id/driver.cxx
@@ -0,0 +1,102 @@
+// file : common/no-id/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test persistent classes without id.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ object o1 (1, "aaa");
+ object o2 (2, "bbb");
+ object o3 (3, "ccc");
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ db->persist (o2); // Ok, since there is no id.
+ db->persist (o3);
+ t.commit ();
+ }
+
+ // Compile errors.
+ //
+ {
+ //db->load<object> (1);
+ //db->find<object> (1);
+ //db->update (o1);
+ //db->erase<object> (1);
+ }
+
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ // Query.
+ //
+ {
+ transaction t (db->begin ());
+
+ {
+ result r (db->query<object> ());
+ assert (size (r) == 4);
+ }
+
+ {
+ result r (db->query<object> (query::str == "aaa"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->num == 1 && i->str == "aaa");
+ object o;
+ i.load (o);
+ //i.id (); // Compile-time error.
+ assert (o.num == 1 && o.str == "aaa");
+ assert (++i == r.end ());
+ }
+
+ {
+ result r (db->query<object> (query::num < 3));
+ assert (size (r) == 3);
+ }
+
+ t.commit ();
+ }
+
+ // Erase (query).
+ //
+ {
+ transaction t (db->begin ());
+ assert (db->erase_query<object> (query::num == 2) == 2);
+ assert (db->erase_query<object> () == 2);
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/no-id/test.hxx b/odb-tests/common/no-id/test.hxx
new file mode 100644
index 0000000..c5b5c65
--- /dev/null
+++ b/odb-tests/common/no-id/test.hxx
@@ -0,0 +1,21 @@
+// file : common/no-id/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+
+#include <odb/core.hxx>
+
+#pragma db object no_id
+struct object
+{
+ object () {}
+ object (unsigned long n, const std::string& s): num (n), str (s) {}
+
+ unsigned long num;
+ std::string str;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/no-id/testscript b/odb-tests/common/no-id/testscript
new file mode 100644
index 0000000..5ec57ee
--- /dev/null
+++ b/odb-tests/common/no-id/testscript
@@ -0,0 +1,33 @@
+# file : common/no-id/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/object/buildfile b/odb-tests/common/object/buildfile
new file mode 100644
index 0000000..cb56aee
--- /dev/null
+++ b/odb-tests/common/object/buildfile
@@ -0,0 +1,41 @@
+# file : common/object/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 object_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/object/driver.cxx b/odb-tests/common/object/driver.cxx
new file mode 100644
index 0000000..1c29417
--- /dev/null
+++ b/odb-tests/common/object/driver.cxx
@@ -0,0 +1,84 @@
+// file : common/object/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test persistent classes.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ // Test persistent class template instantiation.
+ //
+ {
+ using namespace test1;
+
+ pair_object po;
+ po.second = "abc";
+
+ derived d;
+ d.x = "abc";
+ d.n = 123;
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+ db->persist<pair_object> (po);
+ db->persist (d);
+ t.commit ();
+ }
+
+ // load & check
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<pair_object> po1 (db->load<pair_object> (po.first));
+ unique_ptr<derived> d1 (db->load<derived> (d.id));
+ t.commit ();
+
+ assert (po == *po1);
+
+ assert (d.x == d1->x);
+ assert (d.n == d1->n);
+ }
+
+ // Test the API confusion.
+ //
+ {
+ transaction t (db->begin ());
+ db->update<pair_object> (po);
+ db->reload<pair_object> (po);
+ db->erase<pair_object> (po);
+
+ db->query<pair_object> ();
+ t.commit ();
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/object/test.hxx b/odb-tests/common/object/test.hxx
new file mode 100644
index 0000000..87bac91
--- /dev/null
+++ b/odb-tests/common/object/test.hxx
@@ -0,0 +1,49 @@
+// file : common/object/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <utility> // std::pair
+
+#include <odb/core.hxx>
+
+// Test persistent class template instantiation.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ typedef std::pair<unsigned long, std::string> pair_object;
+ #pragma db object(pair_object)
+ #pragma db member(pair_object::first) id auto
+
+ #pragma db object abstract
+ struct base_data
+ {
+ #pragma db id auto
+ unsigned long id;
+ };
+
+ template <typename T>
+ struct base: base_data
+ {
+ T x;
+ };
+
+ typedef base<std::string> base_derived;
+ #pragma db object(base_derived) abstract
+
+ #pragma db object
+ struct derived: base_derived
+ {
+ int n;
+ };
+
+ // Test instantiation in order to "see" id, etc.
+ //
+ typedef base<int> int_base;
+ #pragma db object(int_base)
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/object/testscript b/odb-tests/common/object/testscript
new file mode 100644
index 0000000..6982409
--- /dev/null
+++ b/odb-tests/common/object/testscript
@@ -0,0 +1,33 @@
+# file : common/object/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/optimistic/buildfile b/odb-tests/common/optimistic/buildfile
new file mode 100644
index 0000000..06af705
--- /dev/null
+++ b/odb-tests/common/optimistic/buildfile
@@ -0,0 +1,41 @@
+# file : common/optimistic/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_optimistic_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/optimistic/driver.cxx b/odb-tests/common/optimistic/driver.cxx
new file mode 100644
index 0000000..6dfec6e
--- /dev/null
+++ b/odb-tests/common/optimistic/driver.cxx
@@ -0,0 +1,300 @@
+// file : common/optimistic/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test optimistic concurrency support.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+unsigned long
+version (const unique_ptr<database>& db, unsigned long id)
+{
+ typedef odb::query<object_version> query;
+ typedef odb::result<object_version> result;
+
+ result r (db->query<object_version> (query::id == id));
+ return r.empty () ? 0 : r.begin ()->ver;
+}
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv));
+
+ object o (1);
+ o.num = 123;
+ o.str = "abc";
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ // Verify initial version in the instance and database.
+ //
+ assert (o.ver == 1);
+ {
+ transaction t (db->begin ());
+ assert (version (db, 1) == 1);
+ t.commit ();
+ }
+
+ object c (o);
+ o.num++;
+ o.str += 'd';
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ // Verify updated version in the instance and database.
+ //
+ assert (o.ver == 2);
+ {
+ transaction t (db->begin ());
+ assert (version (db, 1) == 2);
+ t.commit ();
+ }
+
+ // Verify the data has been updated.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> o1 (db->load<object> (1));
+ t.commit ();
+
+ assert (o1->ver == 2 && o1->num == 124 && o1->str == "abcd");
+ }
+
+ // Try to update using outdated object.
+ //
+ c.num--;
+ c.str += 'z';
+
+ {
+ transaction t (db->begin ());
+
+ try
+ {
+ db->update (c);
+ assert (false);
+ }
+ catch (const object_changed&) {}
+
+ // Verify the data hasn't changed.
+ //
+ unique_ptr<object> o1 (db->load<object> (1));
+ assert (o1->ver == 2 && o1->num == 124 && o1->str == "abcd");
+
+ // Reload the object.
+ //
+ db->reload (c);
+ assert (c.ver == 2 && c.num == 124);
+
+ // Check that we don't reload an object that is up-to-date.
+ //
+ c.num--;
+ db->reload (c);
+ assert (c.ver == 2 && c.num == 123);
+
+ t.commit ();
+ }
+
+ // Try to delete using an outdated object.
+ //
+ {
+ transaction t (db->begin ());
+
+ try
+ {
+ db->update (o);
+ db->erase (c);
+ assert (false);
+ }
+ catch (const object_changed&) {}
+
+ t.commit ();
+ }
+
+ // Try to delete using an up-to-date object.
+ //
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+
+ // Try to update deleted object.
+ //
+ {
+ transaction t (db->begin ());
+
+ try
+ {
+ db->update (o);
+ assert (false);
+ }
+ catch (const object_not_persistent&)
+ {
+ assert (false);
+ }
+ catch (const object_changed&) {}
+
+ t.commit ();
+ }
+
+ // Optimistic delete of objects with container requires
+ // extra logic. Test it here.
+ //
+ {
+ container o ("abc");
+ o.nums.push_back (1);
+ o.nums.push_back (2);
+ o.nums.push_back (3);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ container c (o);
+ o.nums.pop_back ();
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ // Try to delete using an outdated object.
+ //
+ {
+ transaction t (db->begin ());
+
+ try
+ {
+ db->erase (c);
+ assert (false);
+ }
+ catch (const object_changed&) {}
+
+ // Verify the container data hasn't changed.
+ //
+ unique_ptr<container> o1 (db->load<container> ("abc"));
+ assert (o1->nums.size () == 2 && o1->nums[0] == 1 && o1->nums[1] == 2);
+
+ t.commit ();
+ }
+
+ // Try to delete using an up-to-date object.
+ //
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test optimistic class inheritance. This is a shortened version
+ // of the object test.
+ //
+ {
+ derived o;
+ o.num = 123;
+ o.str = "abc";
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ derived c (o);
+ o.num++;
+ o.str += 'd';
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ // Try to update using outdated object.
+ //
+ c.num--;
+ c.str += 'z';
+
+ {
+ transaction t (db->begin ());
+
+ try
+ {
+ db->update (c);
+ assert (false);
+ }
+ catch (const object_changed&) {}
+
+ // Reload the object.
+ //
+ db->reload (c);
+ assert (c.ver == 2 && c.num == 124);
+
+ t.commit ();
+ }
+
+ // Try to delete using an outdated object.
+ //
+ {
+ transaction t (db->begin ());
+
+ try
+ {
+ db->update (o);
+ db->erase (c);
+ assert (false);
+ }
+ catch (const object_changed&) {}
+
+ t.commit ();
+ }
+
+ // Try to delete using an up-to-date object.
+ //
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/optimistic/test.hxx b/odb-tests/common/optimistic/test.hxx
new file mode 100644
index 0000000..fcefa3d
--- /dev/null
+++ b/odb-tests/common/optimistic/test.hxx
@@ -0,0 +1,76 @@
+// file : common/optimistic/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <vector>
+
+#include <odb/core.hxx>
+
+#pragma db object optimistic
+struct object
+{
+ object (): ver (123) {}
+ object (unsigned long id): id_ (id), ver (123) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db version
+ unsigned long ver;
+
+ unsigned int num;
+ std::string str;
+};
+
+#pragma db view object(object)
+struct object_version
+{
+ unsigned long ver;
+};
+
+// Optimistic class with a container.
+//
+#pragma db object optimistic
+struct container
+{
+ container (): ver (123) {}
+ container (const std::string& id): id_ (id), ver (123) {}
+
+ #pragma db id
+ std::string id_;
+
+ #pragma db version
+ unsigned long ver;
+
+ std::vector<unsigned int> nums;
+};
+
+// Optimistic class inheritance.
+//
+#pragma db object abstract optimistic
+struct base
+{
+ base (): ver (123) {}
+
+ #pragma db id auto
+ unsigned long id_;
+
+ #pragma db version
+ const unsigned long ver;
+
+ std::string str;
+
+ #pragma db readonly
+ std::string ro;
+};
+
+#pragma db object
+struct derived: base
+{
+ unsigned int num;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/optimistic/testscript b/odb-tests/common/optimistic/testscript
new file mode 100644
index 0000000..9ebafb2
--- /dev/null
+++ b/odb-tests/common/optimistic/testscript
@@ -0,0 +1,33 @@
+# file : common/optimistic/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/pragma/buildfile b/odb-tests/common/pragma/buildfile
new file mode 100644
index 0000000..a2eeaa0
--- /dev/null
+++ b/odb-tests/common/pragma/buildfile
@@ -0,0 +1,39 @@
+# file : common/pragma/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 pragma_
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/pragma/driver.cxx b/odb-tests/common/pragma/driver.cxx
new file mode 100644
index 0000000..a9cc6e0
--- /dev/null
+++ b/odb-tests/common/pragma/driver.cxx
@@ -0,0 +1,27 @@
+// file : common/pragma/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test #pragma db parsing.
+//
+
+#include <memory>
+#include <iostream>
+
+#include <odb/exceptions.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main ()
+{
+}
diff --git a/odb-tests/common/pragma/test.hxx b/odb-tests/common/pragma/test.hxx
new file mode 100644
index 0000000..6877e73
--- /dev/null
+++ b/odb-tests/common/pragma/test.hxx
@@ -0,0 +1,40 @@
+// file : common/template/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <odb/core.hxx>
+
+#pragma db value(bool) type ("INTEGER")
+
+struct x {int i;};
+#pragma db value (x)
+
+namespace N
+{
+ #pragma db object
+ struct object1
+ {
+ object1 () {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db member type ("INTEGER")
+ bool b_;
+ };
+
+ struct object2
+ {
+ object2 () {}
+
+ unsigned long id_;
+ };
+
+ #pragma db object (object2)
+}
+
+PRAGMA_DB (member (N::object2::id_) id auto);
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/pragma/testscript b/odb-tests/common/pragma/testscript
new file mode 100644
index 0000000..089f7a1
--- /dev/null
+++ b/odb-tests/common/pragma/testscript
@@ -0,0 +1,31 @@
+# file : common/include/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $* &!odb-test.db
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $*
+}
diff --git a/odb-tests/common/prepared/buildfile b/odb-tests/common/prepared/buildfile
new file mode 100644
index 0000000..4006a4f
--- /dev/null
+++ b/odb-tests/common/prepared/buildfile
@@ -0,0 +1,43 @@
+# file : common/prepared/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 prepared_ \
+ --generate-schema \
+ --generate-query \
+ --generate-prepared \
+ --omit-unprepared
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/prepared/driver.cxx b/odb-tests/common/prepared/driver.cxx
new file mode 100644
index 0000000..44df651
--- /dev/null
+++ b/odb-tests/common/prepared/driver.cxx
@@ -0,0 +1,444 @@
+// file : common/prepared/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test prepared query functionality.
+//
+
+#include <memory> // std::unique_ptr
+#include <utility> // std::move
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+struct params
+{
+ unsigned short age;
+ std::string name;
+};
+
+static void
+query_factory (const char* name, connection& c)
+{
+ typedef odb::query<person> query;
+
+ unique_ptr<params> p (new params);
+ prepared_query<person> pq (
+ c.prepare_query<person> (
+ name,
+ query::age > query::_ref (p->age) &&
+ query::name != query::_ref (p->name)));
+ c.cache_query (pq, move (p));
+}
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv));
+
+ {
+ person p1 ("John First", 91);
+ person p2 ("John Second", 81);
+ person p3 ("John Third", 71);
+ person p4 ("John Fourth", 61);
+ person p5 ("John Fifth", 51);
+
+ transaction t (db->begin ());
+ db->persist (p1);
+ db->persist (p2);
+ db->persist (p3);
+ db->persist (p4);
+ db->persist (p5);
+ t.commit ();
+ }
+
+ typedef odb::query<person> query;
+ typedef odb::prepared_query<person> prep_query;
+ typedef odb::result<person> result;
+
+ // Uncached query in the same transaction.
+ //
+ {
+ transaction t (db->begin ());
+
+ unsigned short age (90);
+ prep_query pq (
+ db->prepare_query<person> (
+ "person-age-query",
+ query::age > query::_ref (age)));
+
+ for (unsigned short i (1); i < 6; ++i, age -= 10)
+ {
+ result r (pq.execute ());
+ assert (size (r) == i);
+ }
+
+ age = 90;
+ result r (pq.execute ());
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->name_ == "John First" && i->age_ == 91);
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+
+ // Uncached query in multiple transaction.
+ //
+ {
+ connection_ptr c (db->connection ());
+
+ unsigned short age (90);
+ prep_query pq (
+ c->prepare_query<person> (
+ "person-age-query",
+ query::age > query::_ref (age)));
+
+ for (unsigned short i (1); i < 6; ++i, age -= 10)
+ {
+ transaction t (c->begin ());
+
+ result r (pq.execute ());
+ assert (size (r) == i);
+
+ t.commit ();
+ }
+
+ transaction t (c->begin ());
+
+ age = 90;
+ result r (pq.execute ());
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->name_ == "John First" && i->age_ == 91);
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+
+ // Cached query without parameters.
+ //
+ {
+ for (unsigned short i (1); i < 6; ++i)
+ {
+ transaction t (db->begin ());
+
+ prep_query pq (db->lookup_query<person> ("person-val-age-query"));
+
+ if (!pq)
+ {
+ assert (i == 1);
+ pq = db->prepare_query<person> (
+ "person-val-age-query",
+ query::age > 90);
+ db->cache_query (pq);
+ }
+ else if (i == 2)
+ {
+ try
+ {
+ db->cache_query (pq);
+ assert (false);
+ }
+ catch (const odb::prepared_already_cached&)
+ {
+ }
+ }
+
+ result r (pq.execute ());
+ assert (size (r) == 1);
+
+ t.commit ();
+ }
+ }
+
+ // Cached query with parameters.
+ //
+ {
+ for (unsigned short i (1); i < 6; ++i)
+ {
+ transaction t (db->begin ());
+
+ unsigned short* age;
+ prep_query pq (db->lookup_query<person> ("person-ref-age-query", age));
+
+ if (!pq)
+ {
+ assert (i == 1);
+
+ unique_ptr<unsigned short> p (new unsigned short);
+ age = p.get ();
+ pq = db->prepare_query<person> (
+ "person-ref-age-query",
+ query::age > query::_ref (*age));
+
+ db->cache_query (pq, move (p));
+ }
+ else if (i == 2)
+ {
+ // Object type mismatch.
+ //
+ try
+ {
+ db->lookup_query<int> ("person-ref-age-query", age);
+ assert (false);
+ }
+ catch (const odb::prepared_type_mismatch&)
+ {
+ }
+
+ // Parameters type mismatch.
+ //
+ try
+ {
+ int* age;
+ db->lookup_query<person> ("person-ref-age-query", age);
+ assert (false);
+ }
+ catch (const odb::prepared_type_mismatch&)
+ {
+ }
+ }
+
+ *age = 100 - i * 10;
+ result r (pq.execute ());
+ assert (size (r) == i);
+
+ t.commit ();
+ }
+ }
+
+ // Cached query with factory.
+ //
+ {
+ db->query_factory ("person-params-query", &query_factory);
+
+ for (unsigned int i (1); i < 6; ++i)
+ {
+ transaction t (db->begin ());
+
+ params* p;
+ prep_query pq (db->lookup_query<person> ("person-params-query", p));
+ assert (pq);
+
+ p->age = 100 - i * 10;
+ p->name = "John First";
+ result r (pq.execute ());
+ assert (size (r) == i - 1);
+
+ t.commit ();
+ }
+
+ db->query_factory ("person-params-query",
+ database::query_factory_ptr ());
+ }
+
+ // Cached query with wildcard factory.
+ //
+ {
+ db->query_factory ("", &query_factory);
+
+ for (unsigned int i (1); i < 6; ++i)
+ {
+ transaction t (db->begin ());
+
+ params* p;
+ prep_query pq (db->lookup_query<person> ("person-params-query-1", p));
+ assert (pq);
+
+ p->age = 100 - i * 10;
+ p->name = "John First";
+ result r (pq.execute ());
+ assert (size (r) == i - 1);
+
+ t.commit ();
+ }
+
+ db->query_factory ("", database::query_factory_ptr ());
+ }
+
+ // Cached query with lambda factory.
+ //
+ {
+ db->query_factory (
+ "person-params-query-2",
+ [] (const char* name, connection& c)
+ {
+ typedef odb::query<person> query;
+
+ unique_ptr<params> p (new params);
+ prepared_query<person> pq (
+ c.prepare_query<person> (
+ name,
+ query::age > query::_ref (p->age) &&
+ query::name != query::_ref (p->name)));
+ c.cache_query (pq, move (p));
+ });
+
+ for (unsigned int i (1); i < 6; ++i)
+ {
+ transaction t (db->begin ());
+
+ params* p;
+ prep_query pq (db->lookup_query<person> ("person-params-query-2", p));
+ assert (pq);
+
+ p->age = 100 - i * 10;
+ p->name = "John First";
+ result r (pq.execute ());
+ assert (size (r) == i - 1);
+
+ t.commit ();
+ }
+
+ db->query_factory ("person-params-query-2",
+ database::query_factory_ptr ());
+ }
+
+ // Cached query with lambda factory using closure. Forces nonoptimized
+ // representation of std::function.
+ //
+ {
+ const std::string person_name ("John First");
+
+ db->query_factory (
+ "person-params-query-3",
+ [person_name] (const char* name, connection& c)
+ {
+ typedef odb::query<person> query;
+
+ prepared_query<person> pq (
+ c.prepare_query<person> (
+ name,
+ query::age > 50 && query::name != person_name));
+ c.cache_query (pq);
+ });
+
+ {
+ transaction t (db->begin ());
+
+ prep_query pq (db->lookup_query<person> ("person-params-query-3"));
+ assert (pq);
+
+ result r (pq.execute ());
+ assert (size (r) == 4);
+
+ t.commit ();
+ }
+
+ db->query_factory ("person-params-query-3", nullptr);
+ }
+
+ // View prepared query.
+ //
+ {
+ typedef odb::query<person_view> query;
+ typedef odb::prepared_query<person_view> prep_query;
+ typedef odb::result<person_view> result;
+
+ transaction t (db->begin ());
+
+ unsigned short age (90);
+ prep_query pq (
+ db->prepare_query<person_view> (
+ "person-view-age-query",
+ query::age > query::_ref (age)));
+
+ for (unsigned short i (1); i < 6; ++i, age -= 10)
+ {
+ result r (pq.execute ());
+ assert (size (r) == i);
+ }
+
+ age = 90;
+ result r (pq.execute ());
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->name == "John First" && i->age == 91);
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+
+ // By-ref parameter image growth.
+ //
+ {
+ transaction t (db->begin ());
+
+ string name;
+ prep_query pq (
+ db->prepare_query<person> (
+ "person-name-query",
+ query::name != query::_ref (name)));
+
+ {
+ name = "John First";
+ result r (pq.execute ());
+ assert (size (r) == 4);
+ }
+
+ {
+ name.assign (2048, 'x');
+ result r (pq.execute ());
+ assert (size (r) == 5);
+ }
+
+ t.commit ();
+ }
+
+ // Test execute_one() and execute_value().
+ //
+ {
+ transaction t (db->begin ());
+
+ person p ("John Doe", 23);
+ db->persist (p);
+
+ prep_query pq1 (
+ db->prepare_query<person> ("query-1", query::id == p.id_));
+ prep_query pq0 (
+ db->prepare_query<person> ("query-0", query::id == p.id_ + 1));
+
+ {
+ unique_ptr<person> p (pq1.execute_one ());
+ assert (p.get () != 0 && p->name_ == "John Doe");
+ }
+
+ {
+ unique_ptr<person> p (pq0.execute_one ());
+ assert (p.get () == 0);
+ }
+
+ {
+ person p;
+ assert (pq1.execute_one (p) && p.name_ == "John Doe");
+ }
+
+ {
+ person p ("", 0);
+ assert (!pq0.execute_one (p) &&
+ p.id_ == 0 && p.name_.empty () && p.age_ == 0);
+ }
+
+ {
+ person p (pq1.execute_value ());
+ assert (p.name_ == "John Doe");
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/prepared/test.hxx b/odb-tests/common/prepared/test.hxx
new file mode 100644
index 0000000..db16e67
--- /dev/null
+++ b/odb-tests/common/prepared/test.hxx
@@ -0,0 +1,32 @@
+// file : common/prepared/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+
+#include <odb/core.hxx>
+
+#pragma db object
+struct person
+{
+ person (): id_ (0) {}
+ person (const std::string& name, unsigned short age)
+ : id_ (0), name_ (name), age_ (age) {}
+
+ #pragma db id auto
+ unsigned long id_;
+
+ std::string name_;
+ unsigned short age_;
+};
+
+#pragma db view object(person)
+struct person_view
+{
+ std::string name;
+ unsigned short age;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/prepared/testscript b/odb-tests/common/prepared/testscript
new file mode 100644
index 0000000..3530c5b
--- /dev/null
+++ b/odb-tests/common/prepared/testscript
@@ -0,0 +1,33 @@
+# file : common/prepared/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/query/array/buildfile b/odb-tests/common/query/array/buildfile
new file mode 100644
index 0000000..3beb6d0
--- /dev/null
+++ b/odb-tests/common/query/array/buildfile
@@ -0,0 +1,43 @@
+# file : common/query/array/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_query_array_ \
+ --generate-schema \
+ --generate-query \
+ --generate-prepared \
+ --sql-name-case oracle:upper
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/query/array/driver.cxx b/odb-tests/common/query/array/driver.cxx
new file mode 100644
index 0000000..9327751
--- /dev/null
+++ b/odb-tests/common/query/array/driver.cxx
@@ -0,0 +1,220 @@
+// file : common/query/array/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test query support for C arrays.
+//
+
+#include <string>
+#include <memory> // std::unique_ptr
+#include <cstring> // std::memcpy
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_*
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+#ifndef MULTI_DATABASE
+# if defined(DATABASE_MYSQL)
+const odb::mysql::database_type_id bt = odb::mysql::id_blob;
+# elif defined(DATABASE_SQLITE)
+const odb::sqlite::database_type_id bt = odb::sqlite::id_blob;
+# elif defined(DATABASE_PGSQL)
+const odb::pgsql::database_type_id bt = odb::pgsql::id_bytea;
+# elif defined(DATABASE_ORACLE)
+const odb::oracle::database_type_id bt = odb::oracle::id_raw;
+# elif defined(DATABASE_MSSQL)
+const odb::mssql::database_type_id bt = odb::mssql::id_binary;
+# else
+# error unknown database
+# endif
+#endif
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv));
+
+ typedef odb::query<object> query;
+
+ const char buf[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6};
+
+ //
+ //
+ {
+ object o1 (1, "abc", buf);
+ object o2 (2, "bcd", buf);
+ object o3 (3, "cde", buf);
+
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ db->persist (o3);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ // string
+ //
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> (query::s == "abc")) == 1);
+ assert (size (db->query<object> (query::s == query::_val ("bcd"))) == 1);
+ assert (size (db->query<object> ("s = " + query::_val ("bcd"))) == 1);
+ assert (size (db->query<object> ("s = " + query::_ref ("bcd"))) == 1);
+#endif
+
+ {
+ char a[] = "bcd";
+ char* ra = a;
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> (query::s == a)) == 1);
+ assert (size (db->query<object> (query::s == query::_val (a))) == 1);
+#endif
+ assert (size (db->query<object> (query::s == query::_ref (ra))) == 1);
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> ("s = " + query::_val (a))) == 1);
+ assert (size (db->query<object> ("s = " + query::_ref (a))) == 1);
+#endif
+ }
+
+ {
+ const char a[] = "bcd";
+ const char* ra = a;
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> (query::s == a)) == 1);
+ assert (size (db->query<object> (query::s == query::_val (a))) == 1);
+#endif
+ assert (size (db->query<object> (query::s == query::_ref (ra))) == 1);
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> ("s = " + query::_val (a))) == 1);
+ assert (size (db->query<object> ("s = " + query::_ref (a))) == 1);
+#endif
+ }
+
+ {
+ const char* p = "cde";
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> (query::s == p)) == 1);
+ assert (size (db->query<object> (query::s == query::_val (p))) == 1);
+#endif
+ assert (size (db->query<object> (query::s == query::_ref (p))) == 1);
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> ("s = " + query::_val (p))) == 1);
+ assert (size (db->query<object> ("s = " + query::_ref (p))) == 1);
+#endif
+ }
+
+ {
+ char a[] = "cde";
+ char* p = a;
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> (query::s == p)) == 1);
+ assert (size (db->query<object> (query::s == query::_val (p))) == 1);
+#endif
+ assert (size (db->query<object> (query::s == query::_ref (p))) == 1);
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> ("s = " + query::_val (p))) == 1);
+ assert (size (db->query<object> ("s = " + query::_ref (p))) == 1);
+#endif
+ }
+
+#ifndef MULTI_DATABASE
+ string s ("abc");
+ //assert (size (db->query<object> (query::s == s)) == 1);
+ assert (size (db->query<object> (query::s == s.c_str ())) == 1);
+ //assert (size (db->query<object> (query::s == query::_val (s))) == 1);
+ assert (size (db->query<object> (query::s == query::_val (s.c_str ()))) == 1);
+
+ assert (size (db->query<object> ("s = " + query::_val (s))) == 1);
+ assert (size (db->query<object> ("s = " + query::_ref (s))) == 1);
+#endif
+
+ // @@ BUILD2 Ends up with the following warning, but strangely only in the
+ // multi-database mode:
+ //
+ // In file included from odb/odb-tests/common/query/array/test-odb.hxx:31,
+ // from odb/odb-tests/common/query/array/driver.cxx:20:
+ // odb/libodb/odb/query-dynamic.hxx: In instantiation of ‘odb::query_base odb::query_column<T>::operator==(const odb::query_column<T2>&) const [with T2 = char [17]; T = char [17]]’:
+ // odb/odb-tests/common/query/array/driver.cxx:144:7: required from here
+ // odb/libodb/odb/query-dynamic.hxx:895:43: error: comparison between two arrays is deprecated in C++20 [-Werror=array-compare]
+ // 895 | (void) (sizeof (type_instance<T> () == type_instance<T2> ()));
+ // | ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
+ // odb/libodb/odb/query-dynamic.hxx:895:43: note: use unary ‘+’ which decays operands to pointers or ‘&‘indirect_ref’ not supported by dump_decl<declaration error>[0] == &‘indirect_ref’ not supported by dump_decl<declaration error>[0]’ to compare the addresses
+ //
+ // Looks like compile-time assertion. Doesn't make much sense for
+ // arrays since compares pointers to objects rather than objects.
+ // Should we somehow suppress the assertion for arrays or similar?
+ //
+ // Note: temporarily ifndef-ed.
+ //
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> (query::s == query::s1)) == 3);
+#endif
+
+ // std::array
+ //
+ array<char, 17> a;
+ memcpy (a.data (), "abc", 4); // VC++ strcpy deprecation.
+
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> (query::a == a)) == 1);
+ assert (size (db->query<object> (query::a == query::_val (a))) == 1);
+#endif
+ assert (size (db->query<object> (query::a == query::_ref (a))) == 1);
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> ("a = " + query::_val (a))) == 1);
+ assert (size (db->query<object> ("a = " + query::_ref (a))) == 1);
+#endif
+
+ // char
+ //
+ assert (size (db->query<object> (query::c == 'a')) == 1);
+
+ char c ('b');
+ assert (size (db->query<object> (query::c == query::_val (c))) == 1);
+ assert (size (db->query<object> (query::c == query::_ref (c))) == 1);
+
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> ("c = " + query::_val ('c'))) == 1);
+ assert (size (db->query<object> ("c = " + query::_ref (c))) == 1);
+#endif
+
+ assert (size (db->query<object> (query::c == query::c1)) == 3);
+
+ // buffer
+ //
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> (query::b == buf)) == 3);
+ assert (size (db->query<object> (query::b == query::_val (buf))) == 3);
+#endif
+
+ assert (size (db->query<object> (query::b == query::_ref (buf))) == 3);
+
+#ifndef MULTI_DATABASE
+ assert (size (db->query<object> ("b = " + query::_val<bt> (buf))) == 3);
+ assert (size (db->query<object> ("b = " + query::_ref<bt> (buf))) == 3);
+#endif
+
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/query/array/test.hxx b/odb-tests/common/query/array/test.hxx
new file mode 100644
index 0000000..f0d5f3b
--- /dev/null
+++ b/odb-tests/common/query/array/test.hxx
@@ -0,0 +1,70 @@
+// file : common/query/array/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <array>
+#include <cstring> // std::memcpy, std::strlen
+
+#include <odb/core.hxx>
+
+#pragma db object
+struct object
+{
+ object () {}
+ object (unsigned long id, const char* s, const char* b)
+ : id_ (id)
+ {
+ std::memcpy (s_, s, std::strlen (s) + 1); // VC++ strncpy deprecation.
+ std::memcpy (s1_, s, std::strlen (s) + 1);
+ std::memcpy (a_.data (), s, std::strlen (s) + 1);
+ c_ = c1_ = *s;
+ std::memcpy (b_, b, sizeof (b_));
+ }
+
+ #pragma db id
+ unsigned long id_;
+
+ char s_[17];
+ char s1_[17];
+
+#ifdef ODB_COMPILER
+# if defined(ODB_DATABASE_MYSQL) || \
+ defined(ODB_DATABASE_PGSQL) || \
+ defined(ODB_DATABASE_ORACLE) || \
+ defined(ODB_DATABASE_MSSQL)
+# pragma db type("VARCHAR(16)")
+# elif defined(ODB_DATABASE_SQLITE)
+# pragma db type("TEXT")
+# elif defined(ODB_DATABASE_COMMON)
+# pragma db type("DYMMU") // Necessary to make it a value.
+# else
+# error unknown database
+# endif
+#endif
+ std::array<char, 17> a_;
+
+ char c_;
+ char c1_;
+
+#ifdef ODB_COMPILER
+# if defined(ODB_DATABASE_MYSQL)
+# pragma db type("BINARY(16)")
+# elif defined(ODB_DATABASE_SQLITE)
+# pragma db type("BLOB")
+# elif defined(ODB_DATABASE_PGSQL)
+# pragma db type("BYTEA")
+# elif defined(ODB_DATABASE_ORACLE)
+# pragma db type("RAW(16)")
+# elif defined(ODB_DATABASE_MSSQL)
+# pragma db type("BINARY(16)")
+# elif defined(ODB_DATABASE_COMMON)
+# else
+# error unknown database
+# endif
+#endif
+ char b_[16];
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/query/array/testscript b/odb-tests/common/query/array/testscript
new file mode 100644
index 0000000..631ae24
--- /dev/null
+++ b/odb-tests/common/query/array/testscript
@@ -0,0 +1,33 @@
+# file : common/query/array/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/query/basics/buildfile b/odb-tests/common/query/basics/buildfile
new file mode 100644
index 0000000..e38e6fe
--- /dev/null
+++ b/odb-tests/common/query/basics/buildfile
@@ -0,0 +1,42 @@
+# file : common/query/basics/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_query_basics_ \
+ --generate-schema \
+ --generate-query \
+ --generate-prepared
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/query/basics/driver.cxx b/odb-tests/common/query/basics/driver.cxx
new file mode 100644
index 0000000..73b81d2
--- /dev/null
+++ b/odb-tests/common/query/basics/driver.cxx
@@ -0,0 +1,668 @@
+// file : common/query/basics/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test basic query support.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_XXX
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+void
+print (result<person>& r)
+{
+ for (result<person>::iterator i (r.begin ()); i != r.end (); ++i)
+ {
+ unique_ptr<person> o (i.load ());
+ cout << *o << endl;
+ }
+ cout << endl;
+}
+
+const char* names[] = { "John", "Jane", "Joe" };
+const char** names_end = names + sizeof (names)/sizeof (names[0]);
+
+const char* key_data[] = { "\x23\x03\x15", "\x13\x13\x54", "\x08\x62\x35" };
+
+int
+main (int argc, char* argv[])
+{
+ buffer
+ key1 (key_data[0], key_data[0] + 3),
+ key2 (key_data[1], key_data[1] + 3),
+ key3 (key_data[2], key_data[2] + 3);
+
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv));
+ odb::database_id db_id (db->id ());
+
+ typedef odb::query<person> query;
+ typedef odb::result<person> result;
+
+ //
+ //
+ {
+ person p1 (1, "John", "Doe", 30, true, key1);
+ person p2 (2, "Jane", "Doe", 29, true, key2);
+ person p3 (3, "Joe", "Dirt", 31, false, key3);
+ p3.middle_name_.reset (new string ("Squeaky"));
+ person p4 (4, "Johansen", "Johansen", 32, false);
+ p4.middle_name_.reset (new string ("J"));
+
+ transaction t (db->begin ());
+ db->persist (p1);
+ db->persist (p2);
+ db->persist (p3);
+ db->persist (p4);
+ t.commit ();
+ }
+
+ //
+ // Native queries.
+ //
+
+ // Compilation tests.
+ //
+#ifndef MULTI_DATABASE
+ if (false)
+ {
+ string name;
+ unsigned short age;
+
+ db->query<person> ("age = " + query::_ref (age));
+ db->query<person> ("age = " + query::_val (age));
+
+ query age_q (query::_ref (age) + " = age");
+ query name_q ("first = " + query::_val (name));
+ query q (age_q + "AND" + name_q);
+
+ db->query<person> (q);
+ db->query<person> (age_q + "OR" +
+ name_q + "OR" +
+ "age < " + query::_ref (age));
+
+ query q1 (query::_val (name));
+ q1 += " = first";
+ }
+#endif
+
+ // Select-all query.
+ //
+ cout << "test 001" << endl;
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> ());
+
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ {
+ person p;
+ i.load (p);
+ cout << p << endl;
+ }
+
+ t.commit ();
+ }
+
+ // Select-all query with order by.
+ //
+ cout << "test 002" << endl;
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> ("ORDER BY" + query::age));
+
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ {
+ person& p (*i);
+
+ cout << p.first_name_;
+
+ if (i->middle_name_.get () != 0)
+ cout << ' ' << *i->middle_name_;
+
+ cout << ' ' << i->last_name_ << ' ' << i->age_ <<
+ (i->married_ ? " married" : " single") << endl;
+ }
+ cout << endl;
+
+ t.commit ();
+ }
+
+ // String query.
+ //
+ cout << "test 003" << endl;
+ {
+ transaction t (db->begin ());
+
+ result r;
+ if (db_id != odb::id_oracle)
+ r = db->query<person> ("age >= 30 AND last = 'Doe'");
+ else
+ r = db->query<person> ("\"age\" >= 30 AND \"last\" = 'Doe'");
+
+ print (r);
+ t.commit ();
+ }
+
+ // Value binding.
+ //
+ cout << "test 004" << endl;
+ {
+ transaction t (db->begin ());
+
+ const char* name = "Doe";
+
+#if defined(MULTI_DATABASE)
+ result r (
+ db->query<person> (
+ query::age >= query::_val (30) &&
+ query::last_name == query::_val (name)));
+
+#elif defined(DATABASE_ORACLE)
+ result r (
+ db->query<person> (
+ "\"age\" >= " + query::_val (30) + "AND" +
+ "\"last\" = " + query::_val (name)));
+#else
+ result r (
+ db->query<person> (
+ "age >= " + query::_val (30) + "AND" +
+ "last = " + query::_val (name)));
+#endif
+
+ print (r);
+ t.commit ();
+ }
+
+ // Reference binding.
+ //
+ cout << "test 005" << endl;
+ {
+ transaction t (db->begin ());
+
+ string name;
+ unsigned short age;
+
+#if defined(MULTI_DATABASE)
+ query q (query::age >= query::_ref (age) &&
+ query::last_name == query::_ref (name));
+#elif defined(DATABASE_ORACLE)
+ query q ("\"age\" >= " + query::_ref (age) + "AND" +
+ "\"last\" = " + query::_ref (name));
+#else
+ query q ("age >= " + query::_ref (age) + "AND" +
+ "last = " + query::_ref (name));
+#endif
+
+ name = "Doe";
+ age = 30;
+ result r (db->query<person> (q));
+ print (r);
+
+ name = "Dirt";
+ age = 31;
+ r = db->query<person> (q);
+ print (r);
+
+ t.commit ();
+ }
+
+ //
+ // Language-embedded queries.
+ //
+
+ // Compilation tests.
+ //
+ if (false)
+ {
+ string name;
+ unsigned short age;
+
+ // Column operators.
+ //
+ query q1 (query::married);
+ db->query<person> (query::married);
+ db->query<person> (query::married == true);
+
+ //db->query<person> (query::age);
+
+ db->query<person> (query::age == 30);
+ db->query<person> (query::age == age);
+ db->query<person> (query::age == query::_val (30));
+ db->query<person> (query::age == query::_val (age));
+ db->query<person> (query::age == query::_ref (age));
+ //db->query<person> (query::age == "123");
+ //db->query<person> ("123" == query::age);
+ //db->query<person> (query::age == query::_val ("123"));
+ //db->query<person> (query::age == query::_ref (name));
+ db->query<person> (query::last_name == "Doe");
+ db->query<person> (query::last_name == name);
+#ifndef MULTI_DATABASE
+ db->query<person> (query::last_name == query::_val ("Doe"));
+#endif
+ db->query<person> (query::last_name == query::_val (name));
+ db->query<person> (query::last_name == query::_ref (name));
+ //db->query<person> (query::last_name == 30);
+ //db->query<person> (query::last_name == query::_val (30));
+ //db->query<person> (query::last_name == query::_ref (age));
+
+ db->query<person> (query::last_name.is_null ());
+ db->query<person> (query::last_name.is_not_null ());
+
+ db->query<person> (query::first_name == query::last_name);
+
+ db->query<person> (query::first_name.in ("John", "Jane"));
+ db->query<person> (query::first_name.in_range (names, names_end));
+
+ db->query<person> (query::first_name.like ("J%"));
+ db->query<person> (query::first_name.like ("J%!%", "!"));
+
+ // Query operators.
+ //
+ db->query<person> (query::age == 30 && query::last_name == "Doe");
+ db->query<person> (query::age == 30 || query::last_name == "Doe");
+ db->query<person> (!(query::age == 30 || query::last_name == "Doe"));
+ db->query<person> ((query::last_name == "Doe") + "ORDER BY age");
+ }
+
+ // Test is_null/is_not_null.
+ //
+ cout << "test 006" << endl;
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> (query::middle_name.is_null ()));
+ print (r);
+ r = db->query<person> (query::middle_name.is_not_null ());
+ print (r);
+ t.commit ();
+ }
+
+ // Test boolean columns.
+ //
+ cout << "test 007" << endl;
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> (query::married));
+ print (r);
+ r = db->query<person> (!query::married);
+ print (r);
+ t.commit ();
+ }
+
+ // Test implicit by-value, explicit by-value, and by-reference.
+ //
+ cout << "test 008" << endl;
+ {
+ string name ("Dirt");
+
+ transaction t (db->begin ());
+ result r (db->query<person> (query::last_name == "Doe"));
+ print (r);
+ r = db->query<person> (query::last_name == query::_val (name));
+ print (r);
+ query q (query::last_name == query::_ref (name));
+ name = "Doe";
+ r = db->query<person> (q);
+ print (r);
+ t.commit ();
+ }
+
+ // Test column operators (==, !=, <, >, <=, >=).
+ //
+ cout << "test 009" << endl;
+ {
+ transaction t (db->begin ());
+
+ // ==
+ //
+ result r (db->query<person> (query::last_name == "Doe"));
+ print (r);
+
+ // !=
+ //
+ r = db->query<person> (query::last_name != "Doe");
+ print (r);
+
+ // <
+ //
+ r = db->query<person> (query::age < 31);
+ print (r);
+
+ // >
+ //
+ r = db->query<person> (query::age > 30);
+ print (r);
+
+ // <=
+ //
+ r = db->query<person> (query::age <= 30);
+ print (r);
+
+ // >=
+ //
+ r = db->query<person> (query::age >= 31);
+ print (r);
+
+ t.commit ();
+ }
+
+ // Test query operators (&&, ||, (), !, +).
+ //
+ cout << "test 010" << endl;
+ {
+ transaction t (db->begin ());
+
+ // &&
+ //
+ result r (db->query<person> (
+ query::last_name == "Doe" && query::age == 29));
+ print (r);
+
+ // ||
+ //
+ r = db->query<person> (query::last_name == "Doe" || query::age == 31);
+ print (r);
+
+ // ()
+ //
+ r = db->query<person> (
+ (query::last_name != "Doe" || query::age == 29) && query::married);
+ print (r);
+
+ // !=
+ //
+ r = db->query<person> (!(query::last_name == "Doe"));
+ print (r);
+
+ // +
+ //
+ r = db->query<person> ((query::last_name == "Doe") +
+ "ORDER BY" + query::age);
+ print (r);
+
+ t.commit ();
+ }
+
+ // Test in/in_range.
+ //
+ cout << "test 011" << endl;
+ {
+ transaction t (db->begin ());
+
+ result r (db->query<person> (query::first_name.in ("John", "Jane")));
+ print (r);
+
+ r = db->query<person> (query::first_name.in_range (names, names_end));
+ print (r);
+
+ // Empty range.
+ //
+ r = db->query<person> (query::last_name == "Doe" &&
+ query::first_name.in_range (names, names));
+ assert (r.empty ());
+
+ t.commit ();
+ }
+
+ // Test column-to-column comparison.
+ //
+ cout << "test 012" << endl;
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> (query::first_name == query::last_name));
+ print (r);
+ t.commit ();
+ }
+
+ // Test value_traits::value_type != value_traits::query_type.
+ //
+ cout << "test 013" << endl;
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> (query::middle_name == "Squeaky"));
+ print (r);
+ t.commit ();
+ }
+
+ // Test that loading of the same object type during iteration does
+ // not invalidate the result.
+ //
+ cout << "test 014" << endl;
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> (query::last_name == "Doe"));
+
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ ++i;
+ assert (i != r.end ());
+
+ {
+ unique_ptr<person> joe (db->load<person> (3));
+ }
+
+ {
+ person p (5, "Peter", "Peterson", 70, false, key3);
+ db->persist (p);
+ db->erase (p);
+ }
+
+ // SQL Server does not support re-loading of an object with long data
+ // from a query result.
+ //
+ if (db_id != odb::id_mssql)
+ assert (i->last_name_ == "Doe"); // Actual load.
+
+ // Overwrite object image again.
+ //
+ unique_ptr<person> joe (db->load<person> (3));
+ person p;
+ i.load (p);
+ assert (p.last_name_ == "Doe");
+
+ t.commit ();
+ }
+
+ // Test uncached result.
+ //
+ cout << "test 015" << endl;
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> (query::last_name == "Doe", false));
+ print (r);
+ t.commit ();
+ }
+
+ // Test BLOB column operations.
+ //
+ cout << "test 016" << endl;
+ {
+ transaction t (db->begin ());
+
+ result r;
+ result::iterator i;
+
+ // ==
+ //
+
+ // Oracle does not support LOB comparisons.
+ //
+#if defined(MULTI_DATABASE) || !defined(DATABASE_ORACLE)
+ if (db_id != odb::id_oracle)
+ {
+ r = db->query<person> (query::public_key == key2);
+
+ i = r.begin ();
+ assert (i != r.end ());
+
+ assert (*i->public_key_ == key2);
+ assert (++i == r.end ());
+ }
+#endif
+
+ // is_null
+ //
+ r = db->query<person> (query::public_key.is_null ());
+
+ i = r.begin ();
+ assert (i != r.end ());
+
+ assert (i->first_name_ == "Johansen" && i->last_name_ == "Johansen");
+ assert (++i == r.end ());
+
+ // is_not_null
+ //
+ r = db->query<person> (query::public_key.is_not_null ());
+
+ i = r.begin ();
+ assert (i != r.end ());
+
+ assert (i->first_name_ == "John" && i->last_name_ == "Doe");
+ assert (++i != r.end ());
+
+ assert (i->first_name_ == "Jane" && i->last_name_ == "Doe");
+ assert (++i != r.end ());
+
+ assert (i->first_name_ == "Joe" && i->last_name_ == "Dirt");
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+
+ // Test iterator::id().
+ //
+ cout << "test 017" << endl;
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> (query::last_name == "Dirt"));
+
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i.id () == 3);
+
+ t.commit ();
+ }
+
+ // Test empty result set.
+ //
+ cout << "test 018" << endl;
+ {
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> (query::last_name == "None"));
+ assert (r.empty ());
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> (query::last_name == "None"));
+ assert (r.begin () == r.end ());
+ t.commit ();
+ }
+ }
+
+ // Test size() validity at the beginning/middle/end of result set.
+ //
+ cout << "test 019" << endl;
+ if (db_id != odb::id_sqlite &&
+ db_id != odb::id_oracle &&
+ db_id != odb::id_mssql)
+ {
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> ());
+ assert (r.size () == 4);
+ result::iterator i (r.begin ());
+ assert (r.size () == 4);
+ ++i;
+ ++i;
+ ++i;
+ assert (r.size () == 4);
+ ++i;
+ assert (r.size () == 4);
+ }
+
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> (false));
+ result::iterator i (r.begin ());
+ ++i;
+ ++i;
+ r.cache (); // Cache in the middle.
+ assert (r.size () == 4);
+ ++i;
+ assert (r.size () == 4);
+ ++i;
+ assert (r.size () == 4);
+ }
+
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> (false));
+ result::iterator i (r.begin ());
+ ++i;
+ ++i;
+ ++i;
+ r.cache (); // Cache at the end.
+ assert (r.size () == 4);
+ }
+
+ {
+ transaction t (db->begin ());
+ result r (db->query<person> (query::last_name == "None"));
+ assert (r.size () == 0);
+ for (result::iterator i (r.begin ()); i != r.end (); ++i) ;
+ assert (r.size () == 0);
+ }
+ }
+
+ // Test like.
+ //
+ cout << "test 020" << endl;
+ {
+ transaction t (db->begin ());
+
+ result r (db->query<person> (query::first_name.like ("Jo%")));
+ print (r);
+
+ r = db->query<person> (!query::first_name.like ("Jo%"));
+ print (r);
+
+ r = db->query<person> (query::first_name.like ("Jo!%", "!"));
+ print (r);
+
+ // In Oracle one can only escape special characters (% and _).
+ //
+ string v;
+ if (db_id != odb::id_oracle)
+ v = "!Ja%";
+ else
+ v = "Ja%";
+
+ r = db->query<person> (query::first_name.like (query::_ref (v), "!"));
+ print (r);
+
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/query/basics/test.hxx b/odb-tests/common/query/basics/test.hxx
new file mode 100644
index 0000000..239f6d6
--- /dev/null
+++ b/odb-tests/common/query/basics/test.hxx
@@ -0,0 +1,117 @@
+// file : common/query/basics/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <vector>
+#include <memory>
+#include <iostream>
+
+#include <odb/core.hxx>
+#include <odb/nullable.hxx>
+
+#ifdef ODB_COMPILER
+# if defined(ODB_DATABASE_PGSQL)
+# define BLOB_TYPE "BYTEA"
+# elif defined(ODB_DATABASE_MSSQL)
+# define BLOB_TYPE "VARBINARY(max)"
+# else
+# define BLOB_TYPE "BLOB"
+# endif
+#endif
+
+typedef std::vector<char> buffer;
+typedef odb::nullable<buffer> nullable_buffer;
+
+#pragma db object
+struct person
+{
+ person (unsigned long id,
+ const std::string& fn,
+ const std::string& ln,
+ unsigned short age,
+ bool married,
+ const nullable_buffer& public_key = nullable_buffer ())
+ : id_ (id),
+ first_name_ (fn),
+ last_name_ (ln),
+ age_ (age),
+ married_ (married),
+ public_key_ (public_key)
+ {
+ }
+
+ person ()
+ {
+ }
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db column ("first")
+ std::string first_name_;
+
+ #pragma db column ("middle") null
+ std::unique_ptr<std::string> middle_name_;
+
+ #pragma db column ("last")
+ std::string last_name_;
+
+ unsigned short age_;
+ bool married_;
+
+ #pragma db column ("key") type(BLOB_TYPE) null
+ nullable_buffer public_key_;
+};
+
+inline std::ostream&
+operator<< (std::ostream& os, const person& p)
+{
+ os << p.first_name_;
+
+ if (p.middle_name_.get () != 0)
+ os << ' ' << *p.middle_name_;
+
+ os << ' ' << p.last_name_ << ' ' << p.age_ <<
+ (p.married_ ? " married" : " single");
+
+ return os;
+}
+
+// Test member name conflicts (compilation-only test).
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ #pragma db object
+ struct object
+ {
+ #pragma db id
+ int id;
+ };
+
+ #pragma db value
+ struct value
+ {
+ object* m_object;
+ };
+
+ #pragma db value
+ struct bar
+ {
+ value m_value;
+ };
+
+ #pragma db object
+ struct foo
+ {
+ #pragma db id
+ int id;
+
+ bar m_value;
+ };
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/query/basics/testscript b/odb-tests/common/query/basics/testscript
new file mode 100644
index 0000000..9086b66
--- /dev/null
+++ b/odb-tests/common/query/basics/testscript
@@ -0,0 +1,150 @@
+# file : common/query/basics/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
++cat <<EOI >=output
+ test 001
+ John Doe 30 married
+ Jane Doe 29 married
+ Joe Squeaky Dirt 31 single
+ Johansen J Johansen 32 single
+ test 002
+ Jane Doe 29 married
+ John Doe 30 married
+ Joe Squeaky Dirt 31 single
+ Johansen J Johansen 32 single
+
+ test 003
+ John Doe 30 married
+
+ test 004
+ John Doe 30 married
+
+ test 005
+ John Doe 30 married
+
+ Joe Squeaky Dirt 31 single
+
+ test 006
+ John Doe 30 married
+ Jane Doe 29 married
+
+ Joe Squeaky Dirt 31 single
+ Johansen J Johansen 32 single
+
+ test 007
+ John Doe 30 married
+ Jane Doe 29 married
+
+ Joe Squeaky Dirt 31 single
+ Johansen J Johansen 32 single
+
+ test 008
+ John Doe 30 married
+ Jane Doe 29 married
+
+ Joe Squeaky Dirt 31 single
+
+ John Doe 30 married
+ Jane Doe 29 married
+
+ test 009
+ John Doe 30 married
+ Jane Doe 29 married
+
+ Joe Squeaky Dirt 31 single
+ Johansen J Johansen 32 single
+
+ John Doe 30 married
+ Jane Doe 29 married
+
+ Joe Squeaky Dirt 31 single
+ Johansen J Johansen 32 single
+
+ John Doe 30 married
+ Jane Doe 29 married
+
+ Joe Squeaky Dirt 31 single
+ Johansen J Johansen 32 single
+
+ test 010
+ Jane Doe 29 married
+
+ John Doe 30 married
+ Jane Doe 29 married
+ Joe Squeaky Dirt 31 single
+
+ Jane Doe 29 married
+
+ Joe Squeaky Dirt 31 single
+ Johansen J Johansen 32 single
+
+ Jane Doe 29 married
+ John Doe 30 married
+
+ test 011
+ John Doe 30 married
+ Jane Doe 29 married
+
+ John Doe 30 married
+ Jane Doe 29 married
+ Joe Squeaky Dirt 31 single
+
+ test 012
+ Johansen J Johansen 32 single
+
+ test 013
+ Joe Squeaky Dirt 31 single
+
+ test 014
+ test 015
+ John Doe 30 married
+ Jane Doe 29 married
+
+ test 016
+ test 017
+ test 018
+ test 019
+ test 020
+ John Doe 30 married
+ Joe Squeaky Dirt 31 single
+ Johansen J Johansen 32 single
+
+ Jane Doe 29 married
+
+
+ Jane Doe 29 married
+
+ EOI
+
+test.redirects += >>>../output
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/query/one/buildfile b/odb-tests/common/query/one/buildfile
new file mode 100644
index 0000000..76a36b0
--- /dev/null
+++ b/odb-tests/common/query/one/buildfile
@@ -0,0 +1,42 @@
+# file : common/query/one/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_query_one_ \
+ --generate-schema \
+ --generate-query \
+ --generate-prepared
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/query/one/driver.cxx b/odb-tests/common/query/one/driver.cxx
new file mode 100644
index 0000000..4c3dcdc
--- /dev/null
+++ b/odb-tests/common/query/one/driver.cxx
@@ -0,0 +1,205 @@
+// file : common/query/one/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test query one support.
+//
+// We assume that other tests in common/query/ exercise a variety of
+// different kinds of queries. Here we are concerned with what is
+// specific to query_one() and query_value().
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+ odb::database_id db_id (db->id ());
+
+ transaction t (db->begin ());
+
+ // query_one()
+ //
+ {
+ unique_ptr<object> o (db->query_one<object> ());
+ assert (o.get () == 0);
+ }
+
+ {
+ object o (4);
+ assert (!db->query_one<object> (o) && o.id_ == 4 && o.str_.empty ());
+ }
+
+ /*
+ {
+ object o (db->query_value<object> ());
+ assert (false);
+ }
+ */
+
+ object o (1);
+ o.str_ = "value 1";
+ db->persist (o);
+
+ {
+ unique_ptr<object> o (db->query_one<object> ());
+ assert (o.get () != 0 && o->str_ == "value 1");
+ }
+
+ {
+ object o;
+ assert (db->query_one<object> (o) && o.str_ == "value 1");
+ }
+
+ {
+ object o (db->query_value<object> ());
+ assert (o.str_ == "value 1");
+ }
+
+ // query_one(const char*)
+ //
+ const char* q1_c (db_id == odb::id_oracle ? "\"id\" = 1" : "id = 1");
+ const char* q0_c (db_id == odb::id_oracle ? "\"id\" = 2" : "id = 2");
+
+ {
+ unique_ptr<object> o (db->query_one<object> (q1_c));
+ assert (o.get () != 0 && o->str_ == "value 1");
+ }
+
+ {
+ unique_ptr<object> o (db->query_one<object> (q0_c));
+ assert (o.get () == 0);
+ }
+
+ {
+ object o;
+ assert (db->query_one<object> (q1_c, o) && o.str_ == "value 1");
+ }
+
+ {
+ object o (4);
+ assert (!db->query_one<object> (q0_c, o) &&
+ o.id_ == 4 && o.str_.empty ());
+ }
+
+ {
+ object o (db->query_value<object> (q1_c));
+ assert (o.str_ == "value 1");
+ }
+
+ // query_one(std::string)
+ //
+ string q1_s (q1_c);
+ string q0_s (q0_c);
+
+ {
+ unique_ptr<object> o (db->query_one<object> (q1_s));
+ assert (o.get () != 0 && o->str_ == "value 1");
+ }
+
+ {
+ unique_ptr<object> o (db->query_one<object> (q0_s));
+ assert (o.get () == 0);
+ }
+
+ {
+ object o;
+ assert (db->query_one<object> (q1_s, o) && o.str_ == "value 1");
+ }
+
+ {
+ object o (4);
+ assert (!db->query_one<object> (q0_s, o) &&
+ o.id_ == 4 && o.str_.empty ());
+ }
+
+ {
+ object o (db->query_value<object> (q1_s));
+ assert (o.str_ == "value 1");
+ }
+
+ // query_one(odb::query)
+ //
+ typedef odb::query<object> query;
+
+ query q1 (query::id == 1);
+ query q0 (query::id == 2);
+
+ {
+ unique_ptr<object> o (db->query_one<object> (q1));
+ assert (o.get () != 0 && o->str_ == "value 1");
+ }
+
+ {
+ unique_ptr<object> o (db->query_one<object> (q0));
+ assert (o.get () == 0);
+ }
+
+ {
+ object o;
+ assert (db->query_one<object> (q1, o) && o.str_ == "value 1");
+ }
+
+ {
+ object o (4);
+ assert (!db->query_one<object> (q0, o) && o.id_ == 4 && o.str_.empty ());
+ }
+
+ {
+ object o (db->query_value<object> (q1));
+ assert (o.str_ == "value 1");
+ }
+
+ // Assertion on more than one element.
+ //
+ {
+ object o (2);
+ o.str_ = "value 2";
+ db->persist (o);
+ }
+
+ /*
+ {
+ unique_ptr<object> o (db->query_one<object> ());
+ assert (false);
+ }
+ */
+
+ /*
+ {
+ object o;
+ db->query_one<object> (o);
+ assert (false);
+ }
+ */
+
+ /*
+ {
+ object o (db->query_value<object> ());
+ assert (false);
+ }
+ */
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/query/one/test.hxx b/odb-tests/common/query/one/test.hxx
new file mode 100644
index 0000000..3008063
--- /dev/null
+++ b/odb-tests/common/query/one/test.hxx
@@ -0,0 +1,26 @@
+// file : common/query/one/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+
+#pragma db object
+struct object
+{
+ object (unsigned long id)
+ : id_ (id)
+ {
+ }
+
+ object ()
+ {
+ }
+
+ #pragma db id
+ unsigned long id_;
+ std::string str_;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/query/one/testscript b/odb-tests/common/query/one/testscript
new file mode 100644
index 0000000..963a206
--- /dev/null
+++ b/odb-tests/common/query/one/testscript
@@ -0,0 +1,33 @@
+# file : common/query/one/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/readonly/buildfile b/odb-tests/common/readonly/buildfile
new file mode 100644
index 0000000..2d83cf1
--- /dev/null
+++ b/odb-tests/common/readonly/buildfile
@@ -0,0 +1,40 @@
+# file : common/readonly/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 readonly_ \
+ --generate-schema
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/readonly/driver.cxx b/odb-tests/common/readonly/driver.cxx
new file mode 100644
index 0000000..b207627
--- /dev/null
+++ b/odb-tests/common/readonly/driver.cxx
@@ -0,0 +1,324 @@
+// file : common/readonly/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test readonly members/objects. Also test that const members are
+// treated as readonly. For other const member tests, see the const-
+// member test.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_*
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ // Simple.
+ //
+ {
+ simple o (1, 1);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ o.ro++;
+ const_cast<unsigned long&> (o.co)++;
+ o.rw++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->load<simple> (1, o);
+ t.commit ();
+ }
+
+ assert (o.ro == 1 && o.co == 1 && o.rw == 2);
+ }
+
+ // Pointer.
+ //
+ {
+ pointer p (1, new pointer (2));
+ unique_ptr<pointer> p1 (new pointer (3));
+
+ {
+ transaction t (db->begin ());
+ db->persist (p);
+ db->persist (p.ro);
+ db->persist (*p1);
+ t.commit ();
+ }
+
+ delete p.ro;
+ p.ro = p1.release ();
+ const_cast<pointer*&> (p.co) = p.ro;
+ p.rw = p.ro;
+
+ {
+ transaction t (db->begin ());
+ db->update (p);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<pointer> p (db->load<pointer> (1));
+ t.commit ();
+
+ assert (p->ro->id == 2 && p->co->id == 2 && p->rw->id == 3);
+ }
+ }
+
+ // Composite.
+ //
+ {
+ composite o (1, 1);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ o.ro.v++;
+ o.ro.ro++;
+ const_cast<unsigned long&> (o.ro.co)++;
+ o.ro.rw++;
+
+ value& co (const_cast<value&> (o.co));
+ co.v++;
+ co.ro++;
+ const_cast<unsigned long&> (co.co)++;
+ co.rw++;
+
+ o.rw.v++;
+ o.rw.ro++;
+ const_cast<unsigned long&> (o.rw.co)++;
+ o.rw.rw++;
+
+ o.v.v++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->load<composite> (1, o);
+ t.commit ();
+ }
+
+ assert (o.ro.v == 1 &&
+ o.ro.ro == 1 &&
+ o.ro.co == 1 &&
+ o.ro.rw == 1 &&
+
+ o.co.v == 1 &&
+ o.co.ro == 1 &&
+ o.co.co == 1 &&
+ o.co.rw == 1 &&
+
+ o.rw.v == 1 &&
+ o.rw.ro == 1 &&
+ o.rw.co == 1 &&
+ o.rw.rw == 2 &&
+
+ o.v.v == 1);
+ }
+
+ // Container.
+ //
+ {
+ typedef vector<unsigned long> ulongs;
+
+ container o (1);
+
+ o.ro.push_back (1);
+ o.ro.push_back (2);
+
+ ulongs& co (const_cast<ulongs&> (o.co));
+ co.push_back (1);
+ co.push_back (2);
+
+ o.rw.push_back (1);
+ o.rw.push_back (2);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ o.ro[0]++;
+ o.ro.pop_back ();
+
+ co[0]++;
+ co.pop_back ();
+
+ o.rw[0]++;
+ o.rw.pop_back ();
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->load<container> (1, o);
+ t.commit ();
+ }
+
+ assert (o.ro.size () == 2 && o.ro[0] == 1 && o.ro[1] == 2 &&
+ o.co.size () == 2 && o.co[0] == 1 && o.co[1] == 2 &&
+ o.rw.size () == 1 && o.rw[0] == 2);
+ }
+
+ // Readonly object.
+ //
+ {
+#ifndef MULTI_DATABASE
+ typedef odb::object_traits_impl<simple_object, odb::id_common> so_traits;
+ typedef odb::object_traits_impl<ro_object, odb::id_common> ro_traits;
+ typedef odb::object_traits_impl<rw_object, odb::id_common> rw_traits;
+
+ assert (so_traits::column_count ==
+ so_traits::id_column_count + so_traits::readonly_column_count);
+
+ assert (ro_traits::column_count ==
+ ro_traits::id_column_count + ro_traits::readonly_column_count);
+
+ assert (rw_traits::column_count !=
+ rw_traits::id_column_count + rw_traits::readonly_column_count);
+#endif
+
+ simple_object so (1, 1);
+ ro_object ro_o (1, 1);
+ rw_object rw_o (1, 1);
+
+ ro_o.cr.push_back (1);
+ ro_o.cr.push_back (2);
+
+ rw_o.cr.push_back (1);
+ rw_o.cr.push_back (2);
+
+ {
+ transaction t (db->begin ());
+ db->persist (so);
+ db->persist (ro_o);
+ db->persist (rw_o);
+ t.commit ();
+ }
+
+ rw_o.sv++;
+ rw_o.rw_sv++;
+
+ {
+ transaction t (db->begin ());
+ //db->update (so); // Compile error.
+ //db->update (ro_o); // Compile error.
+ db->update (rw_o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->load (1, so);
+ db->load (1, ro_o);
+ db->load (1, rw_o);
+ t.commit ();
+ }
+
+ assert (rw_o.sv == 1 && rw_o.rw_sv == 2);
+ }
+
+ // Readonly object.
+ //
+ {
+ wrapper o (1, 1);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ *o.pl = 2;
+ *o.cpl = 2;
+ o.pcl.reset (new unsigned long (2));
+ const_cast<unsigned long&> (*o.cpcl) = 2;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->load<wrapper> (1, o);
+ t.commit ();
+ }
+
+ assert (*o.pl == 2 && *o.cpl == 2 && *o.pcl == 2 && *o.cpcl == 1);
+ }
+
+ // Readonly object with auto id.
+ //
+ {
+ ro_auto o1 (1);
+ ro_auto o2 (2);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<ro_auto> p1 (db->load<ro_auto> (o1.id));
+ unique_ptr<ro_auto> p2 (db->load<ro_auto> (o2.id));
+ t.commit ();
+
+ assert (p1->num == o1.num);
+ assert (p2->num == o2.num);
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/readonly/test.hxx b/odb-tests/common/readonly/test.hxx
new file mode 100644
index 0000000..45797b6
--- /dev/null
+++ b/odb-tests/common/readonly/test.hxx
@@ -0,0 +1,225 @@
+// file : common/readonly/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <vector>
+#include <memory> // std::unique_ptr
+
+#include <odb/core.hxx>
+
+// Simple readonly object.
+//
+#pragma db object
+struct simple
+{
+ simple (unsigned long i, unsigned long x)
+ : id (i), ro (x), co (x), rw (x)
+ {
+ }
+
+ simple (): co (0) {}
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db readonly
+ unsigned long ro;
+
+ const unsigned long co;
+
+ unsigned long rw;
+};
+
+// Readonly pointer.
+//
+#pragma db object
+struct pointer
+{
+ pointer (unsigned long i, pointer* p = 0): id (i), ro (p), co (p), rw (p) {}
+ pointer (): ro (0), co (0), rw (0) {}
+
+ ~pointer ()
+ {
+ delete ro;
+
+ if (co != ro)
+ delete co;
+
+ if (rw != ro && rw != co)
+ delete rw;
+ }
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db readonly
+ pointer* ro;
+
+ pointer* const co;
+
+ pointer* rw;
+};
+
+// Composite readonly value as well as simple readonly value inside
+// a composite.
+//
+#pragma db value readonly
+struct ro_value
+{
+ ro_value () {}
+ ro_value (unsigned long x): v (x) {}
+
+ unsigned long v;
+};
+
+#pragma db value
+struct value: ro_value
+{
+ value (): co (0) {}
+ value (unsigned long x): ro_value (x), ro (x), co (x), rw (x) {}
+
+ #pragma db readonly
+ unsigned long ro;
+
+ const unsigned long co;
+
+ unsigned long rw;
+};
+
+#pragma db object
+struct composite
+{
+ composite (unsigned long i, unsigned long x)
+ : id (i), ro (x), co (x), rw (x), v (x)
+ {
+ }
+
+ composite () {}
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db readonly
+ value ro;
+
+ const value co;
+
+ value rw;
+ ro_value v;
+};
+
+// Readonly container.
+//
+#pragma db object
+struct container
+{
+ container (unsigned long i): id (i) {}
+ container () {}
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db readonly
+ std::vector<unsigned long> ro;
+
+ const std::vector<unsigned long> co;
+
+ std::vector<unsigned long> rw;
+};
+
+// Readonly object.
+//
+#pragma db object readonly
+struct simple_object
+{
+ simple_object (unsigned long i, unsigned long x): id (i), sv (x) {}
+ simple_object () {}
+
+ #pragma db id
+ unsigned long id;
+
+ unsigned long sv;
+};
+
+#pragma db object
+struct object
+{
+ object (unsigned long i, unsigned long x): id (i), sv (x) {}
+ object () {}
+
+ #pragma db id
+ unsigned long id;
+
+ unsigned long sv;
+};
+
+#pragma db object readonly
+struct ro_object: object
+{
+ ro_object (unsigned long i, unsigned long x)
+ : object (i, x), cv (x)
+ {
+ }
+
+ ro_object () {}
+
+ value cv;
+ std::vector<unsigned long> cr;
+};
+
+#pragma db object
+struct rw_object: ro_object
+{
+ rw_object (unsigned long i, unsigned long x)
+ : ro_object (i, x), rw_sv (x)
+ {
+ }
+
+ rw_object () {}
+
+ unsigned long rw_sv;
+};
+
+// Readonly wrappers. Here we make sure that only const wrappers with
+// const wrapped types are automatically treated as readonly.
+//
+#pragma db object
+struct wrapper
+{
+ wrapper (unsigned long i, unsigned long x)
+ : id (i),
+ pl (new unsigned long (x)),
+ cpl (new unsigned long (x)),
+ pcl (new unsigned long (x)),
+ cpcl (new unsigned long (x))
+ {
+ }
+
+ wrapper () {}
+
+ #pragma db id
+ unsigned long id;
+
+ std::unique_ptr<unsigned long> pl;
+ const std::unique_ptr<unsigned long> cpl;
+ std::unique_ptr<const unsigned long> pcl;
+ const std::unique_ptr<const unsigned long> cpcl;
+};
+
+// Readonly object with auto id.
+//
+#pragma db object readonly
+struct ro_auto
+{
+ ro_auto (unsigned long n): num (n) {}
+ ro_auto () {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ unsigned long num;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/readonly/testscript b/odb-tests/common/readonly/testscript
new file mode 100644
index 0000000..c798201
--- /dev/null
+++ b/odb-tests/common/readonly/testscript
@@ -0,0 +1,33 @@
+# file : common/readonly/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/relationship/basics/buildfile b/odb-tests/common/relationship/basics/buildfile
new file mode 100644
index 0000000..d7bbb7e
--- /dev/null
+++ b/odb-tests/common/relationship/basics/buildfile
@@ -0,0 +1,41 @@
+# file : common/relationship/basics/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_rel_basics_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/relationship/basics/driver.cxx b/odb-tests/common/relationship/basics/driver.cxx
new file mode 100644
index 0000000..e27c127
--- /dev/null
+++ b/odb-tests/common/relationship/basics/driver.cxx
@@ -0,0 +1,150 @@
+// file : common/relationship/basics/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test object relationships.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ aggr a ("aggr");
+ a.o1 = new obj1 ("o1", "obj1");
+ a.o2.reset (new obj2 ("obj2"));
+
+ a.v2.push_back (obj2_ptr (new obj2 ("v1 obj2 1")));
+ a.v2.push_back (0);
+ a.v2.push_back (obj2_ptr (new obj2 ("v1 obj2 2")));
+
+ a.o3.reset (new obj3 ("obj3"));
+
+ a.c.num = 123;
+ a.c.o3.reset (new obj3 ("c"));
+
+ a.cv.push_back (comp (234, obj3_ptr (new obj3 ("cv 0"))));
+ a.cv.push_back (comp (235, obj3_ptr ()));
+ a.cv.push_back (comp (236, obj3_ptr (new obj3 ("cv 2"))));
+
+ a.v1.push_back (new obj1 ("v1 0", "v1 0"));
+ a.v1.push_back (0);
+ a.v1.push_back (new obj1 ("v1 2", "v1 2"));
+
+ // Set cannot contain NULL pointers.
+ //
+ a.s1.insert (new obj1 ("s1 0", "s1 0"));
+ a.s1.insert (new obj1 ("s1 2", "s1 2"));
+
+ a.m1[0] = new obj1 ("m1 0", "m1 0");
+ a.m1[1] = 0;
+ a.m1[2] = new obj1 ("m1 2", "m1 2");
+
+ // persist
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (a.o1);
+ db->persist (a.o2);
+
+ for (obj2_vec::iterator i (a.v2.begin ()); i != a.v2.end (); ++i)
+ if (*i)
+ db->persist (*i);
+
+ db->persist (a.o3);
+
+ db->persist (a.c.o3);
+
+ for (comp_vec::iterator i (a.cv.begin ()); i != a.cv.end (); ++i)
+ if (i->o3)
+ db->persist (i->o3);
+
+ for (obj1_vec::iterator i (a.v1.begin ()); i != a.v1.end (); ++i)
+ if (*i)
+ db->persist (*i);
+
+ for (obj1_set::iterator i (a.s1.begin ()); i != a.s1.end (); ++i)
+ if (*i)
+ db->persist (*i);
+
+ for (obj1_map::iterator i (a.m1.begin ()); i != a.m1.end (); ++i)
+ if (i->second)
+ db->persist (i->second);
+
+ db->persist (a);
+ t.commit ();
+ }
+
+ // load & compare
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<aggr> a1 (db->load<aggr> (a.id));
+ t.commit ();
+
+ assert (*a1 == a);
+ }
+
+ // query
+ //
+ typedef odb::query<aggr> query;
+ typedef odb::result<aggr> result;
+
+ {
+ transaction t (db->begin ());
+
+ result r (db->query<aggr> (query::o1->str == "obj1"));
+ assert (!r.empty ());
+ assert (r.begin ()->o1->id == a.o1->id);
+ assert (size (r) == 1);
+
+ t.commit ();
+ }
+
+ // Test NULL pointer.
+ //
+ delete a.o1;
+ a.o1 = 0;
+ a.o2.reset ();
+ a.o3.reset ();
+
+ {
+ transaction t (db->begin ());
+ db->update (a);
+ t.commit ();
+ }
+
+ // load & compare
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<aggr> a1 (db->load<aggr> (a.id));
+ t.commit ();
+
+ assert (*a1 == a);
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/relationship/basics/test.hxx b/odb-tests/common/relationship/basics/test.hxx
new file mode 100644
index 0000000..8a2742e
--- /dev/null
+++ b/odb-tests/common/relationship/basics/test.hxx
@@ -0,0 +1,260 @@
+// file : common/relationship/basics/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <set>
+#include <map>
+#include <vector>
+#include <string>
+#include <memory>
+
+#include <odb/core.hxx>
+
+// Raw pointer.
+//
+#pragma db object pointer(obj1*)
+struct obj1
+{
+ obj1 () {}
+ obj1 (const std::string& i, const std::string& s): id (i), str (s) {}
+
+ #pragma db id
+ std::string id;
+ std::string str;
+};
+
+inline bool
+operator== (const obj1& x, const obj1& y)
+{
+ return x.id == y.id && x.str == y.str;
+}
+
+// vector
+//
+typedef std::vector<obj1*> obj1_vec;
+
+inline bool
+operator== (const obj1_vec& x, const obj1_vec& y)
+{
+ if (x.size () != y.size ())
+ return false;
+
+ for (obj1_vec::size_type i (0); i < x.size (); ++i)
+ if (!(x[i] ? (y[i] && *x[i] == *y[i]) : !y[i]))
+ return false;
+
+ return true;
+}
+
+// set
+//
+struct obj1_cmp
+{
+ bool
+ operator() (obj1* x, obj1* y) const
+ {
+ return (!x || !y) ? x < y : x->id < y->id;
+ }
+};
+
+typedef std::set<obj1*, obj1_cmp> obj1_set;
+
+inline bool
+operator== (const obj1_set& x, const obj1_set& y)
+{
+ if (x.size () != y.size ())
+ return false;
+
+ for (obj1_set::const_iterator i (x.begin ()); i != x.end (); ++i)
+ {
+ obj1_set::const_iterator j (y.find (*i));
+
+ if (j == y.end ())
+ return false;
+
+ obj1* x (*i);
+ obj1* y (*j);
+
+ if (!(x ? (y && *x == *y) : !y))
+ return false;
+ }
+
+ return true;
+}
+
+// map
+//
+typedef std::map<int, obj1*> obj1_map;
+
+inline bool
+operator== (const obj1_map& x, const obj1_map& y)
+{
+ if (x.size () != y.size ())
+ return false;
+
+ for (obj1_map::const_iterator i (x.begin ()); i != x.end (); ++i)
+ {
+ obj1_map::const_iterator j (y.find (i->first));
+
+ if (j == y.end ())
+ return false;
+
+ obj1* x (i->second);
+ obj1* y (j->second);
+
+ if (!(x ? (y && *x == *y) : !y))
+ return false;
+ }
+
+ return true;
+}
+
+// auto_ptr/unique_ptr
+//
+struct obj2;
+
+typedef std::unique_ptr<obj2> obj2_ptr;
+
+#pragma db object pointer(obj2_ptr)
+struct obj2
+{
+ obj2 () {}
+ obj2 (const std::string& s): str (s) {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ std::string str;
+};
+
+inline bool
+operator== (const obj2& x, const obj2& y)
+{
+ return x.id == y.id && x.str == y.str;
+}
+
+typedef std::vector<obj2_ptr> obj2_vec;
+
+inline bool
+operator== (const obj2_vec& x, const obj2_vec& y)
+{
+ if (x.size () != y.size ())
+ return false;
+
+ for (obj2_vec::size_type i (0); i < x.size (); ++i)
+ if (!(x[i] ? (y[i] && *x[i] == *y[i]) : !y[i]))
+ return false;
+
+ return true;
+}
+
+// shared_ptr
+//
+struct obj3;
+
+typedef std::shared_ptr<obj3> obj3_ptr;
+
+#pragma db object pointer(obj3_ptr)
+struct obj3
+{
+ obj3 () {}
+ obj3 (const std::string& s): str (s) {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ std::string str;
+};
+
+inline bool
+operator== (const obj3& x, const obj3& y)
+{
+ return x.id == y.id && x.str == y.str;
+}
+
+// composite
+//
+#pragma db value
+struct comp
+{
+ comp () {}
+ comp (int n, obj3_ptr o): num (n), o3 (o) {}
+
+ int num;
+ obj3_ptr o3;
+};
+
+inline bool
+operator== (const comp& x, const comp& y)
+{
+ return x.num == y.num &&
+ (x.o3 ? (y.o3 && *x.o3 == *y.o3) : !y.o3);
+}
+
+typedef std::vector<comp> comp_vec;
+
+//
+//
+#pragma db object
+struct aggr
+{
+ aggr (): o1 (0) {}
+ aggr (const std::string& s): o1 (0), str (s) {}
+
+ ~aggr ()
+ {
+ delete o1;
+
+ for (obj1_vec::iterator i (v1.begin ()); i != v1.end (); ++i)
+ delete *i;
+
+ for (obj1_set::iterator i (s1.begin ()); i != s1.end (); ++i)
+ delete *i;
+
+ for (obj1_map::iterator i (m1.begin ()); i != m1.end (); ++i)
+ delete i->second;
+ }
+
+ #pragma db id auto
+ unsigned long id;
+
+ obj1* o1;
+
+ obj2_ptr o2; // std::auto_ptr or std::unique_ptr
+ obj2_vec v2;
+
+ obj3_ptr o3;
+ comp c;
+ comp_vec cv;
+
+ obj1_vec v1;
+ obj1_set s1;
+ obj1_map m1;
+
+ std::string str;
+
+private:
+ aggr (const aggr&);
+ aggr& operator= (const aggr&);
+};
+
+inline bool
+operator== (const aggr& x, const aggr& y)
+{
+ return
+ x.id == y.id &&
+ (x.o1 ? (y.o1 && *x.o1 == *y.o1) : !y.o1) &&
+ (x.o2.get () ? (y.o2.get () && *x.o2 == *y.o2) : !y.o2.get ()) &&
+ x.v2 == y.v2 &&
+ (x.o3.get () ? (y.o3.get () && *x.o3 == *y.o3) : !y.o3.get ()) &&
+ x.c == y.c &&
+ x.cv == y.cv &&
+ x.v1 == y.v1 &&
+ x.s1 == y.s1 &&
+ x.m1 == y.m1 &&
+ x.str == y.str;
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/relationship/basics/testscript b/odb-tests/common/relationship/basics/testscript
new file mode 100644
index 0000000..b498bfa
--- /dev/null
+++ b/odb-tests/common/relationship/basics/testscript
@@ -0,0 +1,33 @@
+# file : common/relationship/basics/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/relationship/on-delete/buildfile b/odb-tests/common/relationship/on-delete/buildfile
new file mode 100644
index 0000000..965a2eb
--- /dev/null
+++ b/odb-tests/common/relationship/on-delete/buildfile
@@ -0,0 +1,43 @@
+# file : common/relationship/on-delete/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_rel_on_d_ \
+ --generate-schema \
+ --fkeys-deferrable-mode mysql:not_deferrable \
+ --fkeys-deferrable-mode mssql:not_deferrable
+
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/relationship/on-delete/driver.cxx b/odb-tests/common/relationship/on-delete/driver.cxx
new file mode 100644
index 0000000..eec57cf
--- /dev/null
+++ b/odb-tests/common/relationship/on-delete/driver.cxx
@@ -0,0 +1,82 @@
+// file : common/relationship/on-delete/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test ON DELETE functionality.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ object o;
+
+ cascade c;
+ c.p = &o;
+
+ cascade_cont cc;
+ cc.p.push_back (&o);
+
+ set_null n;
+ n.p = &o;
+
+ set_null_cont nc;
+ nc.p.push_back (&o);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ db->persist (c);
+ db->persist (cc);
+ db->persist (n);
+ db->persist (nc);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ assert (db->find<cascade> (c.id) == 0);
+
+ unique_ptr<cascade_cont> pcc (db->load<cascade_cont> (cc.id));
+ assert (pcc->p.empty ());
+
+ unique_ptr<set_null> pn (db->load<set_null> (n.id));
+ assert (pn->p == 0);
+
+ unique_ptr<set_null_cont> pnc (db->load<set_null_cont> (nc.id));
+ assert (pnc->p.size () == 1 && pnc->p[0] == 0);
+
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/relationship/on-delete/test.hxx b/odb-tests/common/relationship/on-delete/test.hxx
new file mode 100644
index 0000000..841acd9
--- /dev/null
+++ b/odb-tests/common/relationship/on-delete/test.hxx
@@ -0,0 +1,58 @@
+// file : common/relationship/on-delete/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <vector>
+
+#include <odb/core.hxx>
+
+#pragma db object
+struct object
+{
+ #pragma db id auto
+ unsigned long id;
+};
+
+#pragma db object
+struct cascade
+{
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db on_delete(cascade)
+ object* p;
+};
+
+#pragma db object
+struct cascade_cont
+{
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db on_delete(cascade)
+ std::vector<object*> p;
+};
+
+#pragma db object
+struct set_null
+{
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db on_delete(set_null)
+ object* p;
+};
+
+#pragma db object
+struct set_null_cont
+{
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db on_delete(set_null)
+ std::vector<object*> p;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/relationship/on-delete/testscript b/odb-tests/common/relationship/on-delete/testscript
new file mode 100644
index 0000000..8288ebc
--- /dev/null
+++ b/odb-tests/common/relationship/on-delete/testscript
@@ -0,0 +1,33 @@
+# file : common/relationship/on-delete/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/relationship/query/buildfile b/odb-tests/common/relationship/query/buildfile
new file mode 100644
index 0000000..b70edc4
--- /dev/null
+++ b/odb-tests/common/relationship/query/buildfile
@@ -0,0 +1,42 @@
+# file : common/relationship/query/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_rel_query_ \
+ --generate-schema \
+ --generate-query \
+ --generate-session
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/relationship/query/driver.cxx b/odb-tests/common/relationship/query/driver.cxx
new file mode 100644
index 0000000..20d5370
--- /dev/null
+++ b/odb-tests/common/relationship/query/driver.cxx
@@ -0,0 +1,168 @@
+// file : common/relationship-query/query/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test relationship queries.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/session.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ //
+ //
+ {
+ shared_ptr<country> ca (new country ("CA", "Canada"));
+ shared_ptr<country> za (new country ("ZA", "South Africa"));
+ shared_ptr<country> us (new country ("US", "United States"));
+ shared_ptr<country> se (new country ("SE", "Sweden"));
+
+ shared_ptr<employer> st (new employer ("Simple Tech, Inc", ca));
+ shared_ptr<employer> ct (new employer ("Complex Tech, Inc", us));
+
+ // person
+ //
+ shared_ptr<person> p1 (
+ new person (1, "John", "Doe", 30, ca, true, za));
+
+ shared_ptr<person> p2 (
+ new person (2, "Jane", "Doe", 29, za, false, us));
+ p2->husband = p1;
+
+ shared_ptr<person> p3 (
+ new person (3, "Joe", "Dirt", 31, us, true, us));
+
+ shared_ptr<person> p4 (
+ new person (4, "Johan", "Johansen", 32, se, false, ca));
+
+ // employee
+ //
+ shared_ptr<employee> e1 (
+ new employee (1, "John", "Doe", 30, ca, true, za, st));
+
+ shared_ptr<employee> e2 (
+ new employee (2, "Jane", "Doe", 29, za, false, us, ct));
+ e2->husband = p1;
+
+ shared_ptr<employee> e3 (
+ new employee (3, "Joe", "Dirt", 31, us, true, us, st));
+
+ shared_ptr<employee> e4 (
+ new employee (4, "Johan", "Johansen", 32, se, false, ca, ct));
+
+ transaction t (db->begin ());
+ db->persist (ca);
+ db->persist (za);
+ db->persist (us);
+ db->persist (se);
+
+ db->persist (st);
+ db->persist (ct);
+
+ db->persist (p1);
+ db->persist (p2);
+ db->persist (p3);
+ db->persist (p4);
+
+ db->persist (e1);
+ db->persist (e2);
+ db->persist (e3);
+ db->persist (e4);
+ t.commit ();
+ }
+
+ typedef odb::query<person> p_query;
+ typedef odb::result<person> p_result;
+
+ typedef odb::query<employee> e_query;
+ typedef odb::result<employee> e_result;
+
+ // Make sure we have an independent JOIN for each relationship.
+ //
+ {
+ session s;
+ transaction t (db->begin ());
+
+ p_result pr (db->query<person> (
+ p_query::residence.location->code == "ZA"));
+ assert (size (pr) == 1);
+
+ e_result er (db->query<employee> (
+ e_query::residence.location->code == "ZA"));
+ assert (size (er) == 1);
+
+ t.commit ();
+ }
+
+ // Test Self-JOIN.
+ //
+ {
+ session s;
+ transaction t (db->begin ());
+
+ p_result pr (db->query<person> (p_query::husband->last_name == "Doe"));
+ assert (size (pr) == 1);
+
+ e_result er (db->query<employee> (e_query::husband->last_name == "Doe"));
+ assert (size (er) == 1);
+
+ t.commit ();
+ }
+
+ // Test query conditions from both base and derived.
+ //
+ {
+ session s;
+ transaction t (db->begin ());
+
+ e_result r (
+ db->query<employee> (
+ e_query::employed_by->name == "Simple Tech, Inc" &&
+ e_query::nationality->code == "US"));
+
+ assert (size (r) == 1);
+
+ t.commit ();
+ }
+
+ // Test second-level pointers.
+ //
+ {
+ session s;
+ transaction t (db->begin ());
+
+ p_result r (
+ db->query<person> (
+ p_query::husband->residence.location == "CA"));
+
+ assert (size (r) == 1);
+
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/relationship/query/test.hxx b/odb-tests/common/relationship/query/test.hxx
new file mode 100644
index 0000000..c6e2d6d
--- /dev/null
+++ b/odb-tests/common/relationship/query/test.hxx
@@ -0,0 +1,140 @@
+// file : common/relationship-query/query/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <memory>
+
+#include <odb/core.hxx>
+
+using std::shared_ptr;
+
+struct country;
+
+#pragma db value
+struct residence_info
+{
+ residence_info (bool p, shared_ptr<country> l)
+ : permanent (p), location (l)
+ {
+ }
+
+ residence_info ()
+ {
+ }
+
+ bool permanent;
+
+ #pragma db not_null
+ shared_ptr<country> location;
+};
+
+#pragma db object pointer(shared_ptr)
+struct person
+{
+ person (unsigned long i,
+ const std::string& fn,
+ const std::string& ln,
+ unsigned short a,
+ shared_ptr<country> r,
+ bool p,
+ shared_ptr<country> n)
+ : id (i),
+ first_name (fn),
+ last_name (ln),
+ age (a),
+ residence (p, r),
+ nationality (n)
+ {
+ }
+
+ person ()
+ {
+ }
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db column ("first")
+ std::string first_name;
+
+ #pragma db column ("last")
+ std::string last_name;
+
+ unsigned short age;
+
+ residence_info residence;
+
+ #pragma db not_null
+ shared_ptr<country> nationality;
+
+ shared_ptr<person> husband; // Self-join.
+};
+
+struct employer;
+
+#pragma db object pointer(shared_ptr)
+struct employee: person
+{
+ employee (unsigned long i,
+ const std::string& fn,
+ const std::string& ln,
+ unsigned short a,
+ shared_ptr<country> r,
+ bool p,
+ shared_ptr<country> n,
+ shared_ptr<employer> e)
+ : person (i, fn, ln, a, r, p, n),
+ employed_by (e)
+ {
+ }
+
+ employee ()
+ {
+ }
+
+ shared_ptr<employer> employed_by;
+};
+
+#pragma db object pointer(shared_ptr)
+struct employer
+{
+ employer (const std::string& n, shared_ptr<country> nat)
+ : name (n), nationality (nat)
+ {
+ }
+
+ employer ()
+ {
+ }
+
+ #pragma db id
+ std::string name;
+
+ // The same member name and type as in person (test JOIN alias).
+ //
+ #pragma db not_null
+ shared_ptr<country> nationality;
+};
+
+#pragma db object pointer(shared_ptr)
+struct country
+{
+ country (const std::string& c, const std::string& n)
+ : code (c), name (n)
+ {
+ }
+
+ country ()
+ {
+ }
+
+ #pragma db id
+ std::string code; // ISO 2-letter country code.
+
+ std::string name;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/relationship/query/testscript b/odb-tests/common/relationship/query/testscript
new file mode 100644
index 0000000..d5ad419
--- /dev/null
+++ b/odb-tests/common/relationship/query/testscript
@@ -0,0 +1,33 @@
+# file : common/relationship/query/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/schema/embedded/basics/buildfile b/odb-tests/common/schema/embedded/basics/buildfile
new file mode 100644
index 0000000..0cfe85e
--- /dev/null
+++ b/odb-tests/common/schema/embedded/basics/buildfile
@@ -0,0 +1,42 @@
+# file : common/schema/embedded/basics/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 schema_embd_bscs_ \
+ --generate-schema \
+ --schema-format embedded \
+ --schema-name test
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/schema/embedded/basics/driver.cxx b/odb-tests/common/schema/embedded/basics/driver.cxx
new file mode 100644
index 0000000..7ba2bce
--- /dev/null
+++ b/odb-tests/common/schema/embedded/basics/driver.cxx
@@ -0,0 +1,59 @@
+// file : common/schema/embedded/basics/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test basic embedded schema functionality.
+//
+
+#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 "test.hxx"
+#include "test-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));
+
+ // Create the database schema.
+ //
+ {
+ connection_ptr c (db->connection ());
+
+ // Temporarily disable foreign key constraints for SQLite.
+ //
+ if (db->id () == odb::id_sqlite)
+ c->execute ("PRAGMA foreign_keys=OFF");
+
+ assert (schema_catalog::exists (*db, "test"));
+ assert (!schema_catalog::exists (*db, "test1"));
+ assert (!schema_catalog::exists (*db, ""));
+
+ transaction t (c->begin ());
+ schema_catalog::create_schema (*db, "test");
+ t.commit ();
+
+ if (db->id () == odb::id_sqlite)
+ c->execute ("PRAGMA foreign_keys=ON");
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/schema/embedded/basics/test.hxx b/odb-tests/common/schema/embedded/basics/test.hxx
new file mode 100644
index 0000000..43331f9
--- /dev/null
+++ b/odb-tests/common/schema/embedded/basics/test.hxx
@@ -0,0 +1,23 @@
+// file : common/schema/embedded/basics/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <vector>
+
+#include <odb/core.hxx>
+
+#pragma db object
+struct object
+{
+ #pragma db auto id
+ unsigned long id;
+
+ std::string str;
+
+ std::vector<int> nums;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/schema/embedded/basics/testscript b/odb-tests/common/schema/embedded/basics/testscript
new file mode 100644
index 0000000..24448c0
--- /dev/null
+++ b/odb-tests/common/schema/embedded/basics/testscript
@@ -0,0 +1,31 @@
+# file : common/schema/embedded/basics/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../../mysql.testscript
+
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../../pgsql.testscript
+
+ $*
+}
diff --git a/odb-tests/common/schema/embedded/order/.gitignore b/odb-tests/common/schema/embedded/order/.gitignore
new file mode 100644
index 0000000..5d39d39
--- /dev/null
+++ b/odb-tests/common/schema/embedded/order/.gitignore
@@ -0,0 +1,6 @@
+# ODB-generated files.
+#
+test1-odb.?xx
+test1-odb-*.?xx
+test2-odb.?xx
+test2-odb-*.?xx
diff --git a/odb-tests/common/schema/embedded/order/buildfile b/odb-tests/common/schema/embedded/order/buildfile
new file mode 100644
index 0000000..57ad7dd
--- /dev/null
+++ b/odb-tests/common/schema/embedded/order/buildfile
@@ -0,0 +1,48 @@
+# file : common/schema/embedded/order/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+hs = test1 test2
+
+exe{driver}: {hxx cxx}{* -*-odb -*-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: $hs
+{
+ exe{driver}: {hxx ixx cxx}{$h-odb}
+
+ <{hxx ixx cxx}{$h-odb}>: hxx{$h} libue{test-meta}
+
+ for db: $databases
+ {
+ exe{driver}: {hxx ixx cxx}{$h-odb-$db}: include = $multi
+ <{hxx ixx cxx}{$h-odb-$db}>: hxx{$h} libue{test-meta}
+ }
+}
+
+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 schema_embd_ordr_ \
+ --generate-schema \
+ --schema-format embedded
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/schema/embedded/order/driver.cxx b/odb-tests/common/schema/embedded/order/driver.cxx
new file mode 100644
index 0000000..fde5e96
--- /dev/null
+++ b/odb-tests/common/schema/embedded/order/driver.cxx
@@ -0,0 +1,65 @@
+// file : common/schema/embedded/order/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test statement execution order in embedded schemas.
+//
+
+#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 "test1.hxx"
+#include "test2.hxx"
+
+#include "test1-odb.hxx"
+#include "test2-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));
+ odb::database_id db_id (db->id ());
+
+ // Create the database schema.
+ //
+ {
+ connection_ptr c (db->connection ());
+
+ // Temporarily disable foreign key constraints for MySQL and SQLite.
+ // For MySQL we can actually create the tables in any order. It is
+ // dropping them that's the problem (there is no IF EXISTS).
+ //
+ if (db_id == odb::id_mysql)
+ c->execute ("SET FOREIGN_KEY_CHECKS=0");
+ else if (db_id == odb::id_sqlite)
+ c->execute ("PRAGMA foreign_keys=OFF");
+
+ transaction t (c->begin ());
+ schema_catalog::create_schema (*db);
+ t.commit ();
+
+ if (db_id == odb::id_mysql)
+ c->execute ("SET FOREIGN_KEY_CHECKS=1");
+ else if (db_id == odb::id_sqlite)
+ c->execute ("PRAGMA foreign_keys=ON");
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/schema/embedded/order/test1.hxx b/odb-tests/common/schema/embedded/order/test1.hxx
new file mode 100644
index 0000000..b35074b
--- /dev/null
+++ b/odb-tests/common/schema/embedded/order/test1.hxx
@@ -0,0 +1,23 @@
+// file : common/schema/embedded/order/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#include <string>
+
+#include <odb/core.hxx>
+
+#pragma db object polymorphic
+struct base
+{
+ virtual
+ ~base () {}
+
+ #pragma db auto id
+ unsigned long id;
+
+ std::string str;
+};
+
+#endif // TEST1_HXX
diff --git a/odb-tests/common/schema/embedded/order/test2.hxx b/odb-tests/common/schema/embedded/order/test2.hxx
new file mode 100644
index 0000000..b4e6d20
--- /dev/null
+++ b/odb-tests/common/schema/embedded/order/test2.hxx
@@ -0,0 +1,17 @@
+// file : common/schema/embedded/order/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#include <odb/core.hxx>
+
+#include "test1.hxx"
+
+#pragma db object
+struct derived: base
+{
+ int num;
+};
+
+#endif // TEST2_HXX
diff --git a/odb-tests/common/schema/embedded/order/testscript b/odb-tests/common/schema/embedded/order/testscript
new file mode 100644
index 0000000..6dfe58c
--- /dev/null
+++ b/odb-tests/common/schema/embedded/order/testscript
@@ -0,0 +1,31 @@
+# file : common/schema/embedded/order/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../../mysql.testscript
+
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../../pgsql.testscript
+
+ $*
+}
diff --git a/odb-tests/common/schema/namespace/buildfile b/odb-tests/common/schema/namespace/buildfile
new file mode 100644
index 0000000..fa496d7
--- /dev/null
+++ b/odb-tests/common/schema/namespace/buildfile
@@ -0,0 +1,41 @@
+# file : common/schema/namespace/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 schema_ns_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/schema/namespace/driver.cxx b/odb-tests/common/schema/namespace/driver.cxx
new file mode 100644
index 0000000..25515f2
--- /dev/null
+++ b/odb-tests/common/schema/namespace/driver.cxx
@@ -0,0 +1,113 @@
+// file : common/schema/namespace/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test database schemas (aka database namespaces).
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ // Test database schema (aka database namespace).
+ //
+ using ns::object2;
+
+ object2 o2;
+ o2.id = "aaa";
+ o2.nums.push_back (1);
+ o2.nums.push_back (2);
+ o2.nums.push_back (3);
+ o2.obj1 = new object1;
+ o2.obj1->str = "aaa";
+
+ {
+ transaction t (db->begin ());
+ db->persist (o2.obj1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object2> p2 (db->load<object2> ("aaa"));
+ t.commit ();
+
+ assert (o2 == *p2);
+ }
+
+ {
+ typedef odb::query<object2> query;
+ typedef odb::result<object2> result;
+
+ transaction t (db->begin ());
+
+ {
+ result r (db->query<object2> (query::id == "aaa"));
+ assert (size (r) == 1);
+ }
+
+ {
+ result r (db->query<object2> (query::obj1->str == "aaa"));
+ assert (size (r) == 1);
+ }
+
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object_view> query;
+ typedef odb::result<object_view> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<object_view> (query::object2::id == "aaa"));
+
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->id2 == "aaa" && i->str == "aaa");
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+
+ {
+ typedef odb::result<table_view> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<table_view> ());
+
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->str == "aaa");
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/schema/namespace/test.hxx b/odb-tests/common/schema/namespace/test.hxx
new file mode 100644
index 0000000..0b1844a
--- /dev/null
+++ b/odb-tests/common/schema/namespace/test.hxx
@@ -0,0 +1,158 @@
+// file : common/schema/namespace/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <vector>
+
+#include <odb/core.hxx>
+
+// Table names.
+//
+#pragma db object table("TABLE_EXPLICIT")
+struct table_explicit
+{
+ #pragma db id
+ unsigned long id_;
+};
+
+#pragma db object
+struct table_implicit
+{
+ #pragma db id
+ unsigned long id_;
+};
+
+// Column names.
+//
+#pragma db object
+struct column
+{
+ #pragma db id
+ int m1;
+
+ #pragma db column("foo")
+ int m2;
+
+ int m_m3;
+ int _m4;
+ int m5_;
+ int m_;
+ int m__;
+};
+
+// Column types.
+//
+#pragma db object
+struct type
+{
+ #pragma db id
+ std::string id;
+
+ // Test default C++ to DB type mapping.
+ //
+ bool b;
+ char c;
+ signed char sc;
+ unsigned char uc;
+ short s;
+ unsigned short us;
+ int i;
+ unsigned int ui;
+ long l;
+ unsigned long ul;
+ long long ll;
+ unsigned long long ull;
+ float f;
+ double d;
+ std::string str;
+
+ #pragma db type("INTEGER")
+ bool m1;
+
+ #pragma db transient
+ char* m2;
+};
+
+// Test database schema (aka database namespace).
+//
+#ifdef ODB_COMPILER
+#if defined (ODB_DATABASE_MYSQL)
+//# define DB_SCHEMA "odb_test"
+# define DB_SCHEMA ""
+#elif defined (ODB_DATABASE_SQLITE)
+# define DB_SCHEMA "main"
+#elif defined (ODB_DATABASE_PGSQL)
+# define DB_SCHEMA "public"
+#elif defined (ODB_DATABASE_ORACLE)
+//# define DB_SCHEMA "ODB_TEST"
+# define DB_SCHEMA ""
+#elif defined(ODB_DATABASE_MSSQL)
+# define DB_SCHEMA "dbo"
+#elif defined(ODB_DATABASE_COMMON)
+# define DB_SCHEMA "dummy"
+#else
+# error unknown database
+#endif
+#endif
+
+namespace ns {typedef int my_int;} // Original.
+
+#pragma db object table(DB_SCHEMA."object_1")
+struct object1
+{
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db column("str")
+ std::string str;
+};
+
+inline bool
+operator== (const object1& x, const object1& y)
+{
+ return x.id == y.id && x.str == y.str;
+}
+
+#pragma db namespace schema(DB_SCHEMA)
+namespace ns // Extension.
+{
+ #pragma db object
+ struct object2
+ {
+ object2 (): obj1 (0) {}
+ ~object2 () {delete obj1;}
+
+ #pragma db id
+ std::string id;
+
+ std::vector<unsigned int> nums;
+ object1* obj1;
+ };
+
+ inline bool
+ operator== (const object2& x, const object2& y)
+ {
+ return x.id == y.id && x.nums == y.nums && *x.obj1 == *y.obj1;
+ }
+}
+
+#pragma db view object(object1) object(ns::object2)
+struct object_view
+{
+ #pragma db column(ns::object2::id)
+ std::string id2;
+
+ std::string str;
+};
+
+#pragma db view table(DB_SCHEMA."schema_ns_object_1")
+struct table_view
+{
+ #pragma db column(DB_SCHEMA."schema_ns_object_1"."str")
+ std::string str;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/schema/namespace/testscript b/odb-tests/common/schema/namespace/testscript
new file mode 100644
index 0000000..0fabe6e
--- /dev/null
+++ b/odb-tests/common/schema/namespace/testscript
@@ -0,0 +1,33 @@
+# file : common/schema/namespace/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/section/basics/buildfile b/odb-tests/common/section/basics/buildfile
new file mode 100644
index 0000000..5ccdd6b
--- /dev/null
+++ b/odb-tests/common/section/basics/buildfile
@@ -0,0 +1,41 @@
+# file : common/section/basics/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_section_b_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/section/basics/driver.cxx b/odb-tests/common/section/basics/driver.cxx
new file mode 100644
index 0000000..53783a3
--- /dev/null
+++ b/odb-tests/common/section/basics/driver.cxx
@@ -0,0 +1,1735 @@
+// file : common/section/basics/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test object section basics.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/session.hxx>
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+struct failed {};
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv));
+
+ // Test lazy-loaded, always updating section.
+ //
+ {
+ using namespace test1;
+
+ object o (123, "abc");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (!p->s.loaded ());
+ assert (p->n == o.n &&
+ p->sn != o.sn && p->ss != o.ss && p->sv != o.sv);
+
+ db->load (*p, p->s);
+
+ assert (p->s.loaded ());
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+
+ t.commit ();
+ }
+
+ // Update.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+ t.commit ();
+ }
+
+ // We can also update just the section.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o, o.s);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->n != o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+ t.commit ();
+ }
+
+ // Test updating unloaded section.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+ o.s.unload ();
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->n == o.n &&
+ p->sn != o.sn && p->ss != o.ss && p->sv != o.sv);
+ t.commit ();
+ }
+
+ // Test reloading of loaded/unloaded sections.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+ o.s.unload ();
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ db->reload (o);
+ assert (p->n == o.n &&
+ p->sn != o.sn && p->ss != o.ss && p->sv != o.sv);
+ db->load (o, o.s);
+ t.commit ();
+ }
+
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ db->reload (o);
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+ t.commit ();
+ }
+
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ // Make sure we can access section members in queries.
+ //
+ {
+ transaction t (db->begin ());
+
+ result r (db->query<object> (query::ss == o.ss));
+ result::iterator i (r.begin ());
+
+ assert (i != r.end () && !i->s.loaded ());
+
+ db->load (*i, i->s);
+ assert (i->n == o.n &&
+ i->sn == o.sn && i->ss == o.ss && i->sv == o.sv);
+
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+
+ // Make sure we can load/update sections without messing up the
+ // loaded object's image.
+ //
+ {
+ transaction t (db->begin ());
+
+ result r (db->query<object> (query::ss == o.ss));
+ result::iterator i (r.begin ());
+
+ assert (i != r.end ());
+
+ object o1;
+ i.load (o1);
+ db->load (o1, o1.s);
+ assert (o1.n == o.n &&
+ o1.sn == o.sn && o1.ss == o.ss && o1.sv == o.sv);
+
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+ db->update (o, o.s);
+
+ object o2;
+ i.load (o2);
+ db->load (o2, o2.s);
+ assert (o2.n == o1.n &&
+ o2.sn == o.sn && o2.ss == o.ss && o2.sv == o.sv);
+
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+ }
+
+ // Test lazy-loaded, change-updated section.
+ //
+ {
+ using namespace test2;
+
+ object o (123, "abc");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (!p->s.loaded ());
+ assert (p->n == o.n &&
+ p->sn != o.sn && p->ss != o.ss && p->sv != o.sv);
+
+ db->load (*p, p->s);
+
+ assert (p->s.loaded ());
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+
+ t.commit ();
+ }
+
+ // Update but don't mark as changed.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->n == o.n &&
+ p->sn != o.sn && p->ss != o.ss && p->sv != o.sv);
+ t.commit ();
+ }
+
+ // Mark as changed.
+ //
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ assert (!o.s.changed ());
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+ t.commit ();
+ }
+
+ // We can also update just the section manually.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (o, o.s);
+ assert (!o.s.changed ());
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->n != o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+ t.commit ();
+ }
+ }
+
+ // Test lazy-loaded, manually-updated section.
+ //
+ {
+ using namespace test3;
+
+ object o (123, "abc");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (!p->s.loaded ());
+ assert (p->n == o.n &&
+ p->sn != o.sn && p->ss != o.ss && p->sv != o.sv);
+
+ db->load (*p, p->s);
+
+ assert (p->s.loaded ());
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+
+ t.commit ();
+ }
+
+ // Update the object only.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->n == o.n &&
+ p->sn != o.sn && p->ss != o.ss && p->sv != o.sv);
+ t.commit ();
+ }
+
+ // Update both the object and section.
+ //
+ o.n++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ db->update (o, o.s);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+ t.commit ();
+ }
+
+ // We can also update just the section.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o, o.s);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->n != o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+ t.commit ();
+ }
+
+ // Test detection of unloaded section update.
+ //
+ try
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->update (*p, p->s);
+ assert (false);
+ }
+ catch (const section_not_loaded&)
+ {
+ }
+ }
+
+ // Test eager-loaded, change-updated section.
+ //
+ {
+ using namespace test4;
+
+ object o (123, "abc");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (p->s.loaded ());
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+
+ t.commit ();
+ }
+
+ // Update but don't mark as changed.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->n == o.n &&
+ p->sn != o.sn && p->ss != o.ss && p->sv != o.sv);
+ t.commit ();
+ }
+
+ // Mark as changed.
+ //
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ assert (!o.s.changed ());
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+ t.commit ();
+ }
+ }
+
+ // Test eager-loaded, manually-updated section.
+ //
+ {
+ using namespace test5;
+
+ object o (123, "abc");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (p->s.loaded ());
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+
+ t.commit ();
+ }
+
+ // Update the object only.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->n == o.n &&
+ p->sn != o.sn && p->ss != o.ss && p->sv != o.sv);
+ t.commit ();
+ }
+
+ // Update both the object and section.
+ //
+ o.n++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ db->update (o, o.s);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+ t.commit ();
+ }
+
+ // We can also update just the section.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o, o.s);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->n != o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+ t.commit ();
+ }
+ }
+
+ // Test value-only and container-only section. Also multiple sections
+ // in an object.
+ //
+ {
+ using namespace test6;
+
+ object o (123, "abc");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s1.loaded ());
+ assert (o.s2.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (!p->s1.loaded ());
+ assert (!p->s2.loaded ());
+ assert (p->n == o.n &&
+ p->sn != o.sn && p->ss != o.ss && p->sv != o.sv);
+
+ db->load (*p, p->s1);
+ assert (p->s1.loaded ());
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv != o.sv);
+
+ db->load (*p, p->s2);
+ assert (p->s2.loaded ());
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+
+ t.commit ();
+ }
+
+ // Update.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s1);
+ db->load (*p, p->s2);
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+ t.commit ();
+ }
+
+ // We can also update just the section.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o, o.s1);
+ db->update (o, o.s2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s1);
+ db->load (*p, p->s2);
+ assert (p->n != o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+ t.commit ();
+ }
+ }
+
+ // Test value-only and container-only section. Also multiple sections
+ // in an object.
+ //
+ {
+ using namespace test7;
+
+ object o (123, "abc", true);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s1.loaded ());
+ assert (o.s2.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (!p->s1.loaded ());
+ assert (!p->s2.loaded ());
+ assert (p->sn1 != o.sn1 && p->ss1 != o.ss1 &&
+ p->sn2 != o.sn2 && p->ss2 != o.ss2 && p->sb2 != o.sb2);
+
+ db->load (*p, p->s1);
+ db->load (*p, p->s2);
+ assert (p->s1.loaded ());
+ assert (p->s2.loaded ());
+ assert (p->sn1 == o.sn1 && p->ss1 == o.ss1 &&
+ p->sn2 == o.sn2 && p->ss2 == o.ss2 && p->sb2 == o.sb2);
+
+
+ t.commit ();
+ }
+
+ // Update.
+ //
+ o.sn1++;
+ o.sn2++;
+ o.ss1 += 'd';
+ o.ss2 += 'd';
+ o.sb2 = !o.sb2;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s1);
+ db->load (*p, p->s2);
+ assert (p->sn1 == o.sn1 && p->ss1 == o.ss1 &&
+ p->sn2 == o.sn2 && p->ss2 == o.ss2 && p->sb2 == o.sb2);
+ t.commit ();
+ }
+
+ // Manual update of just the section.
+ //
+ o.sn1++;
+ o.sn2++;
+ o.ss1 += 'd';
+ o.ss2 += 'd';
+ o.sb2 = !o.sb2;
+
+ {
+ transaction t (db->begin ());
+ db->update (o, o.s2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s1);
+ db->load (*p, p->s2);
+ assert (p->sn1 != o.sn1 && p->ss1 != o.ss1 &&
+ p->sn2 == o.sn2 && p->ss2 == o.ss2 && p->sb2 == o.sb2);
+ t.commit ();
+ }
+ }
+
+ // Test readonly and inverse section members.
+ //
+ {
+ using namespace test8;
+
+ object1 o1 (new object (123, "abc"));
+ object& o (*o1.p);
+ o.sp = &o1;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ db->persist (o1);
+ t.commit ();
+
+ assert (o.s.loaded ());
+ }
+
+ {
+ session s;
+
+ transaction t (db->begin ());
+ unique_ptr<object1> p1 (db->load<object1> (o1.id));
+ object* p (p1->p);
+
+ assert (!p->s.loaded ());
+ assert (p->n == o.n &&
+ p->sn != o.sn && p->ss != o.ss && p->sp == 0);
+
+ db->load (*p, p->s);
+
+ assert (p->s.loaded ());
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sp->id == o.sp->id);
+
+ t.commit ();
+ }
+
+ // Update.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ session s;
+
+ transaction t (db->begin ());
+ unique_ptr<object1> p1 (db->load<object1> (o1.id));
+ object* p (p1->p);
+
+ db->load (*p, p->s);
+ assert (p->n == o.n &&
+ p->sn != o.sn && p->ss == o.ss && p->sp->id == o.sp->id);
+
+ t.commit ();
+ }
+ }
+
+ // Test object without any columns to load or update.
+ //
+ {
+ using namespace test9;
+
+ object o (123, "abc");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (!p->s.loaded ());
+ assert (o.id == o.id &&
+ p->sn != o.sn && p->ss != o.ss);
+
+ db->load (*p, p->s);
+
+ assert (p->s.loaded ());
+ assert (o.id == o.id &&
+ p->sn == o.sn && p->ss == o.ss);
+
+ t.commit ();
+ }
+
+ // Update object.
+ //
+ o.sn++;
+ o.ss += 'd';
+
+ {
+ transaction t (db->begin ());
+ db->update (o); // No-op.
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (o.id == o.id &&
+ p->sn != o.sn && p->ss != o.ss);
+ t.commit ();
+ }
+
+ // Update section.
+ //
+ {
+ transaction t (db->begin ());
+ db->update (o, o.s);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (o.id == o.id &&
+ p->sn == o.sn && p->ss == o.ss);
+ t.commit ();
+ }
+ }
+
+ // Test section without any columns or containers to update.
+ //
+ {
+ using namespace test10;
+
+ object o (123);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (!p->s.loaded ());
+ assert (p->n == o.n && p->sn != o.sn);
+
+ db->load (*p, p->s);
+
+ assert (p->s.loaded ());
+ assert (p->n == o.n && p->sn == o.sn);
+
+ t.commit ();
+ }
+
+ // Update.
+ //
+ o.n++;
+ o.sn++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ //db->update (o, o.s); // Error.
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->n == o.n && p->sn != o.sn);
+ t.commit ();
+ }
+ }
+
+ // Test section with composite member.
+ //
+ {
+ using namespace test11;
+
+ object o (123, "abc");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (!p->s.loaded ());
+ assert (p->n == o.n &&
+ p->sn != o.sn && p->sc.s != o.sc.s && p->sc.v != o.sc.v);
+
+ db->load (*p, p->s);
+
+ assert (p->s.loaded ());
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->sc.s == o.sc.s && p->sc.v == o.sc.v);
+
+ t.commit ();
+ }
+
+ // Update.
+ //
+ o.n++;
+ o.sn++;
+ o.sc.s += 'd';
+ o.sc.v[0]++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->sc.s == o.sc.s && p->sc.v == o.sc.v);
+ t.commit ();
+ }
+ }
+
+ // Test change state restoration on transaction rollback.
+ //
+ {
+ using namespace test12;
+
+ object o (123, "abc");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s.loaded ());
+ }
+
+ // Update.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+ o.s.change ();
+
+ try
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ assert (!o.s.changed ());
+ throw failed ();
+ }
+ catch (const failed&)
+ {
+ assert (o.s.changed ());
+ }
+
+ // Retry. Also test the object destruction before transaction
+ // termination case.
+ //
+ {
+ transaction t (db->begin ());
+ {
+ object c (o);
+ db->update (c);
+ assert (!c.s.changed ());
+ }
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->n == o.n && p->sn == o.sn && p->ss == o.ss);
+ t.commit ();
+ }
+ }
+
+ // Test section accessor/modifier.
+ //
+ {
+ using namespace test13;
+
+ object o (123, "abc");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s ().loaded ());
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (!p->s ().loaded ());
+ assert (p->n == o.n && p->sn != o.sn && p->ss != o.ss);
+
+ db->load (*p, p->rw_s ());
+
+ assert (p->s ().loaded ());
+ assert (p->n == o.n && p->sn == o.sn && p->ss == o.ss);
+
+ t.commit ();
+ }
+
+ // Update.
+ //
+ o.n++;
+ o.sn++;
+ o.ss += 'd';
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ db->update (o, o.s ());
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->rw_s ());
+ assert (p->n == o.n && p->sn == o.sn && p->ss == o.ss);
+ t.commit ();
+ }
+
+ // Test detection of section copy.
+ //
+ try
+ {
+ transaction t (db->begin ());
+ section c (o.s ());
+ db->update (o, c);
+ assert (false);
+ }
+ catch (const section_not_in_object&)
+ {
+ }
+ }
+
+ // Test LOB in section streaming, column re-ordering.
+ //
+ {
+ using namespace test14;
+
+ object o (1, 123, "\x01\x02\x03\x04\x05");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (!p->s.loaded ());
+ assert (p->n == o.n && p->sn != o.sn && p->sb != o.sb);
+
+ db->load (*p, p->s);
+
+ assert (p->s.loaded ());
+ assert (p->n == o.n && p->sn == o.sn && p->sb == o.sb);
+
+ t.commit ();
+ }
+
+ // Update.
+ //
+ o.n++;
+ o.sn++;
+ o.sb.push_back ('\x06');
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->n == o.n && p->sn == o.sn && p->sb == o.sb);
+ t.commit ();
+ }
+
+ // We can also update just the section.
+ //
+ o.n++;
+ o.sn++;
+ o.sb.push_back ('\x07');
+
+ {
+ transaction t (db->begin ());
+ db->update (o, o.s);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->n != o.n && p->sn == o.sn && p->sb == o.sb);
+ t.commit ();
+ }
+ }
+
+ // Test sections and optimistic concurrency.
+ //
+ {
+ using namespace test15;
+
+ object o (123, "abc");
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (!p->s.loaded ());
+ assert (p->n == o.n &&
+ p->sn != o.sn && p->ss != o.ss && p->sv != o.sv);
+
+ db->load (*p, p->s);
+
+ assert (p->s.loaded ());
+ assert (p->n == o.n &&
+ p->sn == o.sn && p->ss == o.ss && p->sv == o.sv);
+
+ t.commit ();
+ }
+
+ // Update object.
+ //
+ object o1 (o);
+ o1.n++;
+ o1.sn++;
+ o1.ss += 'd';
+ o1.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ db->update (o1);
+ assert (o.v != o1.v);
+
+ try
+ {
+ db->load (*p, p->s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*p);
+ assert (!p->s.loaded ());
+ db->load (*p, p->s);
+
+ assert (p->n == o1.n &&
+ p->sn == o1.sn && p->ss == o1.ss && p->sv == o1.sv);
+ }
+
+ db->reload (o);
+ assert (o.v == o1.v);
+ assert (o.n == o1.n &&
+ o.sn == o1.sn && o.ss == o1.ss && o.sv == o1.sv);
+ t.commit ();
+ }
+
+ // Update section.
+ //
+ o1.sn++;
+ o1.ss += 'd';
+ o1.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ db->update (o1, o1.s);
+ assert (o.v != o1.v);
+
+ // Double-check object version was updated.
+ //
+ {
+ unique_ptr<object> p1 (db->load<object> (o.id));
+ assert (o1.v == p1->v);
+ }
+
+ try
+ {
+ db->load (*p, p->s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*p);
+ assert (!p->s.loaded ());
+ db->load (*p, p->s);
+
+ assert (p->n == o1.n &&
+ p->sn == o1.sn && p->ss == o1.ss && p->sv == o1.sv);
+ }
+
+ db->reload (o);
+ assert (o.v == o1.v);
+ assert (o.n == o1.n &&
+ o.sn == o1.sn && o.ss == o1.ss && o.sv == o1.sv);
+ t.commit ();
+ }
+
+ // Update changed section.
+ //
+ o1.sn++;
+ o1.ss += 'd';
+ o1.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o1, o1.s);
+
+ try
+ {
+ db->update (o, o.s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (o);
+ db->update (o, o.s);
+ }
+
+ t.commit ();
+ }
+ }
+
+ // Test container-only sections and optimistic concurrency.
+ //
+ {
+ using namespace test16;
+
+ object o (123);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (!p->s.loaded ());
+ assert (p->sv != o.sv);
+
+ db->load (*p, p->s);
+
+ assert (p->s.loaded ());
+ assert (p->sv == o.sv);
+
+ t.commit ();
+ }
+
+ // Update object.
+ //
+ object o1 (o);
+ o1.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ db->update (o1);
+ assert (o.v != o1.v);
+
+ try
+ {
+ db->load (*p, p->s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*p);
+ assert (!p->s.loaded ());
+ db->load (*p, p->s);
+
+ assert (p->sv == o1.sv);
+ }
+
+ db->reload (o);
+ assert (o.v == o1.v);
+ assert (o.sv == o1.sv);
+ t.commit ();
+ }
+
+ // Update section.
+ //
+ o1.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ db->update (o1, o1.s);
+ assert (o.v != o1.v);
+
+ // Double-check object version was updated.
+ //
+ {
+ unique_ptr<object> p1 (db->load<object> (o.id));
+ assert (o1.v == p1->v);
+ }
+
+ try
+ {
+ db->load (*p, p->s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*p);
+ assert (!p->s.loaded ());
+ db->load (*p, p->s);
+
+ assert (p->sv == o1.sv);
+ }
+
+ db->reload (o);
+ assert (o.v == o1.v);
+ assert (o.sv == o1.sv);
+ t.commit ();
+ }
+
+ // Update changed section.
+ //
+ o1.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o1, o1.s);
+
+ try
+ {
+ db->update (o, o.s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (o);
+ db->update (o, o.s);
+ }
+
+ t.commit ();
+ }
+ }
+
+ // Test reuse-inheritance, sections, and optimistic concurrency.
+ //
+ {
+ using namespace test17;
+
+ object o (123);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s1.loaded ());
+ assert (o.s2.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (!p->s1.loaded ());
+ assert (!p->s2.loaded ());
+ assert (p->s1n != o.s1n && p->s2v != o.s2v);
+
+ db->load (*p, p->s1);
+ db->load (*p, p->s2);
+
+ assert (p->s1.loaded ());
+ assert (p->s2.loaded ());
+ assert (p->s1n == o.s1n && p->s2v == o.s2v);
+
+ t.commit ();
+ }
+
+ object o1 (o);
+
+ // Update object.
+ //
+ for (unsigned short s (1); s < 3; ++s)
+ {
+ o1.s1n++;
+ o1.s2v[0]++;
+
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ db->update (o1);
+ assert (o.v != o1.v);
+
+ try
+ {
+ switch (s)
+ {
+ case 1: db->load (*p, p->s1); break;
+ case 2: db->load (*p, p->s2); break;
+ default: break;
+ }
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*p);
+
+ assert (!p->s1.loaded ());
+ assert (!p->s2.loaded ());
+
+ db->load (*p, p->s1);
+ db->load (*p, p->s2);
+
+ assert (p->s1n == o1.s1n && p->s2v == o1.s2v);
+ }
+
+ db->reload (o);
+ assert (o.v == o1.v);
+ assert (o.s1n == o1.s1n && o.s2v == o1.s2v);
+ t.commit ();
+ }
+
+ // Update section.
+ //
+ for (unsigned short s (1); s < 3; ++s)
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ switch (s)
+ {
+ case 1:
+ o1.s1n++;
+ db->update (o1, o1.s1);
+ assert (o.v != o1.v);
+ break;
+ case 2:
+ o1.s2v[0]++;
+ db->update (o1, o1.s2);
+ assert (o.v != o1.v);
+ break;
+ default: break;
+ }
+
+ try
+ {
+ switch (s)
+ {
+ case 1: db->load (*p, p->s1); break;
+ case 2: db->load (*p, p->s2); break;
+ default: break;
+ }
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*p);
+
+ assert (!p->s1.loaded ());
+ assert (!p->s2.loaded ());
+
+ db->load (*p, p->s1);
+ db->load (*p, p->s2);
+
+ assert (p->s1n == o1.s1n && p->s2v == o1.s2v);
+ }
+
+ db->reload (o);
+ assert (o.v == o1.v);
+ assert (o.s1n == o1.s1n && o.s2v == o1.s2v);
+ t.commit ();
+ }
+
+ // Update changed section.
+ //
+ for (unsigned short s (1); s < 3; ++s)
+ {
+ transaction t (db->begin ());
+
+ switch (s)
+ {
+ case 1:
+ o1.s1n++;
+ db->update (o1, o1.s1);
+ break;
+ case 2:
+ o1.s2v[0]++;
+ db->update (o1, o1.s2);
+ break;
+ default: break;
+ }
+
+ try
+ {
+ switch (s)
+ {
+ case 1: db->update (o, o.s1); break;
+ case 2: db->update (o, o.s2); break;
+ default: break;
+ }
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (o);
+
+ switch (s)
+ {
+ case 1: db->update (o, o.s1); break;
+ case 2: db->update (o, o.s2); break;
+ default: break;
+ }
+ }
+
+ db->reload (o1);
+
+ t.commit ();
+ }
+ }
+
+ // Test change-updated section and change-tracking container.
+ //
+ {
+ using namespace test18;
+
+ object o (123);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+
+ assert (o.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ assert (!p->s.loaded ());
+ assert (p->sn != o.sn && p->sv != o.sv);
+
+ db->load (*p, p->s);
+
+ assert (p->s.loaded ());
+ assert (p->sn == o.sn && p->sv == o.sv);
+
+ t.commit ();
+ }
+
+ // Update but don't mark as changed.
+ //
+ o.sn++;
+ o.sv.modify (0)++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o); // Automatically marked as changed.
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->sn == o.sn && p->sv == o.sv);
+ t.commit ();
+ }
+
+ // Test updating just the section manually.
+ //
+ o.sn++;
+ o.sv.modify (0)++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o, o.s);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+ db->load (*p, p->s);
+ assert (p->sn == o.sn && p->sv == o.sv);
+ t.commit ();
+ }
+ }
+
+ // Regression: BLOB in a value type used as a map value that is in a
+ // section.
+ //
+ {
+ using namespace test19;
+
+ object o;
+ o.m[1].b.assign (560, 'x'); // Size greater than the default image.
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ // Hold "old" connection to force a new set of statements/image
+ // buffers.
+ //
+ connection_ptr c (db->connection ());
+
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id));
+
+ db->load (*p, p->s);
+ assert (p->m[1].b == o.m[1].b);
+
+ t.commit ();
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/section/basics/test.hxx b/odb-tests/common/section/basics/test.hxx
new file mode 100644
index 0000000..702ef8b
--- /dev/null
+++ b/odb-tests/common/section/basics/test.hxx
@@ -0,0 +1,628 @@
+// file : common/section/basics/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <vector>
+
+#include <odb/core.hxx>
+#include <odb/section.hxx>
+#include <odb/vector.hxx>
+
+#ifdef ODB_COMPILER
+# if defined(ODB_DATABASE_PGSQL)
+# define BLOB_TYPE "BYTEA"
+# elif defined(ODB_DATABASE_MSSQL)
+# define BLOB_TYPE "VARBINARY(max)"
+# else
+# define BLOB_TYPE "BLOB"
+# endif
+#endif
+
+// Test lazy-loaded, always-updated section.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ #pragma db object
+ struct object
+ {
+ object (int n_ = 999, const std::string& s_ = "xxx")
+ : sn (n_), n (n_), ss (s_) {sv.push_back (n_);}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db load(lazy)
+ odb::section s;
+
+ #pragma db section(s)
+ int sn;
+
+ int n;
+
+ #pragma db section(s)
+ std::string ss;
+
+ #pragma db section(s)
+ std::vector<int> sv;
+ };
+}
+
+// Test lazy-loaded, change-updated section.
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ #pragma db object
+ struct object
+ {
+ object (int n_ = 999, const std::string& s_ = "xxx")
+ : sn (n_), n (n_), ss (s_) {sv.push_back (n_);}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db load(lazy) update(change)
+ odb::section s;
+
+ #pragma db section(s)
+ int sn;
+
+ int n;
+
+ #pragma db section(s)
+ std::string ss;
+
+ #pragma db section(s)
+ std::vector<int> sv;
+ };
+}
+
+// Test lazy-loaded, manually-updated section.
+//
+#pragma db namespace table("t3_")
+namespace test3
+{
+ #pragma db object
+ struct object
+ {
+ object (int n_ = 999, const std::string& s_ = "xxx")
+ : sn (n_), n (n_), ss (s_) {sv.push_back (n_);}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db load(lazy) update(manual)
+ odb::section s;
+
+ #pragma db section(s)
+ int sn;
+
+ int n;
+
+ #pragma db section(s)
+ std::string ss;
+
+ #pragma db section(s)
+ std::vector<int> sv;
+ };
+}
+
+// Test eager-loaded, change-updated section.
+//
+#pragma db namespace table("t4_")
+namespace test4
+{
+ #pragma db object
+ struct object
+ {
+ object (int n_ = 999, const std::string& s_ = "xxx")
+ : sn (n_), n (n_), ss (s_) {sv.push_back (n_);}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db section(s)
+ int sn;
+
+ #pragma db update(change)
+ odb::section s;
+
+ int n;
+
+ #pragma db section(s)
+ std::string ss;
+
+ #pragma db section(s)
+ std::vector<int> sv;
+ };
+}
+
+// Test eager-loaded, manually-updated section.
+//
+#pragma db namespace table("t5_")
+namespace test5
+{
+ #pragma db object
+ struct object
+ {
+ object (int n_ = 999, const std::string& s_ = "xxx")
+ : sn (n_), n (n_), ss (s_) {sv.push_back (n_);}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db section(s)
+ int sn;
+
+ #pragma db update(manual)
+ odb::section s;
+
+ int n;
+
+ #pragma db section(s)
+ std::string ss;
+
+ #pragma db section(s)
+ std::vector<int> sv;
+ };
+}
+
+// Test value-only and container-only section. Also multiple sections
+// in an object.
+//
+#pragma db namespace table("t6_")
+namespace test6
+{
+ #pragma db object
+ struct object
+ {
+ object (int n_ = 999, const std::string& s_ = "xxx")
+ : n (n_), sn (n_), ss (s_) {sv.push_back (n_);}
+
+ #pragma db id auto
+ unsigned long id;
+
+ int n;
+
+ #pragma db load(lazy)
+ odb::section s1;
+
+ #pragma db load(lazy)
+ odb::section s2;
+
+ #pragma db section(s1)
+ int sn;
+
+ #pragma db section(s1)
+ std::string ss;
+
+ #pragma db section(s2)
+ std::vector<int> sv;
+ };
+}
+
+// Test sections and reuse inheritance.
+//
+#pragma db namespace table("t7_")
+namespace test7
+{
+ #pragma db object abstract
+ struct base
+ {
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db load(lazy)
+ odb::section s1; // Empty section.
+
+ #pragma db load(lazy)
+ odb::section s2;
+
+ #pragma db section(s2)
+ int sn2;
+ };
+
+ #pragma db object abstract
+ struct interm: base
+ {
+ // Section s1 is still empty.
+
+ #pragma db section(s2)
+ std::string ss2;
+ };
+
+ #pragma db object
+ struct derived: interm
+ {
+ #pragma db section(s1)
+ int sn1;
+ };
+
+ #pragma db object
+ struct object: derived
+ {
+ object (int n = 999, const std::string& s = "xxx", bool b = false)
+ {
+ sn1 = sn2 = n;
+ ss1 = ss2 = s;
+ sb2 = b;
+ }
+
+ #pragma db section(s1)
+ std::string ss1;
+
+ #pragma db section(s2)
+ bool sb2;
+ };
+}
+
+// Test readonly and inverse section members.
+//
+#pragma db namespace table("t8_")
+namespace test8
+{
+ struct object1;
+
+ #pragma db object session
+ struct object
+ {
+ object (int n_ = 999, const std::string& s_ = "xxx", object1* p_ = 0)
+ : n (n_), sn (n_), ss (s_), sp (p_) {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ int n;
+
+ #pragma db load(lazy)
+ odb::section s;
+
+ #pragma db section(s) readonly
+ int sn;
+
+ #pragma db section(s)
+ std::string ss;
+
+ #pragma db inverse(p) section(s)
+ object1* sp;
+ };
+
+ #pragma db object session
+ struct object1
+ {
+ object1 (object* p_ = 0): p (p_) {}
+ ~object1 () {delete p;}
+
+ #pragma db id auto
+ unsigned long id;
+
+ object* p;
+ };
+}
+
+// Test object without any columns to load or update.
+//
+#pragma db namespace table("t9_")
+namespace test9
+{
+ #pragma db object
+ struct object
+ {
+ object (int n_ = 999, const std::string& s_ = "xxx"): sn (n_), ss (s_) {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db load(lazy) update(manual)
+ odb::section s;
+
+ #pragma db section(s)
+ int sn;
+
+ #pragma db section(s)
+ std::string ss;
+ };
+}
+
+// Test section without any columns or containers to update.
+//
+#pragma db namespace table("t10_")
+namespace test10
+{
+ #pragma db object
+ struct object
+ {
+ object (int n_ = 999): n (n_), sn (n_) {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ int n;
+
+ #pragma db load(lazy)
+ odb::section s;
+
+ #pragma db section(s) readonly
+ int sn;
+ };
+}
+
+// Test section with composite member.
+//
+#pragma db namespace table("t11_")
+namespace test11
+{
+ #pragma db value
+ struct comp
+ {
+ comp (int n_, const std::string& s_): s (s_) {v.push_back (n_);}
+
+ std::string s;
+ std::vector<int> v;
+ };
+
+ #pragma db object
+ struct object
+ {
+ object (int n_ = 999, const std::string& s_ = "xxx")
+ : n (n_), sn (n_), sc (n_, s_) {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ int n;
+
+ #pragma db load(lazy)
+ odb::section s;
+
+ #pragma db section(s)
+ int sn;
+
+ #pragma db section(s)
+ comp sc;
+ };
+}
+
+// Test change state restoration on transaction rollback.
+//
+#pragma db namespace table("t12_")
+namespace test12
+{
+ #pragma db object
+ struct object
+ {
+ object (int n_ = 999, const std::string& s_ = "xxx")
+ : sn (n_), n (n_), ss (s_) {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db load(lazy) update(change)
+ odb::section s;
+
+ #pragma db section(s)
+ int sn;
+
+ int n;
+
+ #pragma db section(s)
+ std::string ss;
+ };
+}
+
+// Test section accessor/modifier.
+//
+#pragma db namespace table("t13_")
+namespace test13
+{
+ #pragma db object
+ struct object
+ {
+ object (int n_ = 999, const std::string& s_ = "xxx")
+ : n (n_), sn (n_), ss (s_) {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ int n;
+
+ #pragma db section(s_)
+ int sn;
+
+ #pragma db section(s_)
+ std::string ss;
+
+ public:
+ const odb::section&
+ s () const {return s_;}
+
+ odb::section&
+ rw_s () {return s_;}
+
+ private:
+ #pragma db load(lazy) update(manual)
+ odb::section s_;
+ };
+}
+
+// Test LOB in section streaming, column re-ordering.
+//
+#pragma db namespace table("t14_")
+namespace test14
+{
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id_ = 0, int n_ = 999, const std::string& s_ = "xxx")
+ : id (id_), sb (s_.begin (), s_.end ()), sn (n_), n (n_) {}
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db load(lazy)
+ odb::section s;
+
+ #pragma db section(s) type(BLOB_TYPE)
+ std::vector<char> sb; // Comes before sn.
+
+ #pragma db section(s)
+ int sn;
+
+ int n;
+ };
+}
+
+// Test sections and optimistic concurrency.
+//
+#pragma db namespace table("t15_")
+namespace test15
+{
+ #pragma db object optimistic
+ struct object
+ {
+ object (int n_ = 999, const std::string& s_ = "xxx")
+ : sn (n_), n (n_), ss (s_) {sv.push_back (n_);}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db version mssql:type("ROWVERSION")
+ unsigned long long v;
+
+ #pragma db load(lazy)
+ odb::section s;
+
+ #pragma db section(s)
+ int sn;
+
+ int n;
+
+ #pragma db section(s)
+ std::string ss;
+
+ #pragma db section(s)
+ std::vector<int> sv;
+ };
+}
+
+// Test container-only sections and optimistic concurrency.
+//
+#pragma db namespace table("t16_")
+namespace test16
+{
+ #pragma db object optimistic
+ struct object
+ {
+ object (int n = 999) {sv.push_back (n);}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db version // mssql:type("ROWVERSION")
+ unsigned long long v;
+
+ #pragma db load(lazy)
+ odb::section s;
+
+ #pragma db section(s)
+ std::vector<int> sv;
+ };
+}
+
+// Test reuse-inheritance, sections, and optimistic concurrency.
+//
+#pragma db namespace table("t17_")
+namespace test17
+{
+ #pragma db object optimistic sectionable abstract
+ struct root
+ {
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db version
+ unsigned long long v;
+ };
+
+ #pragma db object
+ struct base: root
+ {
+ };
+
+ #pragma db object
+ struct object: base
+ {
+ object (int n = 999): s1n (n) {s2v.push_back (n);}
+
+ #pragma db load(lazy)
+ odb::section s1;
+
+ #pragma db section(s1)
+ int s1n;
+
+ #pragma db load(lazy)
+ odb::section s2;
+
+ #pragma db section(s2)
+ std::vector<int> s2v;
+ };
+}
+
+// Test change-updated section and change-tracking container.
+//
+#pragma db namespace table("t18_")
+namespace test18
+{
+ #pragma db object
+ struct object
+ {
+ object (int n = 999): sn (n) {sv.push_back (n);}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db load(lazy) update(change)
+ odb::section s;
+
+ #pragma db section(s)
+ int sn;
+
+ #pragma db section(s)
+ odb::vector<int> sv;
+ };
+}
+
+// Regression: BLOB in a value type used as a map value that is in a section.
+//
+#include <map>
+#include <vector>
+
+#pragma db namespace table("t19_")
+namespace test19
+{
+ #pragma db value
+ struct value
+ {
+ #pragma db type(BLOB_TYPE)
+ std::vector<char> b;
+ };
+
+ #pragma db object
+ struct object
+ {
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db load(lazy) update(always)
+ odb::section s;
+
+ #pragma db section(s)
+ std::map<int, value> m;
+ };
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/section/basics/testscript b/odb-tests/common/section/basics/testscript
new file mode 100644
index 0000000..c0c6617
--- /dev/null
+++ b/odb-tests/common/section/basics/testscript
@@ -0,0 +1,33 @@
+# file : common/section/basics/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/section/polymorphism/buildfile b/odb-tests/common/section/polymorphism/buildfile
new file mode 100644
index 0000000..b9a7514
--- /dev/null
+++ b/odb-tests/common/section/polymorphism/buildfile
@@ -0,0 +1,41 @@
+# file : common/section/polymorphism/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_section_p_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/section/polymorphism/driver.cxx b/odb-tests/common/section/polymorphism/driver.cxx
new file mode 100644
index 0000000..c15d317
--- /dev/null
+++ b/odb-tests/common/section/polymorphism/driver.cxx
@@ -0,0 +1,1807 @@
+// file : common/section/polymorphism/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test sections in polymorphic objects.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/session.hxx>
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+struct failed {};
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv));
+
+ // Test basic polymorphic section functionality.
+ //
+ {
+ using namespace test1;
+
+ base b (123, "abc");
+ derived d (234, "bcd", true);
+
+ {
+ transaction t (db->begin ());
+ db->persist (b);
+ db->persist (d);
+ t.commit ();
+
+ assert (b.rs1.loaded ());
+ assert (b.rs2.loaded ());
+ assert (b.rs3.loaded ());
+ assert (b.rs4.loaded ());
+ assert (b.bs1.loaded ());
+
+ assert (d.rs1.loaded ());
+ assert (d.rs2.loaded ());
+ assert (d.rs3.loaded ());
+ assert (d.rs4.loaded ());
+ assert (d.bs1.loaded ());
+ assert (d.ds1.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ assert (!pb->rs1.loaded ());
+ assert (!pb->rs2.loaded ());
+ assert (!pb->rs3.loaded ());
+ assert (!pb->rs4.loaded ());
+ assert (!pb->bs1.loaded ());
+
+ assert (!pd->rs1.loaded ());
+ assert (!pd->rs2.loaded ());
+ assert (!pd->rs3.loaded ());
+ assert (!pd->rs4.loaded ());
+ assert (!pd->bs1.loaded ());
+ assert (!pd->ds1.loaded ());
+
+ assert (pb->rs1n != b.rs1n && pb->rs1s != b.rs1s &&
+ pb->rs2n != b.rs2n && pb->rs2v != b.rs2v &&
+ pb->rs3v != b.rs3v &&
+ pb->rs4n != b.rs4n &&
+ pb->bs1n != b.bs1n);
+
+ assert (pd->rs1n != d.rs1n && pd->rs1s != d.rs1s &&
+ pd->rs1b != d.rs1b && pd->rs1v != d.rs1v &&
+ pd->rs2n != d.rs2n && pd->rs2v != d.rs2v &&
+ pd->rs3v != d.rs3v && pd->rs3n != d.rs3n &&
+ pd->rs4n != d.rs4n && pd->rs4s != d.rs4s &&
+ pd->bs1n != d.bs1n && pd->bs1s != d.bs1s &&
+ pd->ds1n != d.ds1n);
+
+ db->load (*pb, pb->rs1);
+ db->load (*pb, pb->rs2);
+ db->load (*pb, pb->rs3);
+ db->load (*pb, pb->rs4);
+ db->load (*pb, pb->bs1);
+
+ root* pr (pd.get ());
+ db->load (*pr, pr->rs1); // Via base.
+ db->load (*pd, pd->rs2);
+ db->load (*pr, pr->rs3); // Via base.
+ db->load (*pd, pd->rs4);
+ db->load (*pd, pd->bs1);
+ db->load (*pd, pd->ds1);
+
+ try
+ {
+ db->load (*pr, pd->bs1); // Object-section association is static.
+ assert (false);
+ }
+ catch (const section_not_in_object&) {}
+
+ assert (pb->rs1.loaded ());
+ assert (pb->rs2.loaded ());
+ assert (pb->rs3.loaded ());
+ assert (pb->rs4.loaded ());
+ assert (pb->bs1.loaded ());
+
+ assert (pd->rs1.loaded ());
+ assert (pd->rs2.loaded ());
+ assert (pd->rs3.loaded ());
+ assert (pd->rs4.loaded ());
+ assert (pd->bs1.loaded ());
+ assert (pd->ds1.loaded ());
+
+ assert (pb->rs1n == b.rs1n && pb->rs1s == b.rs1s &&
+ pb->rs2n == b.rs2n && pb->rs2v == b.rs2v &&
+ pb->rs3v == b.rs3v &&
+ pb->rs4n == b.rs4n &&
+ pb->bs1n == b.bs1n);
+
+ assert (pd->rs1n == d.rs1n && pd->rs1s == d.rs1s &&
+ pd->rs1b == d.rs1b && pd->rs1v == d.rs1v &&
+ pd->rs2n == d.rs2n && pd->rs2v == d.rs2v &&
+ pd->rs3v == d.rs3v && pd->rs3n == d.rs3n &&
+ pd->rs4n == d.rs4n && pd->rs4s == d.rs4s &&
+ pd->bs1n == d.bs1n && pd->bs1s == d.bs1s &&
+ pd->ds1n == d.ds1n);
+ t.commit ();
+ }
+
+ // Update object.
+ //
+ b.rs1n++;
+ b.rs1s += 'd';
+ b.rs1.change ();
+ b.rs2n++;
+ b.rs2v[0]++;
+ b.rs3v[0]++;
+ b.rs4n++;
+ b.bs1n++;
+
+ d.rs1n++;
+ d.rs1s += 'e';
+ d.rs1b = !d.rs1b;
+ d.rs1v[0]++;
+ d.rs1.change ();
+ d.rs2n++;
+ d.rs2v[0]++;
+ d.rs3v[0]++;
+ d.rs3n++;
+ d.rs4n++;
+ d.rs4s += 'e';
+ d.bs1n++;
+ d.bs1s += 'e';
+ d.ds1n++;
+
+ {
+ transaction t (db->begin ());
+ db->update (b);
+ db->update (d);
+ t.commit ();
+
+ assert (!b.rs1.changed ());
+ assert (!d.rs1.changed ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->load (*pb, pb->rs1);
+ db->load (*pb, pb->rs2);
+ db->load (*pb, pb->rs3);
+ db->load (*pb, pb->rs4);
+ db->load (*pb, pb->bs1);
+
+ db->load (*pd, pd->rs1);
+ db->load (*pd, pd->rs2);
+ db->load (*pd, pd->rs3);
+ db->load (*pd, pd->rs4);
+ db->load (*pd, pd->bs1);
+ db->load (*pd, pd->ds1);
+
+ assert (pb->rs1n == b.rs1n && pb->rs1s == b.rs1s &&
+ pb->rs2n == b.rs2n && pb->rs2v == b.rs2v &&
+ pb->rs3v == b.rs3v &&
+ pb->rs4n == b.rs4n &&
+ pb->bs1n == b.bs1n);
+
+ assert (pd->rs1n == d.rs1n && pd->rs1s == d.rs1s &&
+ pd->rs1b == d.rs1b && pd->rs1v == d.rs1v &&
+ pd->rs2n == d.rs2n && pd->rs2v == d.rs2v &&
+ pd->rs3v == d.rs3v && pd->rs3n == d.rs3n &&
+ pd->rs4n == d.rs4n && pd->rs4s == d.rs4s &&
+ pd->bs1n == d.bs1n && pd->bs1s == d.bs1s &&
+ pd->ds1n == d.ds1n);
+ t.commit ();
+ }
+
+ // Update section.
+ //
+ b.rs1n++;
+ b.rs1s += 'd';
+ b.rs2n++;
+ b.rs2v[0]++;
+ b.rs3v[0]++;
+ b.rs4n++;
+ b.bs1n++;
+
+ d.rs1n++;
+ d.rs1s += 'e';
+ d.rs1b = !d.rs1b;
+ d.rs1v[0]++;
+ d.rs2n++;
+ d.rs2v[0]++;
+ d.rs3v[0]++;
+ d.rs3n++;
+ d.rs4n++;
+ d.rs4s += 'e';
+ d.bs1n++;
+ d.bs1s += 'e';
+ d.ds1n++;
+
+ {
+ transaction t (db->begin ());
+ db->update (b, b.rs1);
+ db->update (b, b.rs2);
+ db->update (b, b.rs3);
+ db->update (b, b.rs4);
+ db->update (b, b.bs1);
+
+ db->update (d, d.rs1);
+ db->update (d, d.rs2);
+ db->update (d, d.rs3);
+ db->update (d, d.rs4);
+ db->update (d, d.bs1);
+ db->update (d, d.ds1);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->load (*pb, pb->rs1);
+ db->load (*pb, pb->rs2);
+ db->load (*pb, pb->rs3);
+ db->load (*pb, pb->rs4);
+ db->load (*pb, pb->bs1);
+
+ db->load (*pd, pd->rs1);
+ db->load (*pd, pd->rs2);
+ db->load (*pd, pd->rs3);
+ db->load (*pd, pd->rs4);
+ db->load (*pd, pd->bs1);
+ db->load (*pd, pd->ds1);
+
+ assert (pb->rs1n == b.rs1n && pb->rs1s == b.rs1s &&
+ pb->rs2n == b.rs2n && pb->rs2v == b.rs2v &&
+ pb->rs3v == b.rs3v &&
+ pb->rs4n == b.rs4n &&
+ pb->bs1n == b.bs1n);
+
+ assert (pd->rs1n == d.rs1n && pd->rs1s == d.rs1s &&
+ pd->rs1b == d.rs1b && pd->rs1v == d.rs1v &&
+ pd->rs2n == d.rs2n && pd->rs2v == d.rs2v &&
+ pd->rs3v == d.rs3v && pd->rs3n == d.rs3n &&
+ pd->rs4n == d.rs4n && pd->rs4s == d.rs4s &&
+ pd->bs1n == d.bs1n && pd->bs1s == d.bs1s &&
+ pd->ds1n == d.ds1n);
+ t.commit ();
+ }
+
+ // Reload.
+ //
+ b.rs1n++;
+ b.rs1s += 'd';
+ b.rs1.change ();
+ b.rs2n++;
+ b.rs2v[0]++;
+ b.rs3v[0]++;
+ b.rs4n++;
+ b.bs1n++;
+
+ d.rs1n++;
+ d.rs1s += 'e';
+ d.rs1b = !d.rs1b;
+ d.rs1v[0]++;
+ d.rs1.change ();
+ d.rs2n++;
+ d.rs2v[0]++;
+ d.rs3v[0]++;
+ d.rs3n++;
+ d.rs4n++;
+ d.rs4s += 'e';
+ d.bs1n++;
+ d.bs1s += 'e';
+ d.ds1n++;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->load (*pb, pb->rs1);
+ db->load (*pb, pb->rs2);
+ db->load (*pb, pb->rs3);
+ db->load (*pb, pb->rs4);
+ db->load (*pb, pb->bs1);
+
+ db->load (*pd, pd->rs1);
+ db->load (*pd, pd->rs2);
+ db->load (*pd, pd->rs3);
+ db->load (*pd, pd->rs4);
+ db->load (*pd, pd->bs1);
+ db->load (*pd, pd->ds1);
+
+ db->update (b);
+ db->update (d);
+
+ db->reload (*pb);
+ db->reload (*pd);
+
+ assert (pb->rs1n == b.rs1n && pb->rs1s == b.rs1s &&
+ pb->rs2n == b.rs2n && pb->rs2v == b.rs2v &&
+ pb->rs3v == b.rs3v &&
+ pb->rs4n == b.rs4n &&
+ pb->bs1n == b.bs1n);
+
+ assert (pd->rs1n == d.rs1n && pd->rs1s == d.rs1s &&
+ pd->rs1b == d.rs1b && pd->rs1v == d.rs1v &&
+ pd->rs2n == d.rs2n && pd->rs2v == d.rs2v &&
+ pd->rs3v == d.rs3v && pd->rs3n == d.rs3n &&
+ pd->rs4n == d.rs4n && pd->rs4s == d.rs4s &&
+ pd->bs1n == d.bs1n && pd->bs1s == d.bs1s &&
+ pd->ds1n == d.ds1n);
+
+ t.commit ();
+ }
+ }
+
+ // Test empty section and override "gap".
+ //
+ {
+ using namespace test2;
+
+ derived d (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (d);
+ t.commit ();
+
+ assert (d.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ assert (!pd->s.loaded ());
+ assert (pd->sn != d.sn && pd->sv != d.sv);
+
+ root* pr (pd.get ());
+ db->load (*pr, pr->s); // Via root.
+
+ assert (pd->s.loaded ());
+ assert (pd->sn == d.sn && pd->sv == d.sv);
+ t.commit ();
+ }
+
+ // Update object.
+ //
+ d.sn++;
+ d.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ root* pr (&d);
+ db->update (pr); // Via root.
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ base* pb (pd.get ());
+ db->load (*pb, pb->s); // Via base.
+
+ assert (pd->sn == d.sn && pd->sv == d.sv);
+ t.commit ();
+ }
+
+ // Update section.
+ //
+ d.sn++;
+ d.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ root* pr (&d);
+ db->update (*pr, pr->s); // Via root.
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->load (*pd, pd->s);
+
+ assert (pd->sn == d.sn && pd->sv == d.sv);
+ t.commit ();
+ }
+
+ // Reload.
+ //
+ d.sn++;
+ d.sv[0]++;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->load (*pd, pd->s);
+
+ db->update (d);
+
+ root* pr (pd.get ());
+ db->reload (*pr);
+
+ assert (pd->sn == d.sn && pd->sv == d.sv);
+ t.commit ();
+ }
+ }
+
+ // Test value-only/container-only base/override combinations.
+ //
+ {
+ using namespace test3;
+
+ root r (123);
+ base b (234);
+ derived d (345, "abc");
+
+ {
+ transaction t (db->begin ());
+ db->persist (r);
+ db->persist (b);
+ db->persist (d);
+ t.commit ();
+
+ assert (r.s1.loaded ());
+ assert (r.s2.loaded ());
+ assert (r.s3.loaded ());
+ assert (r.s4.loaded ());
+
+ assert (b.s1.loaded ());
+ assert (b.s2.loaded ());
+ assert (b.s3.loaded ());
+ assert (b.s4.loaded ());
+
+ assert (d.s1.loaded ());
+ assert (d.s2.loaded ());
+ assert (d.s3.loaded ());
+ assert (d.s4.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<root> pr (db->load<root> (r.id));
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ assert (!pr->s1.loaded ());
+ assert (!pr->s2.loaded ());
+ assert (!pr->s3.loaded ());
+ assert (!pr->s4.loaded ());
+
+ assert (!pb->s1.loaded ());
+ assert (!pb->s2.loaded ());
+ assert (!pb->s3.loaded ());
+ assert (!pb->s4.loaded ());
+
+ assert (!pd->s1.loaded ());
+ assert (!pd->s2.loaded ());
+ assert (!pd->s3.loaded ());
+ assert (!pd->s4.loaded ());
+
+ assert (pr->s1n != r.s1n &&
+ pr->s2n != r.s2n &&
+ pr->s3v != r.s3v &&
+ pr->s4nv != r.s4nv);
+
+ assert (pb->s1n != b.s1n &&
+ pb->s2n != b.s2n &&
+ pb->s3v != b.s3v &&
+ pb->s4nv != b.s4nv);
+
+ assert (pd->s1n != d.s1n && pd->s1s != d.s1s &&
+ pd->s2n != d.s2n && pd->s2v != d.s2v &&
+ pd->s3v != d.s3v && pd->s3n != d.s3n &&
+ pd->s4nv != d.s4nv && pd->s4sv != d.s4sv);
+
+ db->load (*pr, pr->s1);
+ db->load (*pr, pr->s2);
+ db->load (*pr, pr->s3);
+ db->load (*pr, pr->s4);
+
+ db->load (*pb, pb->s1);
+ db->load (*pb, pb->s2);
+ db->load (*pb, pb->s3);
+ db->load (*pb, pb->s4);
+
+ root* pdr (pd.get ());
+ db->load (*pdr, pdr->s1);
+ db->load (*pdr, pdr->s2);
+ db->load (*pdr, pdr->s3);
+ db->load (*pdr, pdr->s4);
+
+ assert (pr->s1.loaded ());
+ assert (pr->s2.loaded ());
+ assert (pr->s3.loaded ());
+ assert (pr->s4.loaded ());
+
+ assert (pb->s1.loaded ());
+ assert (pb->s2.loaded ());
+ assert (pb->s3.loaded ());
+ assert (pb->s4.loaded ());
+
+ assert (pd->s1.loaded ());
+ assert (pd->s2.loaded ());
+ assert (pd->s3.loaded ());
+ assert (pd->s4.loaded ());
+
+ assert (pr->s1n == r.s1n &&
+ pr->s2n == r.s2n &&
+ pr->s3v == r.s3v &&
+ pr->s4nv == r.s4nv);
+
+ assert (pb->s1n == b.s1n &&
+ pb->s2n == b.s2n &&
+ pb->s3v == b.s3v &&
+ pb->s4nv == b.s4nv);
+
+ assert (pd->s1n == d.s1n && pd->s1s == d.s1s &&
+ pd->s2n == d.s2n && pd->s2v == d.s2v &&
+ pd->s3v == d.s3v && pd->s3n == d.s3n &&
+ pd->s4nv == d.s4nv && pd->s4sv == d.s4sv);
+ t.commit ();
+ }
+
+ // Update object.
+ //
+ r.s1n++;
+ r.s2n++;
+ r.s3v[0]++;
+ r.s4nv[0]++;
+
+ b.s1n++;
+ b.s2n++;
+ b.s3v[0]++;
+ b.s4nv[0]++;
+
+ d.s1n++;
+ d.s1s += 'd';
+ d.s2n++;
+ d.s2v[0]++;
+ d.s3v[0]++;
+ d.s3n++;
+ d.s4nv[0]++;
+ d.s4sv[0] += 'd';
+
+ {
+ transaction t (db->begin ());
+ db->update (r);
+ db->update (b);
+ db->update (d);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<root> pr (db->load<root> (r.id));
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->load (*pr, pr->s1);
+ db->load (*pr, pr->s2);
+ db->load (*pr, pr->s3);
+ db->load (*pr, pr->s4);
+
+ db->load (*pb, pb->s1);
+ db->load (*pb, pb->s2);
+ db->load (*pb, pb->s3);
+ db->load (*pb, pb->s4);
+
+ db->load (*pd, pd->s1);
+ db->load (*pd, pd->s2);
+ db->load (*pd, pd->s3);
+ db->load (*pd, pd->s4);
+
+ assert (pr->s1n == r.s1n &&
+ pr->s2n == r.s2n &&
+ pr->s3v == r.s3v &&
+ pr->s4nv == r.s4nv);
+
+ assert (pb->s1n == b.s1n &&
+ pb->s2n == b.s2n &&
+ pb->s3v == b.s3v &&
+ pb->s4nv == b.s4nv);
+
+ assert (pd->s1n == d.s1n && pd->s1s == d.s1s &&
+ pd->s2n == d.s2n && pd->s2v == d.s2v &&
+ pd->s3v == d.s3v && pd->s3n == d.s3n &&
+ pd->s4nv == d.s4nv && pd->s4sv == d.s4sv);
+ t.commit ();
+ }
+
+ // Update section.
+ //
+ r.s1n++;
+ r.s2n++;
+ r.s3v[0]++;
+ r.s4nv[0]++;
+
+ b.s1n++;
+ b.s2n++;
+ b.s3v[0]++;
+ b.s4nv[0]++;
+
+ d.s1n++;
+ d.s1s += 'd';
+ d.s2n++;
+ d.s2v[0]++;
+ d.s3v[0]++;
+ d.s3n++;
+ d.s4nv[0]++;
+ d.s4sv[0] += 'd';
+
+ {
+ transaction t (db->begin ());
+ db->update (r, r.s1);
+ db->update (r, r.s2);
+ db->update (r, r.s3);
+ db->update (r, r.s4);
+
+ db->update (b, b.s1);
+ db->update (b, b.s2);
+ db->update (b, b.s3);
+ db->update (b, b.s4);
+
+ root& rr (d);
+ db->update (rr, rr.s1);
+ db->update (rr, rr.s2);
+ db->update (rr, rr.s3);
+ db->update (rr, rr.s4);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<root> pr (db->load<root> (r.id));
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->load (*pr, pr->s1);
+ db->load (*pr, pr->s2);
+ db->load (*pr, pr->s3);
+ db->load (*pr, pr->s4);
+
+ db->load (*pb, pb->s1);
+ db->load (*pb, pb->s2);
+ db->load (*pb, pb->s3);
+ db->load (*pb, pb->s4);
+
+ db->load (*pd, pd->s1);
+ db->load (*pd, pd->s2);
+ db->load (*pd, pd->s3);
+ db->load (*pd, pd->s4);
+
+ assert (pr->s1n == r.s1n &&
+ pr->s2n == r.s2n &&
+ pr->s3v == r.s3v &&
+ pr->s4nv == r.s4nv);
+
+ assert (pb->s1n == b.s1n &&
+ pb->s2n == b.s2n &&
+ pb->s3v == b.s3v &&
+ pb->s4nv == b.s4nv);
+
+ assert (pd->s1n == d.s1n && pd->s1s == d.s1s &&
+ pd->s2n == d.s2n && pd->s2v == d.s2v &&
+ pd->s3v == d.s3v && pd->s3n == d.s3n &&
+ pd->s4nv == d.s4nv && pd->s4sv == d.s4sv);
+ t.commit ();
+ }
+
+ // Reload.
+ //
+ r.s1n++;
+ r.s2n++;
+ r.s3v[0]++;
+ r.s4nv[0]++;
+
+ b.s1n++;
+ b.s2n++;
+ b.s3v[0]++;
+ b.s4nv[0]++;
+
+ d.s1n++;
+ d.s1s += 'd';
+ d.s2n++;
+ d.s2v[0]++;
+ d.s3v[0]++;
+ d.s3n++;
+ d.s4nv[0]++;
+ d.s4sv[0] += 'd';
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<root> pr (db->load<root> (r.id));
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->load (*pr, pr->s1);
+ db->load (*pr, pr->s2);
+ db->load (*pr, pr->s3);
+ db->load (*pr, pr->s4);
+
+ db->load (*pb, pb->s1);
+ db->load (*pb, pb->s2);
+ db->load (*pb, pb->s3);
+ db->load (*pb, pb->s4);
+
+ db->load (*pd, pd->s1);
+ db->load (*pd, pd->s2);
+ db->load (*pd, pd->s3);
+ db->load (*pd, pd->s4);
+
+ db->update (r);
+ db->update (b);
+ db->update (d);
+
+ db->reload (*pr);
+ db->reload (*pb);
+ db->reload (*pd);
+
+ assert (pr->s1n == r.s1n &&
+ pr->s2n == r.s2n &&
+ pr->s3v == r.s3v &&
+ pr->s4nv == r.s4nv);
+
+ assert (pb->s1n == b.s1n &&
+ pb->s2n == b.s2n &&
+ pb->s3v == b.s3v &&
+ pb->s4nv == b.s4nv);
+
+ assert (pd->s1n == d.s1n && pd->s1s == d.s1s &&
+ pd->s2n == d.s2n && pd->s2v == d.s2v &&
+ pd->s3v == d.s3v && pd->s3n == d.s3n &&
+ pd->s4nv == d.s4nv && pd->s4sv == d.s4sv);
+ t.commit ();
+ }
+ }
+
+ // Test basic polymorphic optimistic section functionality.
+ //
+ {
+ using namespace test4;
+
+ base b (123, "abc");
+ derived d (234, "bcd", true);
+
+ {
+ transaction t (db->begin ());
+ db->persist (b);
+ db->persist (d);
+ t.commit ();
+
+ assert (b.rs1.loaded ());
+ assert (b.rs2.loaded ());
+ assert (b.rs3.loaded ());
+ assert (b.rs4.loaded ());
+ assert (b.bs1.loaded ());
+
+ assert (d.rs1.loaded ());
+ assert (d.rs2.loaded ());
+ assert (d.rs3.loaded ());
+ assert (d.rs4.loaded ());
+ assert (d.bs1.loaded ());
+ assert (d.ds1.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ assert (!pb->rs1.loaded ());
+ assert (!pb->rs2.loaded ());
+ assert (!pb->rs3.loaded ());
+ assert (!pb->rs4.loaded ());
+ assert (!pb->bs1.loaded ());
+
+ assert (!pd->rs1.loaded ());
+ assert (!pd->rs2.loaded ());
+ assert (!pd->rs3.loaded ());
+ assert (!pd->rs4.loaded ());
+ assert (!pd->bs1.loaded ());
+ assert (!pd->ds1.loaded ());
+
+ assert (pb->rs1n != b.rs1n && pb->rs1s != b.rs1s &&
+ pb->rs2n != b.rs2n &&
+ pb->rs3n != b.rs3n &&
+ pb->rs4n != b.rs4n && pb->rs4s != b.rs4s);
+
+ assert (pd->rs1n != d.rs1n && pd->rs1s != d.rs1s &&
+ pd->rs1b != d.rs1b && pd->rs1v != d.rs1v &&
+ pd->rs2n != d.rs2n &&
+ pd->rs3n != d.rs3n && pd->rs3s != d.rs3s &&
+ pd->rs4n != d.rs4n && pd->rs4s != d.rs4s &&
+ pd->rs4v != d.rs4v &&
+ pd->bs1n != d.bs1n &&
+ pd->ds1v != d.ds1v);
+
+ db->load (*pb, pb->rs1);
+ db->load (*pb, pb->rs2);
+ db->load (*pb, pb->rs3);
+ db->load (*pb, pb->rs4);
+ db->load (*pb, pb->bs1); // No-op.
+
+ root* pr (pd.get ());
+ db->load (*pr, pr->rs1); // Via base.
+ db->load (*pd, pd->rs2);
+ db->load (*pd, pd->rs3);
+ db->load (*pr, pr->rs4); // Via base.
+ db->load (*pd, pd->bs1);
+ db->load (*pd, pd->ds1);
+
+ assert (pb->rs1.loaded ());
+ assert (pb->rs2.loaded ());
+ assert (pb->rs3.loaded ());
+ assert (pb->rs4.loaded ());
+ assert (pb->bs1.loaded ());
+
+ assert (pd->rs1.loaded ());
+ assert (pd->rs2.loaded ());
+ assert (pd->rs3.loaded ());
+ assert (pd->rs4.loaded ());
+ assert (pd->bs1.loaded ());
+ assert (pd->ds1.loaded ());
+
+ assert (pb->rs1n == b.rs1n && pb->rs1s == b.rs1s &&
+ pb->rs2n == b.rs2n &&
+ pb->rs3n == b.rs3n &&
+ pb->rs4n == b.rs4n && pb->rs4s == b.rs4s);
+
+ assert (pd->rs1n == d.rs1n && pd->rs1s == d.rs1s &&
+ pd->rs1b == d.rs1b && pd->rs1v == d.rs1v &&
+ pd->rs2n == d.rs2n &&
+ pd->rs3n == d.rs3n && pd->rs3s == d.rs3s &&
+ pd->rs4n == d.rs4n && pd->rs4s == d.rs4s &&
+ pd->rs4v == d.rs4v &&
+ pd->bs1n == d.bs1n &&
+ pd->ds1v == d.ds1v);
+ t.commit ();
+ }
+
+ base b1 (b);
+ derived d1 (d);
+
+ // Update object.
+ //
+ for (unsigned short s (1); s < 7; ++s)
+ {
+ b1.rs1n++;
+ b1.rs1s += 'd';
+ b1.rs1.change ();
+ b1.rs4s += 'd';
+
+ d1.rs1n++;
+ d1.rs1s += 'e';
+ d1.rs1b = !d.rs1b;
+ d1.rs1v[0]++;
+ d1.rs1.change ();
+ d1.rs4s += 'e';
+ d1.rs4v[0]++;
+ d1.bs1n++;
+ d1.ds1v[0]++;
+
+ transaction t (db->begin ());
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->update (b1);
+ db->update (d1);
+
+ assert (!b1.rs1.changed ());
+ assert (!d1.rs1.changed ());
+
+ assert (b.v != b1.v);
+ assert (d.v != d1.v);
+
+ try
+ {
+ bool a (false);
+ switch (s)
+ {
+ case 1: db->load (*pb, pb->rs1); break;
+ case 2: db->load (*pb, pb->rs2); break;
+ case 3: db->load (*pb, pb->rs3); break;
+ case 4: db->load (*pb, pb->rs4); break;
+ case 5:
+ case 6: a = true; break; // No-op.
+ default: break;
+ }
+ assert (a);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*pb);
+
+ assert (!pb->rs1.loaded ());
+ assert (!pb->rs2.loaded ());
+ assert (!pb->rs3.loaded ());
+ assert (!pb->rs3.loaded ());
+ assert (!pb->bs1.loaded ());
+
+ db->load (*pb, pb->rs1);
+ db->load (*pb, pb->rs2);
+ db->load (*pb, pb->rs3);
+ db->load (*pb, pb->rs4);
+ db->load (*pb, pb->bs1); // No-op.
+
+ assert (pb->rs1n == b1.rs1n && pb->rs1s == b1.rs1s &&
+ pb->rs2n == b1.rs2n &&
+ pb->rs3n == b1.rs3n &&
+ pb->rs4n == b1.rs4n && pb->rs4s == b1.rs4s);
+ }
+
+ try
+ {
+ switch (s)
+ {
+ case 1: db->load (*pd, pd->rs1); break;
+ case 2: db->load (*pd, pd->rs2); break;
+ case 3: db->load (*pd, pd->rs3); break;
+ case 4: db->load (*pd, pd->rs4); break;
+ case 5: db->load (*pd, pd->bs1); break;
+ case 6: db->load (*pd, pd->ds1); break;
+ default: break;
+ }
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*pd);
+
+ assert (!pd->rs1.loaded ());
+ assert (!pd->rs2.loaded ());
+ assert (!pd->rs3.loaded ());
+ assert (!pd->rs4.loaded ());
+ assert (!pd->bs1.loaded ());
+ assert (!pd->ds1.loaded ());
+
+ db->load (*pd, pd->rs1);
+ db->load (*pd, pd->rs2);
+ db->load (*pd, pd->rs3);
+ db->load (*pd, pd->rs4);
+ db->load (*pd, pd->bs1);
+ db->load (*pd, pd->ds1);
+
+ assert (pd->rs1n == d1.rs1n && pd->rs1s == d1.rs1s &&
+ pd->rs1b == d1.rs1b && pd->rs1v == d1.rs1v &&
+ pd->rs2n == d1.rs2n &&
+ pd->rs3n == d1.rs3n && pd->rs3s == d1.rs3s &&
+ pd->rs4n == d1.rs4n && pd->rs4s == d1.rs4s &&
+ pd->rs4v == d1.rs4v &&
+ pd->bs1n == d1.bs1n &&
+ pd->ds1v == d1.ds1v);
+ }
+
+ db->reload (b);
+ db->reload (d);
+
+ assert (b.v == b1.v);
+ assert (d.v == d1.v);
+
+ assert (b.rs1n == b1.rs1n && b.rs1s == b1.rs1s &&
+ b.rs2n == b1.rs2n &&
+ b.rs3n == b1.rs3n &&
+ b.rs4n == b1.rs4n && b.rs4s == b1.rs4s);
+
+ assert (d.rs1n == d1.rs1n && d.rs1s == d1.rs1s &&
+ d.rs1b == d1.rs1b && d.rs1v == d1.rs1v &&
+ d.rs2n == d1.rs2n &&
+ d.rs3n == d1.rs3n && d.rs3s == d1.rs3s &&
+ d.rs4n == d1.rs4n && d.rs4s == d1.rs4s &&
+ d.rs4v == d1.rs4v &&
+ d.bs1n == d1.bs1n &&
+ d.ds1v == d1.ds1v);
+
+ t.commit ();
+ }
+
+ // Update section.
+ //
+ for (unsigned short s (1); s < 7; ++s)
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ switch (s)
+ {
+ case 1:
+ b1.rs1n++;
+ b1.rs1s += 'd';
+
+ d1.rs1n++;
+ d1.rs1s += 'e';
+ d1.rs1b = !d.rs1b;
+ d1.rs1v[0]++;
+
+ db->update (b1, b1.rs1);
+ db->update (d1, d1.rs1);
+
+ assert (b.v != b1.v);
+ assert (d.v != d1.v);
+ break;
+ case 2:
+ db->update (b1, b1.rs2); // No-op.
+ db->update (d1, d1.rs2); // No-op.
+
+ assert (b.v == b1.v);
+ assert (d.v == d1.v);
+ continue; // Object hasn't changed.
+ case 3:
+ db->update (b1, b1.rs3); // No-op.
+ db->update (d1, d1.rs3); // No-op.
+
+ assert (b.v == b1.v);
+ assert (d.v == d1.v);
+ continue; // Object hasn't changed.
+ case 4:
+ b1.rs4s += 'd';
+
+ d1.rs4s += 'e';
+ d1.rs4v[0]++;
+
+ db->update (b1, b1.rs4);
+ db->update (d1, d1.rs4);
+
+ assert (b.v != b1.v);
+ assert (d.v != d1.v);
+ break;
+ case 5:
+ d1.bs1n++;
+
+ db->update (b1, b1.bs1); // No-op.
+ db->update (d1, d1.bs1);
+
+ assert (b.v == b1.v);
+ assert (d.v != d1.v);
+ break;
+ case 6:
+ d1.ds1v[0]++;
+
+ db->update (d1, d1.ds1);
+
+ assert (d.v != d1.v);
+ break;
+ default: break;
+ }
+
+ try
+ {
+ bool a (false);
+ switch (s)
+ {
+ case 1: db->load (*pb, pb->rs1); break;
+ case 4: db->load (*pb, pb->rs4); break;
+ case 5:
+ case 6: a = true; break; // No-op.
+ default: break;
+ }
+ assert (a);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*pb);
+
+ assert (!pb->rs1.loaded ());
+ assert (!pb->rs2.loaded ());
+ assert (!pb->rs3.loaded ());
+ assert (!pb->rs4.loaded ());
+ assert (!pb->bs1.loaded ());
+
+ db->load (*pb, pb->rs1);
+ db->load (*pb, pb->rs2);
+ db->load (*pb, pb->rs3);
+ db->load (*pb, pb->rs4);
+ db->load (*pb, pb->bs1); // No-op.
+
+ assert (pb->rs1n == b1.rs1n && pb->rs1s == b1.rs1s &&
+ pb->rs2n == b1.rs2n &&
+ pb->rs3n == b1.rs3n &&
+ pb->rs4n == b1.rs4n && pb->rs4s == b1.rs4s);
+ }
+
+ try
+ {
+ switch (s)
+ {
+ case 1: db->load (*pd, pd->rs1); break;
+ case 4: db->load (*pd, pd->rs4); break;
+ case 5: db->load (*pd, pd->bs1); break;
+ case 6: db->load (*pd, pd->ds1); break;
+ default: break;
+ }
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*pd);
+
+ assert (!pd->rs1.loaded ());
+ assert (!pd->rs2.loaded ());
+ assert (!pd->rs3.loaded ());
+ assert (!pd->rs4.loaded ());
+ assert (!pd->bs1.loaded ());
+ assert (!pd->ds1.loaded ());
+
+ db->load (*pd, pd->rs1);
+ db->load (*pd, pd->rs2);
+ db->load (*pd, pd->rs3);
+ db->load (*pd, pd->rs4);
+ db->load (*pd, pd->bs1);
+ db->load (*pd, pd->ds1);
+
+ assert (pd->rs1n == d1.rs1n && pd->rs1s == d1.rs1s &&
+ pd->rs1b == d1.rs1b && pd->rs1v == d1.rs1v &&
+ pd->rs2n == d1.rs2n &&
+ pd->rs3n == d1.rs3n && pd->rs3s == d1.rs3s &&
+ pd->rs4n == d1.rs4n && pd->rs4s == d1.rs4s &&
+ pd->rs4v == d1.rs4v &&
+ pd->bs1n == d1.bs1n &&
+ pd->ds1v == d1.ds1v);
+ }
+
+ db->reload (b);
+ db->reload (d);
+
+ assert (b.v == b1.v);
+ assert (d.v == d1.v);
+
+ assert (b.rs1n == b1.rs1n && b.rs1s == b1.rs1s &&
+ b.rs2n == b1.rs2n &&
+ b.rs3n == b1.rs3n &&
+ b.rs4n == b1.rs4n && b.rs4s == b1.rs4s);
+
+ assert (d.rs1n == d1.rs1n && d.rs1s == d1.rs1s &&
+ d.rs1b == d1.rs1b && d.rs1v == d1.rs1v &&
+ d.rs2n == d1.rs2n &&
+ d.rs3n == d1.rs3n && d.rs3s == d1.rs3s &&
+ d.rs4n == d1.rs4n && d.rs4s == d1.rs4s &&
+ d.rs4v == d1.rs4v &&
+ d.bs1n == d1.bs1n &&
+ d.ds1v == d1.ds1v);
+
+ t.commit ();
+ }
+
+ // Update changed section.
+ //
+ for (unsigned short s (1); s < 7; ++s)
+ {
+ if (s == 2 || s == 3) // Readonly sections.
+ continue;
+
+ transaction t (db->begin ());
+
+ switch (s)
+ {
+ case 1:
+ b1.rs1n++;
+ b1.rs1s += 'd';
+
+ d1.rs1n++;
+ d1.rs1s += 'e';
+ d1.rs1b = !d.rs1b;
+ d1.rs1v[0]++;
+
+ db->update (b1, b1.rs1);
+ db->update (d1, d1.rs1);
+ break;
+ case 4:
+ b1.rs4s += 'd';
+
+ d1.rs4s += 'e';
+ d1.rs4v[0]++;
+
+ db->update (b1, b1.rs4);
+ db->update (d1, d1.rs4);
+ break;
+ case 5:
+ d1.bs1n++;
+
+ db->update (b1, b1.bs1); // No-op.
+ db->update (d1, d1.bs1);
+ break;
+ case 6:
+ d1.ds1v[0]++;
+
+ db->update (d1, d1.bs1);
+ break;
+ default: break;
+ }
+
+ try
+ {
+ bool a (false);
+ switch (s)
+ {
+ case 1: db->update (b, b.rs1); break;
+ case 4: db->update (b, b.rs4); break;
+ case 5:
+ case 6: a = true; break; // No-op.
+ default: break;
+ }
+ assert (a);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (b);
+
+ switch (s)
+ {
+ case 1: db->update (b, b.rs1); break;
+ case 4: db->update (b, b.rs4); break;
+ default: break;
+ }
+ }
+
+ try
+ {
+ switch (s)
+ {
+ case 1: db->update (d, d.rs1); break;
+ case 4: db->update (d, d.rs4); break;
+ case 5: db->update (d, d.bs1); break;
+ case 6: db->update (d, d.ds1); break;
+ default: break;
+ }
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (d);
+
+ switch (s)
+ {
+ case 1: db->update (d, d.rs1); break;
+ case 4: db->update (d, d.rs4); break;
+ case 5: db->update (d, d.bs1); break;
+ case 6: db->update (d, d.ds1); break;
+ default: break;
+ }
+ }
+
+ db->reload (b1);
+ db->reload (d1);
+
+ t.commit ();
+ }
+ }
+
+ // Test polymorphic optimistic readonly/empty to readwrite section
+ // override.
+ //
+ {
+ using namespace test5;
+
+ base b;
+ derived d (123);
+
+ {
+ transaction t (db->begin ());
+ db->persist (b);
+ db->persist (d);
+ t.commit ();
+
+ assert (b.s.loaded ());
+ assert (d.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ assert (!pb->s.loaded ());
+ assert (!pd->s.loaded ());
+ assert (pd->sn != d.sn);
+
+ db->load (*pb, pb->s); // No-op.
+ db->load (*pd, pd->s);
+
+ assert (pb->s.loaded ());
+ assert (pd->s.loaded ());
+
+ assert (pd->sn == d.sn);
+
+ t.commit ();
+ }
+
+ // Update object.
+ //
+ base b1 (b);
+ derived d1 (d);
+ d1.sn++;
+ d1.s.change ();
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->update (d1);
+
+ assert (!d1.s.changed ());
+ assert (d.v != d1.v);
+
+ try
+ {
+ db->load (*pd, pd->s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*pd);
+ assert (!pd->s.loaded ());
+ db->load (*pd, pd->s);
+ assert (pd->sn == d1.sn);
+ }
+
+ db->reload (d);
+ assert (d.v == d1.v);
+ assert (d.sn == d1.sn);
+ t.commit ();
+ }
+
+ // Update section.
+ //
+ d1.sn++;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->update (b1, b1.s); // No-op.
+ db->update (d1, d1.s);
+
+ assert (b.v == b1.v);
+ assert (d.v != d1.v);
+
+ try
+ {
+ db->load (*pd, pd->s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*pd);
+ assert (!pd->s.loaded ());
+ db->load (*pd, pd->s);
+
+ assert (pd->sn == d1.sn);
+ }
+
+ db->reload (d);
+ assert (d.v == d1.v);
+ assert (d.sn == d1.sn);
+ t.commit ();
+ }
+
+ // Update changed section.
+ //
+ d1.sn++;
+
+ {
+ transaction t (db->begin ());
+ db->update (d1, d1.s);
+
+ try
+ {
+ db->update (d, d.s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (d);
+ db->update (d, d.s);
+ }
+
+ t.commit ();
+ }
+ }
+
+ // Test polymorphic optimistic readonly/empty to readwrite section
+ // override, eager-loaded case.
+ //
+ {
+ using namespace test6;
+
+ derived d (123);
+
+ {
+ transaction t (db->begin ());
+ db->persist (d);
+ t.commit ();
+
+ assert (d.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ assert (pd->s.loaded ());
+ assert (pd->sn == d.sn);
+
+ t.commit ();
+ }
+
+ // Update object.
+ //
+ derived d1 (d);
+ d1.sn++;
+ d1.s.change ();
+
+ {
+ transaction t (db->begin ());
+
+ db->update (d1);
+
+ assert (!d1.s.changed ());
+ assert (d.v != d1.v);
+
+ db->reload (d);
+ assert (d.v == d1.v);
+ assert (d.sn == d1.sn);
+ t.commit ();
+ }
+
+ // Update section.
+ //
+ d1.sn++;
+
+ {
+ transaction t (db->begin ());
+
+ db->update (d1, d1.s);
+ assert (d.v != d1.v);
+
+ db->reload (d);
+ assert (d.v == d1.v);
+ assert (d.sn == d1.sn);
+ t.commit ();
+ }
+
+ // Update changed section.
+ //
+ d1.sn++;
+
+ {
+ transaction t (db->begin ());
+ db->update (d1, d1.s);
+
+ try
+ {
+ db->update (d, d.s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (d);
+ db->update (d, d.s);
+ }
+
+ t.commit ();
+ }
+ }
+
+ // Test polymorphic optimistic section added in derived.
+ //
+ {
+ using namespace test7;
+
+ base b;
+ derived d (123);
+
+ {
+ transaction t (db->begin ());
+ db->persist (b);
+ db->persist (d);
+ t.commit ();
+
+ assert (b.s.loaded ());
+ assert (d.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> pb (db->load<base> (b.id));
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ assert (!pb->s.loaded ());
+ assert (!pd->s.loaded ());
+ assert (pd->sn != d.sn);
+
+ db->load (*pb, pb->s); // No-op.
+ db->load (*pd, pd->s);
+
+ assert (pb->s.loaded ());
+ assert (pd->s.loaded ());
+
+ assert (pd->sn == d.sn);
+
+ t.commit ();
+ }
+
+ // Update object.
+ //
+ base b1 (b);
+ derived d1 (d);
+ d1.sn++;
+ d1.s.change ();
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->update (d1);
+
+ assert (!d1.s.changed ());
+ assert (d.v != d1.v);
+
+ try
+ {
+ db->load (*pd, pd->s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*pd);
+ assert (!pd->s.loaded ());
+ db->load (*pd, pd->s);
+ assert (pd->sn == d1.sn);
+ }
+
+ db->reload (d);
+ assert (d.v == d1.v);
+ assert (d.sn == d1.sn);
+ t.commit ();
+ }
+
+ // Update section.
+ //
+ d1.sn++;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->update (b1, b1.s); // No-op.
+ db->update (d1, d1.s);
+
+ assert (b.v == b1.v);
+ assert (d.v != d1.v);
+
+ try
+ {
+ db->load (*pd, pd->s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*pd);
+ assert (!pd->s.loaded ());
+ db->load (*pd, pd->s);
+
+ assert (pd->sn == d1.sn);
+ }
+
+ db->reload (d);
+ assert (d.v == d1.v);
+ assert (d.sn == d1.sn);
+ t.commit ();
+ }
+
+ // Update changed section.
+ //
+ d1.sn++;
+
+ {
+ transaction t (db->begin ());
+ db->update (d1, d1.s);
+
+ try
+ {
+ db->update (d, d.s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (d);
+ db->update (d, d.s);
+ }
+
+ t.commit ();
+ }
+ }
+
+ // Test reuse/polymorphic inheritance and optimistic mix.
+ //
+ {
+ using namespace test8;
+
+ derived d (123);
+
+ {
+ transaction t (db->begin ());
+ db->persist (d);
+ t.commit ();
+
+ assert (d.s.loaded ());
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ assert (!pd->s.loaded ());
+ assert (pd->sn != d.sn);
+
+ db->load (*pd, pd->s);
+
+ assert (pd->s.loaded ());
+ assert (pd->sn == d.sn);
+
+ t.commit ();
+ }
+
+ // Update object.
+ //
+ derived d1 (d);
+ d1.sn++;
+ d1.s.change ();
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->update (d1);
+
+ assert (!d1.s.changed ());
+ assert (d.v != d1.v);
+
+ try
+ {
+ db->load (*pd, pd->s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*pd);
+ assert (!pd->s.loaded ());
+ db->load (*pd, pd->s);
+ assert (pd->sn == d1.sn);
+ }
+
+ db->reload (d);
+ assert (d.v == d1.v);
+ assert (d.sn == d1.sn);
+ t.commit ();
+ }
+
+ // Update section.
+ //
+ d1.sn++;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<derived> pd (db->load<derived> (d.id));
+
+ db->update (d1, d1.s);
+ assert (d.v != d1.v);
+
+ try
+ {
+ db->load (*pd, pd->s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (*pd);
+ assert (!pd->s.loaded ());
+ db->load (*pd, pd->s);
+
+ assert (pd->sn == d1.sn);
+ }
+
+ db->reload (d);
+ assert (d.v == d1.v);
+ assert (d.sn == d1.sn);
+ t.commit ();
+ }
+
+ // Update changed section.
+ //
+ d1.sn++;
+
+ {
+ transaction t (db->begin ());
+ db->update (d1, d1.s);
+
+ try
+ {
+ db->update (d, d.s);
+ assert (false);
+ }
+ catch (const object_changed&)
+ {
+ db->reload (d);
+ db->update (d, d.s);
+ }
+
+ t.commit ();
+ }
+ }
+
+ // Test reuse/polymorphic inheritance and optimistic mix.
+ //
+ {
+ using namespace test9;
+ using std::shared_ptr;
+
+ unsigned long long id;
+
+ {
+ container c (123);
+
+ c.e1.push_back (shared_ptr<element> (new element (11)));
+ c.e1.push_back (shared_ptr<element> (new element (12)));
+
+ c.e2.push_back (shared_ptr<element> (new element (21)));
+ c.e2.push_back (shared_ptr<element> (new element (22)));
+
+ transaction t (db->begin ());
+
+ db->persist (c.e1[0]);
+ db->persist (c.e1[1]);
+ db->persist (c.e2[0]);
+ db->persist (c.e2[1]);
+
+ id = db->persist (c);
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ shared_ptr<container> c (db->load<container> (id));
+
+ assert (c->n == 123);
+ db->load (*c, c->s);
+ assert (c->e1.size () == 2 && c->e1[0]->n == 11 && c->e1[1]->n == 12);
+ assert (c->e2.size () == 2 && c->e2[0]->n == 21 && c->e2[1]->n == 22);
+
+ t.commit ();
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/section/polymorphism/test.hxx b/odb-tests/common/section/polymorphism/test.hxx
new file mode 100644
index 0000000..6d524bd
--- /dev/null
+++ b/odb-tests/common/section/polymorphism/test.hxx
@@ -0,0 +1,542 @@
+// file : common/section/polymorphism/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <vector>
+#include <memory>
+
+#include <odb/core.hxx>
+#include <odb/section.hxx>
+
+// Test basic polymorphic section functionality.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ #pragma db object polymorphic abstract
+ struct root
+ {
+ root (int n): rs1n (n), rs2n (n), rs4n (n) {rs2v.push_back (n);}
+ virtual ~root () {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ // rs1: override in base and derived
+ //
+ #pragma db load(lazy) update(change)
+ odb::section rs1;
+
+ #pragma db section(rs1)
+ int rs1n;
+
+ // rs2: no override
+ //
+ #pragma db load(lazy)
+ odb::section rs2;
+
+ #pragma db section(rs2)
+ int rs2n;
+
+ #pragma db section(rs2)
+ std::vector<int> rs2v;
+
+ // rs3: empty
+ //
+ #pragma db load(lazy)
+ odb::section rs3;
+
+ // rs4: override "gap"
+ //
+ #pragma db load(lazy)
+ odb::section rs4;
+
+ #pragma db section(rs4)
+ int rs4n;
+ };
+
+ #pragma db object
+ struct base: root
+ {
+ base (int n = 999, const std::string& s = "xxx")
+ : root (n), rs1s (s), bs1n (n) {rs3v.push_back (n);}
+
+ // rs1
+ //
+ #pragma db section(rs1)
+ std::string rs1s;
+
+ // rs3
+ //
+ #pragma db section(rs3)
+ std::vector<int> rs3v;
+
+ // bs1: override in derived
+ //
+ #pragma db load(lazy)
+ odb::section bs1;
+
+ #pragma db section(bs1)
+ int bs1n;
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived (int n = 999, const std::string& s = "xxx", bool b = false)
+ : base (n, s), rs1b (b), rs3n (n), rs4s (s), bs1s (s), ds1n (n)
+ {rs1v.push_back (n);}
+
+ // rs1
+ //
+ #pragma db section(rs1)
+ bool rs1b;
+
+ #pragma db section(rs1)
+ std::vector<int> rs1v;
+
+ // rs3
+ //
+ #pragma db section(rs3)
+ int rs3n;
+
+ // rs4
+ //
+ #pragma db section(rs4)
+ std::string rs4s;
+
+ // bs1
+ //
+ #pragma db section(bs1)
+ std::string bs1s;
+
+ // ds1: no override
+ //
+ #pragma db load(lazy)
+ odb::section ds1;
+
+ #pragma db section(ds1)
+ int ds1n;
+ };
+}
+
+// Test empty section and override "gap".
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ #pragma db object polymorphic abstract
+ struct root
+ {
+ virtual ~root () {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db load(lazy)
+ odb::section s;
+ };
+
+ #pragma db object abstract
+ struct base: root
+ {
+ // The "gap".
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived (int n = 999): sn (n) {sv.push_back (n);}
+
+ #pragma db section(s)
+ int sn;
+
+ #pragma db section(s)
+ std::vector<int> sv;
+ };
+}
+
+// Test value-only/container-only base/override combinations.
+//
+#pragma db namespace table("t3_")
+namespace test3
+{
+ #pragma db object polymorphic
+ struct root
+ {
+ root (int n = 999)
+ : s1n (n), s2n (n) {s3v.push_back (n); s4nv.push_back (n);}
+ virtual ~root () {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ // value/value
+ //
+ #pragma db load(lazy)
+ odb::section s1;
+
+ #pragma db section(s1)
+ int s1n;
+
+ // value/container
+ //
+ #pragma db load(lazy)
+ odb::section s2;
+
+ #pragma db section(s2)
+ int s2n;
+
+ // container/value
+ //
+ #pragma db load(lazy)
+ odb::section s3;
+
+ #pragma db section(s3)
+ std::vector<int> s3v;
+
+ // container/container
+ //
+ #pragma db load(lazy)
+ odb::section s4;
+
+ #pragma db section(s4)
+ std::vector<int> s4nv;
+ };
+
+ #pragma db object
+ struct base: root
+ {
+ base (int n = 999): root (n) {}
+
+ // The "gap".
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived (int n = 999, const std::string& s = "xxx")
+ : base (n), s1s (s), s3n (n) {s2v.push_back (n); s4sv.push_back (s);}
+
+ #pragma db section(s1)
+ std::string s1s;
+
+ #pragma db section(s2)
+ std::vector<int> s2v;
+
+ #pragma db section(s3)
+ int s3n;
+
+ #pragma db section(s4)
+ std::vector<std::string> s4sv;
+ };
+}
+
+// Test basic polymorphic optimistic section functionality.
+//
+#pragma db namespace table("t4_")
+namespace test4
+{
+ #pragma db object polymorphic optimistic abstract sectionable
+ struct root
+ {
+ root (int n): rs1n (n), rs2n (n), rs3n (n), rs4n (n) {}
+ virtual ~root () {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db version
+ unsigned long long v;
+
+ // rs1: readwrite, override
+ //
+ #pragma db load(lazy) update(change)
+ odb::section rs1;
+
+ #pragma db section(rs1)
+ int rs1n;
+
+ // rs2: readonly, no override
+ //
+ #pragma db load(lazy)
+ odb::section rs2;
+
+ #pragma db section(rs2)
+ const int rs2n;
+
+ // rs3: readonly, readonly override
+ //
+ #pragma db load(lazy)
+ odb::section rs3;
+
+ #pragma db section(rs3)
+ const int rs3n;
+
+ // rs4: readonly, readwrite override
+ //
+ #pragma db load(lazy)
+ odb::section rs4;
+
+ #pragma db section(rs4)
+ const int rs4n;
+ };
+
+ #pragma db object
+ struct base: root
+ {
+ base (int n = 999, const std::string& s = "xxx")
+ : root (n), rs1s (s), rs4s (s) {}
+
+ // rs1
+ //
+ #pragma db section(rs1)
+ std::string rs1s;
+
+ // rs4
+ //
+ #pragma db section(rs4)
+ std::string rs4s;
+
+ // bs2: empty, readwrite override
+ //
+ #pragma db load(lazy)
+ odb::section bs1;
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived (int n = 999, const std::string& s = "xxx", bool b = false)
+ : base (n, s), rs1b (b), rs3s (s), bs1n (n)
+ {
+ rs1v.push_back (n);
+ rs4v.push_back (n);
+ ds1v.push_back (n);
+ }
+
+ // rs1
+ //
+ #pragma db section(rs1)
+ bool rs1b;
+
+ #pragma db section(rs1)
+ std::vector<int> rs1v;
+
+ // rs3
+ //
+ #pragma db section(rs3)
+ const std::string rs3s;
+
+ // rs4
+ //
+ #pragma db section(rs4)
+ std::vector<int> rs4v;
+
+ // bs1
+ //
+ #pragma db section(bs1)
+ int bs1n;
+
+ // ds1: readwrite
+ //
+ #pragma db load(lazy)
+ odb::section ds1;
+
+ #pragma db section(ds1)
+ std::vector<int> ds1v;
+ };
+}
+
+// Test polymorphic optimistic readonly/empty to readwrite section override.
+//
+#pragma db namespace table("t5_")
+namespace test5
+{
+ #pragma db object polymorphic optimistic abstract
+ struct root
+ {
+ virtual ~root () {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db version
+ unsigned long long v;
+
+ #pragma db load(lazy) update(change)
+ odb::section s;
+ };
+
+ #pragma db object
+ struct base: root
+ {
+ // The "gap".
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived (int n = 999): sn (n) {}
+
+ #pragma db section(s)
+ int sn;
+ };
+}
+
+// Test polymorphic optimistic readonly/empty to readwrite section override,
+// eager-loaded case.
+//
+#pragma db namespace table("t6_")
+namespace test6
+{
+ #pragma db object polymorphic optimistic abstract
+ struct root
+ {
+ virtual ~root () {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db version
+ unsigned long long v;
+
+ #pragma db update(change)
+ odb::section s;
+ };
+
+ #pragma db object abstract
+ struct base: root
+ {
+ // The "gap".
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived (int n = 999): sn (n) {}
+
+ #pragma db section(s)
+ int sn;
+ };
+}
+
+// Test polymorphic optimistic section added in derived.
+//
+#pragma db namespace table("t7_")
+namespace test7
+{
+ #pragma db object polymorphic optimistic sectionable
+ struct root
+ {
+ virtual ~root () {}
+
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db version
+ unsigned long long v;
+ };
+
+ #pragma db object
+ struct base: root
+ {
+ #pragma db load(lazy) update(change)
+ odb::section s;
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived (int n = 999): sn (n) {}
+
+ #pragma db section(s)
+ int sn;
+ };
+}
+
+// Test reuse/polymorphic inheritance and optimistic mix.
+//
+#pragma db namespace table("t8_")
+namespace test8
+{
+ #pragma db object optimistic sectionable abstract
+ struct root
+ {
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db version
+ unsigned long long v;
+ };
+
+ #pragma db object polymorphic sectionable
+ struct base: root
+ {
+ virtual ~base () {}
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived (int n = 999): sn (n) {}
+
+ #pragma db load(lazy) update(change)
+ odb::section s;
+
+ #pragma db section(s)
+ int sn;
+ };
+}
+
+// Test id overwrite regression.
+//
+// The key here is the setup: the object that contains the containers in a
+// section and the pointers to objects stored in those containers. And these
+// objects derive polymorphically from the same base (and thus shared the id
+// bindind).
+//
+#pragma db namespace table("t9_")
+namespace test9
+{
+ #pragma db object polymorphic pointer(std::shared_ptr)
+ struct base
+ {
+ virtual ~base () {}
+
+ #pragma db id auto
+ unsigned long id;
+ };
+
+ #pragma db object
+ struct element: base
+ {
+ element (int n_ = 0): n (n_) {}
+
+ int n;
+ };
+
+ typedef std::vector<std::shared_ptr<element>> elements;
+
+ #pragma db object
+ struct container: base
+ {
+ container (int n_ = 0): n (n_) {}
+
+ int n;
+
+ #pragma db load(lazy) update(always)
+ odb::section s;
+
+ #pragma db section(s)
+ elements e1;
+
+ #pragma db section(s)
+ elements e2;
+ };
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/section/polymorphism/testscript b/odb-tests/common/section/polymorphism/testscript
new file mode 100644
index 0000000..f2cd536
--- /dev/null
+++ b/odb-tests/common/section/polymorphism/testscript
@@ -0,0 +1,33 @@
+# file : common/section/polymorphism/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/session/cache/buildfile b/odb-tests/common/session/cache/buildfile
new file mode 100644
index 0000000..6d5b0bc
--- /dev/null
+++ b/odb-tests/common/session/cache/buildfile
@@ -0,0 +1,41 @@
+# file : common/session/cache/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 session_cache_ \
+ --generate-schema \
+ --generate-session
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/session/cache/driver.cxx b/odb-tests/common/session/cache/driver.cxx
new file mode 100644
index 0000000..4b4ea12
--- /dev/null
+++ b/odb-tests/common/session/cache/driver.cxx
@@ -0,0 +1,83 @@
+// file : common/session/cache/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test session object cache.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/session.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ // Test the session_required exception.
+ //
+ {
+ using namespace test1;
+
+ shared_ptr<obj1> o1a (new obj1 (1));
+ shared_ptr<obj1> o1b (new obj1 (2));
+ shared_ptr<obj2> o2 (new obj2 (1));
+
+ o1a->o2 = o2;
+ o1b->o2 = o2;
+
+ o2->o1.push_back (o1a);
+ o2->o1.push_back (o1b);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1a);
+ db->persist (o1b);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ try
+ {
+ shared_ptr<obj1> o1 (db->load<obj1> (1));
+ assert (false);
+ }
+ catch (const session_required&)
+ {
+ }
+
+ t.commit ();
+ }
+
+ {
+ session s;
+ transaction t (db->begin ());
+ shared_ptr<obj1> o1 (db->load<obj1> (1));
+ t.commit ();
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/session/cache/test.hxx b/odb-tests/common/session/cache/test.hxx
new file mode 100644
index 0000000..d2b1b2b
--- /dev/null
+++ b/odb-tests/common/session/cache/test.hxx
@@ -0,0 +1,50 @@
+// file : common/session/cache/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <memory>
+#include <vector>
+
+#include <odb/core.hxx>
+
+// Test the session_required exception.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ using std::shared_ptr;
+ using std::weak_ptr;
+
+ #pragma db namespace(test1) pointer(shared_ptr)
+
+ struct obj2;
+
+ #pragma db object
+ struct obj1
+ {
+ obj1 () {}
+ obj1 (unsigned long id): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ shared_ptr<obj2> o2;
+ };
+
+ #pragma db object
+ struct obj2
+ {
+ obj2 () {}
+ obj2 (unsigned long id): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db inverse (o2)
+ std::vector< weak_ptr<obj1> > o1;
+ };
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/session/cache/testscript b/odb-tests/common/session/cache/testscript
new file mode 100644
index 0000000..6d013eb
--- /dev/null
+++ b/odb-tests/common/session/cache/testscript
@@ -0,0 +1,33 @@
+# file : common/session/cache/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/session/custom/buildfile b/odb-tests/common/session/custom/buildfile
new file mode 100644
index 0000000..1b64de1
--- /dev/null
+++ b/odb-tests/common/session/custom/buildfile
@@ -0,0 +1,43 @@
+# file : common/session/custom/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx txx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 session_custom_ \
+ --generate-schema \
+ --generate-session \
+ --session-type ::session \
+ --hxx-prologue '#include "session.hxx"'
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/session/custom/driver.cxx b/odb-tests/common/session/custom/driver.cxx
new file mode 100644
index 0000000..3056fd6
--- /dev/null
+++ b/odb-tests/common/session/custom/driver.cxx
@@ -0,0 +1,231 @@
+// file : common/session/custom/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test custom session (C++11 only).
+//
+
+#include <memory>
+#include <cstddef> // std::size_t
+#include <iostream>
+
+#include <odb/tracer.hxx>
+#include <odb/database.hxx>
+#include <odb/session.hxx>
+#include <odb/transaction.hxx>
+#include <odb/details/config.hxx> // ODB_CXX11_*
+
+#include <libcommon/common.hxx>
+
+#include "session.hxx"
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+
+using odb::database;
+using odb::transaction;
+
+struct counting_tracer: odb::tracer
+{
+ virtual void
+ execute (odb::connection&, const char*) {count++;}
+ size_t count;
+};
+
+static counting_tracer tracer;
+
+struct failed {};
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv));
+
+ // Simple Tech Ltd.
+ //
+ {
+ shared_ptr<employer> er (new employer ("Simple Tech Ltd", "ST"));
+
+ shared_ptr<employee> john (new employee ("John", "Doe", er));
+ shared_ptr<employee> jane (new employee ("Jane", "Doe", er));
+
+ transaction t (db->begin ());
+
+ db->persist (er);
+ db->persist (john);
+ db->persist (jane);
+
+ t.commit ();
+ }
+
+ // Complex Systems Inc.
+ //
+ {
+ shared_ptr<employer> er (new employer ("Complex Systems Inc", "CS"));
+
+ shared_ptr<employee> john (new employee ("John", "Smith", er));
+ shared_ptr<employee> jane (new employee ("Jane", "Smith", er));
+
+ transaction t (db->begin ());
+
+ db->persist (er);
+ db->persist (john);
+ db->persist (jane);
+
+ t.commit ();
+ }
+
+ {
+ session s;
+ shared_ptr<employer> st, cs;
+ shared_ptr<employee> ste, cse;
+
+ {
+ transaction t (db->begin ());
+
+ st = db->load<employer> ("Simple Tech Ltd");
+#ifdef ODB_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGUMENT
+ ste = db->load<employee> (st->employees ()[0].object_id ());
+#else
+ ste = db->load<employee> (st->employees ()[0].object_id<employee> ());
+#endif
+
+ // Test object cache.
+ //
+ shared_ptr<employee> e (st->employees ()[0].load ());
+ assert (ste->employer () == st);
+ assert (ste == e);
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ cs = db->load<employer> ("Complex Systems Inc");
+#ifdef ODB_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGUMENT
+ cse = db->load<employee> (cs->employees ()[0].object_id ());
+#else
+ cse = db->load<employee> (cs->employees ()[0].object_id<employee> ());
+#endif
+ cs->employees ()[0].load ();
+
+ t.commit ();
+ }
+
+ cs->symbol ("CSI");
+
+ // Swap employees.
+ //
+ ste->employer (cs);
+ cse->employer (st);
+ st->employees ()[0] = cse;
+ cs->employees ()[0] = ste;
+
+ {
+ transaction t (db->begin ());
+ tracer.count = 0;
+ t.tracer (tracer);
+ s.flush (*db);
+ assert (tracer.count == 3);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ tracer.count = 0;
+ t.tracer (tracer);
+ s.flush (*db);
+ assert (tracer.count == 0);
+ t.commit ();
+ }
+
+ cs->symbol ("COMP");
+ st->symbol ("SMPL");
+
+ {
+ transaction t (db->begin ());
+ tracer.count = 0;
+ t.tracer (tracer);
+ s.flush (*db);
+ assert (tracer.count == 2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ tracer.count = 0;
+ t.tracer (tracer);
+ s.flush (*db);
+ assert (tracer.count == 0);
+ t.commit ();
+ }
+
+ // Explicit update.
+ //
+ cs->symbol ("CS");
+ st->symbol ("ST");
+
+ {
+ transaction t (db->begin ());
+ db->update (cs);
+ tracer.count = 0;
+ t.tracer (tracer);
+ s.flush (*db);
+ assert (tracer.count == 1);
+ t.commit ();
+ }
+
+ // Rollback after update.
+ //
+ cs->symbol ("CSI");
+
+ try
+ {
+ transaction t (db->begin ());
+ tracer.count = 0;
+ t.tracer (tracer);
+ s.flush (*db);
+ assert (tracer.count == 1);
+ throw failed ();
+ t.commit ();
+ }
+ catch (const failed&)
+ {
+ transaction t (db->begin ());
+ tracer.count = 0;
+ t.tracer (tracer);
+ s.flush (*db);
+ assert (tracer.count == 1);
+ t.commit ();
+ }
+ }
+
+ // Test session destruction before transaction is commited.
+ //
+ {
+ transaction t (db->begin ());
+ {
+ session s;
+ shared_ptr<employer> st (db->load<employer> ("Simple Tech Ltd"));
+ st->symbol ("STL");
+ tracer.count = 0;
+ t.tracer (tracer);
+ s.flush (*db);
+ assert (tracer.count == 1);
+ }
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/session/custom/session.cxx b/odb-tests/common/session/custom/session.cxx
new file mode 100644
index 0000000..1a08c79
--- /dev/null
+++ b/odb-tests/common/session/custom/session.cxx
@@ -0,0 +1,57 @@
+// file : common/session/custom/session.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#include <cassert>
+
+#include "session.hxx"
+
+session* session::current;
+
+session::
+session ()
+ : tran_ (0)
+{
+ assert (current == 0);
+ current = this;
+}
+
+session::
+~session ()
+{
+ // Unregister from transaction.
+ //
+ if (tran_ != 0)
+ tran_->callback_unregister (this);
+
+ assert (current == this);
+ current = 0;
+}
+
+void session::
+flush (odb::database& db)
+{
+ bool flushed (false);
+
+ for (type_map::iterator i (map_.begin ()), e (map_.end ()); i != e; ++i)
+ {
+ bool r (i->second->flush (db));
+ flushed = flushed || r;
+ }
+
+ // If we flushed anything, then register the post-commit/rollback callback.
+ //
+ if (flushed)
+ {
+ tran_ = &odb::transaction::current ();
+ tran_->callback_register (
+ &mark, this, odb::transaction::event_all, 0, &tran_);
+ }
+}
+
+void session::
+mark (unsigned short event, void* key, unsigned long long)
+{
+ session& s (*static_cast<session*> (key));
+ for (type_map::iterator i (s.map_.begin ()), e (s.map_.end ()); i != e; ++i)
+ i->second->mark (event);
+}
diff --git a/odb-tests/common/session/custom/session.hxx b/odb-tests/common/session/custom/session.hxx
new file mode 100644
index 0000000..2d2f597
--- /dev/null
+++ b/odb-tests/common/session/custom/session.hxx
@@ -0,0 +1,191 @@
+// file : common/session/custom/session.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef SESSION_HXX
+#define SESSION_HXX
+
+#include <map>
+#include <memory>
+#include <typeinfo>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <odb/traits.hxx> // odb::object_traits
+#include <odb/details/type-info.hxx> // odb::details::type_info_comparator
+
+// This custom session implementation assumes we are working with
+// one database at a time.
+//
+class session
+{
+public:
+ session ();
+ ~session ();
+
+private:
+ session (const session&);
+ session& operator= (const session&);
+
+ // Session for the current thread. This can be implemented in pretty
+ // much any way that makes sense to the application. It can be a global
+ // session as we have here. In multi-threaded applications we could use
+ // TLS instead.
+ //
+public:
+ static session* current;
+
+ // Change tracking interface.
+ //
+public:
+ // Call flush() within a transaction to apply the changes to the
+ // database.
+ //
+ void
+ flush (odb::database&);
+
+private:
+ struct object_map_base
+ {
+ virtual
+ ~object_map_base () {}
+
+ // Return true if we flushed anything.
+ //
+ virtual bool
+ flush (odb::database&) = 0;
+
+ virtual void
+ mark (unsigned short event) = 0;
+ };
+
+ enum object_state
+ {
+ tracking, // Tracking any modifications by storing the original copy.
+ changed, // Known to be changed.
+ flushed // Flushed but not yet committed/rolled back.
+ };
+
+ template <typename T>
+ struct object_data
+ {
+ typedef typename odb::object_traits<T>::pointer_type pointer_type;
+
+ explicit
+ object_data (pointer_type o): obj (o), state (tracking) {}
+
+ pointer_type obj;
+ pointer_type orig;
+ object_state state;
+ };
+
+ template <typename T>
+ struct object_map: object_map_base,
+ std::map<typename odb::object_traits<T>::id_type,
+ object_data<T> >
+ {
+ virtual bool
+ flush (odb::database&);
+
+ virtual void
+ mark (unsigned short event);
+ };
+
+ // Object cache interface.
+ //
+public:
+ static bool
+ _has_cache () {return current != 0;}
+
+ template <typename T>
+ struct cache_position
+ {
+ typedef object_map<T> map;
+ typedef typename map::iterator iterator;
+
+ cache_position (): map_ (0) {}
+ cache_position (map& m, const iterator& p): map_ (&m), pos_ (p) {}
+
+ cache_position (const cache_position& p)
+ : map_ (p.map_)
+ {
+ // It might not be ok to use an uninitialized iterator.
+ //
+ if (p.map_ != 0)
+ pos_ = p.pos_;
+ }
+
+ cache_position&
+ operator= (const cache_position& p)
+ {
+ // It might not be ok to use an uninitialized iterator on the rhs.
+ //
+ if (p.map_ != 0)
+ pos_ = p.pos_;
+ map_ = p.map_;
+ return *this;
+ }
+
+ map* map_;
+ iterator pos_;
+ };
+
+ // Cache management.
+ //
+ template <typename T>
+ static cache_position<T>
+ _cache_insert (odb::database&,
+ const typename odb::object_traits<T>::id_type&,
+ const typename odb::object_traits<T>::pointer_type&);
+
+ template <typename T>
+ static typename odb::object_traits<T>::pointer_type
+ _cache_find (odb::database&, const typename odb::object_traits<T>::id_type&);
+
+ template <typename T>
+ static void
+ _cache_erase (const cache_position<T>& p)
+ {
+ if (p.map_ != 0)
+ p.map_->erase (p.pos_);
+ }
+
+ // Notifications.
+ //
+ template <typename T>
+ static void
+ _cache_persist (const cache_position<T>& p)
+ {
+ _cache_load (p);
+ }
+
+ template <typename T>
+ static void
+ _cache_load (const cache_position<T>&);
+
+ template <typename T>
+ static void
+ _cache_update (odb::database&, const T&);
+
+ template <typename T>
+ static void
+ _cache_erase (odb::database&,
+ const typename odb::object_traits<T>::id_type&);
+
+private:
+ // Post-commit/rollback callback.
+ //
+ static void
+ mark (unsigned short event, void* key, unsigned long long);
+
+private:
+ typedef std::map<const std::type_info*,
+ std::shared_ptr<object_map_base>,
+ odb::details::type_info_comparator> type_map;
+ type_map map_;
+ odb::transaction* tran_;
+};
+
+#include "session.txx"
+
+#endif // SESSION_HXX
diff --git a/odb-tests/common/session/custom/session.txx b/odb-tests/common/session/custom/session.txx
new file mode 100644
index 0000000..65ab933
--- /dev/null
+++ b/odb-tests/common/session/custom/session.txx
@@ -0,0 +1,159 @@
+// file : common/session/custom/session.txx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#include <cassert>
+
+template <typename T>
+typename session::cache_position<T> session::
+_cache_insert (odb::database&,
+ const typename odb::object_traits<T>::id_type& id,
+ const typename odb::object_traits<T>::pointer_type& obj)
+{
+ if (current == 0)
+ return cache_position<T> (); // No session, return empty position.
+
+ std::shared_ptr<object_map_base>& pm (current->map_[&typeid (T)]);
+
+ if (!pm)
+ pm.reset (new object_map<T>);
+
+ object_map<T>& m (static_cast<object_map<T>&> (*pm));
+
+ typename object_map<T>::value_type vt (id, object_data<T> (obj));
+ std::pair<typename object_map<T>::iterator, bool> r (m.insert (vt));
+
+ // We shall never try to re-insert the same object into the cache.
+ //
+ assert (r.second);
+
+ return cache_position<T> (m, r.first);
+}
+
+template <typename T>
+typename odb::object_traits<T>::pointer_type session::
+_cache_find (odb::database&, const typename odb::object_traits<T>::id_type& id)
+{
+ typedef typename odb::object_traits<T>::pointer_type pointer_type;
+
+ if (current == 0)
+ return pointer_type (); // No session, return NULL pointer.
+
+ type_map::const_iterator ti (current->map_.find (&typeid (T)));
+
+ if (ti == current->map_.end ())
+ return pointer_type ();
+
+ const object_map<T>& m (static_cast<const object_map<T>&> (*ti->second));
+ typename object_map<T>::const_iterator oi (m.find (id));
+
+ if (oi == m.end ())
+ return pointer_type ();
+
+ return oi->second.obj;
+}
+
+template <typename T>
+void session::
+_cache_load (const cache_position<T>& p)
+{
+ typedef typename odb::object_traits<T>::pointer_type pointer_type;
+
+ if (p.map_ == 0)
+ return; // Empty position.
+
+ // Make a copy for change tracking. If our object model had a
+ // polymorphic hierarchy, then we would have had to use a
+ // virtual function-based mechanism (e.g., clone()) instead of
+ // the copy constructor since for a polymorphic hierarchy all
+ // the derived objects are stored as pointers to the root object.
+ //
+ p.pos_->second.orig = pointer_type (new T (*p.pos_->second.obj));
+}
+
+template <typename T>
+void session::
+_cache_update (odb::database&, const T& obj)
+{
+ typedef odb::object_traits<T> object_traits;
+ typedef typename object_traits::pointer_type pointer_type;
+
+ if (current == 0)
+ return; // No session.
+
+ // User explicitly updated the object by calling database::update().
+ // Change the state to flushed and reset the original copy (we are
+ // still tracking changes after the update).
+ //
+ type_map::iterator ti (current->map_.find (&typeid (T)));
+
+ if (ti == current->map_.end ())
+ return; // This object is not in the session.
+
+ object_map<T>& m (static_cast<object_map<T>&> (*ti->second));
+ typename object_map<T>::iterator oi (m.find (object_traits::id (obj)));
+
+ if (oi == m.end ())
+ return; // This object is not in the session.
+
+ object_data<T>& d (oi->second);
+ d.orig = pointer_type (new T (*d.obj));
+ d.state = flushed;
+}
+
+template <typename T>
+void session::
+_cache_erase (odb::database&,
+ const typename odb::object_traits<T>::id_type& id)
+{
+ if (current == 0)
+ return; // No session.
+
+ type_map::iterator ti (current->map_.find (&typeid (T)));
+
+ if (ti == current->map_.end ())
+ return;
+
+ object_map<T>& m (static_cast<object_map<T>&> (*ti->second));
+ typename object_map<T>::iterator oi (m.find (id));
+
+ if (oi == m.end ())
+ return;
+
+ m.erase (oi);
+
+ if (m.empty ())
+ current->map_.erase (ti);
+}
+
+template <typename T>
+bool session::object_map<T>::
+flush (odb::database& db)
+{
+ bool r (false);
+ for (typename object_map<T>::iterator i (this->begin ()), e (this->end ());
+ i != e; ++i)
+ {
+ object_data<T>& d (i->second);
+
+ if (d.state == changed || d.obj->changed (*d.orig))
+ db.update (d.obj); // State changed by the update() notification.
+
+ r = r || d.state == flushed;
+ }
+
+ return r;
+}
+
+template <typename T>
+void session::object_map<T>::
+mark (unsigned short event)
+{
+ for (typename object_map<T>::iterator i (this->begin ()), e (this->end ());
+ i != e; ++i)
+ {
+ object_data<T>& d (i->second);
+
+ if (d.state == flushed)
+ d.state = event == odb::transaction::event_commit ? tracking : changed;
+ }
+}
diff --git a/odb-tests/common/session/custom/test.hxx b/odb-tests/common/session/custom/test.hxx
new file mode 100644
index 0000000..3f2703f
--- /dev/null
+++ b/odb-tests/common/session/custom/test.hxx
@@ -0,0 +1,118 @@
+// file : common/session/custom/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <memory>
+#include <vector>
+
+#include <odb/core.hxx>
+#include <odb/lazy-ptr.hxx>
+
+class employee;
+
+#pragma db object pointer(std::shared_ptr) session
+class employer
+{
+public:
+ employer (const std::string& name, const std::string& symbol)
+ : name_ (name), symbol_ (symbol) {}
+
+ const std::string&
+ name () const {return name_;}
+
+ const std::string&
+ symbol () const {return symbol_;}
+
+ void
+ symbol (const std::string& symbol) {symbol_ = symbol;}
+
+ // Employees of this employer.
+ //
+ typedef std::vector<odb::lazy_weak_ptr<employee>> employees_type;
+
+ const employees_type&
+ employees () const {return employees_;}
+
+ employees_type&
+ employees () {return employees_;}
+
+ // Change tracking.
+ //
+public:
+ bool
+ changed (const employer& orig) const
+ {
+ // Note that we don't need to track object ids, inverse pointers, nor
+ // readonly/const data members.
+ //
+ return symbol_ != orig.symbol_;
+ }
+
+private:
+ friend class odb::access;
+ employer () {}
+
+ #pragma db id
+ std::string name_;
+
+ std::string symbol_;
+
+ #pragma db value_not_null inverse(employer_)
+ employees_type employees_;
+};
+
+#pragma db object pointer(std::shared_ptr) session
+class employee
+{
+public:
+ typedef ::employer employer_type;
+
+ employee (const std::string& first,
+ const std::string& last,
+ std::shared_ptr<employer_type> employer)
+ : first_ (first), last_ (last), employer_ (employer) {}
+
+ // Name.
+ //
+ const std::string&
+ first () const {return first_;}
+
+ const std::string&
+ last () const {return last_;}
+
+ // Employer.
+ //
+ std::shared_ptr<employer_type>
+ employer () const {return employer_;}
+
+ void
+ employer (std::shared_ptr<employer_type> e) {employer_ = e;}
+
+ // Change tracking.
+ //
+public:
+ bool
+ changed (const employee& orig) const
+ {
+ return first_ != orig.first_ || last_ != orig.last_ ||
+ employer_ != orig.employer_;
+ }
+
+private:
+ friend class odb::access;
+ employee () {}
+
+ #pragma db id auto
+ unsigned long id_;
+
+ std::string first_;
+ std::string last_;
+
+ #pragma db not_null
+ std::shared_ptr<employer_type> employer_;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/session/custom/testscript b/odb-tests/common/session/custom/testscript
new file mode 100644
index 0000000..39c281d
--- /dev/null
+++ b/odb-tests/common/session/custom/testscript
@@ -0,0 +1,33 @@
+# file : common/session/custom/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/statement/processing/buildfile b/odb-tests/common/statement/processing/buildfile
new file mode 100644
index 0000000..97124e8
--- /dev/null
+++ b/odb-tests/common/statement/processing/buildfile
@@ -0,0 +1,8 @@
+# file : common/statement/processing/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libs = libodb%lib{odb}
+
+exe{driver}: {hxx cxx}{*} $libs testscript
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
diff --git a/odb-tests/common/statement/processing/driver.cxx b/odb-tests/common/statement/processing/driver.cxx
new file mode 100644
index 0000000..2d00107
--- /dev/null
+++ b/odb-tests/common/statement/processing/driver.cxx
@@ -0,0 +1,619 @@
+// file : common/statement/processing/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test internal statement processing machinery.
+//
+
+#include <string>
+#include <iostream>
+
+#include <odb/statement.hxx>
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+
+static bool
+insert (const char* stmt,
+ const char* expected,
+ const void* const* bind,
+ size_t bind_size)
+{
+ string r;
+ odb::statement::process_insert (
+ r, stmt, bind, bind_size, sizeof (void*), '$');
+ return r == expected;
+}
+
+static bool
+update (const char* stmt,
+ const char* expected,
+ const void* const* bind,
+ size_t bind_size)
+{
+ string r;
+ odb::statement::process_update (
+ r, stmt, bind, bind_size, sizeof (void*), '$');
+ return r == expected;
+}
+
+static bool
+select (const char* stmt,
+ const char* expected,
+ const void* const* bind,
+ size_t bind_size)
+{
+ string r;
+ odb::statement::process_select (
+ r, stmt, bind, bind_size, sizeof (void*), '[', ']', true);
+ return r == expected;
+}
+
+int
+main (int, char* argv[])
+{
+ //
+ // INSERT
+ //
+
+ // Fast path.
+ //
+ {
+ void* b[] = {argv, argv};
+ assert (insert ("INSERT INTO [foo]\n"
+ "([a],\n[b])\n"
+ "VALUES\n"
+ "(DEFAULT,\n$1)",
+ "INSERT INTO [foo] ([a], [b]) VALUES (DEFAULT, $1)",
+ b, 2));
+ }
+
+ // Empty via statement.
+ //
+ /* LIBODB_DEBUG_STATEMENT_PROCESSING
+ {
+ assert (insert ("INSERT INTO [foo]\n"
+ "DEFAULT VALUES",
+ "INSERT INTO [foo] DEFAULT VALUES",
+ 0, 0));
+ }
+ */
+
+ // Empty via bind.
+ //
+ {
+ void* b[] = {0};
+ assert (insert ("INSERT INTO [foo]\n"
+ "([a])\n"
+ "VALUES\n"
+ "($1)",
+ "INSERT INTO [foo] DEFAULT VALUES",
+ b, 1));
+ }
+
+ // Empty with OUTPUT.
+ //
+ {
+ void* b[] = {0, 0};
+ assert (insert ("INSERT INTO [foo]\n"
+ "([a],\n[b])\n"
+ "OUTPUT INSERTED.[id]\n"
+ "VALUES\n"
+ "($1,\n$2)",
+ "INSERT INTO [foo] OUTPUT INSERTED.[id] DEFAULT VALUES",
+ b, 2));
+ }
+
+ // Empty with RETURNING.
+ //
+ {
+ void* b[] = {0, 0, 0};
+ assert (insert ("INSERT INTO [foo]\n"
+ "([a],\n[b],\n[c])\n"
+ "VALUES\n"
+ "($1,\n$1,\n$2)\n"
+ "RETURNING [id]",
+ "INSERT INTO [foo] DEFAULT VALUES RETURNING [id]",
+ b, 3));
+ }
+
+ // Empty via bind, but not values.
+ //
+ {
+ void* b[] = {0};
+ assert (insert ("INSERT INTO [foo]\n"
+ "([a],\n[b])\n"
+ "VALUES\n"
+ "(1,\n$1)",
+ "INSERT INTO [foo] ([a]) VALUES (1)",
+ b, 1));
+ }
+
+ // Empty via bind, but not values.
+ //
+ {
+ void* b[] = {0};
+ assert (insert ("INSERT INTO [foo]\n"
+ "([a],\n[b],\n[c])\n"
+ "VALUES\n"
+ "(1,\n$1,\nDEFAULT)",
+ "INSERT INTO [foo] ([a], [c]) VALUES (1, DEFAULT)",
+ b, 1));
+ }
+
+ // First not present.
+ //
+ {
+ void* b[] = {0, argv, argv};
+ assert (insert ("INSERT INTO [foo]\n"
+ "([a],\n[b],\n[c])\n"
+ "VALUES\n"
+ "($1,\n$2,\n$3)",
+ "INSERT INTO [foo] ([b], [c]) VALUES ($2, $3)",
+ b, 3));
+ }
+
+ // Last not present.
+ //
+ {
+ void* b[] = {argv, argv, 0};
+ assert (insert ("INSERT INTO [foo]\n"
+ "([a],\n[b],\n[c])\n"
+ "VALUES\n"
+ "($1,\n$2,\n$3)",
+ "INSERT INTO [foo] ([a], [b]) VALUES ($1, $2)",
+ b, 3));
+ }
+
+ // Middle not present.
+ //
+ {
+ void* b[] = {argv, 0, argv};
+ assert (insert ("INSERT INTO [foo]\n"
+ "([a],\n[b],\n[c])\n"
+ "VALUES\n"
+ "($1,\n$2,\n$3)",
+ "INSERT INTO [foo] ([a], [c]) VALUES ($1, $3)",
+ b, 3));
+ }
+
+ // Multiple not present.
+ //
+ {
+ void* b[] = {0, argv, 0, argv, 0};
+ assert (insert ("INSERT INTO [foo]\n"
+ "([a],\n[b],\n[c],\n[d],\n[e])\n"
+ "VALUES\n"
+ "($1,\n$2,\n$3,\n$4,\n$5)",
+ "INSERT INTO [foo] ([b], [d]) VALUES ($2, $4)",
+ b, 5));
+ }
+
+ // Not present and OUTPUT.
+ //
+ {
+ void* b[] = {argv, 0, argv};
+ assert (insert ("INSERT INTO [foo]\n"
+ "([a],\n[b],\n[c])\n"
+ "OUTPUT INSERTED.[id]\n"
+ "VALUES\n"
+ "($1,\n$2,\n$3)",
+ "INSERT INTO [foo] ([a], [c]) OUTPUT INSERTED.[id] "
+ "VALUES ($1, $3)",
+ b, 3));
+ }
+
+ // Not present and RETURNING.
+ //
+ {
+ void* b[] = {argv, 0, argv};
+ assert (insert ("INSERT INTO [foo]\n"
+ "([a],\n[b],\n[c])\n"
+ "VALUES\n"
+ "($1,\n$2,\n$3)\n"
+ "RETURNING [id]",
+ "INSERT INTO [foo] ([a], [c]) VALUES ($1, $3) "
+ "RETURNING [id]",
+ b, 3));
+ }
+
+ // Value expressions.
+ //
+ {
+ void* b[] = {argv, argv, argv};
+ assert (insert ("INSERT INTO [foo]\n"
+ "([a],\n[b],\n[c])\n"
+ "VALUES\n"
+ "($1,\nCAST($2, TEXT),\n$3)",
+ "INSERT INTO [foo] ([a], [b], [c]) "
+ "VALUES ($1, CAST($2, TEXT), $3)",
+ b, 3));
+ }
+
+ //
+ // UPDATE
+ //
+
+ // Fast path.
+ //
+ {
+ void* b[] = {argv, argv};
+ assert (update ("UPDATE [foo]\n"
+ "SET\n"
+ "ver=ver+1,\n[a]=$1\n"
+ "WHERE [id]=$2",
+ "UPDATE [foo] SET ver=ver+1, [a]=$1 WHERE [id]=$2",
+ b, 2));
+ }
+
+ // Empty via bind.
+ //
+ {
+ void* b[] = {0, argv};
+ assert (update ("UPDATE [foo]\n"
+ "SET\n"
+ "[a]=$1\n"
+ "WHERE [id]=$2",
+ "",
+ b, 2));
+ }
+
+ // Empty via bind, but not values.
+ //
+ {
+ void* b[] = {0, argv};
+ assert (update ("UPDATE [foo]\n"
+ "SET\n"
+ "ver=ver+1,\n[a]=$1\n"
+ "WHERE [id]=$2",
+ "UPDATE [foo] SET ver=ver+1 WHERE [id]=$2",
+ b, 2));
+ }
+
+ // First not present.
+ //
+ {
+ void* b[] = {0, argv, argv, argv};
+ assert (update ("UPDATE [foo]\n"
+ "SET\n"
+ "[a]=$1,\n"
+ "[b]=$2,\n"
+ "[c]=$3\n"
+ "WHERE [id]=$4",
+ "UPDATE [foo] SET [b]=$2, [c]=$3 WHERE [id]=$4",
+ b, 4));
+ }
+
+ // Last not present.
+ //
+ {
+ void* b[] = {argv, argv, 0, argv};
+ assert (update ("UPDATE [foo]\n"
+ "SET\n"
+ "[a]=$1,\n"
+ "[b]=$2,\n"
+ "[c]=$3\n"
+ "WHERE [id]=$4",
+ "UPDATE [foo] SET [a]=$1, [b]=$2 WHERE [id]=$4",
+ b, 4));
+ }
+
+ // Middle not present.
+ //
+ {
+ void* b[] = {argv, 0, argv, argv};
+ assert (update ("UPDATE [foo]\n"
+ "SET\n"
+ "[a]=$1,\n"
+ "[b]=$2,\n"
+ "[c]=$3\n"
+ "WHERE [id]=$4",
+ "UPDATE [foo] SET [a]=$1, [c]=$3 WHERE [id]=$4",
+ b, 4));
+ }
+
+ // Multiple not present.
+ //
+ {
+ void* b[] = {0, argv, 0, argv, argv};
+ assert (update ("UPDATE [foo]\n"
+ "SET\n"
+ "[a]=$1,\n"
+ "[b]=$2,\n"
+ "[c]=$3,\n"
+ "[d]=$4\n"
+ "WHERE [id]=$5",
+ "UPDATE [foo] SET [b]=$2, [d]=$4 WHERE [id]=$5",
+ b, 5));
+ }
+
+ // Not present and OUTPUT.
+ //
+ {
+ void* b[] = {argv, 0, argv, argv};
+ assert (update ("UPDATE [foo]\n"
+ "SET\n"
+ "[a]=$1,\n"
+ "[b]=$2,\n"
+ "[c]=$3\n"
+ "OUTPUT INSERTED.[ver] "
+ "WHERE [id]=$4",
+ "UPDATE [foo] SET [a]=$1, [c]=$3 OUTPUT INSERTED.[ver] "
+ "WHERE [id]=$4",
+ b, 4));
+ }
+
+ // Value expressions.
+ //
+ {
+ void* b[] = {argv, argv, argv, argv};
+ assert (update ("UPDATE [foo]\n"
+ "SET\n"
+ "[a]=$1,\n"
+ "[b]=CAST($2, TEXT),\n"
+ "[c]=$3\n"
+ "WHERE [id]=$4",
+ "UPDATE [foo] SET [a]=$1, [b]=CAST($2, TEXT), [c]=$3 "
+ "WHERE [id]=$4",
+ b, 4));
+ }
+
+ // No OUTPUT/WHERE clause.
+ //
+ {
+ void* b[] = {argv, 0, argv};
+ assert (update ("UPDATE [foo]\n"
+ "SET\n"
+ "[a]=$1,\n"
+ "[b]=$2,\n"
+ "[c]=$3",
+ "UPDATE [foo] SET [a]=$1, [c]=$3",
+ b, 4));
+ }
+
+ //
+ // SELECT
+ //
+
+ // Empty.
+ //
+ {
+ void* b[] = {0, 0, 0};
+ assert (select ("SELECT\n"
+ "[a].[x],\n"
+ "[t].[y],\n"
+ "[t].[z]\n"
+ "FROM [t]\n"
+ "LEFT JOIN [t1] AS [a] ON [a].[id]=[t].[id]\n"
+ "WHERE [t].[id]=$1",
+ "",
+ b, 3));
+ }
+
+ // Fast path.
+ //
+ {
+ void* b[] = {argv, argv};
+ assert (select ("SELECT\n"
+ "[s].[t].[x],\n"
+ "[a].[y]\n"
+ "FROM [s].[t]\n"
+ "LEFT JOIN [t1] AS [a] ON [a].[id]=[s].[t].[id]\n"
+ "WHERE [s].[t].[id]=$1",
+ "SELECT [s].[t].[x], [a].[y] FROM [s].[t] "
+ "LEFT JOIN [t1] AS [a] ON [a].[id]=[s].[t].[id] "
+ "WHERE [s].[t].[id]=$1",
+ b, 2));
+ }
+
+ // First not present.
+ //
+ {
+ void* b[] = {0, argv, argv};
+ assert (select ("SELECT\n"
+ "[a].[x],\n"
+ "[t].[y],\n"
+ "[t].[z]\n"
+ "FROM [t]\n"
+ "LEFT JOIN [t1] AS [a] ON [a].[id]=[t].[id]\n"
+ "WHERE [t].[id]=$1",
+ "SELECT [t].[y], [t].[z] FROM [t] WHERE [t].[id]=$1",
+ b, 3));
+ }
+
+ // Last not present.
+ //
+ {
+ void* b[] = {argv, argv, 0};
+ assert (select ("SELECT\n"
+ "[t].[x],\n"
+ "[t].[y],\n"
+ "[a].[z]\n"
+ "FROM [t]\n"
+ "LEFT JOIN [t1] AS [a] ON [a].[id]=[t].[id]\n"
+ "WHERE [t].[id]=$1",
+ "SELECT [t].[x], [t].[y] FROM [t] WHERE [t].[id]=$1",
+ b, 3));
+ }
+
+ // Middle not present.
+ //
+ {
+ void* b[] = {argv, 0, argv};
+ assert (select ("SELECT\n"
+ "[t].[x],\n"
+ "[a].[y],\n"
+ "[t].[z]\n"
+ "FROM [t]\n"
+ "LEFT JOIN [t1] AS [a] ON [a].[id]=[t].[id]\n"
+ "WHERE [t].[id]=$1",
+ "SELECT [t].[x], [t].[z] FROM [t] WHERE [t].[id]=$1",
+ b, 3));
+ }
+
+ // Multiple not present.
+ //
+ {
+ void* b[] = {0, argv, 0, argv};
+ assert (select ("SELECT\n"
+ "[a1].[w],\n"
+ "[t].[x],\n"
+ "[a2].[y],\n"
+ "[a3].[z]\n"
+ "FROM [t]\n"
+ "LEFT JOIN [t1] AS [a1] ON [a1].[id]=[t].[id]\n"
+ "LEFT JOIN [t2] AS [a2] ON [a2].[id]=[t].[id]\n"
+ "LEFT JOIN [t3] AS [a3] ON [a3].[id]=[t].[id]\n"
+ "WHERE [t].[id]=$1",
+ "SELECT [t].[x], [a3].[z] FROM [t] "
+ "LEFT JOIN [t3] AS [a3] ON [a3].[id]=[t].[id] "
+ "WHERE [t].[id]=$1",
+ b, 4));
+ }
+
+ // Column expression.
+ //
+ {
+ void* b[] = {argv, argv, 0};
+ assert (select ("SELECT\n"
+ "[t].[x],\n"
+ "CAST([a].[y], TEXT),\n"
+ "[t].[z]\n"
+ "FROM [t]\n"
+ "LEFT JOIN [t1] AS [a] ON [a].[id]=[t].[id]\n"
+ "WHERE [t].[id]=$1",
+ "SELECT [t].[x], CAST([a].[y], TEXT) FROM [t] "
+ "LEFT JOIN [t1] AS [a] ON [a].[id]=[t].[id] "
+ "WHERE [t].[id]=$1",
+ b, 3));
+ }
+
+ // No WHERE.
+ //
+ {
+ void* b[] = {argv, 0, argv};
+ assert (select ("SELECT\n"
+ "[t].[x],\n"
+ "[t].[y],\n"
+ "[t].[z]\n"
+ "FROM [t]",
+ "SELECT [t].[x], [t].[z] FROM [t]",
+ b, 3));
+ }
+
+ // JOIN without WHERE.
+ //
+ {
+ void* b[] = {argv, 0, argv};
+ assert (select ("SELECT\n"
+ "[t].[x],\n"
+ "[a].[y],\n"
+ "[t].[z]\n"
+ "FROM [t]\n"
+ "LEFT JOIN [t1] AS [a] ON [a].[id]=[t].[id]",
+ "SELECT [t].[x], [t].[z] FROM [t]",
+ b, 3));
+ }
+
+ // JOIN presence because of WHERE.
+ //
+ {
+ void* b[] = {argv, 0, argv};
+ assert (select ("SELECT\n"
+ "[t].[x],\n"
+ "[a].[y],\n"
+ "[t].[z]\n"
+ "FROM [t]\n"
+ "LEFT JOIN [t1] AS [a] ON [a].[id]=[t].[id]\n"
+ "WHERE [t].[id]=$1 AND [a].[id]=$2",
+ "SELECT [t].[x], [t].[z] FROM [t] "
+ "LEFT JOIN [t1] AS [a] ON [a].[id]=[t].[id] "
+ "WHERE [t].[id]=$1 AND [a].[id]=$2",
+ b, 3));
+ }
+
+
+ // JOIN presence because of dependent JOIN.
+ //
+ {
+ void* b[] = {argv, argv, argv};
+ assert (select ("SELECT\n"
+ "[t].[x],\n"
+ "[a_b].[y],\n"
+ "[t].[z]\n"
+ "FROM [t]\n"
+ "LEFT JOIN [d] AS [a_d] ON [a_d].[id]=[t].[id]\n"
+ "LEFT JOIN [b] AS [a_b] ON [a_b].[id]=[a_d].[id]\n"
+ "WHERE [t].[id]=$1",
+ "SELECT [t].[x], [a_b].[y], [t].[z] FROM [t] "
+ "LEFT JOIN [d] AS [a_d] ON [a_d].[id]=[t].[id] "
+ "LEFT JOIN [b] AS [a_b] ON [a_b].[id]=[a_d].[id] "
+ "WHERE [t].[id]=$1",
+ b, 3));
+ }
+
+ // JOIN without alias and with schema.
+ //
+ {
+ void* b[] = {argv, argv, argv};
+ assert (select ("SELECT\n"
+ "[t].[x],\n"
+ "[s].[t1].[y],\n"
+ "[t2].[z]\n"
+ "FROM [t]\n"
+ "LEFT JOIN [s].[t1] ON [s].[t1].[id]=[t].[id]\n"
+ "LEFT JOIN [t2] ON [t2].[id]=[t].[id]\n"
+ "WHERE [t].[id]=$1",
+ "SELECT [t].[x], [s].[t1].[y], [t2].[z] FROM [t] "
+ "LEFT JOIN [s].[t1] ON [s].[t1].[id]=[t].[id] "
+ "LEFT JOIN [t2] ON [t2].[id]=[t].[id] "
+ "WHERE [t].[id]=$1",
+ b, 3));
+ }
+
+ // JOIN alias top-level qualifer test.
+ //
+ {
+ void* b[] = {argv, 0};
+ assert (select ("SELECT\n"
+ "[s].[a].[x],\n"
+ "[a].[y]\n"
+ "FROM [s].[a]\n"
+ "LEFT JOIN [t1] AS [a] ON [a].[id]=[s].[a].[id]\n"
+ "WHERE [s].[a].[id]=$1",
+ "SELECT [s].[a].[x] FROM [s].[a] WHERE [s].[a].[id]=$1",
+ b, 2));
+ }
+
+ // JOIN alias bottom-level qualifer test (FROM case).
+ //
+ {
+ void* b[] = {argv, 0};
+ assert (select ("SELECT\n"
+ "[a].[t].[x],\n"
+ "[a].[y]\n"
+ "FROM [a].[t]\n"
+ "LEFT JOIN [t1] AS [a] ON [a].[id]=[a].[t].[id]\n"
+ "WHERE [a].[t].[id]=$1",
+ "SELECT [a].[t].[x] FROM [a].[t] WHERE [a].[t].[id]=$1",
+ b, 2));
+ }
+
+ // JOIN alias bottom-level qualifer test (LEFT JOIN case).
+ //
+ {
+ void* b[] = {0, argv};
+ assert (select ("SELECT\n"
+ "[a].[y],\n"
+ "[a].[t2].[x]\n"
+ "FROM [t]\n"
+ "LEFT JOIN [t1] AS [a] ON [a].[id]=[t].[id]\n"
+ "LEFT JOIN [a].[t2] ON [a].[t2].[id]=[t].[id]\n"
+ "WHERE [t].[id]=$1",
+ "SELECT [a].[t2].[x] FROM [t] "
+ "LEFT JOIN [a].[t2] ON [a].[t2].[id]=[t].[id] "
+ "WHERE [t].[id]=$1",
+ b, 2));
+ }
+}
diff --git a/odb-tests/common/statement/processing/testscript b/odb-tests/common/statement/processing/testscript
new file mode 100644
index 0000000..2460dc6
--- /dev/null
+++ b/odb-tests/common/statement/processing/testscript
@@ -0,0 +1,6 @@
+# file : common/statement/processing/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+: basics
+:
+$*
diff --git a/odb-tests/common/threads/buildfile b/odb-tests/common/threads/buildfile
new file mode 100644
index 0000000..53b98ec
--- /dev/null
+++ b/odb-tests/common/threads/buildfile
@@ -0,0 +1,49 @@
+# file : common/threads/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 threads_ \
+ --generate-schema \
+ --generate-query \
+ --generate-prepared
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# While we don't call any pthread_*() functions, this appears to be needed for
+# some std::thread implementations (like libstdc++). Note that
+# odb::details::thread inlines some std::thread API calls.
+#
+if ($cxx.target.class != 'windows')
+ cxx.libs += -pthread
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/threads/driver.cxx b/odb-tests/common/threads/driver.cxx
new file mode 100644
index 0000000..1add011
--- /dev/null
+++ b/odb-tests/common/threads/driver.cxx
@@ -0,0 +1,236 @@
+// file : common/threads/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test operations in a multi-threaded environment.
+//
+
+#include <vector>
+#include <memory> // std::unique_ptr
+#include <cstddef> // std::size_t
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <odb/details/shared-ptr.hxx>
+#include <odb/details/thread.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_*
+#include <libcommon/common.hxx>
+
+#if defined(DATABASE_SQLITE)
+# include <odb/sqlite/database.hxx>
+#endif
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+namespace details = odb::details;
+
+const unsigned long thread_count = 24;
+const unsigned long iteration_count = 30;
+const unsigned long sub_iteration_count = 40;
+
+struct task
+{
+ task (database& db, unsigned long n)
+ : db_ (db), n_ (n)
+ {
+ }
+
+ void*
+ execute ()
+ {
+ try
+ {
+ for (unsigned long i (0); i < iteration_count; ++i)
+ {
+ unsigned long id ((n_ * iteration_count + i) * 3);
+
+ object o1 (id, "first object");
+ object o2 (id + 1, "second object");
+ object o3 (id + 2, "third object");
+
+ // The following transactions may lead to deadlocks.
+ //
+ while (true)
+ {
+ try
+ {
+ transaction t (db_.begin ());
+
+ db_.persist (o1);
+ db_.persist (o2);
+ db_.persist (o3);
+ t.commit ();
+ break;
+ }
+ catch (const deadlock&) {}
+ }
+
+ while (true)
+ {
+ try
+ {
+#if !defined(DATABASE_SQLITE)
+ transaction t (db_.begin ());
+#else
+ // SQLite has a peculiar table locking mode (shared cache)
+ // which can lead to any of the transactions in this test
+ // deadlocking even though they shouldn't from the user's
+ // perspective. One way to work around this problem is to
+ // start a "write" transaction as such right away.
+ //
+ transaction t;
+
+ if (db_.id () != odb::id_sqlite)
+ t.reset (db_.begin ());
+ else
+ {
+ t.reset (
+ static_cast<odb::sqlite::database&> (db_).begin_immediate ());
+ }
+#endif
+ unique_ptr<object> o (db_.load<object> (id));
+ assert (o->str_ == "first object");
+ o->str_ = "another value";
+ db_.update (*o);
+ t.commit ();
+ break;
+ }
+ catch (const deadlock&) {}
+ }
+
+ for (unsigned long j (0); j < sub_iteration_count; ++j)
+ {
+ typedef odb::query<object> query;
+ typedef odb::prepared_query<object> prep_query;
+ typedef odb::result<object> result;
+
+ while (true)
+ {
+ try
+ {
+ transaction t (db_.begin ());
+
+ prep_query pq (db_.lookup_query<object> ("object-query"));
+
+ if (!pq)
+ {
+ pq = db_.prepare_query<object> (
+ "object-query", query::str == "another value");
+ db_.cache_query (pq);
+ }
+
+ result r (pq.execute (false));
+
+ bool found (false);
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ {
+ if (i->id_ == id)
+ {
+ found = true;
+ break;
+ }
+ }
+ assert (found);
+ t.commit ();
+ break;
+ }
+ catch (const deadlock&) {}
+ }
+ }
+
+ while (true)
+ {
+ try
+ {
+ transaction t (db_.begin ());
+ db_.erase<object> (id);
+ t.commit ();
+ break;
+ }
+ catch (const deadlock&) {}
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return reinterpret_cast<void*> (1);
+ }
+
+ return 0;
+ }
+
+ static void*
+ execute (void* arg)
+ {
+ return static_cast<task*> (arg)->execute ();
+ }
+
+ database& db_;
+ unsigned long n_;
+};
+
+bool
+test (int argc, char* argv[], size_t max_connections)
+{
+ unique_ptr<database> db (create_database (argc, argv, true, max_connections));
+
+ vector<details::shared_ptr<details::thread> > threads;
+ vector<details::shared_ptr<task> > tasks;
+
+ for (unsigned long i (0); i < thread_count; ++i)
+ {
+ details::shared_ptr<task> t (new (details::shared) task (*db, i));
+ tasks.push_back (t);
+
+ threads.push_back (
+ details::shared_ptr<details::thread> (
+ new (details::shared) details::thread (&task::execute, t.get ())));
+ }
+
+ bool r (true);
+
+ for (unsigned long i (0); i < thread_count; ++i)
+ if (threads[i]->join () != 0)
+ r = false;
+
+ {
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> ());
+
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ db->erase<object> (i->id_);
+
+ t.commit ();
+ }
+
+ return r;
+}
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ if (!(test (argc, argv, 0) &&
+ test (argc, argv, thread_count - 1) &&
+ test (argc, argv, thread_count / 2) &&
+ test (argc, argv, thread_count / 4)))
+ return 1;
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/threads/test.hxx b/odb-tests/common/threads/test.hxx
new file mode 100644
index 0000000..2ed6e67
--- /dev/null
+++ b/odb-tests/common/threads/test.hxx
@@ -0,0 +1,29 @@
+// file : common/template/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+
+#include <odb/core.hxx>
+
+#pragma db object
+struct object
+{
+ object (unsigned long id, const std::string& str)
+ : id_ (id), str_ (str)
+ {
+ }
+
+ object ()
+ {
+ }
+
+ #pragma db id
+ unsigned long id_;
+
+ std::string str_;
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/threads/testscript b/odb-tests/common/threads/testscript
new file mode 100644
index 0000000..87e03e0
--- /dev/null
+++ b/odb-tests/common/threads/testscript
@@ -0,0 +1,50 @@
+# file : common/threads/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ # Note: this is quite slow:
+ #
+ # $ time ./driver --database ~/odb-test.db
+ # real 3m5.593s
+ # user 1m1.244s
+ # sys 0m26.793s
+ #
+ # $ time ./driver --database /tmp/odb-test.db
+ # real 0m13.909s
+ # user 0m16.724s
+ # sys 0m4.874s
+ #
+ # $ time ./driver --database "file::memory:"
+ # real 0m12.406s
+ # user 0m15.694s
+ # sys 0m4.207s
+ #
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/transaction/basics/buildfile b/odb-tests/common/transaction/basics/buildfile
new file mode 100644
index 0000000..f412235
--- /dev/null
+++ b/odb-tests/common/transaction/basics/buildfile
@@ -0,0 +1,13 @@
+# file : common/transaction/basics/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libs = libodb%lib{odb}
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{*} $libs testscript
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/transaction/basics/driver.cxx b/odb-tests/common/transaction/basics/driver.cxx
new file mode 100644
index 0000000..1833555
--- /dev/null
+++ b/odb-tests/common/transaction/basics/driver.cxx
@@ -0,0 +1,151 @@
+// file : common/transaction/basics/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test basic transaction operations.
+//
+
+#include <string>
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/tracer.hxx>
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/statement.hxx>
+#include <odb/exceptions.hxx>
+
+#include <libcommon/common.hxx>
+#include <libcommon/concrete.hxx>
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+struct transaction_tracer: odb::tracer
+{
+ virtual void
+ execute (connection&, const char* s)
+ {
+ string str (s);
+
+ if (str == "BEGIN")
+ cout << "begin transaction" << endl;
+ else if (str == "COMMIT")
+ cout << "commit transaction" << endl;
+ else if (str == "ROLLBACK")
+ cout << "rollback transaction" << endl;
+ }
+
+ // Override the other version to get rid of a Sun CC warning.
+ //
+ virtual void
+ execute (connection& c, const statement& s)
+ {
+ execute (c, s.text ());
+ }
+};
+
+int
+main (int argc, char* argv[])
+{
+ {
+ transaction_tracer tracer;
+ unique_ptr<database> db (create_database (argc, argv, false));
+ db->tracer (tracer);
+
+ assert (!transaction::has_current ());
+
+ // Current and db accessors.
+ //
+ cout << "test 001" << endl;
+ {
+ transaction t (db->begin ());
+ assert (&t.database () == db.get ());
+ assert (transaction::has_current ());
+ assert (&transaction::current () == &t);
+
+ transaction::reset_current ();
+ assert (!transaction::has_current ());
+
+ transaction t2 (db->begin (), false);
+ assert (!transaction::has_current ());
+
+ transaction::current (t2);
+ assert (&transaction::current () == &t2);
+ }
+
+ // Commit.
+ //
+ cout << "test 002" << endl;
+ {
+ transaction t (db->begin ());
+ t.commit ();
+ }
+
+ // Rollback.
+ //
+ cout << "test 003" << endl;
+ {
+ transaction t (db->begin ());
+ t.rollback ();
+ }
+
+ // Auto rollback.
+ //
+ cout << "test 004" << endl;
+ {
+ transaction t (db->begin ());
+ }
+
+ // Nested transaction.
+ //
+ cout << "test 005" << endl;
+ {
+ transaction t (db->begin ());
+
+ try
+ {
+ transaction n (db->begin ());
+ }
+ catch (const already_in_transaction&)
+ {
+ cout << "already_in_transaction" << endl;
+ }
+ }
+
+ // Concrete transaction type.
+ //
+ cout << "test 006" << endl;
+ {
+ assert (sizeof (odb_db::transaction) == sizeof (transaction));
+
+ odb_db::transaction t (static_cast<odb_db::database&> (*db).begin ());
+ odb_db::transaction& r (odb_db::transaction::current ());
+ assert (&t == &r);
+ }
+
+ // Transaction restart.
+ //
+ cout << "test 007" << endl;
+ {
+ transaction t (db->begin ());
+ t.commit ();
+ t.reset (db->begin ());
+ t.commit ();
+ }
+ }
+
+ // Test early connection release.
+ //
+ {
+ unique_ptr<database> db (create_database (argc, argv, false, 1));
+ transaction t1 (db->begin ());
+ t1.commit ();
+ transaction t2 (db->begin ());
+ t2.rollback ();
+ transaction t3 (db->begin ());
+ t3.commit ();
+ }
+}
diff --git a/odb-tests/common/transaction/basics/testscript b/odb-tests/common/transaction/basics/testscript
new file mode 100644
index 0000000..94c58b6
--- /dev/null
+++ b/odb-tests/common/transaction/basics/testscript
@@ -0,0 +1,62 @@
+# file : common/transaction/basics/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
++cat <<EOI >=output
+ test 001
+ begin transaction
+ begin transaction
+ rollback transaction
+ rollback transaction
+ test 002
+ begin transaction
+ commit transaction
+ test 003
+ begin transaction
+ rollback transaction
+ test 004
+ begin transaction
+ rollback transaction
+ test 005
+ begin transaction
+ already_in_transaction
+ rollback transaction
+ test 006
+ begin transaction
+ rollback transaction
+ test 007
+ begin transaction
+ commit transaction
+ begin transaction
+ commit transaction
+ EOI
+
+test.redirects += >>>../output
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $*
+}
diff --git a/odb-tests/common/transaction/callback/buildfile b/odb-tests/common/transaction/callback/buildfile
new file mode 100644
index 0000000..78b1b03
--- /dev/null
+++ b/odb-tests/common/transaction/callback/buildfile
@@ -0,0 +1,13 @@
+# file : common/transaction/callback/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libs = libodb%lib{odb}
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{*} $libs testscript
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/transaction/callback/driver.cxx b/odb-tests/common/transaction/callback/driver.cxx
new file mode 100644
index 0000000..d0af993
--- /dev/null
+++ b/odb-tests/common/transaction/callback/driver.cxx
@@ -0,0 +1,231 @@
+// file : common/transaction/callback/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test transaction callbacks.
+//
+
+#include <memory> // std::unique_ptr
+#include <cstddef> // std::size_t
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+struct callback
+{
+ callback (unsigned short v): v_ (v), t_ (0) {}
+ callback (unsigned short v, transaction& t): v_ (v), t_ (0) {register_ (t);}
+ ~callback () {if (t_ != 0) unregister ();}
+
+ void
+ register_ (transaction& t)
+ {
+ t_ = &t;
+ t.callback_register (&func, this, transaction::event_all, v_, &t_);
+ }
+
+ void
+ unregister ()
+ {
+ cout << " unregister callback " << v_ << endl;
+ t_->callback_unregister (this);
+ t_ = 0;
+ }
+
+ void
+ update (unsigned short v)
+ {
+ v_ = v;
+ t_->callback_update (this, transaction::event_all, v_, &t_);
+ }
+
+private:
+ static void
+ func (unsigned short event, void* key, unsigned long long data)
+ {
+ callback& c (*static_cast<callback*> (key));
+
+ const char* en;
+ switch (event)
+ {
+ case transaction::event_commit:
+ en = "commit";
+ break;
+ case transaction::event_rollback:
+ en = "rollback";
+ break;
+ default:
+ en = "unknown";
+ }
+
+ cout << " callback " << c.v_ << " " << en << endl;
+
+ assert (data == c.v_);
+ assert (c.t_ == 0);
+ }
+
+ unsigned short v_;
+ transaction* t_;
+};
+
+struct failed {};
+
+static void
+throw_func (unsigned short, void*, unsigned long long)
+{
+ throw failed ();
+}
+
+static void
+dummy_func (unsigned short, void* key, unsigned long long data)
+{
+ assert (reinterpret_cast<unsigned long long> (key) == data);
+}
+
+static void
+fill (transaction& t)
+{
+ // 20 is from odb/transaction.hxx.
+ //
+ for (size_t i (0); i < 20; ++i)
+ t.callback_register (&dummy_func,
+ reinterpret_cast<void*> (i),
+ transaction::event_all,
+ i);
+}
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ // We want to test both stack and dynamic slots.
+ //
+ for (unsigned short i (1); i < 3; ++i)
+ {
+ // Test basic logic.
+ //
+ cout << "test " << i << "/001" << endl;
+
+ // Commit callback.
+ //
+ {
+ transaction t (db->begin ());
+ if (i == 2) fill (t);
+ callback c1 (1, t);
+ t.commit ();
+ }
+
+ // Rollback callback.
+ //
+ {
+ transaction t (db->begin ());
+ if (i == 2) fill (t);
+ callback c1 (1, t);
+ t.rollback ();
+ }
+
+ // Rollback via exception callback.
+ //
+ {
+ callback c1 (1);
+
+ try
+ {
+ transaction t (db->begin ());
+ if (i == 2) fill (t);
+ c1.register_ (t);
+ throw failed ();
+ }
+ catch (const failed&)
+ {
+ }
+ }
+
+ // Unregister callback at the end.
+ //
+ {
+ transaction t (db->begin ());
+ if (i == 2) fill (t);
+ callback c1 (1, t);
+ c1.unregister ();
+ t.callback_unregister (&c1); // Test unregistering non-registered key.
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ if (i == 2) fill (t);
+ callback c1 (1, t);
+ c1.unregister ();
+ callback c2 (2, t);
+ t.commit ();
+ }
+
+ // Unregister callback in the middle.
+ //
+ cout << "test " << i << "/002" << endl;
+ {
+ transaction t (db->begin ());
+ if (i == 2) fill (t);
+ callback c1 (1, t);
+ callback c2 (2, t);
+ callback c3 (3, t);
+ c2.unregister ();
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ if (i == 2) fill (t);
+ callback c1 (1, t);
+ callback c2 (2, t);
+ callback c3 (3, t);
+ c2.unregister ();
+ callback c4 (4, t); // Using the free slot.
+ t.commit ();
+ }
+
+ // Test a callback in the middle that throws.
+ //
+ cout << "test " << i << "/003" << endl;
+ try
+ {
+ transaction t (db->begin ());
+ if (i == 2) fill (t);
+ callback c1 (1, t);
+ t.callback_register (&throw_func, 0);
+ callback c2 (2, t);
+ t.commit ();
+ }
+ catch (const failed&)
+ {
+ }
+
+ // Test callback_update().
+ //
+ {
+ transaction t (db->begin ());
+ if (i == 2) fill (t);
+ callback c (1, t);
+ c.update (2);
+ t.commit ();
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/transaction/callback/testscript b/odb-tests/common/transaction/callback/testscript
new file mode 100644
index 0000000..7229ecd
--- /dev/null
+++ b/odb-tests/common/transaction/callback/testscript
@@ -0,0 +1,72 @@
+# file : common/transaction/callback/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
++cat <<EOI >=output
+ test 1/001
+ callback 1 commit
+ callback 1 rollback
+ callback 1 rollback
+ unregister callback 1
+ unregister callback 1
+ callback 2 commit
+ test 1/002
+ unregister callback 2
+ callback 1 commit
+ callback 3 commit
+ unregister callback 2
+ callback 1 commit
+ callback 4 commit
+ callback 3 commit
+ test 1/003
+ callback 1 commit
+ callback 2 commit
+ test 2/001
+ callback 1 commit
+ callback 1 rollback
+ callback 1 rollback
+ unregister callback 1
+ unregister callback 1
+ callback 2 commit
+ test 2/002
+ unregister callback 2
+ callback 1 commit
+ callback 3 commit
+ unregister callback 2
+ callback 1 commit
+ callback 4 commit
+ callback 3 commit
+ test 2/003
+ callback 1 commit
+ callback 2 commit
+ EOI
+
+test.redirects += >>>../output
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $*
+}
diff --git a/odb-tests/common/types/buildfile b/odb-tests/common/types/buildfile
new file mode 100644
index 0000000..95fe5b6
--- /dev/null
+++ b/odb-tests/common/types/buildfile
@@ -0,0 +1,39 @@
+# file : common/types/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 types_
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/types/driver.cxx b/odb-tests/common/types/driver.cxx
new file mode 100644
index 0000000..bdc66b8
--- /dev/null
+++ b/odb-tests/common/types/driver.cxx
@@ -0,0 +1,37 @@
+// file : common/types/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test C++ type handling (anonymous types, aliasing).
+//
+
+#include <memory>
+#include <iostream>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+template <typename T1, typename T2>
+struct same_p
+{
+ static const bool result = false;
+};
+
+template <typename T>
+struct same_p<T, T>
+{
+ static const bool result = true;
+};
+
+int
+main ()
+{
+ assert ((same_p<odb::object_traits<object2>::id_type, int>::result));
+}
diff --git a/odb-tests/common/types/test.hxx b/odb-tests/common/types/test.hxx
new file mode 100644
index 0000000..a99b499
--- /dev/null
+++ b/odb-tests/common/types/test.hxx
@@ -0,0 +1,55 @@
+// file : common/types/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#ifdef ODB_COMPILER
+typedef int int_t;
+typedef short num_t;
+#else
+typedef int num_t;
+#endif
+
+typedef num_t num_type;
+
+#pragma db object
+struct object1
+{
+ typedef int int_type;
+
+ #pragma db id
+ int_type id_;
+};
+
+#pragma db object
+struct object2
+{
+ #pragma db id
+ num_type num_;
+};
+
+// Template-id with "inner" name (compilation test).
+//
+template <typename X>
+struct num_wrap
+{
+#ifdef ODB_COMPILER
+ typedef num_wrap this_type; // Name that we should not use.
+#endif
+
+ num_wrap () {}
+ num_wrap (X v): v_ (v) {}
+ operator X () const {return v_;}
+
+ X v_;
+};
+
+#pragma db object
+struct object3
+{
+ #pragma db id type("INTEGER")
+ num_wrap<long long> num_; // Use long long to avoid warnings.
+};
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/types/testscript b/odb-tests/common/types/testscript
new file mode 100644
index 0000000..159972b
--- /dev/null
+++ b/odb-tests/common/types/testscript
@@ -0,0 +1,6 @@
+# file : common/types/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+: basic
+:
+$*
diff --git a/odb-tests/common/view/basics/buildfile b/odb-tests/common/view/basics/buildfile
new file mode 100644
index 0000000..d9738a4
--- /dev/null
+++ b/odb-tests/common/view/basics/buildfile
@@ -0,0 +1,42 @@
+# file : common/view/basics/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 t_view_b_ \
+ --generate-schema \
+ --generate-query \
+ --generate-prepared
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/view/basics/driver.cxx b/odb-tests/common/view/basics/driver.cxx
new file mode 100644
index 0000000..e2f611a
--- /dev/null
+++ b/odb-tests/common/view/basics/driver.cxx
@@ -0,0 +1,846 @@
+// file : common/view/basics/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test view basics.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+#include <libcommon/config.hxx> // DATABASE_XXX
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+template <typename V>
+void
+view1_check (odb::result<V>& r)
+{
+ typedef odb::result<V> result;
+
+ typename result::iterator i (r.begin ());
+
+ assert (i != r.end ());
+ assert (i->first == "Jane" && i->last == "Doe" && i->age == 29);
+
+ assert (++i != r.end ());
+ V v;
+ i.load (v);
+ assert (v.first == "John" && v.last == "Doe" && v.age == 30);
+
+ assert (++i == r.end ());
+}
+
+template <typename V>
+void
+view2_test (const unique_ptr<database>& db)
+{
+ typedef odb::query<V> query;
+ typedef odb::result<V> result;
+ typedef typename result::iterator iterator;
+
+ transaction t (db->begin ());
+
+ {
+ result r (db->query<V> ());
+ iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->count == 4);
+ }
+
+ {
+ result r;
+ if (db->id () != odb::id_oracle)
+ r = db->query<V> ("age < 31");
+ else
+ r = db->query<V> ("\"age\" < 31");
+
+ iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->count == 2);
+ }
+
+ {
+ result r (db->query<V> (query::age < 31));
+ iterator i (r.begin ());
+ assert (i != r.end ());
+ assert (i->count == 2);
+ }
+
+ {
+ unique_ptr<V> v (db->query_one<V> ());
+ assert (v->count == 4);
+ }
+
+ {
+ unique_ptr<V> v;
+ if (db->id () != odb::id_oracle)
+ v.reset (db->query_one<V> ("age < 31"));
+ else
+ v.reset (db->query_one<V> ("\"age\" < 31"));
+ assert (v->count == 2);
+ }
+
+ {
+ unique_ptr<V> v (db->query_one<V> (query::age < 31));
+ assert (v->count == 2);
+ }
+
+ t.commit ();
+}
+
+template <typename V>
+void
+view4_test (const unique_ptr<database>& db)
+{
+ typedef odb::query<V> query;
+ typedef odb::result<V> result;
+ typedef typename result::iterator iterator;
+
+ transaction t (db->begin ());
+
+ {
+ result r;
+ if (db->id () != odb::id_oracle)
+ r = db->query<V> ((query::person::age > 30) + "ORDER BY age");
+ else
+ r = db->query<V> ((query::person::age > 30) + "ORDER BY \"age\"");
+
+ iterator i (r.begin ());
+
+ assert (i != r.end ());
+ assert (i->first_name == "Joe" && i->last_name == "Dirt" &&
+ i->name == "United States");
+
+ assert (++i != r.end ());
+ assert (i->first_name == "Johan" && i->last_name == "Johansen" &&
+ i->name == "Sweden");
+
+ assert (++i == r.end ());
+ }
+
+ {
+ result r (db->query<V> (
+ (query::person::age > 30) +
+ "ORDER BY " + query::person::age));
+
+ iterator i (r.begin ());
+
+ assert (i != r.end ());
+ assert (i->first_name == "Joe" && i->last_name == "Dirt" &&
+ i->name == "United States");
+
+ assert (++i != r.end ());
+ assert (i->first_name == "Johan" && i->last_name == "Johansen" &&
+ i->name == "Sweden");
+
+ assert (++i == r.end ());
+ }
+
+ {
+ result r (db->query<V> (query::residence::code == "US"));
+
+ iterator i (r.begin ());
+
+ assert (i != r.end ());
+ assert (i->first_name == "Joe" && i->last_name == "Dirt" &&
+ i->name == "United States");
+
+ assert (++i == r.end ());
+ }
+
+ t.commit ();
+}
+
+template <typename V>
+void
+view6_test (const unique_ptr<database>& db, const odb::query<V>& q)
+{
+ typedef odb::result<V> result;
+ typedef typename result::iterator iterator;
+
+ transaction t (db->begin ());
+
+ {
+ result r (db->query<V> (q));
+
+ iterator i (r.begin ());
+
+ assert (i != r.end ());
+ assert (i->first_name == "John" && i->last_name == "Doe" &&
+ i->employer == "Simple Tech, Inc");
+
+ assert (++i != r.end ());
+ assert (i->first_name == "Joe" && i->last_name == "Dirt" &&
+ i->employer == "Simple Tech, Inc");
+
+ assert (++i == r.end ());
+ }
+
+ t.commit ();
+}
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv));
+
+ //
+ //
+ {
+ country ca ("CA", "Canada");
+ country za ("ZA", "South Africa");
+ country us ("US", "United States");
+ country se ("SE", "Sweden");
+
+ person p1 (1, "John", "Doe", 30, male, measures (60, 160), &ca, &ca);
+ person p2 (2, "Jane", "Doe", 29, female, measures (70, 170), &za, &us);
+ person p3 (3, "Joe", "Dirt", 31, male, measures (80, 180), &us, &za);
+ person p4 (4, "Johan", "Johansen", 32, male, measures (90, 190), &se,
+ &se);
+
+ p2.husband = &p1;
+
+ employer st ("Simple Tech, Inc");
+ employer ct ("Complex Tech, Inc");
+
+ p2.previous_employer = st.name;
+ p3.previous_employer = ct.name;
+
+ st.employees.push_back (&p1);
+ st.employees.push_back (&p3);
+ st.head_count = 2;
+
+ ct.employees.push_back (&p2);
+ ct.employees.push_back (&p4);
+ ct.head_count = 2;
+
+ transaction t (db->begin ());
+ db->persist (ca);
+ db->persist (za);
+ db->persist (us);
+ db->persist (se);
+
+ db->persist (p1);
+ db->persist (p2);
+ db->persist (p3);
+ db->persist (p4);
+
+ db->persist (st);
+ db->persist (ct);
+ t.commit ();
+ }
+
+ // view1
+ //
+ {
+ typedef odb::result<view1> result;
+
+ {
+ transaction t (db->begin ());
+
+ {
+ result r (db->query<view1> ());
+ assert (size (r) == 4);
+ }
+
+ {
+ result r;
+ if (db->id () != odb::id_oracle)
+ r = db->query<view1> ("ORDER BY age");
+ else
+ r = db->query<view1> ("ORDER BY \"age\"");
+
+ assert (size (r) == 4);
+ }
+
+ {
+ result r;
+ if (db->id () != odb::id_oracle)
+ r = db->query<view1> ("age < 31 ORDER BY age");
+ else
+ r = db->query<view1> ("\"age\" < 31 ORDER BY \"age\"");
+
+ view1_check (r);
+ }
+
+ t.commit ();
+ }
+ }
+
+ // view1a
+ //
+ {
+ typedef odb::result<view1a> result;
+
+ {
+ transaction t (db->begin ());
+
+ result r (db->query<view1a> ());
+ view1_check (r);
+
+ t.commit ();
+ }
+ }
+
+ // view1b
+ //
+ {
+ typedef odb::result<view1b> result;
+
+ {
+ transaction t (db->begin ());
+
+ result r (db->query<view1b> ());
+ view1_check (r);
+
+ t.commit ();
+ }
+
+ // No native parameter support in dynamic multi-database mode.
+ //
+#ifndef MULTI_DATABASE
+ {
+ typedef odb::query<view1b> query;
+
+ transaction t (db->begin ());
+
+#ifndef DATABASE_ORACLE
+ result r (db->query<view1b> ("first = " + query::_val ("Jane")));
+#else
+ result r (db->query<view1b> ("\"first\" = " + query::_val ("Jane")));
+#endif
+
+ result::iterator i (r.begin ());
+
+ assert (i != r.end ());
+ assert (i->first == "Jane" && i->last == "Doe");
+ assert (++i == r.end ());
+
+ t.commit ();
+ }
+#endif
+ }
+
+ // view1c
+ //
+ {
+ typedef odb::result<view1c> result;
+
+ {
+ transaction t (db->begin ());
+
+ result r;
+ if (db->id () != odb::id_oracle)
+ r = db->query<view1c> ("SELECT first, last, age "
+ "FROM t_view_b_person "
+ "WHERE age < 31 ORDER BY age");
+ else
+ r = db->query<view1c> ("SELECT \"first\", \"last\", \"age\" "
+ "FROM \"t_view_b_person\" "
+ "WHERE \"age\" < 31 ORDER BY \"age\"");
+ view1_check (r);
+
+ t.commit ();
+ }
+ }
+
+ // view1d
+ //
+ {
+ typedef odb::result<view1d> result;
+
+ {
+ transaction t (db->begin ());
+
+ {
+ result r;
+ if (db->id () != odb::id_oracle)
+ r = db->query<view1d> ("age < 31 ORDER BY age");
+ else
+ r = db->query<view1d> ("\"age\" < 31 ORDER BY \"age\"");
+
+ view1_check (r);
+ }
+
+ t.commit ();
+ }
+ }
+
+ // view2
+ //
+ view2_test<view2> (db);
+ view2_test<view2a> (db);
+ view2_test<view2b> (db);
+ view2_test<view2c> (db);
+
+ // view3
+ //
+ {
+ typedef odb::result<const view3> result; // Test const result.
+
+ {
+ transaction t (db->begin ());
+
+ {
+ result r (db->query<view3> ());
+
+ size_t count (0);
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ {
+ if (i->last_name == "Doe")
+ assert (i->count == 2);
+ else if (i->last_name == "Dirt" ||
+ i->last_name == "Johansen")
+ assert (i->count == 1);
+ else
+ assert (false);
+
+ count++;
+ }
+
+ assert (count == 3);
+ }
+
+ t.commit ();
+ }
+ }
+
+ // view3a
+ //
+ {
+ typedef odb::query<view3a> query;
+ typedef odb::result<view3a> result;
+
+ {
+ transaction t (db->begin ());
+
+ {
+ result r (db->query<view3a> (query::last_name == "Doe"));
+ result::iterator i (r.begin ());
+
+ assert (i != r.end ());
+ assert (i->last_name == "Doe" && i->count == 2);
+ assert (++i == r.end ());
+ }
+
+ t.commit ();
+ }
+ }
+
+ // view4
+ //
+ view4_test<view4> (db);
+ view4_test<view4a> (db);
+
+ // view5
+ //
+ {
+ typedef odb::query<view5> query;
+ typedef odb::result<view5> result;
+
+ {
+ transaction t (db->begin ());
+
+ {
+ result r (
+ db->query<view5> (
+ query::residence::name == query::nationality::name));
+
+ result::iterator i (r.begin ());
+
+ assert (i != r.end ());
+ assert (i->first_name == "John" && i->last_name == "Doe" &&
+ i->rname == "Canada" && i->rname == "Canada");
+
+ assert (++i != r.end ());
+ assert (i->first_name == "Johan" && i->last_name == "Johansen" &&
+ i->rname == "Sweden" && i->rname == "Sweden");
+
+ assert (++i == r.end ());
+ }
+
+ t.commit ();
+ }
+ }
+
+ // view6
+ //
+ view6_test<view6> (
+ db, odb::query<view6>::employer::name == "Simple Tech, Inc");
+
+ view6_test<view6a> (
+ db, odb::query<view6a>::employer::name == "Simple Tech, Inc");
+
+ view6_test<view6b> (
+ db, odb::query<view6b>::employer::name == "Simple Tech, Inc");
+
+ // No native parameter support in dynamic multi-database mode.
+ //
+#ifndef MULTI_DATABASE
+ view6_test<view6c> (
+#ifndef DATABASE_ORACLE
+ db, "e.name = " + odb::query<view6c>::_val ("Simple Tech, Inc"));
+#else
+ db, "\"e\".\"name\" = " + odb::query<view6c>::_val ("Simple Tech, Inc"));
+#endif
+#endif
+
+ // view7
+ //
+ {
+ typedef odb::query<view7> query;
+ typedef odb::result<view7> result;
+
+ {
+ transaction t (db->begin ());
+
+ {
+ result r (db->query<view7> (query::person::last_name == "Doe"));
+
+ result::iterator i (r.begin ());
+
+ assert (i != r.end ());
+ assert (i->first_name == "Jane" && i->last_name == "Doe" &&
+ !i->head_count.null () && *i->head_count == 2);
+
+ assert (++i != r.end ());
+ assert (i->first_name == "John" && i->last_name == "Doe" &&
+ i->head_count.null ());
+
+ assert (++i == r.end ());
+ }
+
+ t.commit ();
+ }
+ }
+
+ // view8
+ //
+ {
+ typedef odb::result<view8> result;
+
+ {
+ transaction t (db->begin ());
+
+ {
+ result r (db->query<view8> ());
+
+ result::iterator i (r.begin ());
+
+ assert (i != r.end ());
+ assert (i->wife_name == "Jane" && i->husb_name == "John");
+ assert (++i == r.end ());
+ }
+
+ t.commit ();
+ }
+ }
+
+ // view9
+ //
+ {
+ typedef odb::query<view9> query;
+ typedef odb::result<view9> result;
+
+ {
+ transaction t (db->begin ());
+
+ {
+ // Test case-insensitive clause prefix detection.
+ //
+ result r (db->query<view9> ("where" + (query::gender == female)));
+
+ result::iterator i (r.begin ());
+
+ assert (i != r.end ());
+ assert (i->first_name == "Jane" && i->last_name == "Doe" &&
+ i->gender == female);
+ assert (++i == r.end ());
+ }
+
+ t.commit ();
+ }
+ }
+
+ // view10
+ //
+ {
+ typedef odb::query<view10> query;
+ typedef odb::result<view10> result;
+
+ {
+ transaction t (db->begin ());
+
+ {
+ result r (db->query<view10> (
+ query::measures.weight > 60 &&
+ query::measures.hight < 190));
+
+ result::iterator i (r.begin ());
+
+ assert (i != r.end ());
+ assert (i->last_name == "Doe" &&
+ i->measures.weight == 70 && i->measures.hight == 170);
+
+ assert (++i != r.end ());
+ assert (i->last_name == "Dirt" &&
+ i->measures.weight == 80 && i->measures.hight == 180);
+
+ assert (++i == r.end ());
+ }
+
+ t.commit ();
+ }
+ }
+
+ // view11
+ //
+ {
+ typedef odb::result<view11> result;
+
+ {
+ transaction t (db->begin ());
+
+ {
+ result r (db->query<view11> ());
+
+ result::iterator i (r.begin ());
+
+ assert (i != r.end ());
+ assert (i->last_name == "Doe" && i->hight == 170);
+
+ assert (++i != r.end ());
+ assert (i->last_name == "Dirt" && i->hight == 180);
+
+ assert (++i == r.end ());
+ }
+
+ t.commit ();
+ }
+ }
+
+ // view12
+ //
+ {
+ typedef odb::query<view12> query;
+ typedef odb::result<view12> result;
+
+ {
+ transaction t (db->begin ());
+
+ {
+ result r (db->query<view12> (query::last_name == "Dirt"));
+
+ result::iterator i (r.begin ());
+
+ assert (i != r.end ());
+ assert (i->residence == "US");
+ assert (++i == r.end ());
+ }
+
+ t.commit ();
+ }
+ }
+
+ // view13
+ //
+ {
+ typedef odb::query<view13> query;
+ typedef odb::result<view13> result;
+
+ {
+ transaction t (db->begin ());
+
+ {
+ result r (db->query<view13> (
+ (query::person::age < 32) +
+ "ORDER BY" + query::employer::name));
+
+ assert (size (r) == 2);
+ }
+
+ t.commit ();
+ }
+ }
+
+ // view14
+ //
+ {
+ transaction t (db->begin ());
+ assert (size (db->query<view14> ()) == 2);
+ t.commit ();
+ }
+
+ // Test join types.
+ //
+ {
+ using namespace test2;
+
+ {
+ obj1 o11 (1, 1);
+ obj1 o12 (2, 2);
+
+ obj2 o21 (1, 1);
+ obj2 o22 (2, 1);
+ obj2 o23 (3, 3);
+
+ transaction t (db->begin ());
+ db->persist (o11);
+ db->persist (o12);
+ db->persist (o21);
+ db->persist (o22);
+ db->persist (o23);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<vleft> query;
+ typedef odb::result<vleft> result;
+
+ transaction t (db->begin ());
+ result r (db->query<vleft> (
+ "ORDER BY" + query::o1::id1 + "," + query::o2::id2));
+ result::iterator i (r.begin ());
+ assert ( i != r.end () && i->id1 == 1 && *i->id2 == 1);
+ assert (++i != r.end () && i->id1 == 1 && *i->id2 == 2);
+ assert (++i != r.end () && i->id1 == 2 && i->id2.null ());
+ assert (++i == r.end ());
+ t.commit ();
+ }
+
+ // @@ BUILD2 Also disable for DATABASE_MYSQL and DATABASE_PGSQL (see
+ // vright definition for details).
+ //
+#if !defined(DATABASE_MYSQL) && \
+ !defined(DATABASE_SQLITE) && \
+ !defined(DATABASE_PGSQL)
+ {
+ typedef odb::query<vright> query;
+ typedef odb::result<vright> result;
+
+ transaction t (db->begin ());
+ result r (db->query<vright> (
+ "ORDER BY" + query::o1::id1 + "," + query::o2::id2));
+ result::iterator i (r.begin ());
+ assert ( i != r.end () && i->id1 == 1 && *i->id2 == 1);
+ assert (++i != r.end () && i->id1 == 1 && *i->id2 == 2);
+ assert (++i != r.end () && i->id1 == 2 && i->id2.null ());
+ assert (++i == r.end ());
+ t.commit ();
+ }
+#endif
+
+ // @@ BUILD2 Also disable for DATABASE_PGSQL (see vfull definition for
+ // details).
+ //
+#if !defined(DATABASE_MYSQL) && \
+ !defined(DATABASE_SQLITE) && \
+ !defined(DATABASE_PGSQL)
+ {
+ typedef odb::query<vfull> query;
+ typedef odb::result<vfull> result;
+
+ transaction t (db->begin ());
+ result r (db->query<vfull> (
+ "ORDER BY" + query::o1::id1 + "," + query::o2::id2));
+ result::iterator i (r.begin ());
+
+ // SQL Server orders NULL values first. Got to be different.
+ //
+#ifdef DATABASE_MSSQL
+ assert ( i != r.end () && i->id1.null () && *i->id2 == 3);
+ assert (++i != r.end () && *i->id1 == 1 && *i->id2 == 1);
+ assert (++i != r.end () && *i->id1 == 1 && *i->id2 == 2);
+ assert (++i != r.end () && *i->id1 == 2 && i->id2.null ());
+#else
+ assert ( i != r.end () && *i->id1 == 1 && *i->id2 == 1);
+ assert (++i != r.end () && *i->id1 == 1 && *i->id2 == 2);
+ assert (++i != r.end () && *i->id1 == 2 && i->id2.null ());
+ assert (++i != r.end () && i->id1.null () && *i->id2 == 3);
+#endif
+ assert (++i == r.end ());
+ t.commit ();
+ }
+#endif
+
+ {
+ typedef odb::query<vinner> query;
+ typedef odb::result<vinner> result;
+
+ transaction t (db->begin ());
+ result r (db->query<vinner> (
+ "ORDER BY" + query::o1::id1 + "," + query::o2::id2));
+ result::iterator i (r.begin ());
+ assert ( i != r.end () && i->id1 == 1 && i->id2 == 1);
+ assert (++i != r.end () && i->id1 == 1 && i->id2 == 2);
+ assert (++i == r.end ());
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<vcross> query;
+ typedef odb::result<vcross> result;
+
+ transaction t (db->begin ());
+ result r (db->query<vcross> (
+ "ORDER BY" + query::o1::id1 + "," + query::o2::id2));
+ result::iterator i (r.begin ());
+ assert ( i != r.end () && i->id1 == 1 && i->id2 == 1);
+ assert (++i != r.end () && i->id1 == 1 && i->id2 == 2);
+ assert (++i != r.end () && i->id1 == 1 && i->id2 == 3);
+ assert (++i != r.end () && i->id1 == 2 && i->id2 == 1);
+ assert (++i != r.end () && i->id1 == 2 && i->id2 == 2);
+ assert (++i != r.end () && i->id1 == 2 && i->id2 == 3);
+ assert (++i == r.end ());
+ t.commit ();
+ }
+
+ // Inner JOIN via relationship/container.
+ //
+ {
+ obj3 o31 (1, 1);
+ obj3 o32 (2, 2);
+
+ obj4 o41 (1, 1);
+ obj4 o42 (2, 2);
+ o42.o3.push_back (&o32);
+
+ transaction t (db->begin ());
+ db->persist (o31);
+ db->persist (o32);
+ db->persist (o41);
+ db->persist (o42);
+ t.commit ();
+ }
+
+ {
+ typedef odb::result<vrel> result;
+
+ transaction t (db->begin ());
+ result r (db->query<vrel> ());
+ result::iterator i (r.begin ());
+ assert ( i != r.end () && i->id4 == 2);
+ assert (++i == r.end ());
+ t.commit ();
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/view/basics/test.hxx b/odb-tests/common/view/basics/test.hxx
new file mode 100644
index 0000000..130bcd4
--- /dev/null
+++ b/odb-tests/common/view/basics/test.hxx
@@ -0,0 +1,640 @@
+// file : common/view/basics/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <vector>
+#include <cstddef> // std::size_t
+
+#include <odb/core.hxx>
+#include <odb/nullable.hxx>
+
+struct employer;
+
+#pragma db object
+struct country
+{
+ country (const std::string& c, const std::string& n)
+ : code (c), name (n)
+ {
+ }
+
+ country ()
+ {
+ }
+
+ #pragma db id
+ std::string code; // ISO 2-letter country code.
+
+ std::string name;
+};
+
+enum gender_type {male, female};
+
+#pragma db value
+struct measures
+{
+ measures (unsigned short w, unsigned short h) : weight (w), hight (h) {}
+ measures () {}
+
+ unsigned short weight;
+ unsigned short hight;
+};
+
+#pragma db object
+struct person
+{
+ typedef ::measures measures_type;
+
+ person (unsigned long i,
+ const std::string& fn,
+ const std::string& ln,
+ unsigned short a,
+ gender_type g,
+ const measures_type m,
+ country* r,
+ country* n)
+ : id (i),
+ first_name_ (fn),
+ last_name_ (ln),
+ age (a),
+ gender (g),
+ measures (m),
+ residence (r),
+ nationality (n),
+ husband (0)
+ {
+ }
+
+ person ()
+ {
+ }
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db column("first")
+ std::string first_name_;
+
+ #pragma db column("last")
+ std::string last_name_;
+
+ unsigned short age;
+
+ // #pragma db type("INT") - in MySQL test type pragma copying
+ gender_type gender;
+
+ measures_type measures;
+
+ #pragma db not_null
+ country* residence;
+
+ #pragma db not_null
+ country* nationality;
+
+ #pragma db inverse(employees)
+ employer* employed_by;
+
+ // A non-pointer relationship.
+ //
+ odb::nullable<std::string> previous_employer;
+
+ person* husband; // Self-reference.
+};
+
+#pragma db object
+struct employer
+{
+ employer (const std::string& n)
+ : name (n)
+ {
+ }
+
+ employer ()
+ {
+ }
+
+ #pragma db id
+ std::string name;
+ unsigned int head_count;
+ std::vector<person*> employees;
+};
+
+//
+// General view with no associated objects.
+//
+
+// Complete suffix query template.
+//
+#ifndef ODB_DATABASE_ORACLE
+# pragma db view query("SELECT first, last, age FROM t_view_b_person")
+#else
+# pragma db view query("SELECT \"first\", \"last\", \"age\" " \
+ "FROM \"t_view_b_person\"")
+#endif
+struct view1
+{
+ std::string first;
+ std::string last;
+ unsigned short age;
+};
+
+// Complete query.
+//
+#ifndef ODB_DATABASE_ORACLE
+# pragma db view query("SELECT first, last, age " \
+ "FROM t_view_b_person " \
+ "WHERE age < 31 ORDER BY age")
+#else
+# pragma db view query("SELECT \"first\", \"last\", \"age\" " \
+ "FROM \"t_view_b_person\" " \
+ "WHERE \"age\" < 31 ORDER BY \"age\"")
+#endif
+struct view1a
+{
+ std::string first;
+ std::string last;
+ unsigned short age;
+};
+
+// Complete placeholder query template.
+//
+#ifndef ODB_DATABASE_ORACLE
+# pragma db view query("SELECT first, last, age " \
+ "FROM t_view_b_person " \
+ "WHERE age < 31 AND (?) ORDER BY age")
+#else
+# pragma db view query("SELECT \"first\", \"last\", \"age\" " \
+ "FROM \"t_view_b_person\" " \
+ "WHERE \"age\" < 31 AND (?) ORDER BY \"age\"")
+#endif
+struct view1b
+{
+ std::string first;
+ std::string last;
+ unsigned short age;
+};
+
+// Runtime query.
+//
+#pragma db view //query()
+struct view1c
+{
+ std::string first;
+ std::string last;
+ unsigned short age;
+};
+
+// Assembled SELECT and FROM-lists.
+//
+#pragma db view table("t_view_b_person")
+struct view1d
+{
+ #pragma db column("first")
+ std::string first;
+
+ #pragma db column("last")
+ std::string last;
+
+ #pragma db column("age")
+ unsigned short age;
+};
+
+//
+// Count view plus associated object.
+//
+
+// Complete suffix query.
+//
+#ifndef ODB_DATABASE_ORACLE
+# pragma db view object(person) \
+ query("SELECT count(id) FROM t_view_b_person")
+#else
+# pragma db view object(person) \
+ query("SELECT count(\"id\") FROM \"t_view_b_person\"")
+#endif
+struct view2
+{
+ std::size_t count;
+};
+
+// Generated query, literal column.
+//
+#pragma db view object(person)
+struct view2a
+{
+#ifndef ODB_DATABASE_ORACLE
+ #pragma db column("count(id)")
+#else
+ #pragma db column("count(\"id\")")
+#endif
+ std::size_t count;
+};
+
+// Generated query, qualified literal column.
+//
+#pragma db view object(person)
+struct view2b
+{
+#ifndef ODB_DATABASE_ORACLE
+ #pragma db column("count(t_view_b_person.id)")
+#else
+ #pragma db column("count(\"t_view_b_person\".\"id\")")
+#endif
+ std::size_t count;
+};
+
+// Generated query, expression column.
+//
+#pragma db view object(person)
+struct view2c
+{
+ #pragma db column("count(" + person::id + ")")
+ std::size_t count;
+};
+
+//
+// Aggregate view plus associated object with a custom alias.
+//
+
+// Complete suffix query.
+//
+#ifndef ODB_DATABASE_ORACLE
+# pragma db view object(person = test) \
+ query("SELECT last, count(last) " \
+ "FROM t_view_b_person " \
+ "GROUP BY last")
+#else
+# pragma db view object(person = test) \
+ query("SELECT \"last\", count(\"last\") " \
+ "FROM \"t_view_b_person\" " \
+ "GROUP BY \"last\"")
+#endif
+struct view3
+{
+ std::string last_name;
+ std::size_t count;
+};
+
+// Generated query with integrated query condition and placeholder.
+//
+#pragma db view object(person = test) \
+ query((?) + "GROUP BY" + test::last_name_)
+struct view3a
+{
+ // Automatically resolved to test::last_name_.
+ //
+ std::string last_name;
+
+ #pragma db column("count(" + test::last_name_ + ")")
+ std::size_t count;
+};
+
+//
+// JOIN view plus associated objects, some with custom aliases.
+//
+
+// Complete suffix query.
+//
+#ifndef ODB_DATABASE_ORACLE
+# pragma db view object(person) object(country = residence) \
+ query("SELECT first, last, residence.name " \
+ "FROM t_view_b_person " \
+ "LEFT JOIN t_view_b_country AS residence " \
+ "ON t_view_b_person.residence = residence.code")
+#else
+# pragma db view object(person) object(country = residence) \
+ query("SELECT \"first\", \"last\", \"residence\".\"name\" " \
+ "FROM \"t_view_b_person\" " \
+ "LEFT JOIN \"t_view_b_country\" \"residence\" " \
+ "ON \"t_view_b_person\".\"residence\" = \"residence\".\"code\"")
+#endif
+struct view4
+{
+ std::string first_name;
+ std::string last_name;
+ std::string name;
+};
+
+// Generated query.
+//
+#pragma db view object(person) \
+ object(country = residence: person::residence)
+struct view4a
+{
+ std::string first_name;
+ std::string last_name;
+ std::string name;
+};
+
+//
+// JOIN the same object twice.
+//
+#pragma db view object(person) \
+ object(country = residence: person::residence) \
+ object(country = nationality: person::nationality) \
+ query((?) + "ORDER BY" + person::age)
+struct view5
+{
+ std::string first_name;
+ std::string last_name;
+
+ #pragma db column(residence::name)
+ std::string rname;
+
+ #pragma db column(nationality::name)
+ std::string nname;
+};
+
+//
+// JOIN via one(i)-to-many relationship.
+//
+
+// Automatic relationship discovery.
+//
+#pragma db view object(person) object(employer)
+struct view6
+{
+ std::string first_name;
+ std::string last_name;
+
+ #pragma db column(::employer::name)
+ std::string employer;
+};
+
+// Manual relationship specification, left side.
+//
+#pragma db view object(person) object(employer: person::employed_by)
+struct view6a
+{
+ std::string first_name;
+ std::string last_name;
+
+ #pragma db column(::employer::name)
+ std::string employer;
+};
+
+// Manual relationship specification, right side.
+//
+#pragma db view object(person) object(employer: employer::employees)
+struct view6b
+{
+ std::string first_name;
+ std::string last_name;
+
+ #pragma db column(::employer::name)
+ std::string employer;
+};
+
+// The same using tables.
+//
+#if defined(ODB_DATABASE_ORACLE)
+#pragma db view table("t_view_b_person" = "p") \
+ table("t_view_b_employer_employees" = "ee": "\"ee\".\"value\" = \"p\".\"id\"")\
+ table("t_view_b_employer" = "e": "\"ee\".\"object_id\" = \"e\".\"name\"")
+#elif defined(ODB_DATABASE_MSSQL)
+#pragma db view table("t_view_b_person" = "p") \
+ table("t_view_b_employer_employees" = "ee": "ee.value = p.id") \
+ table("t_view_b_employer" = "e": "[ee].[object_id] = e.name")
+#elif defined(ODB_DATABASE_MYSQL)
+#pragma db view table("t_view_b_person" = "p") \
+ table("t_view_b_employer_employees" = "ee": "ee.value = p.id") \
+ table("t_view_b_employer" = "e": "`ee`.`object_id` = e.name")
+#else
+#pragma db view table("t_view_b_person" = "p") \
+ table("t_view_b_employer_employees" = "ee": "ee.value = p.id") \
+ table("t_view_b_employer" = "e": "\"ee\".\"object_id\" = e.name")
+#endif
+struct view6c
+{
+ #pragma db column("p.first")
+ std::string first_name;
+
+ #pragma db column("p.last")
+ std::string last_name;
+
+ #pragma db column("e"."name")
+ std::string employer;
+};
+
+//
+// JOIN via a custom condition.
+//
+#pragma db view object(person) \
+ object(employer: person::previous_employer == employer::name)\
+ query((?) + "ORDER BY" + person::age)
+struct view7
+{
+ std::string first_name;
+ std::string last_name;
+
+ odb::nullable<unsigned int> head_count;
+};
+
+//
+// Self-JOIN.
+//
+#pragma db view object(person = wife) object(person = husb) \
+ query (wife::husband.is_not_null ())
+struct view8
+{
+ #pragma db column(wife::first_name_)
+ std::string wife_name;
+
+ #pragma db column(husb::first_name_)
+ std::string husb_name;
+};
+
+//
+// Enum mapping.
+//
+#pragma db view object(person)
+struct view9
+{
+ std::string first_name;
+ std::string last_name;
+ gender_type gender;
+};
+
+//
+// Composite in view.
+//
+#pragma db view object(person) query((?) + "ORDER BY" + person::age)
+struct view10
+{
+ std::string last_name;
+ ::measures measures;
+};
+
+//
+// Composite in object.
+//
+#pragma db view object(person) \
+ query((person::measures.weight > 60 && person::measures.hight < 190 && (?)) \
+ + "ORDER BY" + person::age)
+struct view11
+{
+ std::string last_name;
+
+ #pragma db column(person::measures.hight)
+ unsigned short hight;
+};
+
+//
+// Extract object pointer as object id.
+//
+#pragma db view object(person)
+struct view12
+{
+ std::string residence;
+};
+
+//
+// Test 'distinct' result modifier.
+//
+#pragma db view object(employer) object(person) query(distinct)
+struct view13
+{
+ std::string name;
+};
+
+//
+// Test 'for_update' result modifier.
+//
+#pragma db view object(employer) query((?), for_update)
+struct view14
+{
+ std::string name;
+};
+
+// Test join types.
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ #pragma db object
+ struct obj1
+ {
+ obj1 (int id = 0, int n_ = 0): id1 (id), n (n_) {}
+
+ #pragma db id
+ int id1;
+
+ int n;
+ };
+
+ #pragma db object no_id
+ struct obj2
+ {
+ obj2 (int id = 0, int n_ = 0): id2 (id), n (n_) {}
+
+ #pragma db id
+ int id2;
+
+ int n;
+ };
+
+ #pragma db view object(obj1 = o1) object(obj2 = o2 left: o1::n == o2::n)
+ struct vleft
+ {
+ int id1;
+ odb::nullable<int> id2;
+ };
+
+ // @@ BUILD2 Also disable for ODB_DATABASE_MYSQL and ODB_DATABASE_PGSQL,
+ // otherwise we end up with the following error:
+ //
+ // test-odb-mysql.hxx:3202:20: error: invalid use of incomplete type ‘class odb::access::view_traits<test2::vright>’
+ // 3202 | public access::view_traits< ::test2::vright >
+ // | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ //
+//#if !defined(ODB_DATABASE_SQLITE) && !defined(ODB_DATABASE_COMMON)
+#if !defined(ODB_DATABASE_MYSQL) && \
+ !defined(ODB_DATABASE_SQLITE) && \
+ !defined(ODB_DATABASE_PGSQL) && \
+ !defined(ODB_DATABASE_COMMON)
+
+ #pragma db view object(obj2 = o2) object(obj1 = o1 right: o2::n == o1::n)
+ struct vright
+ {
+ int id1;
+ odb::nullable<int> id2;
+ };
+
+#endif
+
+ // @@ BUILD2 Also disable for ODB_DATABASE_PGSQL, otherwise we end up with the
+ // following error:
+ //
+ // test-odb-pgsql.hxx:3325:20: error: invalid use of incomplete type ‘class odb::access::view_traits<test2::vfull>’
+ // 3325 | public access::view_traits< ::test2::vfull >
+ // | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ //
+/*
+#if !defined(ODB_DATABASE_MYSQL) && \
+ !defined(ODB_DATABASE_SQLITE) && \
+ !defined(ODB_DATABASE_COMMON)
+*/
+#if !defined(ODB_DATABASE_MYSQL) && \
+ !defined(ODB_DATABASE_SQLITE) && \
+ !defined(ODB_DATABASE_PGSQL) && \
+ !defined(ODB_DATABASE_COMMON)
+
+ #pragma db view object(obj1 = o1) object(obj2 = o2 full: o1::n == o2::n)
+ struct vfull
+ {
+ odb::nullable<int> id1;
+ odb::nullable<int> id2;
+ };
+
+#endif
+
+ #pragma db view object(obj1 = o1) object(obj2 = o2 inner: o1::n == o2::n)
+ struct vinner
+ {
+ int id1;
+ int id2;
+ };
+
+ #pragma db view object(obj1 = o1) object(obj2 = o2 cross)
+ struct vcross
+ {
+ int id1;
+ int id2;
+ };
+
+ // Inner JOIN via relationship/container.
+ //
+ #pragma db object
+ struct obj3
+ {
+ obj3 (int id = 0, int n_ = 0): id3 (id), n (n_) {}
+
+ #pragma db id
+ int id3;
+
+ int n;
+ };
+
+ #pragma db object no_id
+ struct obj4
+ {
+ obj4 (int id = 0, int n_ = 0): id4 (id), n (n_) {}
+
+ #pragma db id
+ int id4;
+
+ int n;
+ std::vector<obj3*> o3;
+ };
+
+ #pragma db view object(obj4) object(obj3 inner)
+ struct vrel
+ {
+ int id4;
+ };
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/view/basics/testscript b/odb-tests/common/view/basics/testscript
new file mode 100644
index 0000000..faa8408
--- /dev/null
+++ b/odb-tests/common/view/basics/testscript
@@ -0,0 +1,33 @@
+# file : common/view/basics/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/view/olv/.gitignore b/odb-tests/common/view/olv/.gitignore
new file mode 100644
index 0000000..2b95165
--- /dev/null
+++ b/odb-tests/common/view/olv/.gitignore
@@ -0,0 +1,46 @@
+# ODB-generated files.
+#
+test1-odb.?xx
+test1-odb-*.?xx
+test1.sql
+test1-*.sql
+
+test2-odb.?xx
+test2-odb-*.?xx
+test2.sql
+test2-*.sql
+
+test3-odb.?xx
+test3-odb-*.?xx
+test3.sql
+test3-*.sql
+
+test4-odb.?xx
+test4-odb-*.?xx
+test4.sql
+test4-*.sql
+
+test5-odb.?xx
+test5-odb-*.?xx
+test5.sql
+test5-*.sql
+
+test6-odb.?xx
+test6-odb-*.?xx
+test6.sql
+test6-*.sql
+
+test7-odb.?xx
+test7-odb-*.?xx
+test7.sql
+test7-*.sql
+
+test8-odb.?xx
+test8-odb-*.?xx
+test8.sql
+test8-*.sql
+
+test9-odb.?xx
+test9-odb-*.?xx
+test9.sql
+test9-*.sql
diff --git a/odb-tests/common/view/olv/buildfile b/odb-tests/common/view/olv/buildfile
new file mode 100644
index 0000000..89ecbcf
--- /dev/null
+++ b/odb-tests/common/view/olv/buildfile
@@ -0,0 +1,50 @@
+# file : common/view/olv/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+hs = test1 test2 test3 test4 test5 test6 test7 test8 test9
+
+exe{driver}: {hxx cxx}{* -*-odb -*-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: $hs
+{
+ exe{driver}: {hxx ixx cxx}{$h-odb}
+
+ <{hxx ixx cxx}{$h-odb}>: hxx{$h} libue{test-meta}
+
+ for db: $databases
+ {
+ exe{driver}: {hxx ixx cxx}{$h-odb-$db}: include = $multi
+ <{hxx ixx cxx}{$h-odb-$db}>: hxx{$h} libue{test-meta}
+ }
+}
+
+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 t_view_olv_ \
+ --generate-schema \
+ --generate-query
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../../alias{database-client}: include = adhoc
+
+testscript@./: schemas = $hs
diff --git a/odb-tests/common/view/olv/driver.cxx b/odb-tests/common/view/olv/driver.cxx
new file mode 100644
index 0000000..c08015e
--- /dev/null
+++ b/odb-tests/common/view/olv/driver.cxx
@@ -0,0 +1,654 @@
+// file : common/view/olv/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test object loading views.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+#include <typeinfo>
+
+#include <odb/session.hxx>
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test1.hxx"
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test4.hxx"
+#include "test5.hxx"
+#include "test6.hxx"
+#include "test7.hxx"
+#include "test8.hxx"
+#include "test9.hxx"
+
+#include "test1-odb.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+#include "test4-odb.hxx"
+#include "test5-odb.hxx"
+#include "test6-odb.hxx"
+#include "test7-odb.hxx"
+#include "test8-odb.hxx"
+#include "test9-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));
+
+ // Test basic object loading functionality.
+ //
+ {
+ using namespace test1;
+
+ {
+ object1 o1a (1, 123);
+ object2 o2 (1, "abc");
+
+ transaction t (db->begin ());
+ db->persist (o1a);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<view1> query;
+
+ transaction t (db->begin ());
+ view1 v (db->query_value<view1> (query::object1::n == 123));
+ assert (v.o2->s == "abc");
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view2 v (db->query_value<view2> ());
+ assert (v.o1->n == 123 && v.o2->s == "abc");
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view3 v (db->query_value<view3> ());
+ assert (v.o1->n == 123 && v.o2->s == "abc");
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view4 v (db->query_value<view4> ());
+ assert (v.s == "abc" && v.o2->s == "abc" && v.id == 1 &&
+ v.o1->n == 123 && v.n == 123);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view4 v (db->query_value<view4> ());
+ assert (v.s == "abc" && v.o2->s == "abc" && v.id == 1 &&
+ v.o1->n == 123 && v.n == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<view5> query;
+
+ object1 o1b (123, 1);
+
+ transaction t (db->begin ());
+ db->persist (o1b);
+ view5 v (db->query_value<view5> (query::o1b::n == 1));
+ assert (v.o1a->n == 123 && v.o2->s == "abc" && v.o1b->n == 1);
+ t.commit ();
+ }
+ }
+
+ // Test loading of object pointers inside objects.
+ //
+ {
+ using namespace test2;
+
+ shared_ptr<object1> o1 (new object1 (123));
+ shared_ptr<object2> o2 (new object2 ("abc", o1));
+
+ {
+
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view1 v (db->query_value<view1> ());
+ assert (v.o2->s == "abc" && v.o2->o1->n == 123);
+ t.commit ();
+ }
+
+ {
+ // Check session interaction.
+ //
+ transaction t (db->begin ());
+ session s;
+ shared_ptr<object2> o2a (db->load<object2> (o2->id));
+ view1 v (db->query_value<view1> ());
+ assert (v.o2 == o2a);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ session s;
+ view2 v (db->query_value<view2> ());
+ assert (v.o1->n == 123 && v.o2->s == "abc" && v.o2->o1 == v.o1);
+ t.commit ();
+ }
+
+ shared_ptr<object3> o3 (new object3 (o2));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o3);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ session s;
+ view3 v (db->query_value<view3> ());
+ assert (v.o1->n == 123 && v.o3->o2->s == "abc" &&
+ v.o3->o2->o1 == v.o1);
+ t.commit ();
+ }
+
+ shared_ptr<object1> o1b (new object1 (234));
+ shared_ptr<object2> o2b (new object2 ("bcd", o1b));
+ shared_ptr<object4> o4 (new object4);
+ o4->o2.push_back (o2);
+ o4->o2.push_back (o2b);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1b);
+ db->persist (o2b);
+ db->persist (o4);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view4 v (db->query_value<view4> ());
+ assert (v.o4->o2[0]->s == "abc" && v.o4->o2[0]->o1->n == 123 &&
+ v.o4->o2[1]->s == "bcd" && v.o4->o2[1]->o1->n == 234);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<view5> query;
+ typedef odb::result<view5> result;
+
+ transaction t (db->begin ());
+ session s;
+ result r (db->query<view5> ("ORDER BY" + query::object1::id));
+ result::iterator i (r.begin ());
+
+ assert (i != r.end ());
+ {
+ const view5& v (*i);
+
+ assert (v.o4->o2[0]->s == "abc" && v.o4->o2[0]->o1->n == 123 &&
+ v.o4->o2[1]->s == "bcd" && v.o4->o2[1]->o1->n == 234 &&
+ v.o4->o2[0]->o1 == v.o1);
+ }
+ assert (++i != r.end ());
+ {
+ const view5& v (*i);
+
+ assert (v.o4->o2[0]->s == "abc" && v.o4->o2[0]->o1->n == 123 &&
+ v.o4->o2[1]->s == "bcd" && v.o4->o2[1]->o1->n == 234 &&
+ v.o4->o2[1]->o1 == v.o1);
+ }
+ assert (++i == r.end ());
+ t.commit ();
+ }
+
+ shared_ptr<object5> o5 (new object5 (o1b, o2));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o5);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view6 v (db->query_value<view6> ());
+ assert (v.o1a->n == 123 && v.o1b->n == 234);
+ t.commit ();
+ }
+ }
+
+ // Test JOINs for pointed-to objects, existing and automatically added.
+ //
+ {
+ using namespace test3;
+
+ shared_ptr<object1> o1 (new object1 (123));
+ shared_ptr<object2> o2 (new object2 ("abc"));
+
+ o1->o2 = o2;
+ o2->o1 = o1;
+
+ {
+
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view1a v (db->query_value<view1a> ());
+ // VC11
+ assert (v.o1->n == 123 && v.o1->o2.object_id<object2> () == o2->id);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view1b v (db->query_value<view1b> ());
+ // VC11
+ assert (v.o1->n == 123 && v.o1->o2.object_id<object2> () == o2->id);
+ t.commit ();
+ }
+
+ // Container case.
+ //
+
+ shared_ptr<object3> o3 (new object3 (123));
+ shared_ptr<object4> o4 (new object4 ("abc"));
+
+ o3->o4 = o4;
+ o4->o3.push_back (o3);
+
+ {
+
+ transaction t (db->begin ());
+ db->persist (o3);
+ db->persist (o4);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view2a v (db->query_value<view2a> ());
+ // VC11
+ assert (v.o3->n == 123 && v.o3->o4.object_id<object4> () == o4->id);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view2b v (db->query_value<view2b> ());
+ // VC11
+ assert (v.o3->n == 123 && v.o3->o4.object_id<object4> () == o4->id);
+ t.commit ();
+ }
+ }
+
+ // Test by-value load.
+ //
+ {
+ using namespace test4;
+
+ {
+ object1 o1 (1, 123);
+ object2 o2 (1, "abc", &o1);
+
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view1 v (db->query_value<view1> ());
+ assert (v.o1.n == 123);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view1a v (db->query_value<view1a> ());
+ assert (!v.o1_null && v.o1.n == 123);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view1b v (db->query_value<view1b> ());
+ assert (/*v.o1_p == &v.o1 && */ v.o1.n == 123); // Copy ctor.
+ t.commit ();
+ }
+
+ {
+ typedef odb::result<view1c> result;
+
+ transaction t (db->begin ());
+ result r (db->query<view1c> ());
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+
+ object1 o1;
+ view1c v (o1);
+ i.load (v);
+
+ assert (v.o1_p == &o1 && o1.n == 123);
+
+ assert (++i == r.end ());
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ session s;
+ view2 v (db->query_value<view2> ());
+
+ // @@ BUILD2 As of cl 19.29.30136 (VS 2019 16.11.5) v.o2.o1 points to
+ // the address of o1 member of the object being returned by
+ // query_value<view2>() which v is a copy of, and thus the
+ // original assertion fails. Note that changing `view2 v` to
+ // `const view2& v` doesn't help.
+ //
+ //assert (v.o1.n == 123 && v.o2.s == "abc" && v.o2.o1 == &v.o1);
+ assert (v.o1.n == 123 && v.o2.s == "abc");
+ t.commit ();
+ }
+
+ object1 o1b (2, 234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1b);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<view2a> query;
+
+ transaction t (db->begin ());
+ session s;
+ view2a v (db->query_value<view2a> (query::object1::id == 2));
+ assert (v.o1.n == 234 && v.o2_null);
+ t.commit ();
+ }
+
+ shared_ptr<object3> o3 (new object3 (1, 123));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o3);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ {
+ view3 v (db->query_value<view3> ());
+ assert (v.o3_p == &v.o3 && v.o3.n == 123); // Load into value.
+ }
+ session s; // Load into cache.
+ shared_ptr<object3> o3a (db->load<object3> (o3->id));
+ {
+ view3 v (db->query_value<view3> ());
+ assert (v.o3_p == o3a.get ()); // Load from cache.
+ }
+ t.commit ();
+ }
+ }
+
+ // Test NULL object pointers.
+ //
+ {
+ using namespace test5;
+
+ shared_ptr<object1> o1a (new object1 (123));
+ shared_ptr<object1> o1b (new object1 (234));
+ shared_ptr<object2> o2 (new object2 ("abc", o1a));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1a);
+ db->persist (o1b);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<view1> query;
+ typedef odb::result<view1> result;
+
+ transaction t (db->begin ());
+ session s;
+ result r (db->query<view1> ("ORDER BY" + query::object1::id));
+ result::iterator i (r.begin ());
+
+ assert (i != r.end ());
+ {
+ const view1& v (*i);
+ assert (v.o1->n == 123 && v.o2->s == "abc" && v.o2->o1 == v.o1);
+ }
+ assert (++i != r.end ());
+ {
+ const view1& v (*i);
+ assert (v.o1->n == 234 && !v.o2);
+ }
+ assert (++i == r.end ());
+ t.commit ();
+ }
+
+ shared_ptr<object3> o3a (new object3 (make_pair (1, 1), 123));
+ shared_ptr<object3> o3b (new object3 (make_pair (2, 2), 234));
+ shared_ptr<object4> o4 (new object4 ("abc", o3a));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o3a);
+ db->persist (o3b);
+ db->persist (o4);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<view2> query;
+ typedef odb::result<view2> result;
+
+ transaction t (db->begin ());
+ session s;
+ result r (db->query<view2> ("ORDER BY" + query::object3::n));
+ result::iterator i (r.begin ());
+
+ assert (i != r.end ());
+ {
+ const view2& v (*i);
+ assert (v.o3->n == 123 && v.o4->s == "abc" && v.o4->o3 == v.o3);
+ }
+ assert (++i != r.end ());
+ {
+ const view2& v (*i);
+ assert (v.o3->n == 234 && !v.o4);
+ }
+ assert (++i == r.end ());
+ t.commit ();
+ }
+ }
+
+ // Test interaction with sections.
+ //
+ {
+ using namespace test6;
+
+ shared_ptr<object1> o1 (new object1 (123));
+ shared_ptr<object2> o2 (new object2 ("abc", o1));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ view1 v (db->query_value<view1> ());
+
+ assert (v.o1->n == 123 && v.o2->s == "abc" &&
+ !v.o2->r.loaded () && !v.o2->o1);
+
+ db->load (*v.o2, v.o2->r);
+ assert (v.o2->r.loaded () && v.o2->o1 && v.o2->o1->n == 123);
+
+ t.commit ();
+ }
+ }
+
+ // Test explicit conversion to smart pointer member.
+ //
+ {
+ using namespace test7;
+
+ object1 o1 (123);
+ object2 o2 ("abc", &o1);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ session s;
+ view1 v (db->query_value<view1> ());
+ assert (v.o1->n == 123 && v.o2->s == "abc" && v.o2->o1 == v.o1.get ());
+ t.commit ();
+ }
+ }
+
+ // Test loading objects without id.
+ //
+ {
+ using namespace test8;
+
+ object1 o1 (123);
+ object2 o2 ("abc", &o1);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ session s;
+ view1 v (db->query_value<view1> ());
+ assert (v.o1->n == 123 && v.o2->s == "abc" && v.o2->o1 == v.o1.get ());
+ t.commit ();
+ }
+ }
+
+ // Test loading polymorphic objects.
+ //
+ {
+ using namespace test9;
+
+ root r (1);
+ base b (2, "a");
+ derived d (3, "b", true);
+
+ {
+ transaction t (db->begin ());
+ db->persist (r);
+ db->persist (b);
+ db->persist (d);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ // Load via root.
+ //
+ {
+ view1r r (db->query_value<view1r> (query<view1r>::n == 1));
+ auto& o (*r.o);
+ assert (r.n == 1 && r.o->n == 1 && typeid (o) == typeid (root));
+ }
+
+ {
+ view1r r (db->query_value<view1r> (query<view1r>::n == 2));
+ auto& o (*r.o);
+ assert (r.n == 2 && r.o->n == 2 && typeid (o) == typeid (base));
+ base& b (dynamic_cast<base&> (*r.o));
+ assert (b.s == "a");
+ }
+
+ {
+ view1r r (db->query_value<view1r> (query<view1r>::n == 3));
+ auto& o (*r.o);
+ assert (r.n == 3 && r.o->n == 3 && typeid (o) == typeid (derived));
+ derived& d (dynamic_cast<derived&> (o));
+ assert (d.s == "b" && d.b);
+ }
+
+ // Load via base.
+ //
+ {
+ view1b r (db->query_value<view1b> (query<view1b>::n == 2));
+ assert (r.s == "a" && r.n == 2 && r.o->n == 2 && b.s == "a");
+ }
+
+ {
+ view1b r (db->query_value<view1b> (query<view1b>::n == 3));
+ auto& o (*r.o);
+ assert (r.s == "b" && r.n == 3 && r.o->n == 3 &&
+ typeid (o) == typeid (derived));
+ derived& d (dynamic_cast<derived&> (o));
+ assert (d.s == "b" && d.b);
+ }
+
+ // Load via derived.
+ //
+ {
+ view1d r (db->query_value<view1d> ());
+ assert (r.s == "b" && r.n == 3 &&
+ r.o->n == 3 && r.o->s == "b" && r.o->b);
+ }
+
+ t.commit ();
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/view/olv/test1.hxx b/odb-tests/common/view/olv/test1.hxx
new file mode 100644
index 0000000..0de9483
--- /dev/null
+++ b/odb-tests/common/view/olv/test1.hxx
@@ -0,0 +1,116 @@
+// file : common/view/olv/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#include <string>
+#include <memory> // unique_ptr
+#include <utility> // std::move
+
+#include <odb/core.hxx>
+
+// Test basic object loading functionality.
+//
+#pragma db namespace table("t1_") pointer(std::unique_ptr)
+namespace test1
+{
+ #pragma db object
+ struct object1
+ {
+ object1 (int id_ = 0, int n_ = 0): id (id_), n (n_) {}
+
+ #pragma db id
+ int id;
+
+ int n;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 (int id_ = 0, const char* s_ = ""): id (id_), s (s_) {}
+
+ #pragma db id
+ int id;
+
+ std::string s;
+ };
+
+ #pragma db view object(object1) object(object2: object1::id == object2::id)
+ struct view1
+ {
+ // VC12 workaround (no default move constructor generation).
+ //
+ view1 () {}
+ view1 (view1&& x): o2 (std::move (x.o2)) {}
+
+ std::unique_ptr<object2> o2;
+ };
+
+ #pragma db view object(object1) object(object2: object1::id == object2::id)
+ struct view2
+ {
+ // VC12 workaround (no default move constructor generation).
+ //
+ view2 () {}
+ view2 (view2&& x): o2 (std::move (x.o2)), o1 (std::move (x.o1)) {}
+
+ std::unique_ptr<object2> o2;
+ std::unique_ptr<object1> o1;
+ };
+
+ #pragma db view object(object1 = o1) object(object2 = o2: o1::id == o2::id)
+ struct view3
+ {
+ // VC12 workaround (no default move constructor generation).
+ //
+ view3 () {}
+ view3 (view3&& x): o1 (std::move (x.o1)), o2 (std::move (x.o2)) {}
+
+ std::unique_ptr<object1> o1;
+ std::unique_ptr<object2> o2;
+ };
+
+ #pragma db view object(object1 = o1) object(object2 = o2: o1::id == o2::id)
+ struct view4
+ {
+ // VC12 workaround (no default move constructor generation).
+ //
+ view4 () {}
+ view4 (view4&& x): s (std::move (x.s)),
+ o2 (std::move (x.o2)),
+ id (x.id),
+ o1 (std::move (x.o1)),
+ n (x.n) {}
+
+ std::string s;
+ std::unique_ptr<object2> o2;
+
+ #pragma db column(o1::id)
+ int id;
+
+ std::unique_ptr<object1> o1;
+ int n;
+ };
+
+ #pragma db view \
+ object(object1) \
+ object(object2: object1::id == object2::id) \
+ object(object1 = o1b: object1::id == o1b::n)
+ struct view5
+ {
+ // VC12 workaround (no default move constructor generation).
+ //
+ view5 () {}
+ view5 (view5&& x): o1a (std::move (x.o1a)),
+ o2 (std::move (x.o2)),
+ o1b (std::move (x.o1b)) {}
+
+ std::unique_ptr<object1> o1a;
+ std::unique_ptr<object2> o2;
+ std::unique_ptr<object1> o1b;
+ };
+}
+
+#endif // TEST1_HXX
diff --git a/odb-tests/common/view/olv/test2.hxx b/odb-tests/common/view/olv/test2.hxx
new file mode 100644
index 0000000..a769daa
--- /dev/null
+++ b/odb-tests/common/view/olv/test2.hxx
@@ -0,0 +1,122 @@
+// file : common/view/olv/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#include <string>
+#include <vector>
+#include <memory> // shared_ptr
+
+#include <odb/core.hxx>
+
+// Test loading of object pointers inside objects.
+//
+#pragma db namespace table("t2_") pointer(std::shared_ptr) session
+namespace test2
+{
+ using std::shared_ptr;
+
+ #pragma db object
+ struct object1
+ {
+ object1 (int n_ = 0): n (n_) {}
+
+ #pragma db id auto
+ int id;
+
+ int n;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 () {}
+ object2 (const char* s_, shared_ptr<object1> o1_): s (s_), o1 (o1_) {}
+
+ #pragma db id auto
+ int id;
+
+ std::string s;
+ shared_ptr<object1> o1;
+ };
+
+ #pragma db view object(object1) object(object2)
+ struct view1
+ {
+ shared_ptr<object2> o2;
+ };
+
+ #pragma db view object(object1) object(object2)
+ struct view2
+ {
+ shared_ptr<object2> o2; // "Unfortunate" order.
+ shared_ptr<object1> o1;
+ };
+
+ #pragma db object
+ struct object3
+ {
+ object3 () {}
+ object3 (shared_ptr<object2> o2_): o2 (o2_) {}
+
+ #pragma db id auto
+ int id;
+
+ shared_ptr<object2> o2;
+ };
+
+ #pragma db view object(object1) object(object2) object(object3)
+ struct view3
+ {
+ shared_ptr<object3> o3; // "Unfortunate" order.
+ shared_ptr<object1> o1;
+ };
+
+ #pragma db object
+ struct object4
+ {
+ #pragma db id auto
+ int id;
+
+ std::vector<shared_ptr<object2>> o2;
+ };
+
+ #pragma db view object(object4)
+ struct view4
+ {
+ shared_ptr<object4> o4;
+ };
+
+ #pragma db view object(object4) object (object2) object(object1)
+ struct view5
+ {
+ shared_ptr<object4> o4; // "Unfortunate" order.
+ shared_ptr<object1> o1;
+ };
+
+ #pragma db object
+ struct object5
+ {
+ object5 () {}
+ object5 (shared_ptr<object1> o1_, shared_ptr<object2> o2_)
+ : o1 (o1_), o2 (o2_) {}
+
+ #pragma db id auto
+ int id;
+
+ shared_ptr<object1> o1;
+ shared_ptr<object2> o2;
+ };
+
+ #pragma db view object(object5) object (object2) \
+ object(object1 = o1a: object2::o1) \
+ object(object1 = o1b: object5::o1)
+ struct view6
+ {
+ shared_ptr<object1> o1a;
+ shared_ptr<object1> o1b;
+ };
+}
+
+#endif // TEST2_HXX
diff --git a/odb-tests/common/view/olv/test3.hxx b/odb-tests/common/view/olv/test3.hxx
new file mode 100644
index 0000000..8cf4344
--- /dev/null
+++ b/odb-tests/common/view/olv/test3.hxx
@@ -0,0 +1,106 @@
+// file : common/view/olv/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#include <string>
+#include <vector>
+#include <memory> // shared_ptr
+
+#include <odb/core.hxx>
+#include <odb/lazy-ptr.hxx>
+
+// Test JOINs for pointed-to objects, existing and automatically added.
+//
+#pragma db namespace table("t3_") pointer(std::shared_ptr) session
+namespace test3
+{
+ using std::shared_ptr;
+
+ struct object2;
+
+ #pragma db object
+ struct object1
+ {
+ object1 (int n_ = 0): n (n_) {}
+
+ #pragma db id auto
+ int id;
+
+ int n;
+
+ #pragma db inverse(o1)
+ odb::lazy_weak_ptr<object2> o2;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 (const char* s_ = ""): s (s_) {}
+
+ #pragma db id auto
+ int id;
+
+ std::string s;
+
+ shared_ptr<object1> o1;
+ };
+
+ #pragma db view object(object1) object(object2)
+ struct view1a // Existing JOIN.
+ {
+ shared_ptr<object1> o1;
+ };
+
+ #pragma db view object(object1)
+ struct view1b // Automatic JOIN.
+ {
+ shared_ptr<object1> o1;
+ };
+
+ // Container case.
+ //
+ struct object4;
+
+ #pragma db object
+ struct object3
+ {
+ object3 (int n_ = 0): n (n_) {}
+
+ #pragma db id auto
+ int id;
+
+ int n;
+
+ #pragma db inverse(o3)
+ odb::lazy_weak_ptr<object4> o4;
+ };
+
+ #pragma db object
+ struct object4
+ {
+ object4 (const char* s_ = ""): s (s_) {}
+
+ #pragma db id auto
+ int id;
+
+ std::string s;
+
+ std::vector<shared_ptr<object3>> o3;
+ };
+
+ #pragma db view object(object3) object(object4 = o4)
+ struct view2a // Existing JOIN.
+ {
+ shared_ptr<object3> o3;
+ };
+
+ #pragma db view object(object3)
+ struct view2b // Automatic JOIN.
+ {
+ shared_ptr<object3> o3;
+ };
+}
+
+#endif // TEST3_HXX
diff --git a/odb-tests/common/view/olv/test4.hxx b/odb-tests/common/view/olv/test4.hxx
new file mode 100644
index 0000000..f2af5fd
--- /dev/null
+++ b/odb-tests/common/view/olv/test4.hxx
@@ -0,0 +1,151 @@
+// file : common/view/olv/test4.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST4_HXX
+#define TEST4_HXX
+
+#include <string>
+#include <memory> // shared_ptr
+
+#include <odb/core.hxx>
+
+// Test by-value load.
+//
+#pragma db namespace table("t4_") session
+namespace test4
+{
+ #pragma db object
+ struct object1
+ {
+ object1 (int id_ = 0, int n_ = 0): id (id_), n (n_) {}
+
+ #pragma db id
+ int id;
+
+ int n;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 (int id_ = 0, const char* s_ = "", object1* o1_ = 0)
+ : id (id_), s (s_), o1 (o1_) {}
+
+ #pragma db id
+ int id;
+
+ std::string s;
+ object1* o1; // Shallow copy.
+ };
+
+ typedef object1* object1_ptr;
+ typedef object2* object2_ptr;
+
+ #pragma db view object(object1)
+ struct view1
+ {
+ #pragma db member(o1_) virtual(object1_ptr) get(&this.o1) set()
+
+ #pragma db transient
+ object1 o1;
+ };
+
+ #pragma db view object(object1) transient
+ struct view1a
+ {
+ view1a (): o1_null (true) {}
+
+ #pragma db member(o1_) virtual(object1_ptr) get(&this.o1) \
+ set(this.o1_null = !(?))
+
+ object1 o1;
+ bool o1_null;
+ };
+
+ #pragma db view object(object1)
+ struct view1b
+ {
+ view1b (): o1_p (0) {}
+
+ #pragma db transient
+ object1 o1;
+
+ #pragma db get(&this.o1) set(o1_p = (?))
+ object1* o1_p;
+ };
+
+ #pragma db view object(object1)
+ struct view1c
+ {
+ view1c (object1& o1): o1_p (&o1) {}
+
+ object1* o1_p;
+ };
+
+ #pragma db view object(object1) object(object2) transient
+ struct view2
+ {
+ #pragma db member(o2_) virtual(object2_ptr) get(&this.o2) set()
+ #pragma db member(o1_) virtual(object1_ptr) get(&this.o1) set()
+
+ object1 o1;
+ object2 o2;
+ };
+
+ #pragma db view object(object1) object(object2) transient
+ struct view2a
+ {
+ #pragma db member(o2_) virtual(object2_ptr) get(&this.o2) \
+ set(o2_null = !(?))
+ #pragma db member(o1_) virtual(object1_ptr) get(&this.o1) set()
+
+ object1 o1;
+ object2 o2;
+ bool o2_null;
+ };
+
+ // Test loading into raw pointer with non-raw object pointer.
+ //
+ using std::shared_ptr;
+
+ #pragma db object pointer(shared_ptr)
+ struct object3
+ {
+ object3 (int id_ = 0, int n_ = 0): id (id_), n (n_) {}
+
+ #pragma db id
+ int id;
+
+ int n;
+ };
+
+ #pragma db view object(object3)
+ struct view3
+ {
+ // This view implements the following slightly twisted logic: if the
+ // object is already in the cache, then set o3_p to that. Otherwise,
+ // load it into the by-value instance. We can also check whether o3_p
+ // points to o3 to distinguish between the two outcomes.
+ //
+
+ // Since we may be getting the pointer as both smart and raw, we
+ // need to create a bit of support code to use in the modifier
+ // expression.
+ //
+ void set_o3 (object3* p) {o3_p = p;} // &o3 or NULL.
+ void set_o3 (shared_ptr<object3> p) {o3_p = p.get ();} // From cache.
+
+ #pragma db get(&this.o3) set(set_o3(?))
+ object3* o3_p;
+
+ #pragma db transient
+ object3 o3;
+
+ // Return-by-value support (query_value()).
+ //
+ view3 (): o3_p (0) {}
+ view3 (const view3& x): o3_p (x.o3_p == &x.o3 ? &o3 : x.o3_p), o3 (x.o3) {}
+ };
+}
+
+#endif // TEST4_HXX
diff --git a/odb-tests/common/view/olv/test5.hxx b/odb-tests/common/view/olv/test5.hxx
new file mode 100644
index 0000000..e3a671b
--- /dev/null
+++ b/odb-tests/common/view/olv/test5.hxx
@@ -0,0 +1,86 @@
+// file : common/view/olv/test5.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST5_HXX
+#define TEST5_HXX
+
+#include <string>
+#include <memory> // shared_ptr
+#include <utility> // pair
+
+#include <odb/core.hxx>
+
+// Test NULL object pointers.
+//
+#pragma db namespace table("t5_") pointer(std::shared_ptr) session
+namespace test5
+{
+ using std::shared_ptr;
+
+ #pragma db object
+ struct object1
+ {
+ object1 (int n_ = 0): n (n_) {}
+
+ #pragma db id auto
+ int id;
+
+ int n;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 () {}
+ object2 (const char* s_, shared_ptr<object1> o1_): s (s_), o1 (o1_) {}
+
+ #pragma db id auto
+ int id;
+
+ std::string s;
+ shared_ptr<object1> o1;
+ };
+
+ #pragma db view object(object1) object(object2)
+ struct view1
+ {
+ shared_ptr<object1> o1;
+ shared_ptr<object2> o2;
+ };
+
+ typedef std::pair<int, int> comp_id;
+ #pragma db value(comp_id)
+
+ #pragma db object
+ struct object3
+ {
+ object3 (comp_id id_ = comp_id (), int n_ = 0): id (id_), n (n_) {}
+
+ #pragma db id
+ comp_id id;
+
+ int n;
+ };
+
+ #pragma db object
+ struct object4
+ {
+ object4 () {}
+ object4 (const char* s_, shared_ptr<object3> o3_): s (s_), o3 (o3_) {}
+
+ #pragma db id auto
+ int id;
+
+ std::string s;
+ shared_ptr<object3> o3;
+ };
+
+ #pragma db view object(object3) object(object4)
+ struct view2
+ {
+ shared_ptr<object4> o4;
+ shared_ptr<object3> o3;
+ };
+}
+
+#endif // TEST5_HXX
diff --git a/odb-tests/common/view/olv/test6.hxx b/odb-tests/common/view/olv/test6.hxx
new file mode 100644
index 0000000..5336fa6
--- /dev/null
+++ b/odb-tests/common/view/olv/test6.hxx
@@ -0,0 +1,57 @@
+// file : common/view/olv/test6.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST6_HXX
+#define TEST6_HXX
+
+#include <string>
+#include <memory> // shared_ptr
+
+#include <odb/core.hxx>
+#include <odb/section.hxx>
+
+// Test interaction with sections.
+//
+#pragma db namespace table("t6_") pointer(std::shared_ptr)
+namespace test6
+{
+ using std::shared_ptr;
+
+ #pragma db object
+ struct object1
+ {
+ object1 (int n_ = 0): n (n_) {}
+
+ #pragma db id auto
+ int id;
+
+ int n;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 () {}
+ object2 (const char* s_, shared_ptr<object1> o1_): s (s_), o1 (o1_) {}
+
+ #pragma db id auto
+ int id;
+
+ std::string s;
+
+ #pragma db load(lazy)
+ odb::section r;
+
+ #pragma db section(r)
+ shared_ptr<object1> o1;
+ };
+
+ #pragma db view object(object1) object(object2)
+ struct view1
+ {
+ shared_ptr<object1> o1;
+ shared_ptr<object2> o2;
+ };
+}
+
+#endif // TEST6_HXX
diff --git a/odb-tests/common/view/olv/test7.hxx b/odb-tests/common/view/olv/test7.hxx
new file mode 100644
index 0000000..dbdc663
--- /dev/null
+++ b/odb-tests/common/view/olv/test7.hxx
@@ -0,0 +1,57 @@
+// file : common/view/olv/test7.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST7_HXX
+#define TEST7_HXX
+
+#include <string>
+#include <memory> // unique_ptr
+#include <utility> // std::move
+
+#include <odb/core.hxx>
+
+// Test explicit conversion to smart pointer member.
+//
+#pragma db namespace table("t7_") pointer(*) session
+namespace test7
+{
+ using std::unique_ptr;
+
+ #pragma db object
+ struct object1
+ {
+ object1 (int n_ = 0): n (n_) {}
+
+ #pragma db id auto
+ int id;
+
+ int n;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 () {}
+ object2 (const char* s_, object1* o1_): s (s_), o1 (o1_) {}
+
+ #pragma db id auto
+ int id;
+
+ std::string s;
+ object1* o1; // Shallow.
+ };
+
+ #pragma db view object(object1) object(object2)
+ struct view1
+ {
+ // VC12 workaround (no default move constructor generation).
+ //
+ view1 () {}
+ view1 (view1&& x): o2 (std::move (x.o2)), o1 (std::move (x.o1)) {}
+
+ unique_ptr<object2> o2;
+ unique_ptr<object1> o1;
+ };
+}
+
+#endif // TEST7_HXX
diff --git a/odb-tests/common/view/olv/test8.hxx b/odb-tests/common/view/olv/test8.hxx
new file mode 100644
index 0000000..607d222
--- /dev/null
+++ b/odb-tests/common/view/olv/test8.hxx
@@ -0,0 +1,54 @@
+// file : common/view/olv/test8.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST8_HXX
+#define TEST8_HXX
+
+#include <string>
+#include <memory> // unique_ptr
+#include <utility> // std::move
+
+#include <odb/core.hxx>
+
+// Test loading objects without id.
+//
+#pragma db namespace table("t8_") pointer(*) session
+namespace test8
+{
+ using std::unique_ptr;
+
+ #pragma db object
+ struct object1
+ {
+ object1 (int n_ = 0): n (n_) {}
+
+ #pragma db id auto
+ int id;
+
+ int n;
+ };
+
+ #pragma db object no_id
+ struct object2
+ {
+ object2 () {}
+ object2 (const char* s_, object1* o1_): s (s_), o1 (o1_) {}
+
+ std::string s;
+ object1* o1; // Shallow.
+ };
+
+ #pragma db view object(object1) object(object2)
+ struct view1
+ {
+ // VC12 workaround (no default move constructor generation).
+ //
+ view1 () {}
+ view1 (view1&& x): o2 (std::move (x.o2)), o1 (std::move (x.o1)) {}
+
+ unique_ptr<object2> o2;
+ unique_ptr<object1> o1;
+ };
+}
+
+#endif // TEST8_HXX
diff --git a/odb-tests/common/view/olv/test9.hxx b/odb-tests/common/view/olv/test9.hxx
new file mode 100644
index 0000000..b109de3
--- /dev/null
+++ b/odb-tests/common/view/olv/test9.hxx
@@ -0,0 +1,78 @@
+// file : common/view/olv/test9.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST9_HXX
+#define TEST9_HXX
+
+#include <string>
+#include <memory> // shared_ptr
+
+#include <odb/core.hxx>
+
+// Test loading polymorphic objects.
+//
+#pragma db namespace table("t9_") session
+namespace test9
+{
+ using std::shared_ptr;
+
+ #pragma db object polymorphic pointer(shared_ptr)
+ struct root
+ {
+ virtual ~root () {}
+ root (int n_ = 0): n (n_) {}
+
+ #pragma db id auto
+ int id;
+
+ int n;
+ };
+
+ #pragma db object
+ struct base: root
+ {
+ base (int n_ = 0, const char* s_ = ""): root (n_), s (s_) {}
+
+ std::string s;
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived (int n_ = 0, const char* s_ = "", bool b_ = false)
+ : base (n_, s_), b (b_) {}
+
+ bool b;
+ };
+
+ // Load via root.
+ //
+ #pragma db view object(root)
+ struct view1r
+ {
+ shared_ptr<root> o;
+ int n;
+ };
+
+ // Load via base.
+ //
+ #pragma db view object(base)
+ struct view1b
+ {
+ std::string s;
+ shared_ptr<base> o;
+ int n;
+ };
+
+ // Load via derived.
+ //
+ #pragma db view object(derived)
+ struct view1d
+ {
+ std::string s;
+ shared_ptr<derived> o;
+ int n;
+ };
+}
+
+#endif // TEST9_HXX
diff --git a/odb-tests/common/view/olv/testscript b/odb-tests/common/view/olv/testscript
new file mode 100644
index 0000000..160426d
--- /dev/null
+++ b/odb-tests/common/view/olv/testscript
@@ -0,0 +1,39 @@
+# file : common/view/olv/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../../mysql-schema.testscript
+
+ for s: $schemas
+ cat $out_base/"$s"($multi ? '-mysql' : '').sql | $create_schema_cmd
+ end;
+
+ $* ($multi ? 'mysql' : ) $mysql_options
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../../pgsql-schema.testscript
+
+ for s: $schemas
+ $create_schema_cmd -f $out_base/"$s"($multi ? '-pgsql' : '').sql
+ end;
+
+ $* ($multi ? 'pgsql' : ) $pgsql_options
+}
diff --git a/odb-tests/common/virtual/buildfile b/odb-tests/common/virtual/buildfile
new file mode 100644
index 0000000..96d062e
--- /dev/null
+++ b/odb-tests/common/virtual/buildfile
@@ -0,0 +1,42 @@
+# file : common/virtual/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 virtual_ \
+ --generate-schema \
+ --generate-query \
+ --generate-session
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/virtual/driver.cxx b/odb-tests/common/virtual/driver.cxx
new file mode 100644
index 0000000..f96f543
--- /dev/null
+++ b/odb-tests/common/virtual/driver.cxx
@@ -0,0 +1,154 @@
+// file : common/virtual/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test virtual data members.
+//
+
+#include <memory> // std::auto_ptr
+#include <iostream>
+
+#include <odb/session.hxx>
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ // Test basic virtual data member functionality.
+ //
+ {
+ using namespace test1;
+
+ object o;
+ o.i (123);
+ o.c1.i = 123;
+ o.c1.s = "abc";
+ o.v1.push_back ("abc");
+ o.v1.push_back ("abd");
+ o.v1.push_back ("abe");
+ o.p1 = new object;
+
+ {
+ transaction t (db->begin ());
+ db->persist (*o.p1);
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (o.id1.v));
+ t.commit ();
+
+ assert (o == *p);
+ }
+ }
+
+ // Test pragma resolution to virtual data member.
+ //
+ {
+ using namespace test2;
+
+ object1 o1 (1);
+ o1.o2 = new object2 (1);
+ o1.o2->o1 = &o1;
+
+ {
+ transaction t (db->begin ());
+ db->persist (*o1.o2);
+ o1.n1 = o1.o2->id;
+ db->persist (o1);
+ t.commit ();
+ }
+
+ {
+ session s;
+ transaction t (db->begin ());
+ unique_ptr<object1> p (db->load<object1> (o1.id));
+ t.commit ();
+
+ assert (p->o2->id == o1.o2->id);
+ }
+
+ {
+ typedef odb::query<view1> query;
+ typedef odb::result<view1> result;
+
+ transaction t (db->begin ());
+ result r (db->query<view1> (query::object2::id == o1.o2->id));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->i == o1.n1);
+ assert (++i == r.end ());
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<view2> query;
+ typedef odb::result<view2> result;
+
+ transaction t (db->begin ());
+ result r (db->query<view2> (query::o2::id == o1.o2->id));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->i == o1.n1);
+ assert (++i == r.end ());
+ t.commit ();
+ }
+
+ {
+ typedef odb::result<view3> result;
+
+ transaction t (db->begin ());
+ result r (db->query<view3> ());
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->i == o1.n1);
+ assert (++i == r.end ());
+ t.commit ();
+ }
+ }
+
+ // Use virtual data members to implement multi-member composite object id.
+ //
+ {
+ using namespace test3;
+
+ person o;
+ o.first_ = "John";
+ o.last_ = "Doe";
+
+ name id;
+ {
+ transaction t (db->begin ());
+ id = db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<person> p (db->load<person> (id));
+ t.commit ();
+
+ assert (o.first_ == p->first_ && o.last_ == p->last_);
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/virtual/test.hxx b/odb-tests/common/virtual/test.hxx
new file mode 100644
index 0000000..2654d09
--- /dev/null
+++ b/odb-tests/common/virtual/test.hxx
@@ -0,0 +1,171 @@
+// file : common/virtual/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <vector>
+
+#include <odb/core.hxx>
+
+// Test basic virtual data member functionality.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ #pragma db value
+ struct comp
+ {
+ int i;
+
+ #pragma db transient
+ std::string s;
+ #pragma db member(s_) virtual(std::string) access(s)
+
+ bool operator== (const comp& v) const
+ {
+ return i == v.i && s == v.s;
+ }
+ };
+
+ #pragma db object transient
+ struct object
+ {
+ object (): p1 (0) {}
+ ~object () {delete p1;}
+
+ struct {unsigned long v;} id1;
+
+ #pragma db id
+ #pragma db member(id) get(id1.v) virtual(unsigned long) set(id1.v)
+ #pragma db member(id) auto
+
+ int i () const {return i1;}
+ void i (int i) {i1 = i;}
+ int i1;
+
+ comp c1;
+ #pragma db member(c) virtual(comp) access(c1)
+
+ typedef std::vector<std::string> strings;
+ strings v1;
+ #pragma db member(v) virtual(strings) access(v1)
+
+ typedef object* object_ptr;
+ object_ptr p1;
+ #pragma db member(p) virtual(object_ptr) access(p1)
+
+ bool operator== (const object& o) const
+ {
+ return id1.v == o.id1.v &&
+ i1 == o.i1 &&
+ c1 == o.c1 &&
+ v1 == o.v1 &&
+ (p1 != 0 ? o.p1 != 0 && *p1 == *o.p1 : o.p1 == 0);
+ }
+ };
+
+ #pragma db member(object::i) virtual(int)
+}
+
+#pragma db member(test1::object::id) column("oid")
+
+// Test pragma resolution to virtual data member.
+//
+#pragma db namespace table("t2_")
+namespace test2
+{
+ struct object1;
+ struct object2;
+
+ typedef object1* object1_ptr;
+ typedef object2* object2_ptr;
+
+ #pragma db object
+ struct object2
+ {
+ object2 (unsigned long i = 0): id (i) {}
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db inverse(o)
+ object1_ptr o1;
+ };
+
+ #pragma db object
+ struct object1
+ {
+ object1 (unsigned long i = 0): id (i), o2 (0) {}
+ ~object1 () {delete o2;}
+
+ #pragma db id
+ unsigned long id;
+
+ #pragma db transient
+ object2_ptr o2;
+ #pragma db member(o) virtual(object2_ptr) access(o2)
+
+ #pragma db transient
+ unsigned long n1;
+ #pragma db member(n) virtual(unsigned long) access(n1)
+ #pragma db index member(n)
+ };
+
+ #pragma db view object(object1) object(object2)
+ struct view1
+ {
+ #pragma db column(object1::n)
+ unsigned long i;
+ };
+
+ #pragma db view object(object1 = o1) object(object2 = o2: o1::n == o2::id)
+ struct view2
+ {
+ #pragma db column(o1::n)
+ unsigned long i;
+ };
+
+ #pragma db view object(object1: object1::n != 0)
+ struct view3
+ {
+ #pragma db column(test2::object1::n)
+ unsigned long i;
+ };
+}
+
+// Use virtual data members to implement multi-member composite object id.
+//
+#pragma db namespace table("t3_")
+namespace test3
+{
+ #pragma db value
+ struct name
+ {
+ name () {}
+ name (const std::string& f, const std::string& l)
+ : first (f), last(l) {}
+
+ std::string first;
+ std::string last;
+
+ bool operator< (const name& x) const
+ {
+ return first < x.first || (first == x.first && last < x.last);
+ }
+ };
+
+ #pragma db object transient
+ struct person
+ {
+ std::string first_;
+ std::string last_;
+
+ #pragma db member(name) virtual(name) id \
+ get(::test3::name (this.first_, this.last_)) \
+ set(this.first_ = (?).first; this.last_ = (?).last)
+ };
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/virtual/testscript b/odb-tests/common/virtual/testscript
new file mode 100644
index 0000000..769c7f9
--- /dev/null
+++ b/odb-tests/common/virtual/testscript
@@ -0,0 +1,33 @@
+# file : common/virtual/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}
diff --git a/odb-tests/common/wrapper/buildfile b/odb-tests/common/wrapper/buildfile
new file mode 100644
index 0000000..57f43f2
--- /dev/null
+++ b/odb-tests/common/wrapper/buildfile
@@ -0,0 +1,40 @@
+# file : common/wrapper/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+import libodb = libodb%lib{odb}
+
+libs =
+
+for db: $databases
+ import libs += libodb-$db%lib{odb-$db}
+
+import libs += lib{common}
+
+exe{driver}: {hxx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-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
+
+<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta}
+
+for db: $databases
+{
+ exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi
+ <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta}
+}
+
+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 wrapper_ \
+ --generate-schema
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
diff --git a/odb-tests/common/wrapper/driver.cxx b/odb-tests/common/wrapper/driver.cxx
new file mode 100644
index 0000000..9c352fc
--- /dev/null
+++ b/odb-tests/common/wrapper/driver.cxx
@@ -0,0 +1,216 @@
+// file : common/wrapper/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test wrapper machinery.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test.hxx"
+#include "test-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));
+
+ // Test 1: simple values.
+ //
+ {
+ using namespace test1;
+
+ unsigned long id1, id2;
+ {
+ object1 o1;
+ object2 o2;
+
+ o1.num.reset (new int (123));
+ o1.nstrs.push_back (nullable_string ());
+ o1.nstrs.push_back (nullable_string ("123"));
+
+ o2.sstrs.push_back (str_sptr ());
+ o2.sstrs.push_back (str_sptr (new string ("123")));
+
+ transaction t (db->begin ());
+ id1 = db->persist (o1);
+ id2 = db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object1> o1 (db->load<object1> (id1));
+ unique_ptr<object2> o2 (db->load<object2> (id2));
+ t.commit ();
+
+ assert (*o1->num == 123);
+ assert (o1->str.get () == 0);
+ assert (o1->nstr.null ());
+ assert (o1->nstrs[0].null ());
+ assert (o1->nstrs[1].get () == "123");
+
+ assert (!o2->sstr);
+ assert (!o2->sstrs[0]);
+ assert (*o2->sstrs[1] == "123");
+ }
+ }
+
+ //
+ // Composite values.
+ //
+ unsigned long id;
+ {
+ comp_object co;
+
+ co.c1.reset (new comp1 ("123", 123));
+ co.vc1.push_back (comp1 ("1", 1));
+ co.vc1.push_back (comp1 ("2", 2));
+ co.vc1.push_back (comp1 ("3", 3));
+
+ co.c2.reset (new comp2 ("123", 123));
+ co.c2->strs.push_back ("1");
+ co.c2->strs.push_back ("2");
+ co.c2->strs.push_back ("3");
+
+ {
+ transaction t (db->begin ());
+ id = db->persist (co);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<comp_object> o (db->load<comp_object> (id));
+ t.commit ();
+
+ assert (*o->c1 == *co.c1);
+ assert (o->vc1 == co.vc1);
+ assert (*o->c2 == *co.c2);
+ }
+ }
+
+ //
+ // Containers.
+ //
+ {
+ cont_object co;
+
+ co.nums.reset (new vector<int>);
+ co.nums->push_back (1);
+ co.nums->push_back (2);
+ co.nums->push_back (3);
+
+ co.c.num = 123;
+ co.c.strs.reset (new vector<string>);
+ co.c.strs->push_back ("1");
+ co.c.strs->push_back ("2");
+ co.c.strs->push_back ("3");
+
+ {
+ transaction t (db->begin ());
+ id = db->persist (co);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<cont_object> o (db->load<cont_object> (id));
+ t.commit ();
+
+ assert (*o->nums == *co.nums);
+ assert (o->c == co.c);
+ }
+ }
+
+ // Test 5: composite NULL values.
+ //
+ {
+ using namespace test5;
+
+ object o1, o2;
+
+ o1.v.push_back (nullable<comp> ());
+
+ o2.p.reset (new comp (1, "a"));
+ o2.n = comp (2, "b");
+ o2.v.push_back (comp (3, "c"));
+
+ // Persist.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ // Load.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id));
+ unique_ptr<object> p2 (db->load<object> (o2.id));
+ t.commit ();
+
+ assert (p1->p.get () == 0);
+ assert (!p1->n);
+ assert (!p1->v[0]);
+
+ assert (p2->p.get () != 0 && *p2->p == *o2.p);
+ assert (p2->n && *p2->n == *o2.n);
+ assert (p2->v[0] && *p2->v[0] == *o2.v[0]);
+ }
+
+ // Update.
+ //
+ {
+ o1.p.reset (new comp (1, "a"));
+ o1.n = comp (2, "b");
+ o1.v[0] = comp (3, "c");
+
+ o2.p.reset ();
+ o2.n.reset ();
+ o2.v[0].reset ();
+
+ transaction t (db->begin ());
+ db->update (o1);
+ db->update (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p1 (db->load<object> (o1.id));
+ unique_ptr<object> p2 (db->load<object> (o2.id));
+ t.commit ();
+
+ assert (p1->p.get () != 0 && *p1->p == *o1.p);
+ assert (p1->n && *p1->n == *o1.n);
+ assert (p1->v[0] && *p1->v[0] == *o1.v[0]);
+
+ assert (p2->p.get () == 0);
+ assert (!p2->n);
+ assert (!p2->v[0]);
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/common/wrapper/test.hxx b/odb-tests/common/wrapper/test.hxx
new file mode 100644
index 0000000..3ae4151
--- /dev/null
+++ b/odb-tests/common/wrapper/test.hxx
@@ -0,0 +1,214 @@
+// file : common/wrapper/test.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST_HXX
+#define TEST_HXX
+
+#include <string>
+#include <memory> // std::unique_ptr
+#include <vector>
+
+#include <odb/core.hxx>
+#include <odb/nullable.hxx>
+
+using odb::nullable;
+
+// Test 1: simple values.
+//
+#pragma db namespace table("t1_")
+namespace test1
+{
+ typedef nullable<std::string> nullable_string;
+
+ typedef std::unique_ptr<int> num_uptr;
+ typedef std::unique_ptr<std::string> str_uptr;
+ typedef std::shared_ptr<std::string> str_sptr;
+
+ #pragma db object table("obj1")
+ struct object1
+ {
+ #pragma db id auto
+ unsigned long id_;
+
+ num_uptr num;
+
+ #pragma db null
+ str_uptr str;
+
+ nullable_string nstr;
+ std::vector<nullable_string> nstrs;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ #pragma db id auto
+ unsigned long id_;
+
+ #pragma db null
+ str_sptr sstr;
+
+ #pragma db value_null
+ std::vector<str_sptr> sstrs;
+ };
+}
+
+//
+// Composite values.
+//
+
+#pragma db value
+struct comp1
+{
+ comp1 () {}
+ comp1 (const std::string& s, int n): str (s), num (n) {}
+
+ std::string str;
+ int num;
+};
+
+inline bool
+operator== (const comp1& x, const comp1& y)
+{
+ return x.str == y.str && x.num == y.num;
+}
+
+
+#pragma db value
+struct comp2
+{
+ comp2 () {}
+ comp2 (const std::string& s, int n): str (s), num (n) {}
+
+ std::string str;
+ int num;
+
+ std::vector<std::string> strs;
+};
+
+inline bool
+operator== (const comp2& x, const comp2& y)
+{
+ return x.str == y.str && x.num == y.num && x.strs == y.strs;
+}
+
+struct comp3;
+
+typedef std::unique_ptr<comp1> comp1_uptr;
+typedef std::unique_ptr<comp2> comp2_uptr;
+typedef std::unique_ptr<comp3> comp3_uptr;
+
+#pragma db object
+struct comp_object
+{
+ #pragma db id auto
+ unsigned long id_;
+
+ comp1_uptr c1; // Wrapped comp value.
+ std::vector<nullable<comp1> > vc1; // Container of wrapped comp values.
+ comp2_uptr c2; // Container inside wrapped comp value.
+};
+
+// This one is just a compilation test to cover more convolute cases.
+//
+#pragma db value
+struct comp3: comp2
+{
+ comp1_uptr c1;
+ std::vector<nullable<comp1> > vc1;
+};
+
+#pragma db object
+struct comp_object2
+{
+ #pragma db id auto
+ unsigned long id_;
+
+ comp3_uptr c3;
+};
+
+//
+// Containers.
+//
+
+typedef std::unique_ptr<std::vector<int>> nums_uptr;
+typedef std::unique_ptr<std::vector<std::string>> strs_uptr;
+
+#pragma db value
+struct cont_comp
+{
+ int num;
+ strs_uptr strs;
+};
+
+inline bool
+operator== (const cont_comp& x, const cont_comp& y)
+{
+ return x.num == y.num && *x.strs == *y.strs;
+}
+
+#pragma db object
+struct cont_object
+{
+ #pragma db id auto
+ unsigned long id_;
+
+ nums_uptr nums; // Wrapped container.
+ cont_comp c; // Wrapped container in comp value.
+};
+
+// Test composite NULL values.
+//
+#pragma db namespace table("t5_")
+namespace test5
+{
+ #pragma db value
+ struct base
+ {
+ base () {}
+ base (int n): num (n) {}
+
+ int num = 0;
+ };
+
+ inline bool
+ operator== (const base& x, const base& y)
+ {
+ return x.num == y.num;
+ }
+
+ #pragma db value
+ struct comp: base
+ {
+ comp () {}
+ comp (int n, const std::string s): base (n), str (s), extra (n + 1) {}
+
+ std::string str;
+ base extra;
+
+ odb::nullable<int> always_null;
+ };
+
+ inline bool
+ operator== (const comp& x, const comp& y)
+ {
+ return static_cast<const base&> (x) == y &&
+ x.str == y.str && x.extra == y.extra;
+ }
+
+ #pragma db object
+ struct object
+ {
+ #pragma db id auto
+ unsigned long id;
+
+ #pragma db null
+ std::unique_ptr<comp> p;
+
+ odb::nullable<comp> n;
+
+ std::vector< odb::nullable<comp> > v;
+ };
+}
+
+#endif // TEST_HXX
diff --git a/odb-tests/common/wrapper/testscript b/odb-tests/common/wrapper/testscript
new file mode 100644
index 0000000..6630813
--- /dev/null
+++ b/odb-tests/common/wrapper/testscript
@@ -0,0 +1,33 @@
+# file : common/wrapper/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+: mysql
+:
+if $mysql
+{
+ .include ../../mysql.testscript
+
+ $create_schema;
+ $*
+}
+
+: sqlite
+:
+if $sqlite
+{
+ .include ../../sqlite.testscript
+
+ $*
+}
+
+: pgsql
+:
+if $pgsql
+{
+ .include ../../pgsql.testscript
+
+ $create_schema;
+ $*
+}