use std::fmt;
use crate::casing::IdentifierStyle;
use crate::catalog::Catalog;
use crate::diagnostic::TableLevelDiagnostic;
use crate::error::Error;
use crate::extractor::{ExtractorOptions, StatementKind, TableOperationExtractor};
use crate::reference::{TableRead, TableReference, TableWrite};
use sqlparser::ast::{Insert, SetExpr, Statement};
use sqlparser::dialect::Dialect;
pub fn extract_crud_tables(
dialect: &dyn Dialect,
sql: &str,
) -> Result<Vec<Result<CrudTables, Error>>, Error> {
CrudTableExtractor::extract(dialect, sql)
}
pub fn extract_crud_tables_with_options(
dialect: &dyn Dialect,
sql: &str,
options: ExtractorOptions,
) -> Result<Vec<Result<CrudTables, Error>>, Error> {
CrudTableExtractor::extract_with_options(dialect, sql, options)
}
#[derive(Default, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CrudTables {
pub create_tables: Vec<TableWrite>,
pub read_tables: Vec<TableRead>,
pub update_tables: Vec<TableWrite>,
pub delete_tables: Vec<TableWrite>,
pub diagnostics: Vec<TableLevelDiagnostic>,
}
impl fmt::Display for CrudTables {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let write_refs = |ws: &[TableWrite]| -> Vec<TableReference> {
ws.iter().map(|w| w.reference.clone()).collect()
};
let read_refs = |rs: &[TableRead]| -> Vec<TableReference> {
rs.iter().map(|r| r.reference.clone()).collect()
};
write!(
f,
"Create: [{}], Read: [{}], Update: [{}], Delete: [{}]",
TableReference::format_list(&write_refs(&self.create_tables)),
TableReference::format_list(&read_refs(&self.read_tables)),
TableReference::format_list(&write_refs(&self.update_tables)),
TableReference::format_list(&write_refs(&self.delete_tables)),
)
}
}
#[derive(Default, Debug)]
pub struct CrudTableExtractor;
impl CrudTableExtractor {
pub fn extract(
dialect: &dyn Dialect,
sql: &str,
) -> Result<Vec<Result<CrudTables, Error>>, Error> {
Self::extract_with_options(dialect, sql, ExtractorOptions::new())
}
pub fn extract_with_options(
dialect: &dyn Dialect,
sql: &str,
options: ExtractorOptions,
) -> Result<Vec<Result<CrudTables, Error>>, Error> {
crate::extractor::extract_each(dialect, sql, options, Self::extract_from_statement)
}
fn extract_from_statement(
statement: &Statement,
catalog: Option<&Catalog>,
style: IdentifierStyle,
) -> Result<CrudTables, Error> {
let (ops, merge_actions, insert_updates, cte_crud) =
TableOperationExtractor::extract_inner(statement, catalog, style)?;
let reads = ops.reads;
let writes = match &cte_crud {
Some(c) => c.outer_writes.clone(),
None => ops.writes,
};
let diagnostics = ops.diagnostics;
let mut crud = CrudTables {
diagnostics,
..Default::default()
};
match ops.statement_kind {
StatementKind::Insert => {
if insert_updates {
crud.update_tables = writes.clone();
}
if peel_to_insert(statement).is_some_and(|i| i.replace_into || i.overwrite) {
crud.delete_tables = writes.clone();
}
crud.create_tables = writes;
crud.read_tables = reads;
}
StatementKind::Update => {
crud.update_tables = writes;
crud.read_tables = reads;
}
StatementKind::Delete => {
crud.delete_tables = writes;
crud.read_tables = reads;
}
StatementKind::Merge => {
let actions = merge_actions.unwrap_or_default();
for target in &writes {
if actions.has_insert {
crud.create_tables.push(target.clone());
}
if actions.has_update {
crud.update_tables.push(target.clone());
}
if actions.has_delete {
crud.delete_tables.push(target.clone());
}
}
crud.read_tables = reads;
}
StatementKind::CreateTable | StatementKind::CreateView => {
crud.create_tables = writes;
crud.read_tables = reads;
}
StatementKind::AlterTable | StatementKind::AlterView => {
crud.update_tables = writes;
crud.read_tables = reads;
}
StatementKind::Drop | StatementKind::Truncate => {
crud.delete_tables = writes;
crud.read_tables = reads;
}
StatementKind::Select => {
crud.create_tables = writes;
crud.read_tables = reads;
}
StatementKind::Unsupported => {
crud.read_tables = reads;
crud.read_tables
.extend(writes.into_iter().map(|w| TableRead {
reference: w.reference,
resolution: w.resolution,
}));
}
}
if let Some(c) = cte_crud {
crud.create_tables.extend(c.create);
crud.update_tables.extend(c.update);
crud.delete_tables.extend(c.delete);
}
Ok(crud)
}
}
fn peel_to_insert(statement: &Statement) -> Option<&Insert> {
match statement {
Statement::Insert(insert) => Some(insert),
Statement::Query(query) => {
let mut body = query.body.as_ref();
loop {
match body {
SetExpr::Insert(inner) => return peel_to_insert(inner),
SetExpr::Query(inner) => body = inner.body.as_ref(),
_ => return None,
}
}
}
_ => None,
}
}