ODB

ODB is a cross-platform, cross-database ORM library for C++. It enables persistence of C++ objects to relational databases without manual mapping code, allowing development without awareness of tables, columns, or SQL. Through compiler-based code generation, it achieves high-performance and type-safe database access.

ORMC++Cross-platformEnterprisePostgreSQLMySQLSQLite

GitHub Overview

codesynthesis-com/odb

Object-relational mapping (ORM) system for C++

Stars28
Watchers3
Forks4
Created:December 14, 2024
Language:C++
License:Other

Topics

None

Star History

codesynthesis-com/odb Star History
Data as of: 7/19/2025, 02:41 AM

Library

ODB

Overview

ODB is a cross-platform, cross-database ORM library for C++. It enables persistence of C++ objects to relational databases without manual mapping code, allowing development without awareness of tables, columns, or SQL. Through compiler-based code generation, it achieves high-performance and type-safe database access.

Details

ODB 2025 edition is established as the most mature choice in the C++ ORM field. Supporting PostgreSQL, MySQL, SQLite, Oracle, and SQL Server, it maintains stable adoption in enterprise-level C++ application development. Its compiler-based approach provides powerful type checking and code optimization while minimizing runtime overhead.

Key Features

  • Compiler-based: Automatic code generation via ODB compiler
  • Multi-database: Support for PostgreSQL, MySQL, SQLite, Oracle, SQL Server
  • High Performance: Minimized runtime overhead
  • Type Safety: Strong compile-time type checking
  • Transactions: Full ACID transaction support
  • Query Language: Native C++ syntax for query writing

Pros and Cons

Pros

  • Mature stability with extensive enterprise track record
  • Support for multiple major databases
  • High performance through compile-time optimization
  • Excellent integration with existing C++ codebases
  • Comprehensive documentation and support
  • Professional support through commercial licensing

Cons

  • GPL/Commercial dual licensing (paid for commercial use)
  • Complex build process due to code generation
  • Relatively steep learning curve
  • Conservative support for modern C++ features
  • No NoSQL database support

References

Examples

Basic Setup

// person.hxx - Entity definition
#pragma once
#include <string>
#include <memory>
#include <odb/core.hxx>

#pragma db object
class person
{
public:
  person() {}
  person(const std::string& first,
         const std::string& last,
         unsigned short age)
    : first_(first), last_(last), age_(age)
  {
  }

  // Accessors
  const std::string& first() const { return first_; }
  void first(const std::string& f) { first_ = f; }

  const std::string& last() const { return last_; }
  void last(const std::string& l) { last_ = l; }

  unsigned short age() const { return age_; }
  void age(unsigned short a) { age_ = a; }

private:
  friend class odb::access;

  #pragma db id auto
  unsigned long id_;

  std::string first_;
  std::string last_;
  unsigned short age_;
};

// Generate code with ODB compiler
// odb -d mysql --generate-query --generate-schema person.hxx

Database Operations

// main.cpp - Basic CRUD operations
#include <iostream>
#include <memory>
#include <odb/database.hxx>
#include <odb/transaction.hxx>
#include <odb/mysql/database.hxx>

#include "person.hxx"
#include "person-odb.hxx"

using namespace std;
using namespace odb::core;

int main()
{
  try
  {
    // Database connection
    unique_ptr<database> db(
      new odb::mysql::database("user", "password", "database", "host"));

    // CREATE - Create new records
    {
      person john("John", "Doe", 33);
      person jane("Jane", "Smith", 28);

      transaction t(db->begin());
      
      // Persist objects
      db->persist(john);
      db->persist(jane);
      
      t.commit();
      
      cout << "Created person with id: " << john.id() << endl;
    }

    // READ - Read records
    {
      transaction t(db->begin());
      
      // Load by ID
      unique_ptr<person> p(db->load<person>(1));
      cout << "Loaded: " << p->first() << " " << p->last() << endl;
      
      // Query-based search
      typedef odb::query<person> query;
      typedef odb::result<person> result;
      
      result r(db->query<person>(query::age > 25));
      
      for (result::iterator i(r.begin()); i != r.end(); ++i)
      {
        cout << "Found: " << i->first() << " " << i->last() 
             << ", age " << i->age() << endl;
      }
      
      t.commit();
    }

    // UPDATE - Update records
    {
      transaction t(db->begin());
      
      unique_ptr<person> p(db->load<person>(1));
      p->age(34);
      
      db->update(*p);
      
      t.commit();
      cout << "Updated person age to: " << p->age() << endl;
    }

    // DELETE - Delete records
    {
      transaction t(db->begin());
      
      unique_ptr<person> p(db->load<person>(1));
      db->erase(*p);
      
      t.commit();
      cout << "Deleted person" << endl;
    }
  }
  catch (const odb::exception& e)
  {
    cerr << "Database error: " << e.what() << endl;
    return 1;
  }

  return 0;
}

Advanced Features

// Relationships and queries
#pragma db object
class employer
{
public:
  employer() {}
  employer(const std::string& name) : name_(name) {}

  const std::string& name() const { return name_; }
  void name(const std::string& n) { name_ = n; }

private:
  friend class odb::access;

