Zenn
🧐

Rustで作る自作データベース:第1回「基本的なSQL対応RDBMSの設計と実装」

2025/03/13に公開
1

この記事では、前回
https://zenn.dev/kazuma0606/articles/9a086386fe8af2
に続いて早速基本的なCRUD操作が出来るインメモリのデータベースを設計・実装します。
🔸モノリスではコード量が多くなる可能性
🔸将来的な機能追加を考慮
🔸依存関係を整理
以上の点からクリーンアーキテクチャを採用しています。ただし、従来のクリーンアーキテクチャのようにユースケースを一つ一つ実装していくのはデータベースを実装する上では難しいので、ディレクトリ構成やDIパターンに基づいて実装しています。
※コード量が多いので、お急ぎの方はまずは保存して、時間のあるときにじっくり読むことを推奨します

それでは、これからRustで実装した基本的なSQLデータベースの構造とコードを解説します。繰り返しになりますが、クリーンアーキテクチャに基づいた設計で、SQLクエリのパース、インメモリストレージ、REST APIインターフェースまでを含んでいます。

プロジェクト構造🗃️

rustydb/
├── src/
│   ├── domain/                # ドメイン層(中心的な概念)
│   │   ├── entity/           # 基本エンティティ
│   │   │   ├── data_type.rs  # データ型定義
│   │   │   ├── value.rs      # 値の表現
│   │   │   ├── column.rs     # カラム定義
│   │   │   ├── table.rs      # テーブル構造
│   │   │   └── mod.rs        # エンティティモジュール
│   │   ├── repository/       # データアクセス抽象化
│   │   │   ├── table_repository.rs  # リポジトリインターフェース
│   │   │   └── mod.rs        # リポジトリモジュール
│   │   └── mod.rs            # ドメイン層モジュール
│   ├── application/          # アプリケーション層(未実装)
│   │   └── mod.rs
│   ├── infrastructure/       # インフラストラクチャ層(具体的実装)
│   │   ├── parser/           # SQLパーサー
│   │   │   ├── sql_parser.rs # SQL解析実装
│   │   │   └── mod.rs        # パーサーモジュール
│   │   ├── storage/          # ストレージエンジン
│   │   │   ├── memory.rs     # インメモリ実装
│   │   │   └── mod.rs        # ストレージモジュール
│   │   ├── repository/       # リポジトリ実装
│   │   │   ├── memory_repository.rs  # インメモリリポジトリ
│   │   │   └── mod.rs        # リポジトリ実装モジュール
│   │   └── mod.rs            # インフラ層モジュール
│   ├── interface/            # インターフェース層(UI/API)
│   │   ├── api/              # REST API
│   │   │   ├── server.rs     # サーバー設定
│   │   │   ├── handler.rs    # リクエストハンドラー
│   │   │   └── mod.rs        # API モジュール
│   │   ├── cli/              # コマンドライン(未実装)
│   │   │   └── mod.rs
│   │   └── mod.rs            # インターフェース層モジュール
│   ├── lib.rs                # ライブラリルート
│   └── main.rs               # エントリーポイント
├── examples/                 # 使用例
│   ├── basic_usage.rs        # 基本的な使用例
│   └── api_client.rs         # API クライアント例
├── Cargo.toml                # 依存関係定義
└── README.md                 # プロジェクト概要

Cargo.toml

Cargo.toml
[package]
name = "rustydb"
version = "0.1.0"
edition = "2021"
authors = ["yoshimura.hisa@gmail.com"]
description = "A Rust-based database system for educational purposes"

[dependencies]
# コア依存関係
sqlparser = "0.35"                                 # SQLパーサー
serde = { version = "1.0", features = ["derive"] } # シリアライズ/デシリアライズ
serde_json = "1.0"                                 # JSONサポート
thiserror = "1.0"                                  # エラー定義
async-trait = "0.1"                                # 非同期トレイト

# Webフレームワーク
axum = "0.6"                                                   # Webサーバー
tower = "0.4"                                                  # HTTPミドルウェア
tower-http = { version = "0.4", features = ["cors", "trace"] }

# 非同期ランタイム
tokio = { version = "1", features = ["full"] }

# ロギング
tracing = "0.1"                                                     # 構造化ロギング
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

# 開発効率化
derive_more = "0.99"                                # ボイラープレート削減
typed-builder = "0.16"                              # ビルダーパターン
strum = { version = "0.25", features = ["derive"] }

