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