  #pragma db id auto
  unsigned long id_;
  
  std::string name_;
};

#pragma db object
class employee
{
public:
  employee() {}
  
  // Relationship
  #pragma db not_null
  shared_ptr<employer> employer() const { return employer_; }
  void employer(shared_ptr<employer> e) { employer_ = e; }

private:
  friend class odb::access;
  
  #pragma db id auto
  unsigned long id_;
  
  std::string first_name_;
  std::string last_name_;
  
  #pragma db not_null
  shared_ptr<employer> employer_;
};

// Complex queries
void complex_queries(database& db)
{
  transaction t(db.begin());
  
  // JOIN operations
  typedef odb::query<employee> query;
  typedef odb::result<employee> result;
  
  // Find employees of specific employer
  result r(db.query<employee>(
    query::employer->name == "ABC Corporation"));
  
  // Conditional queries
  std::string pattern = "John%";
  result r2(db.query<employee>(
    query::first_name.like(pattern) && 
    query::employer->id == 1));
  
  // Aggregation
  typedef odb::query<person> pquery;
  auto count = db.query_value<std::size_t>(
    pquery::age >= 18 && pquery::age <= 65);
  
  cout << "Working age people: " << count << endl;
  
  t.commit();
}

// Using views
#pragma db view object(person)
struct person_stat
{
  #pragma db column("count(" + person::id_ + ")")
  std::size_t count;
  
  #pragma db column("avg(" + person::age_ + ")")
  double avg_age;
  
  #pragma db column("max(" + person::age_ + ")")
  unsigned short max_age;
};

void view_example(database& db)
{
  transaction t(db.begin());
  
  typedef odb::result<person_stat> result;
  result r(db.query<person_stat>());
  
  const person_stat& ps(*r.begin());
  cout << "Total: " << ps.count << endl;
  cout << "Average age: " << ps.avg_age << endl;
  cout << "Max age: " << ps.max_age << endl;
  
  t.commit();
}

Practical Example

// User management system example
#include <vector>
#include <algorithm>

class user_repository
{
private:
  std::shared_ptr<database> db_;
  
public:
  user_repository(std::shared_ptr<database> db) : db_(db) {}
  
  // Search with pagination
  struct page_result
  {
    std::vector<std::shared_ptr<person>> items;
    std::size_t total_count;
    std::size_t page;
    std::size_t page_size;
  };
  
  page_result find_all(std::size_t page = 1, std::size_t page_size = 10)
  {
    transaction t(db_->begin());
    
    // Get total count
    std::size_t total = db_->query_value<std::size_t>(
      odb::query<person>::true_expr);
    
    // Pagination
    typedef odb::query<person> query;
    typedef odb::result<person> result;
    
    result r(db_->query<person>(
      query::true_expr + 
      " ORDER BY " + query::last_ + ", " + query::first_ +
      " LIMIT " + std::to_string(page_size) +
      " OFFSET " + std::to_string((page - 1) * page_size)));
    
    std::vector<std::shared_ptr<person>> items;
    for (auto& p : r)
    {
      items.push_back(std::make_shared<person>(p));
    }
    
    t.commit();
    
    return {items, total, page, page_size};
  }
  
  // Search with conditions
  std::vector<std::shared_ptr<person>> search(
    const std::string& keyword,
    unsigned short min_age = 0,
    unsigned short max_age = 100)
  {
    transaction t(db_->begin());
    
    typedef odb::query<person> query;
    typedef odb::result<person> result;
    
    query q(query::age >= min_age && query::age <= max_age);
    
    if (!keyword.empty())
    {
      std::string pattern = "%" + keyword + "%";
      q = q && (query::first_.like(pattern) || 
                query::last_.like(pattern));
    }
    
    result r(db_->query<person>(q));
    
    std::vector<std::shared_ptr<person>> results;
    for (auto& p : r)
    {
      results.push_back(std::make_shared<person>(p));
    }
    
    t.commit();
    
    return results;
  }
  
  // Batch operations
  void batch_insert(const std::vector<person>& persons)
  {
    transaction t(db_->begin());
    
    for (const auto& p : persons)
    {
      db_->persist(p);
    }
    
    t.commit();
  }
  
  // Transaction processing
  bool transfer_ownership(unsigned long from_id, unsigned long to_id)
  {
    try
    {
      transaction t(db_->begin());
      
      auto from = db_->load<person>(from_id);
      auto to = db_->load<person>(to_id);
      
      // Execute business logic
      // ...
      
      db_->update(*from);
      db_->update(*to);
      
      t.commit();
      return true;
    }
    catch (const odb::object_not_persistent&)
    {
      return false;
    }
  }
};

// Usage example
int main()
{
  auto db = std::make_shared<odb::mysql::database>(
    "user", "password", "database");
  
  user_repository repo(db);
  
  // Paginated search
  auto page = repo.find_all(1, 20);
  cout << "Total users: " << page.total_count << endl;
  
  // Keyword search
  auto results = repo.search("John", 25, 40);
  cout << "Found " << results.size() << " users" << endl;
  
  return 0;
}