Persisting C++ Classes in Relational Databases With ODB - Boris Kolpackov - CppCon 2014
Persisting C++ Classes in Relational Databases With ODB - Boris Kolpackov - CppCon 2014
with ODB
Boris Kolpackov
Code Synthesis
CODE
SYNTHESIS
-1-
ODB, an ORM for C++
-2-
Object Relational Mapping
-3-
Object Relational Mapping
Why ORM?
• Object-oriented vs relational mismatch
• Type and name safety
• Parameter binding and result set extraction
• Database schema evolution
-4-
Manual Schema Evolution
-5-
Object Relational Mapping
-6-
sword OCIBindDynamic ( OCIBind *bindp,
OCIError *errhp,
void *ictxp,
OCICallbackInBind (icbfp)(
void *ictxp,
OCIBind *bindp,
ub4 iter,
ub4 index,
void **bufpp,
ub4 *alenp,
ub1 *piecep,
void **indpp ),
void *octxp,
OCICallbackOutBind (ocbfp)(
void *octxp,
OCIBind *bindp,
-7-
Object Relational Mapping
Why Relational?
-8-
ODB, and ORM for C++
What’s ODB?
• Three levels
• Not a framework
• No magic
• One-to-one ORM-Database operation mapping
-9-
ODB, and ORM for C++
-10-
C++ Standards
• Rvalue references
• Range-based for loop
• std::function and lambdas
• C++11 Standard Library integration
• C++11 in examples
-11-
Databases
Cross-Database
• MySQL
• SQLite
• PostgreSQL
• Oracle
• Microsoft SQL Server
-12-
Platforms and Compilers
Cross-Platform
-13-
-14-
Mobile & Embedded
• ODB + SQLite
• “Hello, World” example is 500Kb
• Cross-compiler friendly
• Android, Raspberry Pi guides
-15-
Performance
-16-
Performance
Load performance
• SQLite — 60,000 object per second — 17 µs per object
• PostgreSQL — 15,000 objects per second — 65 µs per object
-16-
License
Dual-Licensed
• ODB License
• www.codesynthesis.com/products/odb/license.xhtml
-17-
C++ Support
-18-
C++ Support
-18-
C++ Support
-19-
C++ Support
-19-
C++ Support
-19-
C++ Support
Standard C++ In
-20-
C++ Support
Standard C++ In
Standard C++ Out
-20-
Persistent Class
class bug
{
public:
...
private:
unsigned long long id_;
status status_;
std::string summary_;
std::string description_;
};
-21-
Persistent Class
.
#include <odb/core.hxx>
#pragma db object
class bug
{
...
private:
friend class odb::access;
bug () {}
#pragma db id auto
unsigned long long id_;
status status_;
std::string summary_;
std::string description_;
};
-22-
Persistent Class
.
#include <odb/core.hxx>
#pragma db object
class bug
{
...
private:
friend class odb::access;
bug () {}
#pragma db id auto
unsigned long long id_;
status status_;
std::string summary_;
std::string description_;
};
-22-
Persistent Class
.
#include <odb/core.hxx>
#pragma db object
class bug
{
...
private:
friend class odb::access;
bug () {}
#pragma db id auto
unsigned long long id_;
status status_;
std::string summary_;
std::string description_;
};
-22-
Persistent Class
.
#include <odb/core.hxx>
#pragma db object
class bug
{
...
private:
friend class odb::access;
bug () {}
#pragma db id auto
unsigned long long id_;
status status_;
std::string summary_;
std::string description_;
};
-22-
Persistent Class
.
#include <odb/core.hxx>
#pragma db object
class bug
{
...
private:
friend class odb::access;
bug () {}
#pragma db id auto
unsigned long long id_;
status status_;
std::string summary_;
std::string description_;
};
-22-
Persistent Class
.
#include <odb/core.hxx>
#pragma db object
class bug
{
...
private:
friend class odb::access;
bug () {}
#pragma db id auto
unsigned long long id_;
status status_;
std::string summary_;
std::string description_;
};
-22-
Persistent Class
.
#pragma db object
class bug
{
public:
unsigned long long id () const;
void id (unsigned long long);
...
private:
#pragma db id auto
unsigned long long id_;
...
-23-
Persistent Class
.
#pragma db object
class bug
{
public:
unsigned long long id () const;
void id (unsigned long long);
...
private:
#pragma db id auto
unsigned long long id_;
...
-23-
Persistent Class
class bug
{
...
private:
unsigned long long id_;
...
};
#ifdef ODB_COMPILER
# pragma db object(bug)
# pragma db member(bug::id_) id auto
#endif
-24-
Persistent Class
// bug.hxx
class bug
{
...
private:
unsigned long long id_;
...
};
// bug-mapping.hxx
#pragma db object(bug)
#pragma db member(bug::id_) id auto
-25-
Workflow
..
#include
C++ Header
C++ Source
C++ Compiler
-26-
Workflow
..
#include
C++ Header
C++ Compiler
-26-
Workflow
..
#include
C++ Header
C++ Compiler
-26-
Workflow
..
#include
C++ Header
Database
C++ Source C++ Header
Schema
C++ Compiler
-26-
Workflow
..
#include
C++ Header
Database #include
C++ Source C++ Header
Schema
C++ Compiler
-26-
Workflow
..
#include
C++ Header
Database #include
C++ Source C++ Header
Schema
C++ Compiler
-26-
ODB Compiler
-27-
ODB Compiler
$ ls
bug.hxx
bug-odb.cxx
bug-odb.hxx
bug-odb.ixx
-27-
ODB Compiler
-28-
ODB Compiler
-28-
ODB Compiler
$ odb --generate-schema -d mysql bug.hxx
-29-
ODB Compiler
$ odb --generate-schema -d mysql bug.hxx
$ ls
bug.hxx
bug-odb.cxx
bug-odb.hxx
bug-odb.ixx
bug.sql
-29-
ODB Compiler
$ odb --generate-schema -d mysql bug.hxx
$ ls
bug.hxx
bug-odb.cxx
bug-odb.hxx
bug-odb.ixx
bug.sql
$ cat bug.sql
-29-
ODB Compiler
$ odb --generate-schema -d mysql bug.hxx
$ ls
bug.hxx
bug-odb.cxx
bug-odb.hxx
bug-odb.ixx
bug.sql
$ cat bug.sql
#include <odb/pgsql/database.hxx>
-30-
Database
#include <odb/pgsql/database.hxx>
#include <odb/sqlite/database.hxx>
-30-
Database
#include <odb/pgsql/database.hxx>
#include <odb/sqlite/database.hxx>
#include <odb/database.hxx>
-30-
Database Schema
• Automatically generated
• Map to a custom schema
-31-
Generated Schema
-32-
Generated Schema
#include <odb/schema-catalog.hxx>
-32-
Custom Schema
• Map classes to tables
• Map data members to columns
• Map C++ types to database types
-33-
Custom Schema
• Map classes to tables
• Map data members to columns
• Map C++ types to database types
...
};
-33-
Making Objects Persistent
.
bug b (open,
”Support for DB2”,
”ODB does not yet support IBM DB2.”);
db.persist (b);
t.commit ();
-34-
Making Objects Persistent
.
bug b (open,
”Support for DB2”,
”ODB does not yet support IBM DB2.”);
db.persist (b);
t.commit ();
-34-
Transactions
.
try
{
transaction t (db.begin ());
db.persist (b1);
db.persist (b2);
t.commit ();
}
catch (const odb::connection_lost&)
{
// Try again.
...
}
-35-
Transactions
.
try
{
transaction t (db.begin ());
db.persist (b1);
db.persist (b2);
t.commit ();
}
catch (const odb::connection_lost&)
{
// Try again.
...
}
-35-
Transactions
.
try
{
transaction t (db.begin ());
db.persist (b1);
db.persist (b2);
t.commit ();
}
catch (const odb::connection_lost&)
{
// Try again.
...
}
-35-
Making Objects Persistent
.
bug b (open,
”Support for DB2”,
”ODB does not yet support IBM DB2.”);
t.commit ();
-36-
Making Objects Persistent
.
bug b (open,
”Support for DB2”,
”ODB does not yet support IBM DB2.”);
t.commit ();
-36-
Making Objects Persistent
.
bug b (open,
”Support for DB2”,
”ODB does not yet support IBM DB2.”);
t.commit ();
bug b;
db.load (id, b);
t.commit ();
-37-
Loading Persistent Objects
.
transaction t (db.begin ());
bug b;
db.load (id, b);
t.commit ();
-37-
Loading Persistent Objects
.
transaction t (db.begin ());
bug b;
db.load (id, b);
t.commit ();
=> SELECT
status,
summary,
description
FROM bug WHERE id = $1
-37-
Updating Persistent Objects
.
transaction t (db.begin ());
t.commit ();
-38-
Updating Persistent Objects
.
transaction t (db.begin ());
t.commit ();
-38-
Querying the Database
.
typedef odb::query<bug> query;
typedef odb::result<bug> result;
result r = ...
for (bug& b: r)
...
-39-
Querying the Database
.
typedef odb::query<bug> query;
typedef odb::result<bug> result;
result r = ...
for (bug& b: r)
...
-39-
Querying the Database
.
typedef odb::query<bug> query;
typedef odb::result<bug> result;
result r = ...
for (bug& b: r)
...
-39-
Querying the Database
.
typedef odb::query<bug> query;
typedef odb::result<bug> result;
t.commit ();
-40-
Querying the Database
.
typedef odb::query<bug> query;
typedef odb::result<bug> result;
t.commit ();
-40-
Querying the Database
.
typedef odb::query<bug> query;
t.commit ();
-41-
Querying the Database
.
typedef odb::query<bug> query;
t.commit ();
=> SELECT
id
status,
summary,
description
FROM bug WHERE status = $1
-41-
Querying the Database
.
db.query<bug> (query::status == open ||
query::status == confirmed);
status s;
query q (query::status == query::_ref (s));
s = open;
db.query<bug> (q); // status == open
s = closed;
db.query<bug> (q); // status == closed
-42-
Querying the Database
.
db.query<bug> (query::status == open ||
query::status == confirmed);
status s;
query q (query::status == query::_ref (s));
s = open;
db.query<bug> (q); // status == open
s = closed;
db.query<bug> (q); // status == closed
-42-
Querying the Database
.
db.query<bug> (query::status == open ||
query::status == confirmed);
status s;
query q (query::status == query::_ref (s));
s = open;
db.query<bug> (q); // status == open
s = closed;
db.query<bug> (q); // status == closed
-42-
Querying the Database
.
db.query<bug> (query::status == open ||
query::status == confirmed);
status s;
query q (query::status == query::_ref (s));
s = open;
db.query<bug> (q); // status == open
s = closed;
db.query<bug> (q); // status == closed
-42-
Querying the Database
.
db.query<bug> (query::status == open ||
query::status == confirmed);
status s;
query q (query::status == query::_ref (s));
s = open;
db.query<bug> (q); // status == open
s = closed;
db.query<bug> (q); // status == closed
-42-
Deleting Persistent Objects
.
transaction t (db.begin ());
db.erase<bug> (id);
bug b = ...;
db.erase (b);
t.commit ();
-43-
Deleting Persistent Objects
.
transaction t (db.begin ());
db.erase<bug> (id);
bug b = ...;
db.erase (b);
t.commit ();
-43-
Deleting Persistent Objects
.
transaction t (db.begin ());
db.erase<bug> (id);
bug b = ...;
db.erase (b);
t.commit ();
-43-
Deleting Persistent Objects
.
transaction t (db.begin ());
db.erase<bug> (id);
bug b = ...;
db.erase (b);
t.commit ();
-43-
Adding Timestamps
.
#pragma db object
class bug
{
...
#pragma db id auto
unsigned long long id_;
status status_;
std::string summary_;
std::string description_;
boost::posix_time::ptime created_;
boost::posix_time::ptime updated_;
};
-44-
Adding Timestamps
.
#pragma db object
class bug
{
...
#pragma db id auto
unsigned long long id_;
status status_;
std::string summary_;
std::string description_;
boost::posix_time::ptime created_;
boost::posix_time::ptime updated_;
};
-44-
Profiles
-45-
Boost Profile
• uuid
• date_time
• optional
-46-
NULL Semantics
#pragma db object
class bug
{
...
boost::optional<std::string> description_;
};
-47-
Qt Profile
-48-
Adding Creation and Modification Dates (Qt)
#pragma db object
class Bug
{
...
#pragma db id auto
unsigned long long id_;
Status status_;
QString summary_;
QString description_;
QDateTime created_;
QDateTime updated_;
};
-49-
Containers
-50-
Adding Comments and Tags
.
#pragma db object
class bug
{
...
#pragma db id auto
unsigned long long id_;
status status_;
std::string summary_;
std::string description_;
boost::posix_time::ptime created_;
boost::posix_time::ptime updated_;
std::vector<std::string> comments_;
std::unordered_set<std::string> tags_;
};
-51-
Adding Comments and Tags (Qt)
.
#pragma db object
class Bug
{
...
#pragma db id auto
unsigned long long id_;
Status status_;
QString summary_;
QString description_;
QDateTime created_;
QDateTime updated_;
QList<QString> comments_;
QHash<QString> tags_;
};
-52-
Composite Value Types
-53-
Extending Comments
.
#pragma db value
class comment
{
...
std::string text_;
boost::posix_time::ptime created_;
};
#pragma db object
class bug
{
...
std::vector<comment> comments_;
};
-54-
Relationships
-55-
Adding User Object
#pragma db object
class user
{
...
#pragma db id
std::string email_;
std::string first_;
std::string last_;
};
-56-
Adding Bug Reporter
.
#pragma db object
class bug
{
...
std::shared_ptr<user> reporter_;
};
-57-
Adding Bug Reporter
.
#pragma db object
class bug
{
...
std::shared_ptr<user> reporter_;
};
-57-
Adding Bug List
.
#pragma db object
class user
{
...
#pragma db id
std::string email_;
std::string first_name_;
std::string last_name_;
std::vector<std::shared_ptr<bug>> reported_bugs_;
};
-58-
Adding Bug List
.
#pragma db object
class user
{
...
#pragma db id
std::string email_;
std::string first_name_;
std::string last_name_;
std::vector<std::shared_ptr<bug>> reported_bugs_;
};
-58-
We Have a Problem
#pragma db object
class user
{
...
std::vector<std::shared_ptr<bug>> reported_bugs_;
};
#pragma db object
class bug
{
...
std::shared_ptr<user> reporter_;
};
-59-
We Have a Problem
.
#pragma db object
class user
{
...
std::vector<std::weak_ptr<bug>> reported_bugs_;
};
#pragma db object
class bug
{
...
std::shared_ptr<user> reporter_;
};
-60-
Another Problem
.
CREATE TABLE bug (
...
reporter TEXT NULL,
CONSTRAINT reporter_fk
FOREIGN KEY (reporter)
REFERENCES user (email));
-61-
Another Problem
.
CREATE TABLE bug (
...
reporter TEXT NULL,
CONSTRAINT reporter_fk
FOREIGN KEY (reporter)
REFERENCES user (email));
-61-
Another Problem
.
#pragma db object
class user
{
...
#pragma db inverse(reporter_)
std::vector<std::weak_ptr<bug>> reported_bugs_;
};
#pragma db object
class bug
{
...
std::shared_ptr<user> reporter_;
};
-62-
Adding Bug Reporter and Bug List (Qt)
#pragma db object
class User
{
...
#pragma db inverse(reporter_)
QList<QWeakPointer<Bug>> reportedBugs_;
};
#pragma db object
class Bug
{
...
QSharedPointer<User> reporter_;
};
-63-
Relationships in Queries
.
typedef odb::query<bug> query;
-64-
Multi-Database Support
• Static
• Dynamic
-65-
Multi-Database Support
• Static
• Dynamic
• Mixed
-65-
Multi-Database Support
-66-
Multi-Database Support
$ ls
bug.hxx
bug-odb.cxx bug-odb-sqlite.cxx bug-odb-pgsql.cxx
bug-odb.hxx bug-odb-sqlite.hxx bug-odb-pgsql.cxx
bug-odb.ixx bug-odb-sqlite.ixx bug-odb-pgsql.cxx
-66-
Static Multi-Database Support
.
#include ”bug-odb-pgsql.hxx”
#include ”bug-odb-sqlite.hxx”
std::shared_ptr<bug> b;
{
odb::transaction t (cache.begin ());
b = cache.find<bug> (id);
t.commit ();
}
if (b == nullptr)
{
odb::transaction t (store.begin ());
b = store.load<bug> (id);
t.commit ();
} -67-
Static Multi-Database Support
.
#include ”bug-odb-pgsql.hxx”
#include ”bug-odb-sqlite.hxx”
std::shared_ptr<bug> b;
{
odb::transaction t (cache.begin ());
b = cache.find<bug> (id);
t.commit ();
}
if (b == nullptr)
{
odb::transaction t (store.begin ());
b = store.load<bug> (id);
t.commit ();
} -67-
Static Multi-Database Support
.
#include ”bug-odb-pgsql.hxx”
#include ”bug-odb-sqlite.hxx”
std::shared_ptr<bug> b;
{
odb::transaction t (cache.begin ());
b = cache.find<bug> (id);
t.commit ();
}
if (b == nullptr)
{
odb::transaction t (store.begin ());
b = store.load<bug> (id);
t.commit ();
} -67-
Static Multi-Database Support
.
#include ”bug-odb-pgsql.hxx”
#include ”bug-odb-sqlite.hxx”
std::shared_ptr<bug> b;
{
odb::transaction t (cache.begin ());
b = cache.find<bug> (id);
t.commit ();
}
if (b == nullptr)
{
odb::transaction t (store.begin ());
b = store.load<bug> (id);
t.commit ();
} -67-
Static Multi-Database Support
.
#include ”bug-odb-pgsql.hxx”
#include ”bug-odb-sqlite.hxx”
std::shared_ptr<bug> b;
{
odb::transaction t (cache.begin ());
b = cache.find<bug> (id);
t.commit ();
}
if (b == nullptr)
{
odb::transaction t (store.begin ());
b = store.load<bug> (id);
t.commit ();
} -67-
Dynamic Multi-Database Support
.
#include ”bug-odb.hxx”
std::shared_ptr<bug>
find_bug (odb::database& db, unsigned long long id)
{
odb::transaction t (db.begin ());
std::shared_ptr<bug> r (db.find<bug> (id));
t.commit ();
return r;
}
if (b == nullptr)
b = find_bug (store, id);
-68-
Dynamic Multi-Database Support
.
#include ”bug-odb.hxx”
std::shared_ptr<bug>
find_bug (odb::database& db, unsigned long long id)
{
odb::transaction t (db.begin ());
std::shared_ptr<bug> r (db.find<bug> (id));
t.commit ();
return r;
}
if (b == nullptr)
b = find_bug (store, id);
-68-
Dynamic Multi-Database Support
.
#include ”bug-odb.hxx”
std::shared_ptr<bug>
find_bug (odb::database& db, unsigned long long id)
{
odb::transaction t (db.begin ());
std::shared_ptr<bug> r (db.find<bug> (id));
t.commit ();
return r;
}
if (b == nullptr)
b = find_bug (store, id);
-68-
Dynamic Multi-Database Support
.
#include ”bug-odb.hxx”
std::shared_ptr<bug>
find_bug (odb::database& db, unsigned long long id)
{
odb::transaction t (db.begin ());
std::shared_ptr<bug> r (db.find<bug> (id));
t.commit ();
return r;
}
if (b == nullptr)
b = find_bug (store, id);
-68-
Dynamic Loading
void
load_db (const std::string& db_name)
{
#ifdef _WIN32
string dll (”bug-” + db_name + ”.dll”);
HMODULE h (LoadLibraryA (dll.c_str ()));
#else
string so (”libbug-” + db_name + ”.so”);
void* h (dlopen (so.c_str (), RTLD_NOW));
#endif
if (h == 0)
{
// Handle error.
}
}
-69-
Database Schema Evolution
• No magic
• Simple, easy to understand building blocks
• Schema migration
• Data migration
-70-
Object Model Version
.
#pragma db model version(1, 1)
#pragma db object
class bug
{
...
};
#pragma db object
class bug
{
...
std::string platform_;
};
-71-
Object Model Version
.
#pragma db model version(1, 1)
#pragma db object
class bug
{
...
};
#pragma db object
class bug
{
...
std::string platform_;
};
-71-
Changelog
-72-
Changelog
<changeset version=”2”>
<alter-table name=”bug”>
<add-column name=”platform” type=”TEXT” null=”false”/>
</alter-table>
</changeset>
<model version=”1”>
...
</model>
-72-
Schema Migration
-73-
Schema Migration
-73-
Schema Migration
.
/* bug-002-pre.sql */
/* bug-002-post.sql */
-74-
Schema Migration
.
/* bug-002-pre.sql */
/* bug-002-post.sql */
-74-
Data Migration
.
transaction t (db.begin ());
t.commit ();
-75-
Data Migration
.
transaction t (db.begin ());
t.commit ();
-75-
Data Migration
.
transaction t (db.begin ());
t.commit ();
-75-
Data Migration
schema_catalog::data_migration_function (
2,
[] (database& db)
{
for (bug& b: db.query<bug> ())
{
b.platform (”Unknown”);
db.update (b);
}
});
-76-
Schema Evolution
.
#pragma db model version(1, 2)
#pragma db object
class user
{
std::string first_;
std::string last_;
};
#pragma db object
class user
{
std::string name_;
};
-77-
Schema Evolution
.
#pragma db model version(1, 2)
#pragma db object
class user
{
std::string first_;
std::string last_;
};
#pragma db object
class user
{
std::string name_;
};
-77-
Changelog Diff
+ <changeset version=”3”>
+ <alter-table name=”user”>
+ <add-column name=”name” type=”TEXT” null=”false”/>
+ <drop-column name=”first”/>
+ <drop-column name=”last”/>
+ </alter-table>
+ </changeset>
-78-
Data Migration
.
schema_catalog::data_migration_function (
3,
[] (database& db)
{
for (bug& b: db.query<bug> ())
{
b.name (b.first () + ” ” + b.last ());
db.update (b);
}
});
-79-
Data Migration
.
schema_catalog::data_migration_function (
3,
[] (database& db)
{
for (bug& b: db.query<bug> ())
{
b.name (b.first () + ” ” + b.last ());
db.update (b);
}
});
-79-
Resources
• ODB Page
• www.codesynthesis.com/products/odb/
• ODB Manual
• www.codesynthesis.com/products/odb/doc/manual.xhtml
• Blog
• www.codesynthesis.com/~boris/blog/
-80-