use crate::types::DataValue;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TableSchema {
pub name: String,
pub columns: Vec<ColumnDefinition>,
pub indexes: Vec<IndexDefinition>,
pub constraints: Vec<ConstraintDefinition>,
pub options: TableOptions,
pub version: u32,
pub created_at: Option<chrono::DateTime<chrono::Utc>>,
pub updated_at: Option<chrono::DateTime<chrono::Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ColumnDefinition {
pub name: String,
pub column_type: ColumnType,
pub nullable: bool,
pub default_value: Option<String>,
pub primary_key: bool,
pub auto_increment: bool,
pub unique: bool,
pub comment: Option<String>,
pub length: Option<u32>,
pub precision: Option<u32>,
pub scale: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ColumnType {
Integer,
BigInteger,
SmallInteger,
Float,
Double,
Decimal { precision: u32, scale: u32 },
String { length: Option<u32> },
Text,
LongText,
Boolean,
Date,
Time,
DateTime,
Timestamp,
Binary { length: Option<u32> },
Blob,
Json,
Uuid,
Enum { values: Vec<String> },
Custom { type_name: String },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct IndexDefinition {
pub name: String,
pub index_type: IndexType,
pub columns: Vec<String>,
pub unique: bool,
pub options: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum IndexType {
BTree,
Hash,
FullText,
Spatial,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ConstraintDefinition {
pub name: String,
pub constraint_type: ConstraintType,
pub columns: Vec<String>,
pub reference_table: Option<String>,
pub reference_columns: Option<Vec<String>>,
pub on_delete: Option<ReferentialAction>,
pub on_update: Option<ReferentialAction>,
pub check_condition: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ConstraintType {
PrimaryKey,
ForeignKey,
Unique,
Check,
NotNull,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ReferentialAction {
Cascade,
SetNull,
SetDefault,
Restrict,
NoAction,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TableOptions {
pub engine: Option<String>,
pub charset: Option<String>,
pub collation: Option<String>,
pub comment: Option<String>,
pub auto_increment: Option<u64>,
pub row_format: Option<String>,
pub extra_options: HashMap<String, String>,
}
impl Default for TableOptions {
fn default() -> Self {
Self {
engine: None,
charset: Some("utf8mb4".to_string()),
collation: Some("utf8mb4_unicode_ci".to_string()),
comment: None,
auto_increment: None,
row_format: None,
extra_options: HashMap::new(),
}
}
}
impl TableSchema {
pub fn new(name: String) -> Self {
Self {
name,
columns: Vec::new(),
indexes: Vec::new(),
constraints: Vec::new(),
options: TableOptions::default(),
version: 1,
created_at: Some(chrono::Utc::now()),
updated_at: Some(chrono::Utc::now()),
}
}
pub fn add_column(mut self, column: ColumnDefinition) -> Self {
self.columns.push(column);
self.updated_at = Some(chrono::Utc::now());
self
}
pub fn add_index(mut self, index: IndexDefinition) -> Self {
self.indexes.push(index);
self.updated_at = Some(chrono::Utc::now());
self
}
pub fn add_constraint(mut self, constraint: ConstraintDefinition) -> Self {
self.constraints.push(constraint);
self.updated_at = Some(chrono::Utc::now());
self
}
pub fn with_options(mut self, options: TableOptions) -> Self {
self.options = options;
self.updated_at = Some(chrono::Utc::now());
self
}
pub fn get_primary_key_columns(&self) -> Vec<&ColumnDefinition> {
self.columns.iter().filter(|col| col.primary_key).collect()
}
pub fn has_column(&self, column_name: &str) -> bool {
self.columns.iter().any(|col| col.name == column_name)
}
pub fn get_column(&self, column_name: &str) -> Option<&ColumnDefinition> {
self.columns.iter().find(|col| col.name == column_name)
}
pub fn has_index(&self, index_name: &str) -> bool {
self.indexes.iter().any(|idx| idx.name == index_name)
}
pub fn get_index(&self, index_name: &str) -> Option<&IndexDefinition> {
self.indexes.iter().find(|idx| idx.name == index_name)
}
pub fn validate(&self) -> Result<(), String> {
if self.columns.is_empty() {
return Err("表必须至少有一个列".to_string());
}
let mut column_names = std::collections::HashSet::new();
for column in &self.columns {
if !column_names.insert(&column.name) {
return Err(format!("列名 '{}' 重复", column.name));
}
}
let mut index_names = std::collections::HashSet::new();
for index in &self.indexes {
if !index_names.insert(&index.name) {
return Err(format!("索引名 '{}' 重复", index.name));
}
for column_name in &index.columns {
if !self.has_column(column_name) {
return Err(format!(
"索引 '{}' 引用的列 '{}' 不存在",
index.name, column_name
));
}
}
}
let mut constraint_names = std::collections::HashSet::new();
for constraint in &self.constraints {
if !constraint_names.insert(&constraint.name) {
return Err(format!("约束名 '{}' 重复", constraint.name));
}
for column_name in &constraint.columns {
if !self.has_column(column_name) {
return Err(format!(
"约束 '{}' 引用的列 '{}' 不存在",
constraint.name, column_name
));
}
}
}
Ok(())
}
pub fn infer_from_data(table_name: String, data: &HashMap<String, DataValue>) -> Self {
let mut columns = Vec::new();
let mut has_id_field = false;
for (field_name, field_value) in data {
if field_name == "id" {
has_id_field = true;
let column_type = match field_value {
DataValue::Null => ColumnType::BigInteger, _ => Self::infer_column_type(field_value),
};
columns.push(ColumnDefinition {
name: field_name.clone(),
column_type,
nullable: false, default_value: None,
primary_key: true,
auto_increment: matches!(field_value, DataValue::Int(_) | DataValue::Null), unique: true,
comment: Some("主键ID".to_string()),
length: None,
precision: None,
scale: None,
});
} else {
let column_type = Self::infer_column_type(field_value);
columns.push(ColumnDefinition {
name: field_name.clone(),
column_type,
nullable: matches!(field_value, DataValue::Null),
default_value: None,
primary_key: false,
auto_increment: false,
unique: false,
comment: None,
length: None,
precision: None,
scale: None,
});
}
}
if !has_id_field {
columns.insert(
0,
ColumnDefinition {
name: "id".to_string(),
column_type: ColumnType::BigInteger,
nullable: false,
default_value: None,
primary_key: true,
auto_increment: true,
unique: true,
comment: Some("主键ID".to_string()),
length: None,
precision: None,
scale: None,
},
);
}
Self {
name: table_name,
columns,
indexes: Vec::new(),
constraints: Vec::new(),
options: TableOptions::default(),
version: 1,
created_at: Some(chrono::Utc::now()),
updated_at: Some(chrono::Utc::now()),
}
}
fn infer_column_type(value: &DataValue) -> ColumnType {
match value {
DataValue::Null => ColumnType::String { length: Some(255) }, DataValue::Bool(_) => ColumnType::Boolean,
DataValue::Int(_) => ColumnType::BigInteger,
DataValue::UInt(_) => ColumnType::BigInteger,
DataValue::Float(_) => ColumnType::Double,
DataValue::String(s) => {
if s.len() > 65535 {
ColumnType::LongText
} else if s.len() > 255 {
ColumnType::Text
} else {
ColumnType::String { length: Some(255) }
}
}
DataValue::Bytes(_) => ColumnType::Blob,
DataValue::DateTime(_) => ColumnType::DateTime,
DataValue::DateTimeUTC(_) => ColumnType::DateTime,
DataValue::Uuid(_) => ColumnType::Uuid,
DataValue::Json(_) => ColumnType::Json,
DataValue::Array(_) => ColumnType::Json, DataValue::Object(_) => ColumnType::Json, }
}
}
impl ColumnDefinition {
pub fn new(name: String, column_type: ColumnType) -> Self {
Self {
name,
column_type,
nullable: true,
default_value: None,
primary_key: false,
auto_increment: false,
unique: false,
comment: None,
length: None,
precision: None,
scale: None,
}
}
pub fn primary_key(mut self) -> Self {
self.primary_key = true;
self.nullable = false;
self
}
pub fn not_null(mut self) -> Self {
self.nullable = false;
self
}
pub fn unique(mut self) -> Self {
self.unique = true;
self
}
pub fn auto_increment(mut self) -> Self {
self.auto_increment = true;
self.nullable = false;
self
}
pub fn default_value<T: ToString>(mut self, value: T) -> Self {
self.default_value = Some(value.to_string());
self
}
pub fn comment<T: ToString>(mut self, comment: T) -> Self {
self.comment = Some(comment.to_string());
self
}
}
impl IndexDefinition {
pub fn new(name: String, columns: Vec<String>) -> Self {
Self {
name,
index_type: IndexType::BTree,
columns,
unique: false,
options: HashMap::new(),
}
}
pub fn unique(mut self) -> Self {
self.unique = true;
self
}
pub fn index_type(mut self, index_type: IndexType) -> Self {
self.index_type = index_type;
self
}
pub fn option<K: ToString, V: ToString>(mut self, key: K, value: V) -> Self {
self.options.insert(key.to_string(), value.to_string());
self
}
}
impl ConstraintDefinition {
pub fn primary_key(name: String, columns: Vec<String>) -> Self {
Self {
name,
constraint_type: ConstraintType::PrimaryKey,
columns,
reference_table: None,
reference_columns: None,
on_delete: None,
on_update: None,
check_condition: None,
}
}
pub fn foreign_key(
name: String,
columns: Vec<String>,
reference_table: String,
reference_columns: Vec<String>,
) -> Self {
Self {
name,
constraint_type: ConstraintType::ForeignKey,
columns,
reference_table: Some(reference_table),
reference_columns: Some(reference_columns),
on_delete: Some(ReferentialAction::Restrict),
on_update: Some(ReferentialAction::Restrict),
check_condition: None,
}
}
pub fn unique(name: String, columns: Vec<String>) -> Self {
Self {
name,
constraint_type: ConstraintType::Unique,
columns,
reference_table: None,
reference_columns: None,
on_delete: None,
on_update: None,
check_condition: None,
}
}
pub fn check(name: String, condition: String) -> Self {
Self {
name,
constraint_type: ConstraintType::Check,
columns: Vec::new(),
reference_table: None,
reference_columns: None,
on_delete: None,
on_update: None,
check_condition: Some(condition),
}
}
pub fn on_delete(mut self, action: ReferentialAction) -> Self {
self.on_delete = Some(action);
self
}
pub fn on_update(mut self, action: ReferentialAction) -> Self {
self.on_update = Some(action);
self
}
}