LiteSQL

LiteSQLは、コードジェネレータとC++ライブラリからなるORMフレームワークです。C++オブジェクトとリレーショナルデータベースの密結合によるオブジェクト永続化層を提供し、SQLite3、PostgreSQL、MySQL、Oracleをサポートします。XMLスキーマ定義から自動的にC++クラスを生成し、型安全なデータベースアクセスを実現します。

ORMC++コードジェネレータSQLitePostgreSQLMySQLOracle

GitHub概要

gittiver/litesql

LiteSQL is a C++ ORM (object relational mapper) consisting of a codegenerator and C++ library that integrates C++ objects tightly to relational database and thus provides an object persistence layer. LiteSQL supports SQLite3, PostgreSQL, MySQL and oracle as backends.

スター34
ウォッチ3
フォーク7
作成日:2017年10月18日
言語:C++
ライセンス:Other

トピックス

backendcodegeneratordatabasemysqloracleormpersistencepersistence-layerrelational-databasessql

スター履歴

gittiver/litesql Star History
データ取得日時: 2025/7/17 07:05

ライブラリ

LiteSQL

概要

LiteSQLは、コードジェネレータとC++ライブラリからなるORMフレームワークです。C++オブジェクトとリレーショナルデータベースの密結合によるオブジェクト永続化層を提供し、SQLite3、PostgreSQL、MySQL、Oracleをサポートします。XMLスキーマ定義から自動的にC++クラスを生成し、型安全なデータベースアクセスを実現します。

詳細

LiteSQL 2025年版は、オープンソースC++ ORMの選択肢として継続利用されています。主要データベース対応の幅広さを活かし、多データベース環境のプロジェクトで採用されています。コードジェネレータアプローチにより、データベーススキーマの変更に対する保守性を高め、開発効率を向上させます。軽量設計ながら、リレーションシップ管理やトランザクション処理などの基本的なORM機能を網羅しています。

主な特徴

  • コード生成: XMLスキーマからC++クラスを自動生成
  • マルチデータベース: SQLite3、PostgreSQL、MySQL、Oracle対応
  • 軽量設計: 最小限の依存関係と小さなフットプリント
  • リレーション管理: 1対1、1対多、多対多の関係をサポート
  • 型安全: コンパイル時の型チェックによる安全性
  • BSDライセンス: 商用利用も可能なオープンソース

メリット・デメリット

メリット

  • XMLスキーマによる宣言的なデータモデル定義
  • 複数データベースへの統一的なインターフェース
  • 軽量で組み込みシステムでも使用可能
  • BSDライセンスによる柔軟な利用条件
  • 自動生成コードによる開発効率の向上
  • シンプルで理解しやすいAPI設計

デメリット

  • 開発とメンテナンスのペースが遅い
  • モダンC++機能への対応が限定的
  • ドキュメントとコミュニティサポートが不足
  • 高度なORM機能(遅延読み込み等)の欠如
  • パフォーマンス最適化オプションが限定的

参考ページ

書き方の例

XMLスキーマ定義

<!-- person.xml - データモデル定義 -->
<?xml version="1.0"?>
<database name="PersonDB" namespace="example">
  
  <object name="Person">
    <field name="name" type="string" length="256"/>
    <field name="age" type="integer"/>
    <field name="email" type="string" length="128" unique="true"/>
    <field name="created" type="datetime" default="now()"/>
  </object>
  
  <object name="Department">
    <field name="name" type="string" length="128"/>
    <field name="description" type="string" length="1024"/>
  </object>
  
  <object name="Project">
    <field name="title" type="string" length="256"/>
    <field name="deadline" type="date"/>
    <field name="budget" type="double"/>
  </object>
  
  <!-- リレーション定義 -->
  <relation name="Employment">
    <relate object="Person" limit="many" handle="employees"/>
    <relate object="Department" limit="one" handle="department"/>
  </relation>
  
  <relation name="Assignment">
    <relate object="Person" limit="many" handle="members"/>
    <relate object="Project" limit="many" handle="projects"/>
    <field name="role" type="string" length="64"/>
    <field name="hours_per_week" type="integer"/>
  </relation>
  
</database>

基本的な使用方法

// main.cpp - 生成されたコードの使用例
#include "persondb.hpp"
#include <iostream>
#include <litesql.hpp>

using namespace litesql;
using namespace example;