# ユーティリティ
bytes = "1.4"      # バイト操作
itertools = "0.11" # イテレータ拡張

# 日付・時刻操作
chrono = { version = "0.4", features = ["serde"] }

# APIクライアントテスト用
reqwest = { version = "0.11", features = ["json"] }

[dev-dependencies]
# テスト用クレート
proptest = "1.2"  # プロパティベーステスト
test-case = "3.1" # パラメータ化テスト
mockall = "0.11"  # モックオブジェクト
criterion = "0.5" # ベンチマーク

[[bench]]
name = "query_benchmarks"
harness = false

主要コンポーネントの解説

1. ドメイン層 (src/domain/)

ドメイン層はデータベースの中心的な概念を定義します。SQL文の解析結果やデータの操作方法に関わらず、データベースとして必要な基本的な概念はここに定義されます。


1.1 エンティティ (src/domain/entity/)

データ型定義

data_type.rs
// src/domain/entity/data_type.rs
use derive_more::Display;
use serde::{Deserialize, Serialize};
use std::fmt;
use strum::EnumString;

/// データベースでサポートされるデータ型
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, EnumString, Serialize, Deserialize)]
pub enum DataType {
    /// 整数型(64ビット符号付き整数)
    #[strum(serialize = "INTEGER")]
    Integer,
    
    /// 浮動小数点型(64ビット)
    #[strum(serialize = "FLOAT")]
    Float,
    
    /// 文字列型(UTF-8)
    #[strum(serialize = "TEXT")]
    Text,
    
    /// 真偽値型
    #[strum(serialize = "BOOLEAN")]
    Boolean,
    
    /// 日付時刻型
    #[strum(serialize = "TIMESTAMP")]
    Timestamp,
    
    /// NULL値が許容される型を表す装飾子
    #[strum(serialize = "NULL")]
    Null,
}

/// SQL型制約の表現
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Constraint {
    /// プライマリキー制約
    PrimaryKey,
    
    /// 一意制約
    Unique,
    
    /// NULLを許さない制約
    NotNull,
    
    /// デフォルト値制約
    Default(String),
}

値の表現

value.rs
// src/domain/entity/value.rs
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;
use crate::domain::entity::data_type::DataType;
use thiserror::Error;

/// 値型エラー
#[derive(Error, Debug, PartialEq)]
pub enum ValueError {
    #[error("Type mismatch: expected {expected}, got {actual}")]
    TypeMismatch {
        expected: DataType,
        actual: DataType,
    },
    
    #[error("Cannot convert from {0} to {1}")]
    ConversionError(String, String),
    
    #[error("Null value not allowed")]
    NullValueError,
}

/// データベース内の値の表現
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Value {
    /// 整数値
    Integer(i64),
    
    /// 浮動小数点値
    Float(f64),
    
    /// 文字列値
    Text(String),
    
    /// 真偽値
    Boolean(bool),
    
    /// 日時値
    Timestamp(DateTime<Utc>),
    
    /// NULL値
    Null,
}

カラム定義

column.rs
// src/domain/entity/column.rs
use crate::domain::entity::data_type::{Constraint, DataType};
use derive_more::Display;
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;

/// テーブルカラムを表現する構造体
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)]
pub struct Column {
    /// カラム名
    pub name: String,
    
    /// カラムのデータ型
    pub data_type: DataType,
    
    /// カラムに適用される制約のリスト
    #[builder(default)]
    pub constraints: Vec<Constraint>,
}

テーブル構造

table.rs
// src/domain/entity/table.rs
use crate::domain::entity::column::Column;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;

/// データベーステーブルを表現する構造体
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Table {
    /// テーブル名
    pub name: String,
    
    /// テーブルのカラム
    pub columns: Vec<Column>,
}

/// 1行のデータを表現する
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Row {
    /// カラム名と値のマッピング
    pub values: HashMap<String, crate::domain::entity::value::Value>,
}

/// クエリ結果セットを表現する
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResultSet {
    /// 結果セットのスキーマ(カラム定義)
    pub columns: Vec<Column>,
    
    /// 結果の行データ
    pub rows: Vec<Row>,
}

1.2 リポジトリ (src/domain/repository/)

リポジトリインターフェース

