Delete src/core/table.rs
This commit is contained in:
parent
dce895165b
commit
b7d377601a
@ -1,938 +0,0 @@
|
||||
//! Модуль управления таблицами flusql
|
||||
//!
|
||||
//! Этот модуль реализует функциональность таблиц базы данных:
|
||||
//! - Создание и загрузку таблиц
|
||||
//! - Вставку и выборку данных
|
||||
//! - Экспорт/импорт данных в формате CSV
|
||||
//! - Управление индексами
|
||||
//! - Валидацию данных по схеме таблицы
|
||||
//! - Поддержку внешних ключей, проверок и триггеров
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::parser::sql::{Value, WhereClause, Operator, Expression};
|
||||
use crate::core::index::Index;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Схема таблицы
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TableSchema {
|
||||
/// Список столбцов таблицы
|
||||
pub columns: Vec<ColumnSchema>,
|
||||
|
||||
/// Имя столбца, являющегося первичным ключом
|
||||
pub primary_key: Option<String>,
|
||||
|
||||
/// Список индексированных столбцов
|
||||
pub indexes: Vec<String>,
|
||||
|
||||
/// Внешние ключи
|
||||
pub foreign_keys: Vec<ForeignKeyDef>,
|
||||
|
||||
/// Проверочные ограничения
|
||||
pub checks: Vec<String>,
|
||||
}
|
||||
|
||||
/// Схема внешнего ключа
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ForeignKeyDef {
|
||||
pub name: String,
|
||||
pub local_columns: Vec<String>,
|
||||
pub referenced_table: String,
|
||||
pub referenced_columns: Vec<String>,
|
||||
pub on_delete: String,
|
||||
pub on_update: String,
|
||||
}
|
||||
|
||||
/// Схема столбца
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ColumnSchema {
|
||||
/// Имя столбца
|
||||
pub name: String,
|
||||
|
||||
/// Тип данных столбца
|
||||
pub data_type: DataType,
|
||||
|
||||
/// Может ли столбец содержать NULL значения
|
||||
pub nullable: bool,
|
||||
|
||||
/// Должны ли значения в столбце быть уникальными
|
||||
pub unique: bool,
|
||||
}
|
||||
|
||||
/// Тип данных
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum DataType {
|
||||
/// Целочисленный тип
|
||||
Integer,
|
||||
|
||||
/// Текстовый тип
|
||||
Text,
|
||||
|
||||
/// Логический тип
|
||||
Boolean,
|
||||
|
||||
/// Число с плавающей точкой
|
||||
Float,
|
||||
}
|
||||
|
||||
/// Запись в таблице
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Record {
|
||||
/// Уникальный идентификатор записи
|
||||
pub id: u64,
|
||||
|
||||
/// Значения записи по столбцам
|
||||
pub values: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
/// Таблица базы данных
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Table {
|
||||
/// Имя таблицы
|
||||
name: String,
|
||||
|
||||
/// Схема таблицы
|
||||
schema: TableSchema,
|
||||
|
||||
/// Директория для хранения данных таблицы
|
||||
data_dir: String,
|
||||
|
||||
/// Список записей таблицы (для обратной совместимости)
|
||||
records: Vec<Record>,
|
||||
|
||||
/// Следующий доступный ID для новой записи
|
||||
next_id: u64,
|
||||
|
||||
/// Первичный индекс (если есть первичный ключ)
|
||||
primary_index: Option<Index>,
|
||||
|
||||
/// Вторичные индексы
|
||||
secondary_indexes: HashMap<String, Index>,
|
||||
}
|
||||
|
||||
impl Table {
|
||||
/// Создание новой таблицы
|
||||
pub fn new(name: &str, schema: TableSchema, data_dir: &str) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
schema: schema.clone(),
|
||||
data_dir: data_dir.to_string(),
|
||||
records: Vec::new(),
|
||||
next_id: 1,
|
||||
primary_index: if schema.primary_key.is_some() {
|
||||
Some(Index::new(&schema.primary_key.clone().unwrap()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
secondary_indexes: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Загрузка таблицы из файла
|
||||
pub fn load(data_dir: &str, name: &str) -> Result<Self, TableError> {
|
||||
let schema_path = format!("{}/{}/schema.json", data_dir, name);
|
||||
let data_path = format!("{}/{}/data.json", data_dir, name);
|
||||
|
||||
if !Path::new(&schema_path).exists() {
|
||||
return Err(TableError::NotFound(name.to_string()));
|
||||
}
|
||||
|
||||
// Загрузка схемы
|
||||
let schema_content = fs::read_to_string(&schema_path)
|
||||
.map_err(|e| TableError::IoError(e))?;
|
||||
let schema: TableSchema = serde_json::from_str(&schema_content)
|
||||
.map_err(|e| TableError::ParseError(e))?;
|
||||
|
||||
// Загрузка данных
|
||||
let mut table = Self::new(name, schema, data_dir);
|
||||
|
||||
if Path::new(&data_path).exists() {
|
||||
let data_content = fs::read_to_string(&data_path)
|
||||
.map_err(|e| TableError::IoError(e))?;
|
||||
let records: Vec<Record> = serde_json::from_str(&data_content)
|
||||
.map_err(|e| TableError::ParseError(e))?;
|
||||
|
||||
table.records = records;
|
||||
table.next_id = table.records.iter().map(|r| r.id).max().unwrap_or(0) + 1;
|
||||
|
||||
// Восстановление индексов
|
||||
table.rebuild_indexes();
|
||||
}
|
||||
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
/// Сохранение таблицы
|
||||
pub fn save(&self) -> Result<(), TableError> {
|
||||
let table_dir = format!("{}/{}", self.data_dir, self.name);
|
||||
|
||||
if !Path::new(&table_dir).exists() {
|
||||
fs::create_dir_all(&table_dir)
|
||||
.map_err(|e| TableError::IoError(e))?;
|
||||
}
|
||||
|
||||
// Сохранение схемы
|
||||
let schema_path = format!("{}/schema.json", table_dir);
|
||||
let schema_content = serde_json::to_string_pretty(&self.schema)
|
||||
.map_err(|e| TableError::SerializeError(e))?;
|
||||
fs::write(schema_path, schema_content)
|
||||
.map_err(|e| TableError::IoError(e))?;
|
||||
|
||||
// Сохранение данных
|
||||
let data_path = format!("{}/data.json", table_dir);
|
||||
let data_content = serde_json::to_string_pretty(&self.records)
|
||||
.map_err(|e| TableError::SerializeError(e))?;
|
||||
fs::write(data_path, data_content)
|
||||
.map_err(|e| TableError::IoError(e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Вставка записи с автоматическим добавлением timestamp
|
||||
pub fn insert(&mut self, values: HashMap<String, Value>) -> Result<u64, TableError> {
|
||||
// Валидация данных
|
||||
self.validate_record(&values)?;
|
||||
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
|
||||
let mut record = Record { id, values };
|
||||
|
||||
// Применение значений по умолчанию и добавление timestamp
|
||||
for column in &self.schema.columns {
|
||||
if !record.values.contains_key(&column.name) {
|
||||
if column.name == "timestamp" {
|
||||
// Автоматическое добавление текущего времени
|
||||
let timestamp = chrono::Local::now().to_rfc3339();
|
||||
record.values.insert(column.name.clone(), Value::Text(timestamp));
|
||||
} else if column.nullable {
|
||||
record.values.insert(column.name.clone(), Value::Null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка уникальности
|
||||
for column in &self.schema.columns {
|
||||
if column.unique && column.name != "timestamp" {
|
||||
if let Some(value) = record.values.get(&column.name) {
|
||||
for existing_record in &self.records {
|
||||
if let Some(existing_value) = existing_record.values.get(&column.name) {
|
||||
if values_equal(value, existing_value) {
|
||||
return Err(TableError::DuplicateValue(
|
||||
column.name.clone(),
|
||||
format!("{:?}", value)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка внешних ключей
|
||||
self.validate_foreign_keys(&record)?;
|
||||
|
||||
// Проверка ограничений CHECK
|
||||
self.validate_checks(&record)?;
|
||||
|
||||
self.records.push(record.clone());
|
||||
|
||||
// Обновление индексов
|
||||
self.update_indexes(&record);
|
||||
|
||||
// Выполнение триггеров (если есть)
|
||||
self.execute_triggers("INSERT", &record);
|
||||
|
||||
// Используем функцию логирования вместо макроса
|
||||
crate::utils::logger::log_debug(&format!("Inserted record with id {} into table '{}'", id, self.name));
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
/// Выборка записей с сохранением порядка столбцов
|
||||
pub fn select(&self, columns: &[String], where_clause: Option<&WhereClause>, limit: Option<usize>,
|
||||
order_by: Option<Vec<(String, bool)>>, group_by: Option<Vec<String>>,
|
||||
joins: Option<Vec<crate::parser::sql::JoinClause>>)
|
||||
-> Result<Vec<HashMap<String, Value>>, TableError> {
|
||||
|
||||
let mut results = Vec::new();
|
||||
|
||||
// Определяем порядок столбцов для результата
|
||||
let result_columns = if columns.len() == 1 && columns[0] == "*" {
|
||||
// Если SELECT *, используем порядок столбцов из схемы
|
||||
self.schema.columns.iter().map(|c| c.name.clone()).collect::<Vec<_>>()
|
||||
} else {
|
||||
// Иначе используем порядок из запроса
|
||||
columns.to_vec()
|
||||
};
|
||||
|
||||
// Используем индекс для поиска, если возможно
|
||||
if let Some(clause) = where_clause {
|
||||
let column_name = self.extract_column_from_where(clause);
|
||||
if let Some(index) = self.get_index_for_column(&column_name) {
|
||||
let clause_value = self.extract_value_from_where(clause);
|
||||
if let Some(clause_value) = clause_value {
|
||||
if let Some(record_ids) = index.search(&clause_value) {
|
||||
for id in record_ids {
|
||||
if let Some(record) = self.records.iter().find(|r| r.id == id) {
|
||||
if self.matches_where(record, clause) {
|
||||
results.push(self.project_record(record, &result_columns));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Полный перебор
|
||||
for record in &self.records {
|
||||
if self.matches_where(record, clause) {
|
||||
results.push(self.project_record(record, &result_columns));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Без условия WHERE
|
||||
for record in &self.records {
|
||||
results.push(self.project_record(record, &result_columns));
|
||||
}
|
||||
}
|
||||
|
||||
// Применяем сортировку
|
||||
if let Some(order_by) = order_by {
|
||||
self.sort_results(&mut results, &order_by);
|
||||
}
|
||||
|
||||
// Применяем группировку
|
||||
if let Some(group_by) = group_by {
|
||||
results = self.group_results(results, &group_by);
|
||||
}
|
||||
|
||||
// Применяем объединения
|
||||
if let Some(joins) = joins {
|
||||
// Упрощенная реализация JOIN
|
||||
for join in joins {
|
||||
// В реальной реализации здесь нужно выполнять JOIN
|
||||
let _ = join;
|
||||
}
|
||||
}
|
||||
|
||||
// Применяем LIMIT
|
||||
if let Some(limit) = limit {
|
||||
results.truncate(limit);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Обновление записей
|
||||
pub fn update(&mut self, updates: HashMap<String, Value>, where_clause: Option<WhereClause>)
|
||||
-> Result<usize, TableError> {
|
||||
let mut count = 0;
|
||||
|
||||
// Сначала собираем ID записей, которые нужно обновить
|
||||
let mut ids_to_update = Vec::new();
|
||||
let mut record_data = Vec::new();
|
||||
|
||||
for record in &self.records {
|
||||
let mut matches = true;
|
||||
|
||||
if let Some(clause) = &where_clause {
|
||||
if !self.matches_where(record, clause) {
|
||||
matches = false;
|
||||
}
|
||||
}
|
||||
|
||||
if matches {
|
||||
ids_to_update.push(record.id);
|
||||
// Сохраняем копию значений для проверки
|
||||
let mut new_values = record.values.clone();
|
||||
for (column, value) in &updates {
|
||||
new_values.insert(column.clone(), value.clone());
|
||||
}
|
||||
record_data.push((record.id, new_values, record.values.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем валидность всех обновлений
|
||||
for (id, new_values, _) in &record_data {
|
||||
self.validate_record(new_values)?;
|
||||
}
|
||||
|
||||
// Применяем обновления
|
||||
for (id, new_values, old_values) in record_data {
|
||||
// Находим запись по ID и обновляем ее
|
||||
if let Some(record_idx) = self.records.iter().position(|r| r.id == id) {
|
||||
// Выполнение триггеров BEFORE UPDATE
|
||||
let record_before = &self.records[record_idx];
|
||||
self.execute_triggers("BEFORE_UPDATE", record_before);
|
||||
|
||||
// Сохраняем старые значения для индексов
|
||||
let old_values_for_indexes: HashMap<String, Value> = updates.keys()
|
||||
.filter_map(|col| old_values.get(col).map(|v| (col.clone(), v.clone())))
|
||||
.collect();
|
||||
|
||||
// Применяем обновления к записи
|
||||
let record = &mut self.records[record_idx];
|
||||
for (column, value) in &updates {
|
||||
record.values.insert(column.clone(), value.clone());
|
||||
}
|
||||
|
||||
// Обновляем индексы
|
||||
let record_clone = record.clone();
|
||||
self.update_indexes_for_record(id, &updates, &old_values_for_indexes);
|
||||
|
||||
// Выполнение триггеров AFTER UPDATE
|
||||
self.execute_triggers("AFTER_UPDATE", &record_clone);
|
||||
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
crate::utils::logger::log_debug(&format!("Updated {} records in table '{}'", count, self.name));
|
||||
}
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
/// Удаление записей
|
||||
pub fn delete(&mut self, where_clause: Option<WhereClause>) -> Result<usize, TableError> {
|
||||
let mut count = 0;
|
||||
let mut to_remove = Vec::new();
|
||||
|
||||
for (i, record) in self.records.iter().enumerate() {
|
||||
let mut matches = true;
|
||||
|
||||
if let Some(clause) = &where_clause {
|
||||
if !self.matches_where(record, clause) {
|
||||
matches = false;
|
||||
}
|
||||
}
|
||||
|
||||
if matches {
|
||||
// Выполнение триггеров BEFORE DELETE
|
||||
self.execute_triggers("BEFORE_DELETE", record);
|
||||
|
||||
// Проверка внешних ключей (ограничение удаления)
|
||||
self.check_foreign_key_constraints(record)?;
|
||||
|
||||
to_remove.push((i, record.clone()));
|
||||
|
||||
// Выполнение триггеров AFTER DELETE
|
||||
self.execute_triggers("AFTER_DELETE", record);
|
||||
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Удаляем записи в обратном порядке
|
||||
for (i, record) in to_remove.into_iter().rev() {
|
||||
self.records.remove(i);
|
||||
self.remove_from_indexes(&record);
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
crate::utils::logger::log_debug(&format!("Deleted {} records from table '{}'", count, self.name));
|
||||
}
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
/// Экспорт в CSV
|
||||
pub fn export_csv(&self, file_path: &str) -> Result<(), TableError> {
|
||||
use csv::Writer;
|
||||
|
||||
let mut wtr = Writer::from_path(file_path)
|
||||
.map_err(|e| TableError::IoError(std::io::Error::new(
|
||||
std::io::ErrorKind::Other, e.to_string()
|
||||
)))?;
|
||||
|
||||
// Записываем заголовки в порядке из схемы
|
||||
let headers: Vec<&str> = self.schema.columns.iter()
|
||||
.map(|c| c.name.as_str())
|
||||
.collect();
|
||||
wtr.write_record(&headers)
|
||||
.map_err(|e| TableError::ExportError(e.to_string()))?;
|
||||
|
||||
// Записываем данные
|
||||
for record in &self.records {
|
||||
let mut row = Vec::new();
|
||||
for column in &self.schema.columns {
|
||||
// Используем метод to_string из модуля parser::sql
|
||||
let value = record.values.get(&column.name)
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_else(|| "NULL".to_string());
|
||||
row.push(value);
|
||||
}
|
||||
wtr.write_record(&row)
|
||||
.map_err(|e| TableError::ExportError(e.to_string()))?;
|
||||
}
|
||||
|
||||
wtr.flush()
|
||||
.map_err(|e| TableError::IoError(std::io::Error::new(
|
||||
std::io::ErrorKind::Other, e.to_string()
|
||||
)))?;
|
||||
|
||||
// Используем функцию логирования вместо макроса
|
||||
crate::utils::logger::log_info(&format!("Exported table '{}' to '{}'", self.name, file_path));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Импорт из CSV
|
||||
pub fn import_csv(&mut self, file_path: &str) -> Result<usize, TableError> {
|
||||
use csv::Reader;
|
||||
|
||||
let mut rdr = Reader::from_path(file_path)
|
||||
.map_err(|e| TableError::IoError(std::io::Error::new(
|
||||
std::io::ErrorKind::Other, e.to_string()
|
||||
)))?;
|
||||
|
||||
let headers = rdr.headers()
|
||||
.map_err(|e| TableError::ImportError(e.to_string()))?
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut count = 0;
|
||||
for result in rdr.records() {
|
||||
let record = result
|
||||
.map_err(|e| TableError::ImportError(e.to_string()))?;
|
||||
|
||||
let mut values = HashMap::new();
|
||||
for (i, field) in record.iter().enumerate() {
|
||||
if i < headers.len() {
|
||||
let value = if field == "NULL" {
|
||||
Value::Null
|
||||
} else if let Ok(int_val) = field.parse::<i64>() {
|
||||
Value::Integer(int_val)
|
||||
} else if let Ok(float_val) = field.parse::<f64>() {
|
||||
Value::Float(float_val)
|
||||
} else if field == "TRUE" {
|
||||
Value::Boolean(true)
|
||||
} else if field == "FALSE" {
|
||||
Value::Boolean(false)
|
||||
} else {
|
||||
Value::Text(field.to_string())
|
||||
};
|
||||
|
||||
values.insert(headers[i].clone(), value);
|
||||
}
|
||||
}
|
||||
|
||||
// Автоматически добавляем timestamp если его нет
|
||||
if !values.contains_key("timestamp") {
|
||||
let timestamp = chrono::Local::now().to_rfc3339();
|
||||
values.insert("timestamp".to_string(), Value::Text(timestamp));
|
||||
}
|
||||
|
||||
self.insert(values)?;
|
||||
count += 1;
|
||||
}
|
||||
|
||||
// Используем функцию логирования вместо макроса
|
||||
crate::utils::logger::log_info(&format!("Imported {} records into table '{}' from '{}'", count, self.name, file_path));
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
/// Получение схемы таблицы
|
||||
pub fn get_schema(&self) -> TableSchema {
|
||||
self.schema.clone()
|
||||
}
|
||||
|
||||
/// Извлечение имени столбца из условия WHERE
|
||||
fn extract_column_from_where(&self, clause: &WhereClause) -> String {
|
||||
match &clause.left {
|
||||
Expression::Column(name) => name.clone(),
|
||||
_ => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Извлечение значения из условия WHERE
|
||||
fn extract_value_from_where(&self, clause: &WhereClause) -> Option<Value> {
|
||||
match &clause.right {
|
||||
Some(Expression::Value(value)) => Some(value.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Валидация записи
|
||||
fn validate_record(&self, values: &HashMap<String, Value>) -> Result<(), TableError> {
|
||||
for column in &self.schema.columns {
|
||||
if let Some(value) = values.get(&column.name) {
|
||||
// Пропускаем валидацию timestamp если он автоматический
|
||||
if column.name == "timestamp" {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Проверка типа данных
|
||||
match (&column.data_type, value) {
|
||||
(DataType::Integer, Value::Integer(_)) => {},
|
||||
(DataType::Text, Value::Text(_)) => {},
|
||||
(DataType::Boolean, Value::Boolean(_)) => {},
|
||||
(DataType::Float, Value::Float(_)) => {},
|
||||
(DataType::Float, Value::Integer(i)) => {
|
||||
// Разрешаем целые числа для float
|
||||
let _ = i;
|
||||
},
|
||||
_ => {
|
||||
return Err(TableError::TypeMismatch(
|
||||
column.name.clone(),
|
||||
format!("{:?}", column.data_type),
|
||||
format!("{:?}", value)
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if !column.nullable && column.name != "timestamp" {
|
||||
return Err(TableError::NotNullViolation(column.name.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Проверка внешних ключей
|
||||
fn validate_foreign_keys(&self, record: &Record) -> Result<(), TableError> {
|
||||
// Упрощенная реализация
|
||||
// В реальной системе здесь нужно проверять ссылочную целостность
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Проверка ограничений CHECK
|
||||
fn validate_checks(&self, record: &Record) -> Result<(), TableError> {
|
||||
// Упрощенная реализация
|
||||
// В реальной системе здесь нужно проверять CHECK ограничения
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Проверка ограничений внешних ключей при удалении
|
||||
fn check_foreign_key_constraints(&self, record: &Record) -> Result<(), TableError> {
|
||||
// Упрощенная реализация
|
||||
// В реальной системе здесь нужно проверять, что удаление не нарушает
|
||||
// ссылочную целостность в других таблицах
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Выполнение триггеров
|
||||
fn execute_triggers(&self, event: &str, record: &Record) {
|
||||
// Упрощенная реализация
|
||||
// В реальной системе здесь нужно выполнять зарегистрированные триггеры
|
||||
crate::utils::logger::log_debug(&format!("Trigger {} executed for record {} in table '{}'",
|
||||
event, record.id, self.name));
|
||||
}
|
||||
|
||||
/// Сортировка результатов
|
||||
fn sort_results(&self, results: &mut Vec<HashMap<String, Value>>, order_by: &[(String, bool)]) {
|
||||
results.sort_by(|a, b| {
|
||||
for (column, ascending) in order_by {
|
||||
let a_val = a.get(column);
|
||||
let b_val = b.get(column);
|
||||
|
||||
match (a_val, b_val) {
|
||||
(Some(a), Some(b)) => {
|
||||
let cmp = self.compare_values(a, b);
|
||||
if cmp != std::cmp::Ordering::Equal {
|
||||
return if *ascending { cmp } else { cmp.reverse() };
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
std::cmp::Ordering::Equal
|
||||
});
|
||||
}
|
||||
|
||||
/// Группировка результатов
|
||||
fn group_results(&self, results: Vec<HashMap<String, Value>>, group_by: &[String])
|
||||
-> Vec<HashMap<String, Value>> {
|
||||
// Упрощенная реализация группировки
|
||||
let mut grouped = Vec::new();
|
||||
let mut groups = HashMap::new();
|
||||
|
||||
for row in results {
|
||||
let key: Vec<String> = group_by.iter()
|
||||
.filter_map(|col| row.get(col).map(|v| v.to_string()))
|
||||
.collect();
|
||||
let key_str = key.join("|");
|
||||
|
||||
groups.entry(key_str).or_insert_with(Vec::new).push(row);
|
||||
}
|
||||
|
||||
for (_, rows) in groups {
|
||||
// Для упрощения берем первую строку из каждой группы
|
||||
if let Some(first_row) = rows.into_iter().next() {
|
||||
grouped.push(first_row);
|
||||
}
|
||||
}
|
||||
|
||||
grouped
|
||||
}
|
||||
|
||||
/// Сравнение значений для сортировки
|
||||
fn compare_values(&self, a: &Value, b: &Value) -> std::cmp::Ordering {
|
||||
match (a, b) {
|
||||
(Value::Integer(a), Value::Integer(b)) => a.cmp(b),
|
||||
(Value::Float(a), Value::Float(b)) => {
|
||||
if a < b {
|
||||
std::cmp::Ordering::Less
|
||||
} else if a > b {
|
||||
std::cmp::Ordering::Greater
|
||||
} else {
|
||||
std::cmp::Ordering::Equal
|
||||
}
|
||||
},
|
||||
(Value::Text(a), Value::Text(b)) => a.cmp(b),
|
||||
(Value::Boolean(a), Value::Boolean(b)) => a.cmp(b),
|
||||
(Value::Null, Value::Null) => std::cmp::Ordering::Equal,
|
||||
(Value::Null, _) => std::cmp::Ordering::Less,
|
||||
(_, Value::Null) => std::cmp::Ordering::Greater,
|
||||
_ => std::cmp::Ordering::Equal,
|
||||
}
|
||||
}
|
||||
|
||||
/// Проверка условия WHERE
|
||||
fn matches_where(&self, record: &Record, clause: &WhereClause) -> bool {
|
||||
// В новой структуре WhereClause нет полей column и value
|
||||
// Используем временную реализацию для совместимости
|
||||
let column_name = match &clause.left {
|
||||
Expression::Column(name) => name,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
let clause_value = match &clause.right {
|
||||
Some(Expression::Value(value)) => value,
|
||||
None => {
|
||||
// Обработка IS NULL и IS NOT NULL
|
||||
return match &clause.operator {
|
||||
Operator::IsNull => {
|
||||
match record.values.get(column_name) {
|
||||
Some(value) => matches!(value, Value::Null),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
Operator::IsNotNull => {
|
||||
match record.values.get(column_name) {
|
||||
Some(value) => !matches!(value, Value::Null),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
match record.values.get(column_name) {
|
||||
Some(value) => match &clause.operator {
|
||||
Operator::Eq => values_equal(value, clause_value),
|
||||
Operator::Ne => !values_equal(value, clause_value),
|
||||
Operator::Gt => value_gt(value, clause_value),
|
||||
Operator::Lt => value_lt(value, clause_value),
|
||||
Operator::Ge => values_equal(value, clause_value) || value_gt(value, clause_value),
|
||||
Operator::Le => values_equal(value, clause_value) || value_lt(value, clause_value),
|
||||
Operator::Like => {
|
||||
if let (Value::Text(s), Value::Text(pattern)) = (value, clause_value) {
|
||||
// Простая реализация LIKE
|
||||
pattern == "%" || s.contains(pattern.trim_matches('%'))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false, // Другие операторы пока не поддерживаются
|
||||
},
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Проекция записи с сохранением порядка столбцов
|
||||
fn project_record(&self, record: &Record, columns: &[String]) -> HashMap<String, Value> {
|
||||
let mut result = HashMap::new();
|
||||
|
||||
// Добавляем значения в заданном порядке столбцов
|
||||
for column_name in columns {
|
||||
if let Some(value) = record.values.get(column_name) {
|
||||
result.insert(column_name.clone(), value.clone());
|
||||
} else {
|
||||
// Если столбец не существует в записи, добавляем NULL
|
||||
result.insert(column_name.clone(), Value::Null);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Получение индекса для столбца
|
||||
fn get_index_for_column(&self, column: &str) -> Option<&Index> {
|
||||
if Some(column) == self.schema.primary_key.as_deref() {
|
||||
self.primary_index.as_ref()
|
||||
} else {
|
||||
self.secondary_indexes.get(column)
|
||||
}
|
||||
}
|
||||
|
||||
/// Перестроение индексов
|
||||
fn rebuild_indexes(&mut self) {
|
||||
// Перестраиваем первичный индекс
|
||||
if let Some(pk) = &self.schema.primary_key {
|
||||
let mut index = Index::new(pk);
|
||||
for record in &self.records {
|
||||
if let Some(value) = record.values.get(pk) {
|
||||
index.insert(value.clone(), record.id);
|
||||
}
|
||||
}
|
||||
self.primary_index = Some(index);
|
||||
}
|
||||
|
||||
// Перестраиваем вторичные индексы
|
||||
for index_name in &self.schema.indexes {
|
||||
let mut index = Index::new(index_name);
|
||||
for record in &self.records {
|
||||
if let Some(value) = record.values.get(index_name) {
|
||||
index.insert(value.clone(), record.id);
|
||||
}
|
||||
}
|
||||
self.secondary_indexes.insert(index_name.clone(), index);
|
||||
}
|
||||
}
|
||||
|
||||
/// Обновление индексов для новой записи
|
||||
fn update_indexes(&mut self, record: &Record) {
|
||||
// Обновляем первичный индекс
|
||||
if let (Some(pk), Some(index)) = (&self.schema.primary_key, &mut self.primary_index) {
|
||||
if let Some(value) = record.values.get(pk) {
|
||||
index.insert(value.clone(), record.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем вторичные индексы
|
||||
for (index_name, index) in &mut self.secondary_indexes {
|
||||
if let Some(value) = record.values.get(index_name) {
|
||||
index.insert(value.clone(), record.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Обновление индексов для измененной записи
|
||||
fn update_indexes_for_record(&mut self, record_id: u64, updates: &HashMap<String, Value>, old_values: &HashMap<String, Value>) {
|
||||
// Обновляем первичный индекс
|
||||
if let (Some(pk), Some(index)) = (&self.schema.primary_key, &mut self.primary_index) {
|
||||
if let Some(new_value) = updates.get(pk) {
|
||||
// Удаляем старое значение
|
||||
if let Some(old_value) = old_values.get(pk) {
|
||||
index.remove(old_value, record_id);
|
||||
}
|
||||
// Вставляем новое значение
|
||||
index.insert(new_value.clone(), record_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем вторичные индексы
|
||||
for (index_name, index) in &mut self.secondary_indexes {
|
||||
if let Some(new_value) = updates.get(index_name) {
|
||||
// Удаляем старое значение
|
||||
if let Some(old_value) = old_values.get(index_name) {
|
||||
index.remove(old_value, record_id);
|
||||
}
|
||||
// Вставляем новое значение
|
||||
index.insert(new_value.clone(), record_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Удаление из индексов
|
||||
fn remove_from_indexes(&mut self, record: &Record) {
|
||||
// Удаляем из первичного индекса
|
||||
if let (Some(pk), Some(index)) = (&self.schema.primary_key, &mut self.primary_index) {
|
||||
if let Some(value) = record.values.get(pk) {
|
||||
index.remove(value, record.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Удаляем из вторичных индексов
|
||||
for (index_name, index) in &mut self.secondary_indexes {
|
||||
if let Some(value) = record.values.get(index_name) {
|
||||
index.remove(value, record.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Получение имени таблицы
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Получение количества записей в таблице
|
||||
pub fn record_count(&self) -> usize {
|
||||
self.records.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Сравнение значений на равенство
|
||||
fn values_equal(v1: &Value, v2: &Value) -> bool {
|
||||
match (v1, v2) {
|
||||
(Value::Integer(a), Value::Integer(b)) => a == b,
|
||||
(Value::Text(a), Value::Text(b)) => a == b,
|
||||
(Value::Boolean(a), Value::Boolean(b)) => a == b,
|
||||
(Value::Float(a), Value::Float(b)) => (a - b).abs() < f64::EPSILON,
|
||||
(Value::Null, Value::Null) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Сравнение значений (больше)
|
||||
fn value_gt(v1: &Value, v2: &Value) -> bool {
|
||||
match (v1, v2) {
|
||||
(Value::Integer(a), Value::Integer(b)) => a > b,
|
||||
(Value::Float(a), Value::Float(b)) => a > b,
|
||||
(Value::Text(a), Value::Text(b)) => a > b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Сравнение значений (меньше)
|
||||
fn value_lt(v1: &Value, v2: &Value) -> bool {
|
||||
match (v1, v2) {
|
||||
(Value::Integer(a), Value::Integer(b)) => a < b,
|
||||
(Value::Float(a), Value::Float(b)) => a < b,
|
||||
(Value::Text(a), Value::Text(b)) => a < b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Ошибки таблицы
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TableError {
|
||||
#[error("Table not found: {0}")]
|
||||
NotFound(String),
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error("Parse error: {0}")]
|
||||
ParseError(serde_json::Error),
|
||||
|
||||
#[error("Serialize error: {0}")]
|
||||
SerializeError(serde_json::Error),
|
||||
|
||||
#[error("Type mismatch in column '{0}': expected {1}, got {2}")]
|
||||
TypeMismatch(String, String, String),
|
||||
|
||||
#[error("NOT NULL violation in column '{0}'")]
|
||||
NotNullViolation(String),
|
||||
|
||||
#[error("Duplicate value in unique column '{0}': {1}")]
|
||||
DuplicateValue(String, String),
|
||||
|
||||
#[error("Foreign key violation: {0}")]
|
||||
ForeignKeyViolation(String),
|
||||
|
||||
#[error("Check constraint violation: {0}")]
|
||||
CheckViolation(String),
|
||||
|
||||
#[error("Export error: {0}")]
|
||||
ExportError(String),
|
||||
|
||||
#[error("Import error: {0}")]
|
||||
ImportError(String),
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user