int main()
{
  try
  {
    // データベース接続
    PersonDB db("sqlite3", "database=person.db");
    
    // テーブル作成
    db.create();
    
    // CREATE - 新規レコード作成
    Person person(db);
    person.name = "田中太郎";
    person.age = 30;
    person.email = "[email protected]";
    person.update();  // 保存
    
    std::cout << "Created person with ID: " << person.id << std::endl;
    
    // 部署の作成
    Department dept(db);
    dept.name = "開発部";
    dept.description = "ソフトウェア開発部門";
    dept.update();
    
    // リレーションの設定
    Employment(db, person, dept).link();
    
    // READ - データ取得
    // 全件取得
    std::vector<Person> allPersons = select<Person>(db).all();
    for (const auto& p : allPersons)
    {
      std::cout << "Person: " << p.name << ", Age: " << p.age << std::endl;
    }
    
    // 条件検索
    std::vector<Person> adults = select<Person>(db, Person::Age > 18).all();
    
    // 単一レコード取得
    Person foundPerson = select<Person>(db, Person::Email == "[email protected]").one();
    
    // UPDATE - 更新
    foundPerson.age = 31;
    foundPerson.update();
    
    // DELETE - 削除
    foundPerson.del();
    
  }
  catch (const Except& e)
  {
    std::cerr << "Database error: " << e << std::endl;
    return 1;
  }
  
  return 0;
}

高度なクエリ操作

#include "persondb.hpp"
#include <algorithm>

using namespace litesql;
using namespace example;

class PersonRepository
{
private:
  PersonDB& db_;
  
public:
  PersonRepository(PersonDB& db) : db_(db) {}
  
  // 複雑な条件検索
  std::vector<Person> findByAgeRange(int minAge, int maxAge)
  {
    return select<Person>(db_, 
      Person::Age >= minAge && Person::Age <= maxAge)
      .orderBy(Person::Age)
      .all();
  }
  
  // LIKE検索
  std::vector<Person> searchByName(const std::string& keyword)
  {
    return select<Person>(db_, 
      Person::Name.like("%" + keyword + "%"))
      .all();
  }
  
  // JOIN操作
  void listEmployeesWithDepartment()
  {
    // 部署付き従業員リスト
    auto persons = select<Person>(db_).all();
    
    for (const auto& person : persons)
    {
      // リレーション経由で部署取得
      auto depts = person.department().get().all();
      if (!depts.empty())
      {
        std::cout << person.name << " - " << depts[0].name << std::endl;
      }
    }
  }
  
  // 集計関数
  int getAverageAge()
  {
    DataSource<Person> personDS = DataSource<Person>::get(db_);
    
    // カスタムSQL実行
    std::string sql = "SELECT AVG(age) FROM " + personDS.table() + ";";
    Records recs = db_.query(sql);
    
    if (!recs.empty() && !recs[0].empty())
    {
      return atoi(recs[0][0].c_str());
    }
    return 0;
  }
  
  // トランザクション処理
  bool transferToNewDepartment(int personId, int newDeptId)
  {
    db_.begin();
    
    try
    {
      Person person = select<Person>(db_, Person::Id == personId).one();
      Department newDept = select<Department>(db_, Department::Id == newDeptId).one();
      
      // 既存の部署関係を削除
      Employment::del(db_, 
        Employment::PersonId == personId);
      
      // 新しい部署関係を作成
      Employment(db_, person, newDept).link();
      
      db_.commit();
      return true;
    }
    catch (const NotFound&)
    {
      db_.rollback();
      return false;
    }
  }
  
  // ページネーション
  struct PageResult
  {
    std::vector<Person> items;
    int totalCount;
    int currentPage;
    int pageSize;
  };
  
  PageResult getPaginatedPersons(int page, int pageSize)
  {
    int offset = (page - 1) * pageSize;
    
    // 総数取得
    int totalCount = select<Person>(db_).count();
    
    // ページデータ取得
    std::vector<Person> items = select<Person>(db_)
      .orderBy(Person::Name)
      .limit(pageSize)
      .offset(offset)
      .all();
    
    return {items, totalCount, page, pageSize};
  }
};

// 多対多リレーションの操作
void projectAssignmentExample(PersonDB& db)
{
  // プロジェクト作成
  Project project(db);
  project.title = "新システム開発";
  project.deadline = Date(2025, 12, 31);
  project.budget = 10000000.0;
  project.update();
  
  // 人員アサイン
  std::vector<Person> developers = select<Person>(db_, 
    Person::Name.like("%開発%")).all();
  
  for (const auto& dev : developers)
  {
    Assignment assignment(db, dev, project);
    assignment.role = "Developer";
    assignment.hours_per_week = 40;
    assignment.link();
  }
  
  // プロジェクトメンバー一覧
  auto members = project.members().get().all();
  for (const auto& member : members)
  {
    std::cout << "Member: " << member.name << std::endl;
  }
}