table_repository.rs
// src/domain/repository/table_repository.rs
use async_trait::async_trait;
use crate::domain::entity::table::{Table, Row, ResultSet};
use crate::domain::entity::value::Value;
use thiserror::Error;
use std::sync::Arc;

/// テーブルリポジトリのエラー型
#[derive(Error, Debug)]
pub enum RepositoryError {
    #[error("Table {0} not found")]
    TableNotFound(String),
    
    #[error("Table {0} already exists")]
    TableAlreadyExists(String),
    
    #[error("Column {0} not found in table {1}")]
    ColumnNotFound(String, String),
    
    #[error("Storage error: {0}")]
    StorageError(String),
    
    #[error("Data error: {0}")]
    DataError(String),
    
    #[error("Internal error: {0}")]
    InternalError(String),
}

/// クエリフィルター条件
#[derive(Debug, Clone)]
pub enum FilterCondition {
    /// 単一条件(カラム名、演算子、値)
    Simple {
        column: String,
        operator: FilterOperator,
        value: Value,
    },
    
    /// 複数条件のAND結合
    And(Vec<FilterCondition>),
    
    /// 複数条件のOR結合
    Or(Vec<FilterCondition>),
}

/// フィルター演算子
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FilterOperator {
    Equal,
    NotEqual,
    Greater,
    GreaterOrEqual,
    Less,
    LessOrEqual,
    Like,
}

/// テーブルリポジトリ - データベーステーブルの永続化と取得のための抽象インターフェース
#[async_trait]
pub trait TableRepository: Send + Sync {
    /// データベースに新しいテーブルを作成する
    async fn create_table(&self, table: &Table) -> Result<(), RepositoryError>;
    
    /// テーブルが存在するかどうかを確認する
    async fn table_exists(&self, table_name: &str) -> Result<bool, RepositoryError>;
    
    /// テーブルを削除する
    async fn drop_table(&self, table_name: &str) -> Result<(), RepositoryError>;
    
    /// 名前でテーブルを取得する
    async fn get_table(&self, table_name: &str) -> Result<Table, RepositoryError>;
    
    /// すべてのテーブル名を取得する
    async fn get_table_names(&self) -> Result<Vec<String>, RepositoryError>;
    
    /// テーブルに1行のデータを挿入する
    async fn insert(&self, table_name: &str, row: &Row) -> Result<(), RepositoryError>;
    
    /// 複数行のデータを一括挿入する
    async fn insert_many(&self, table_name: &str, rows: &[Row]) -> Result<(), RepositoryError>;
    
    /// テーブルからデータを取得する
    async fn select(
        &self, 
        table_name: &str, 
        columns: &[String], 
        filter: Option<&FilterCondition>
    ) -> Result<ResultSet, RepositoryError>;
    
    /// 条件に合致する行を更新する
    async fn update(
        &self,
        table_name: &str,
        updates: &[(String, Value)],
        filter: Option<&FilterCondition>
    ) -> Result<usize, RepositoryError>;
    
    /// 条件に合致する行を削除する
    async fn delete(
        &self,
        table_name: &str,
        filter: Option<&FilterCondition>
    ) -> Result<usize, RepositoryError>;
}

2. インフラストラクチャ層 (src/infrastructure/)

インフラストラクチャ層はドメイン層で定義されたインターフェースの具体的な実装を提供します。

2.1 パーサー (src/infrastructure/parser/)

SQL解析実装

sql_parser.rs
// src/infrastructure/parser/sql_parser.rs
use sqlparser::dialect::GenericDialect;
use sqlparser::parser::Parser;
use sqlparser::ast::{Statement, Query, SetExpr, TableFactor, Values, Expr, Value as SqlValue, 
                     SelectItem, ObjectName, Ident, TableWithJoins};

use crate::domain::entity::{DataType, Column, Value};
use crate::domain::repository::{FilterCondition, FilterOperator};
use thiserror::Error;

/// SQL解析エラー
#[derive(Error, Debug)]
pub enum ParseError {
    #[error("SQL syntax error: {0}")]
    SyntaxError(String),
    
    #[error("Unsupported SQL feature: {0}")]
    UnsupportedFeature(String),
    
    #[error("Invalid data type: {0}")]
    InvalidDataType(String),
    
    #[error("Invalid value: {0}")]
    InvalidValue(String),
    
    #[error("Internal parser error: {0}")]
    InternalError(String),
}

/// SQLパーサーの実装
pub struct SqlParser {
    dialect: GenericDialect,
}

