Rustでデータベースを設計しよう2(insert, create table)

### データ挿入時のエラー処理
impl Tableでinsert_rowとして、カラムにマッチするデータのみ挿入可能とする。

impl Table {
    fn insert_row(&mut self, row: Vec<Value>) -> Result<(), String> {
        if row.len() != self.columns.columns.len() {
            return Err(format!(
                "Row length({}) does not match column count({}",
                row.len(),
                self.columns.columns.len()
            ));
        }

        for (i, (value, column)) in row.iter().zip(&self.columns.columns).enumerate() {
            let expected = column.constraint.to_lowercase();
            let actual = match value {
                Value::Integer(_) => "integer",
                Value::Float(_) => "float",
                Value::Text(_) => "text",
                Value::Boolean(_) => "boolean",
                Value::Date(_) => "date",
                Value::DateTime(_) => "datetime",
                Value::Time(_) => "time",
                Value::Null => "null",
                Value::Binary(_) => "binary",
                Value::UUID(_) => "uuid",
                Value::Json(_) => "json",
                Value::Decimal(_) => "decimal",
            };

            if expected != actual {
                return Err(format!(
                    "Type mismatch at column {} ({}): expected {}, got {}",
                    i, column.name, expected, actual
                ));
            }
        }

        self.rows.push(row);
        Ok(())
    }
}


fn main() {
    let columns = Columns {
        columns: vec![
            Column {
                name: "id".to_string(),
                constraint: "integer".to_string(),
            },
            Column {
                name: "name".to_string(),
                constraint: "text".to_string(),
            },
            Column {
                name: "age".to_string(),
                constraint: "integer".to_string(),
            },
        ]
    };

    let mut table = Table {
        name: "users".to_string(),
        columns: columns,
        rows: vec![],
    };

    let result = table.insert_row(vec![
        Value::Integer(1),
        Value::Text("Alice".to_string()),
        Value::Integer(30),
    ]);
    println!("{:?}", table);
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.50s
Running `target/debug/rds`
Table { name: “users”, columns: Columns { columns: [Column { name: “id”, constraint: “integer” }, Column { name: “name”, constraint: “text” }, Column { name: “age”, constraint: “integer” }] }, rows: [[Integer(1), Text(“Alice”), Integer(30)]] }

### テーブル作成時のカラムの指定
カラムタイプもenumで指定し、その中のtypeしか設定できないようにする。

#[derive(Debug, Clone, PartialEq)]
enum ColumnType {
    Integer,
    Float,
    Text,
    Boolean,
    Date,
    DateTime,
    Time,
    Null,
    Binary,
    UUID,
    Json,
    Decimal,
}

すると、impl Tableのinsert_rowも以下のようになる。

        for (i, (value, column)) in row.iter().zip(self.columns.columns.iter()).enumerate() {
            if !type_matches(value, &column.constraint) {
                return Err(format!(
                    "Type mismatch at column {} ({}): expected {:?}, got {:?}",
                    i, column.name, column.constraint, value
                ));
            }
        }

//

fn type_matches(value: &Value, column_type: &ColumnType) -> bool {
    match(value, column_type) {
        (Value::Integer(_), ColumnType::Integer) => true,
        (Value::Float(_), ColumnType::Float) => true,
        (Value::Text(_), ColumnType::Text) => true,
        (Value::Boolean(_), ColumnType::Boolean) => true,
        (Value::Date(_), ColumnType::Date) => true,
        (Value::DateTime(_), ColumnType::DateTime) => true,
        (Value::Time(_), ColumnType::Time) => true,
        (Value::Null, ColumnType::Null) => true,
        (Value::Binary(_), ColumnType::Binary) => true,
        (Value::UUID(_), ColumnType::UUID) => true,
        (Value::Json(_), ColumnType::Json) => true,
        (Value::Decimal(_), ColumnType::Decimal) => true,
        _ => false,
    }
}

実際の使用例

fn main() {
    let columns = Columns {
        columns: vec![
            Column {
                name: "id".to_string(),
                constraint: ColumnType::Integer,
            },
            Column {
                name: "name".to_string(),
                constraint: ColumnType::Text,
            },
            Column {
                name: "age".to_string(),
                constraint: ColumnType::Integer,
            },
        ]
    };

    let mut table = Table {
        name: "users".to_string(),
        columns: columns,
        rows: vec![],
    };

    let result = table.insert_row(vec![
        Value::Integer(1),
        Value::Text("Alice".to_string()),
        Value::Integer(30),
    ]);

    let result = table.insert_row(vec![
        Value::Integer(2),
        Value::Text("Bob".to_string()),
        Value::Integer(25),
    ]);
    println!("{:?}", table);
}

Table { name: “users”, columns: Columns { columns: [Column { name: “id”, constraint: Integer }, Column { name: “name”, constraint: Text }, Column { name: “age”, constraint: Integer }] }, rows: [[Integer(1), Text(“Alice”), Integer(30)], [Integer(2), Text(“Bob”), Integer(25)]] }

### 検索機能
colum名と値を指定して、そのレコードを抽出したい

    fn find_rows_by_column_value(&self, column_name: &str, target: &Value) -> Vec<&Vec<Value>> {
        let index = self.columns.columns.iter().position(|col| col.name == column_name);

        if let Some(idx) = index {
            self.rows
                .iter()
                .filter(|row| row.get(idx) == Some(target))
                .collect()
        } else {
            vec![]
        }
    }

//

    let matching_rows = table.find_rows_by_column_value("id", &Value::Integer(2));

    for row in matching_rows {
        println!("Matched row: {:?}", row);
    }

Matched row: [Integer(2), Text(“Bob”), Integer(25)]