実用的なアプリケーション例

// REST APIサーバーでの使用例
#include <httplib.h>
#include <json.hpp>
#include "persondb.hpp"

using namespace litesql;
using namespace example;
using json = nlohmann::json;

class PersonAPI
{
private:
  PersonDB db_;
  
public:
  PersonAPI() : db_("sqlite3", "database=api.db") 
  {
    db_.create();
  }
  
  // GET /api/persons
  std::string getAllPersons()
  {
    json result = json::array();
    
    auto persons = select<Person>(db_).all();
    for (const auto& person : persons)
    {
      result.push_back({
        {"id", person.id},
        {"name", person.name.value()},
        {"age", person.age.value()},
        {"email", person.email.value()},
        {"created", person.created.value().asString()}
      });
    }
    
    return result.dump();
  }
  
  // GET /api/persons/:id
  std::string getPersonById(int id)
  {
    try
    {
      Person person = select<Person>(db_, Person::Id == id).one();
      
      json result = {
        {"id", person.id},
        {"name", person.name.value()},
        {"age", person.age.value()},
        {"email", person.email.value()},
        {"created", person.created.value().asString()}
      };
      
      // 部署情報も含める
      auto depts = person.department().get().all();
      if (!depts.empty())
      {
        result["department"] = {
          {"id", depts[0].id},
          {"name", depts[0].name.value()}
        };
      }
      
      return result.dump();
    }
    catch (const NotFound&)
    {
      return json({{"error", "Person not found"}}).dump();
    }
  }
  
  // POST /api/persons
  std::string createPerson(const json& data)
  {
    try
    {
      db_.begin();
      
      Person person(db_);
      person.name = data["name"].get<std::string>();
      person.age = data["age"].get<int>();
      person.email = data["email"].get<std::string>();
      person.update();
      
      // 部署割り当て
      if (data.contains("department_id"))
      {
        int deptId = data["department_id"].get<int>();
        Department dept = select<Department>(db_, 
          Department::Id == deptId).one();
        Employment(db_, person, dept).link();
      }
      
      db_.commit();
      
      return json({
        {"success", true},
        {"id", person.id}
      }).dump();
    }
    catch (const std::exception& e)
    {
      db_.rollback();
      return json({
        {"success", false},
        {"error", e.what()}
      }).dump();
    }
  }
  
  // PUT /api/persons/:id
  std::string updatePerson(int id, const json& data)
  {
    try
    {
      Person person = select<Person>(db_, Person::Id == id).one();
      
      if (data.contains("name"))
        person.name = data["name"].get<std::string>();
      if (data.contains("age"))
        person.age = data["age"].get<int>();
      if (data.contains("email"))
        person.email = data["email"].get<std::string>();
      
      person.update();
      
      return json({{"success", true}}).dump();
    }
    catch (const NotFound&)
    {
      return json({
        {"success", false},
        {"error", "Person not found"}
      }).dump();
    }
  }
  
  // DELETE /api/persons/:id
  std::string deletePerson(int id)
  {
    try
    {
      Person person = select<Person>(db_, Person::Id == id).one();
      person.del();
      
      return json({{"success", true}}).dump();
    }
    catch (const NotFound&)
    {
      return json({
        {"success", false},
        {"error", "Person not found"}
      }).dump();
    }
  }
};

// サーバー実装
int main()
{
  httplib::Server svr;
  PersonAPI api;
  
  svr.Get("/api/persons", [&](const httplib::Request&, httplib::Response& res) {
    res.set_content(api.getAllPersons(), "application/json");
  });
  
  svr.Get(R"(/api/persons/(\d+))", [&](const httplib::Request& req, httplib::Response& res) {
    int id = std::stoi(req.matches[1]);
    res.set_content(api.getPersonById(id), "application/json");
  });
  
  svr.Post("/api/persons", [&](const httplib::Request& req, httplib::Response& res) {
    auto data = json::parse(req.body);
    res.set_content(api.createPerson(data), "application/json");
  });
  
  svr.Put(R"(/api/persons/(\d+))", [&](const httplib::Request& req, httplib::Response& res) {
    int id = std::stoi(req.matches[1]);
    auto data = json::parse(req.body);
    res.set_content(api.updatePerson(id, data), "application/json");
  });
  
  svr.Delete(R"(/api/persons/(\d+))", [&](const httplib::Request& req, httplib::Response& res) {
    int id = std::stoi(req.matches[1]);
    res.set_content(api.deletePerson(id), "application/json");
  });
  
  std::cout << "Server running on http://localhost:8080" << std::endl;
  svr.listen("localhost", 8080);
  
  return 0;
}