/// 解析されたSQL文
pub enum ParsedStatement {
    CreateTable(CreateTableStatement),
    Select(SelectStatement),
    Insert(InsertStatement),
    Update(UpdateStatement),
    Delete(DeleteStatement),
    DropTable(DropTableStatement),
}

2.2 ストレージ (src/infrastructure/storage/)

インメモリストレージ実装

memory.rs
// src/infrastructure/storage/memory.rs
use std::collections::HashMap;
use std::sync::RwLock;

use crate::domain::entity::{Table, Column, Row, Value, DataType};
use crate::domain::repository::{FilterCondition, FilterOperator};
use thiserror::Error;

/// ストレージエラー
#[derive(Error, Debug)]
pub enum StorageError {
    #[error("Table {0} not found")]
    TableNotFound(String),
    
    #[error("Table {0} already exists")]
    TableAlreadyExists(String),
    
    // ... 他のエラー
}

/// テーブルのデータを保持する構造体
#[derive(Debug, Clone)]
struct TableData {
    schema: Table,
    rows: Vec<Row>,
}

/// インメモリストレージの実装
#[derive(Debug, Default)]
pub struct MemoryStorage {
    tables: RwLock<HashMap<String, TableData>>,
}

2.3 リポジトリ実装 (src/infrastructure/repository/)

インメモリリポジトリ実装

// src/infrastructure/repository/memory_repository.rs
use std::sync::Arc;
use async_trait::async_trait;

use crate::domain::entity::{Table, Row, Value, ResultSet};
use crate::domain::repository::{TableRepository, RepositoryError, FilterCondition};
use crate::infrastructure::storage::{MemoryStorage, StorageError};

/// インメモリリポジトリの実装
pub struct MemoryTableRepository {
    storage: Arc<MemoryStorage>,
}

#[async_trait]
impl TableRepository for MemoryTableRepository {
    // TableRepositoryトレイトのメソッド実装
    async fn create_table(&self, table: &Table) -> Result<(), RepositoryError> {
        // 実装...
    }
    
    // 他のメソッド実装...
}

3. インターフェース層 (src/interface/)

インターフェース層はユーザーがデータベースと対話するためのインターフェースを提供します。


3.1 API (src/interface/api/)

サーバー設定

server.rs
// src/interface/api/server.rs
use axum::{
    Router, 
    routing::{get, post},
    Extension,
    Server,
};
use std::net::SocketAddr;
use std::sync::Arc;
use tracing::info;

use crate::domain::repository::TableRepository;
use crate::infrastructure::storage::MemoryStorage;
use crate::infrastructure::repository::MemoryTableRepository;
use crate::infrastructure::parser::SqlParser;
use crate::interface::api::handler::{
    health_check_handler, 
    get_tables_handler, 
    get_table_handler, 
    execute_sql_handler
};

#[derive(Clone)]
pub struct ServerConfig {
    pub port: u16,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            port: 8080, // デフォルトポート番号
        }
    }
}

pub async fn start_server(config: ServerConfig) -> Result<(), Box<dyn std::error::Error>> {
    // ストレージとリポジトリの初期化
    let storage = Arc::new(MemoryStorage::new());
    let repository: Arc<dyn TableRepository> = Arc::new(MemoryTableRepository::new(storage.clone()));
    
    // SQLパーサーの初期化
    let parser = Arc::new(SqlParser::new());

    // ルーターの設定
    let app = Router::new()
        .route("/health", get(health_check_handler))
        .route("/api/tables", get(get_tables_handler))
        .route("/api/tables/:table_name", get(get_table_handler))
        .route("/api/query", post(execute_sql_handler))
        .layer(Extension(repository))  // リポジトリの拡張
        .layer(Extension(parser));     // パーサーの拡張

    // サーバーのアドレス設定
    let addr = SocketAddr::from(([0, 0, 0, 0], config.port));
    
    info!("サーバーを{}で起動中...", addr);
    
    // サーバーの起動
    Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
}

リクエストハンドラー

