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