Files
haex-hub-mirror/src-tauri/src/extension/database/executor.rs
haex a52e1b43fa Remove unused code and modernize Rust format strings
Applied cargo clippy fixes to clean up codebase:
- Removed unused imports (serde_json::json, std::collections::HashSet)
- Removed unused function encode_hex_for_log
- Modernized format strings to use inline variables
- Fixed clippy warnings for better code quality

All changes applied automatically by cargo clippy --fix
2025-11-02 14:48:01 +01:00

285 lines
10 KiB
Rust

// src-tauri/src/extension/database/executor.rs
use crate::crdt::hlc::HlcService;
use crate::crdt::transformer::CrdtTransformer;
use crate::crdt::trigger;
use crate::database::core::{convert_value_ref_to_json, parse_sql_statements};
use crate::database::error::DatabaseError;
use rusqlite::{params_from_iter, types::Value as SqliteValue, ToSql, Transaction};
use serde_json::Value as JsonValue;
use sqlparser::ast::Statement;
use std::collections::HashSet;
/// SQL-Executor OHNE Berechtigungsprüfung - für interne Nutzung
pub struct SqlExecutor;
impl SqlExecutor {
/// Führt ein SQL Statement OHNE RETURNING aus (mit CRDT)
/// Returns: modified_schema_tables
pub fn execute_internal_typed(
tx: &Transaction,
hlc_service: &HlcService,
sql: &str,
params: &[&dyn ToSql],
) -> Result<HashSet<String>, DatabaseError> {
let mut ast_vec = parse_sql_statements(sql)?;
if ast_vec.len() != 1 {
return Err(DatabaseError::ExecutionError {
sql: sql.to_string(),
reason: "execute_internal_typed should only receive a single SQL statement"
.to_string(),
table: None,
});
}
let mut statement = ast_vec.pop().unwrap();
let transformer = CrdtTransformer::new();
let hlc_timestamp =
hlc_service
.new_timestamp_and_persist(tx)
.map_err(|e| DatabaseError::HlcError {
reason: e.to_string(),
})?;
let mut modified_schema_tables = HashSet::new();
if let Some(table_name) = transformer.transform_execute_statement_with_table_info(
&mut statement,
&hlc_timestamp,
)? {
modified_schema_tables.insert(table_name);
}
let sql_str = statement.to_string();
eprintln!("DEBUG: Transformed execute SQL: {sql_str}");
// Führe Statement aus
tx.execute(&sql_str, params)
.map_err(|e| DatabaseError::ExecutionError {
sql: sql_str.clone(),
table: None,
reason: format!("Execute failed: {e}"),
})?;
// Trigger-Logik für CREATE TABLE
if let Statement::CreateTable(create_table_details) = statement {
let raw_name = create_table_details.name.to_string();
// Remove quotes from table name
let table_name_str = raw_name
.trim_matches('"')
.trim_matches('`')
.to_string();
eprintln!("DEBUG: Setting up triggers for table: {table_name_str}");
trigger::setup_triggers_for_table(tx, &table_name_str, false)?;
}
Ok(modified_schema_tables)
}
/// Führt ein SQL Statement MIT RETURNING aus (mit CRDT)
/// Returns: (modified_schema_tables, returning_results)
pub fn query_internal_typed(
tx: &Transaction,
hlc_service: &HlcService,
sql: &str,
params: &[&dyn ToSql],
) -> Result<(HashSet<String>, Vec<Vec<JsonValue>>), DatabaseError> {
let mut ast_vec = parse_sql_statements(sql)?;
if ast_vec.len() != 1 {
return Err(DatabaseError::ExecutionError {
sql: sql.to_string(),
reason: "query_internal_typed should only receive a single SQL statement"
.to_string(),
table: None,
});
}
let mut statement = ast_vec.pop().unwrap();
let transformer = CrdtTransformer::new();
let hlc_timestamp =
hlc_service
.new_timestamp_and_persist(tx)
.map_err(|e| DatabaseError::HlcError {
reason: e.to_string(),
})?;
let mut modified_schema_tables = HashSet::new();
if let Some(table_name) = transformer.transform_execute_statement_with_table_info(
&mut statement,
&hlc_timestamp,
)? {
modified_schema_tables.insert(table_name);
}
let sql_str = statement.to_string();
eprintln!("DEBUG: Transformed SQL (with RETURNING): {sql_str}");
// Prepare und query ausführen
let mut stmt = tx
.prepare(&sql_str)
.map_err(|e| DatabaseError::ExecutionError {
sql: sql_str.clone(),
table: None,
reason: e.to_string(),
})?;
let column_names: Vec<String> = stmt
.column_names()
.into_iter()
.map(|s| s.to_string())
.collect();
let num_columns = column_names.len();
let mut rows = stmt
.query(params_from_iter(params.iter()))
.map_err(|e| DatabaseError::ExecutionError {
sql: sql_str.clone(),
table: None,
reason: e.to_string(),
})?;
let mut result_vec: Vec<Vec<JsonValue>> = Vec::new();
// Lese alle RETURNING Zeilen
while let Some(row) = rows.next().map_err(|e| DatabaseError::ExecutionError {
sql: sql_str.clone(),
table: None,
reason: e.to_string(),
})? {
let mut row_values: Vec<JsonValue> = Vec::new();
for i in 0..num_columns {
let value_ref = row.get_ref(i).map_err(|e| DatabaseError::ExecutionError {
sql: sql_str.clone(),
table: None,
reason: e.to_string(),
})?;
let json_value = convert_value_ref_to_json(value_ref)?;
row_values.push(json_value);
}
result_vec.push(row_values);
}
// Trigger-Logik für CREATE TABLE
if let Statement::CreateTable(create_table_details) = statement {
let raw_name = create_table_details.name.to_string();
// Remove quotes from table name
let table_name_str = raw_name
.trim_matches('"')
.trim_matches('`')
.to_string();
eprintln!("DEBUG: Setting up triggers for table (RETURNING): {table_name_str}");
trigger::setup_triggers_for_table(tx, &table_name_str, false)?;
}
Ok((modified_schema_tables, result_vec))
}
/// Führt ein einzelnes SQL Statement OHNE Typinformationen aus (JSON params)
pub fn execute_internal(
tx: &Transaction,
hlc_service: &HlcService,
sql: &str,
params: &[JsonValue],
) -> Result<HashSet<String>, DatabaseError> {
let sql_params: Vec<SqliteValue> = params
.iter()
.map(crate::database::core::ValueConverter::json_to_rusqlite_value)
.collect::<Result<Vec<_>, _>>()?;
let param_refs: Vec<&dyn ToSql> = sql_params.iter().map(|p| p as &dyn ToSql).collect();
Self::execute_internal_typed(tx, hlc_service, sql, &param_refs)
}
/// Query-Variante (mit RETURNING) OHNE Typinformationen (JSON params)
pub fn query_internal(
tx: &Transaction,
hlc_service: &HlcService,
sql: &str,
params: &[JsonValue],
) -> Result<(HashSet<String>, Vec<Vec<JsonValue>>), DatabaseError> {
let sql_params: Vec<SqliteValue> = params
.iter()
.map(crate::database::core::ValueConverter::json_to_rusqlite_value)
.collect::<Result<Vec<_>, _>>()?;
let param_refs: Vec<&dyn ToSql> = sql_params.iter().map(|p| p as &dyn ToSql).collect();
Self::query_internal_typed(tx, hlc_service, sql, &param_refs)
}
/// Führt mehrere SQL Statements als Batch aus
pub fn execute_batch_internal(
tx: &Transaction,
hlc_service: &HlcService,
sqls: &[String],
params: &[Vec<JsonValue>],
) -> Result<HashSet<String>, DatabaseError> {
if sqls.len() != params.len() {
return Err(DatabaseError::ExecutionError {
sql: format!("{} statements but {} param sets", sqls.len(), params.len()),
reason: "Statement count and parameter count mismatch".to_string(),
table: None,
});
}
let mut all_modified_tables = HashSet::new();
for (sql, param_set) in sqls.iter().zip(params.iter()) {
let modified_tables = Self::execute_internal(tx, hlc_service, sql, param_set)?;
all_modified_tables.extend(modified_tables);
}
Ok(all_modified_tables)
}
/// Query für SELECT-Statements (read-only, kein CRDT nötig außer Filter)
pub fn query_select(
conn: &rusqlite::Connection,
sql: &str,
params: &[JsonValue],
) -> Result<Vec<Vec<JsonValue>>, DatabaseError> {
let mut ast_vec = parse_sql_statements(sql)?;
if ast_vec.len() != 1 {
return Err(DatabaseError::ExecutionError {
sql: sql.to_string(),
reason: "query_select should only receive a single SELECT statement".to_string(),
table: None,
});
}
// Hard Delete: Keine SELECT-Transformation mehr nötig
let stmt_to_execute = ast_vec.pop().unwrap();
let transformed_sql = stmt_to_execute.to_string();
eprintln!("DEBUG: SELECT (no transformation): {transformed_sql}");
// Convert JSON params to SQLite values
let sql_params: Vec<SqliteValue> = params
.iter()
.map(crate::database::core::ValueConverter::json_to_rusqlite_value)
.collect::<Result<Vec<_>, _>>()?;
let mut prepared_stmt = conn.prepare(&transformed_sql)?;
let num_columns = prepared_stmt.column_count();
let param_refs: Vec<&dyn ToSql> = sql_params.iter().map(|p| p as &dyn ToSql).collect();
let mut rows = prepared_stmt.query(params_from_iter(param_refs.iter()))?;
let mut result: Vec<Vec<JsonValue>> = Vec::new();
while let Some(row) = rows.next()? {
let mut row_values: Vec<JsonValue> = Vec::new();
for i in 0..num_columns {
let value_ref = row.get_ref(i)?;
let json_value = convert_value_ref_to_json(value_ref)?;
row_values.push(json_value);
}
result.push(row_values);
}
Ok(result)
}
}