handler.rs
// src/interface/api/handler.rs
use axum::{
    extract::{Path, Json, Extension},
    http::StatusCode,
    response::{IntoResponse, Response},
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use thiserror::Error;

use crate::domain::repository::TableRepository;
use crate::infrastructure::parser::{SqlParser, ParsedStatement};

/// SQLクエリのリクエスト
#[derive(Deserialize)]
pub struct QueryRequest {
    sql: String,
}

/// クエリ実行結果
#[derive(Serialize)]
pub struct QueryResult {
    #[serde(skip_serializing_if = "Option::is_none")]
    columns: Option<Vec<String>>,
    
    #[serde(skip_serializing_if = "Option::is_none")]
    rows: Option<Vec<serde_json::Value>>,
    
    #[serde(skip_serializing_if = "Option::is_none")]
    affected_rows: Option<usize>,
    
    statement_type: String,
}

/// SQL実行ハンドラー
pub async fn execute_sql_handler(
    Extension(repository): Extension<Arc<dyn TableRepository>>,
    Extension(parser): Extension<Arc<SqlParser>>,
    Json(payload): Json<QueryRequest>,
) -> Result<Json<QueryResult>, ApiError> {
    // SQLの解析と実行...
}

4. その他の重要なファイル

ライブラリルート

lib.rs
// src/lib.rs
pub mod domain;
pub mod application;
pub mod infrastructure;
pub mod interface;

/// RustyDB version
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

/// Database result type
pub type Result<T> = std::result::Result<T, Error>;

/// Database error type
#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("SQL parsing error: {0}")]
    Parse(String),

    #[error("Schema error: {0}")]
    Schema(String),

    #[error("Execution error: {0}")]
    Execution(String),

    #[error("Storage error: {0}")]
    Storage(String),

    #[error("Internal error: {0}")]
    Internal(String),
}

エントリーポイント

main.rs
// src/main.rs
use tracing::info;
use rustydb::interface::api::{start_server, ServerConfig};
use rustydb::VERSION;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // ロガーを初期化
    tracing_subscriber::fmt::init();
    
    info!("Starting RustyDB v{}", VERSION);
    
    // サーバー設定(デフォルト:localhost:8080)
    let config = ServerConfig::default();
    
    // サーバーの起動
    start_server(config).await?;
    
    Ok(())
}

使用例 (examples/)

基本的な使用例

basic_usage.rs
// examples/basic_usage.rs
use rustydb::domain::entity::{ Table, Row};
use rustydb::domain::repository::TableRepository;
use rustydb::infrastructure::parser::SqlParser;
use rustydb::infrastructure::storage::MemoryStorage;
use rustydb::infrastructure::repository::MemoryTableRepository;
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // ストレージとリポジトリの初期化
    let storage = Arc::new(MemoryStorage::new());
    let repository = MemoryTableRepository::new(storage.clone());
    
    // SQLパーサーの初期化
    let parser = SqlParser::new();
    
    println!("=== RustyDB 基本動作チェック ===\n");
    
    // 1. テーブル作成
    println!("1. テーブルの作成");
    let create_table_sql = "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER, active BOOLEAN DEFAULT true)";
    println!("SQL: {}", create_table_sql);
    
    let parsed = parser.parse(create_table_sql)?;
    if let Some(rustydb::infrastructure::parser::ParsedStatement::CreateTable(stmt)) = parsed.first() {
        let mut table = Table::new(&stmt.table_name);
        for column in &stmt.columns {
            table.add_column(column.clone())?;
        }
        repository.create_table(&table).await?;
        println!("テーブル 'users' を作成しました\n");
    }
    
    // 2. データ挿入
    println!("2. データの挿入");
    let insert_sql = "INSERT INTO users (id, name, age, active) VALUES (1, 'Alice', 30, true), (2, 'Bob', 25, false), (3, 'Charlie', 35, true)";
    println!("SQL: {}", insert_sql);
    
    let parsed = parser.parse(insert_sql)?;
    if let Some(rustydb::infrastructure::parser::ParsedStatement::Insert(stmt)) = parsed.first() {
        for values in &stmt.values {
            let mut row = Row::new();
            for (i, value) in values.iter().enumerate() {
                if i < stmt.columns.len() {
                    row.set(stmt.columns[i].clone(), value.clone());
                }
            }
            repository.insert(&stmt.table_name, &row).await?;
        }
        println!("3行挿入しました\n");
    }
    
    // 3. データ取得
    println!("3. データの取得");
    let select_sql = "SELECT * FROM users";
    println!("SQL: {}", select_sql);
    
    let parsed = parser.parse(select_sql)?;
    if let Some(rustydb::infrastructure::parser::ParsedStatement::Select(stmt)) = parsed.first() {
        let cols = stmt.columns.as_ref().map_or(Vec::new(), |c| c.clone());
        let result = repository.select(&stmt.table_name, &cols, stmt.filter.as_ref()).await?;
        
        // 結果の表示
        println!("\n結果:");
        // ヘッダーの表示
        for col in &result.columns {
            print!("{}\t", col.name);
        }
        println!();
        
        // 行の表示
        for row in &result.rows {
            for col in &result.columns {
                match row.get(&col.name) {
                    Some(value) => print!("{}\t", value),
                    None => print!("NULL\t"),
                }
            }
            println!();
        }
        println!();
    }
    
    // 4. データ更新
    println!("4. データの更新");
    let update_sql = "UPDATE users SET age = 31 WHERE id = 1";
    println!("SQL: {}", update_sql);
    
    let parsed = parser.parse(update_sql)?;
    if let Some(rustydb::infrastructure::parser::ParsedStatement::Update(stmt)) = parsed.first() {
        let count = repository.update(&stmt.table_name, &stmt.updates, stmt.filter.as_ref()).await?;
        println!("{}行更新しました\n", count);
    }
    
    // 更新後のデータを表示
    println!("5. 更新後のデータ確認");
    let select_sql = "SELECT * FROM users WHERE id = 1";
    println!("SQL: {}", select_sql);
    
    let parsed = parser.parse(select_sql)?;
    if let Some(rustydb::infrastructure::parser::ParsedStatement::Select(stmt)) = parsed.first() {
        let cols = stmt.columns.as_ref().map_or(Vec::new(), |c| c.clone());
        let result = repository.select(&stmt.table_name, &cols, stmt.filter.as_ref()).await?;
        
        // 結果の表示
        println!("\n結果:");
        // ヘッダーの表示
        for col in &result.columns {
            print!("{}\t", col.name);
        }
        println!();
        
        // 行の表示
        for row in &result.rows {
            for col in &result.columns {
                match row.get(&col.name) {
                    Some(value) => print!("{}\t", value),
                    None => print!("NULL\t"),
                }
            }
            println!();
        }
        println!();
    }
    
    // 6. データ削除
    println!("6. データの削除");
    let delete_sql = "DELETE FROM users WHERE active = false";
    println!("SQL: {}", delete_sql);
    
    let parsed = parser.parse(delete_sql)?;
    if let Some(rustydb::infrastructure::parser::ParsedStatement::Delete(stmt)) = parsed.first() {
        let count = repository.delete(&stmt.table_name, stmt.filter.as_ref()).await?;
        println!("{}行削除しました\n", count);
    }
    
    // 削除後のデータを表示
    println!("7. 削除後のデータ確認");
    let select_sql = "SELECT * FROM users";
    println!("SQL: {}", select_sql);
    
    let parsed = parser.parse(select_sql)?;
    if let Some(rustydb::infrastructure::parser::ParsedStatement::Select(stmt)) = parsed.first() {
        let cols = stmt.columns.as_ref().map_or(Vec::new(), |c| c.clone());
        let result = repository.select(&stmt.table_name, &cols, stmt.filter.as_ref()).await?;
        
        // 結果の表示
        println!("\n結果:");
        // ヘッダーの表示
        for col in &result.columns {
            print!("{}\t", col.name);
        }
        println!();
        
        // 行の表示
        for row in &result.rows {
            for col in &result.columns {
                match row.get(&col.name) {
                    Some(value) => print!("{}\t", value),
                    None => print!("NULL\t"),
                }
            }
            println!();
        }
        println!();
    }
    
    println!("テスト完了!");
    
    Ok(())
}

APIクライアント例

api_client.rs
// examples/api_client.rs
use reqwest::Client;
use serde_json::{json, Value};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    let base_url = "http://localhost:8080";
    
    println!("=== RustyDB API テスト ===\n");
    
    // 1. ヘルスチェック
    println!("1. ヘルスチェック");
    let resp = client.get(format!("{}/health", base_url)).send().await?;
    println!("ステータス: {}", resp.status());
    println!("レスポンス: {}", resp.text().await?);
    println!();
    
    // 2. テーブル作成
    println!("2. テーブル作成");
    let create_query = json!({
        "sql": "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER, active BOOLEAN DEFAULT true)"
    });
    
    let resp = client.post(format!("{}/api/query", base_url))
        .json(&create_query)
        .send()
        .await?;
    
    println!("ステータス: {}", resp.status());
    println!("レスポンス: {}", resp.text().await?);
    println!();
    
    // 3. データ挿入
    println!("3. データ挿入");
    let insert_query = json!({
        "sql": "INSERT INTO users (id, name, age, active) VALUES (1, 'Alice', 30, true), (2, 'Bob', 25, false), (3, 'Charlie', 35, true)"
    });
    
    let resp = client.post(format!("{}/api/query", base_url))
        .json(&insert_query)
        .send()
        .await?;
    
    println!("ステータス: {}", resp.status());
    println!("レスポンス: {}", resp.text().await?);
    println!();
    
    // 4. データ取得
    println!("4. データ取得");
    let select_query = json!({
        "sql": "SELECT * FROM users"
    });
    
    let resp = client.post(format!("{}/api/query", base_url))
        .json(&select_query)
        .send()
        .await?;
    
    println!("ステータス: {}", resp.status());
    let result_text = resp.text().await?;
    println!("レスポンス: {}", result_text);
    
    // JSON形式のレスポンスをきれいに表示
    if let Ok(result) = serde_json::from_str::<Value>(&result_text) {
        println!("整形レスポンス: {}", serde_json::to_string_pretty(&result)?);
    }
    println!();
    
    // 5. テーブル一覧取得
    println!("5. テーブル一覧取得");
    let resp = client.get(format!("{}/api/tables", base_url))
        .send()
        .await?;
    
    println!("ステータス: {}", resp.status());
    println!("レスポンス: {}", resp.text().await?);
    println!();
    
    // 6. テーブル詳細取得
    println!("6. テーブル詳細取得");
    let resp = client.get(format!("{}/api/tables/users", base_url))
        .send()
        .await?;
    
    println!("ステータス: {}", resp.status());
    let result_text = resp.text().await?;
    println!("レスポンス: {}", result_text);
    
    // JSON形式のレスポンスをきれいに表示
    if let Ok(result) = serde_json::from_str::<Value>(&result_text) {
        println!("整形レスポンス: {}", serde_json::to_string_pretty(&result)?);
    }
    println!();
    
    // 7. データ更新
    println!("7. データ更新");
    let update_query = json!({
        "sql": "UPDATE users SET age = 31 WHERE id = 1"
    });
    
    let resp = client.post(format!("{}/api/query", base_url))
        .json(&update_query)
        .send()
        .await?;
    
    println!("ステータス: {}", resp.status());
    println!("レスポンス: {}", resp.text().await?);
    println!();
    
    // 8. 更新後のデータ確認
    println!("8. 更新後のデータ確認");
    let select_query = json!({
        "sql": "SELECT * FROM users WHERE id = 1"
    });
    
    let resp = client.post(format!("{}/api/query", base_url))
        .json(&select_query)
        .send()
        .await?;
    
    println!("ステータス: {}", resp.status());
    let result_text = resp.text().await?;
    println!("レスポンス: {}", result_text);
    
    // JSON形式のレスポンスをきれいに表示
    if let Ok(result) = serde_json::from_str::<Value>(&result_text) {
        println!("整形レスポンス: {}", serde_json::to_string_pretty(&result)?);
    }
    println!();
    
    println!("APIテスト完了!");
    
    Ok(())
}

まとめ

この実装では、クリーンアーキテクチャを採用することで、各レイヤーが明確に分離され、変更に強い設計になっています。ドメイン層でデータベースの基本概念を定義し、インフラストラクチャ層で具体的な実装を提供し、インターフェース層でユーザーとの対話を可能にしています。

現時点の実装はインメモリストレージのみをサポートしていますが、ドメインロジックを変更せずに、永続化ストレージやインデックス機能を追加することが可能です。また、RESTful APIを通じて、さまざまなクライアントからデータベースを操作できます。

次回は、B-Treeインデックスの実装とクエリ最適化に取り組み、より高速で効率的なデータベースを目指します。
見逃さないように僕のフォロー&いいねもお忘れなく🤗

リポジトリ

この連載のコードはこちらのGitHubリポジトリで公開しています:

https://github.com/kazuma0606/rustydb

実装を進める中で、質問やフィードバックがあれば、コメントやIssueでお気軽にお知らせください。
一緒にRustでデータベースを作る旅を楽しみましょう!

Discussion

ログインするとコメントできます