mod binder;
mod lineage;
mod logical_plan;
mod origins;
mod reads;
mod tables;
use logical_plan::LogicalPlan;
use crate::extractor::{ColumnLineageEdge, TableLineageEdge};
use crate::reference::{ColumnRead, ColumnWrite, TableRead, TableWrite};
pub(crate) use binder::build_with_diagnostics as build;
fn source_order(name: &sqlparser::ast::Ident) -> (u64, u64) {
let start = name.span.start;
(start.line, start.column)
}
pub(crate) fn reads(plan: &LogicalPlan) -> Vec<ColumnRead> {
let mut reads = reads::collect_reads(plan);
reads.sort_by_key(|r| source_order(&r.reference.name));
reads
}
pub(crate) fn table_reads(plan: &LogicalPlan) -> Vec<TableRead> {
let mut reads = reads::collect_table_reads(plan);
reads.sort_by_key(|r| source_order(&r.reference.name));
reads
}
pub(crate) fn writes(plan: &LogicalPlan) -> Vec<ColumnWrite> {
tables::collect_writes(plan)
}
pub(crate) fn table_writes(plan: &LogicalPlan) -> Vec<TableWrite> {
tables::collect_table_writes(plan)
}
pub(crate) fn column_lineage(
plan: &LogicalPlan,
casing: crate::casing::IdentifierCasing,
) -> Vec<ColumnLineageEdge> {
let mut edges = lineage::collect_column_lineage(plan, casing);
edges.sort_by_key(|e| source_order(&e.source.reference.name));
edges
}
pub(crate) fn table_lineage(
plan: &LogicalPlan,
casing: crate::casing::IdentifierCasing,
) -> Vec<TableLineageEdge> {
let mut edges = lineage::collect_table_lineage(plan, casing);
edges.sort_by_key(|e| source_order(&e.source.reference.name));
edges
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct MergeActions {
pub(crate) has_insert: bool,
pub(crate) has_update: bool,
pub(crate) has_delete: bool,
}
impl MergeActions {
pub(crate) fn writes_data(&self) -> bool {
self.has_insert || self.has_update
}
}
pub(crate) fn merge_actions(plan: &LogicalPlan) -> Option<MergeActions> {
use logical_plan::{peel_with, MergeClause};
let LogicalPlan::Merge(merge) = peel_with(plan) else {
return None;
};
let mut actions = MergeActions::default();
for clause in &merge.clauses {
match clause {
MergeClause::Insert { .. } => actions.has_insert = true,
MergeClause::Update { .. } => actions.has_update = true,
MergeClause::Delete => actions.has_delete = true,
}
}
Some(actions)
}
pub(crate) fn insert_updates_on_conflict(plan: &LogicalPlan) -> bool {
use logical_plan::peel_with;
matches!(peel_with(plan), LogicalPlan::Insert(i) if !i.on_conflict.is_empty())
}
pub(crate) fn has_data_modifying_cte(plan: &LogicalPlan) -> bool {
use logical_plan::{dml_roots, peel_with};
let outer = peel_with(plan);
dml_roots(plan)
.iter()
.any(|root| !std::ptr::eq(*root, outer))
}
pub(crate) struct DataModifyingCteCrud {
pub(crate) create: Vec<TableWrite>,
pub(crate) update: Vec<TableWrite>,
pub(crate) delete: Vec<TableWrite>,
pub(crate) outer_writes: Vec<TableWrite>,
}
pub(crate) fn data_modifying_cte_crud(plan: &LogicalPlan) -> Option<DataModifyingCteCrud> {
use logical_plan::{dml_roots, peel_with};
let outer = peel_with(plan);
let roots = dml_roots(plan);
if roots.iter().all(|root| std::ptr::eq(*root, outer)) {
return None;
}
let mut crud = DataModifyingCteCrud {
create: Vec::new(),
update: Vec::new(),
delete: Vec::new(),
outer_writes: Vec::new(),
};
for root in roots {
let targets = tables::write_root_tables(root);
if std::ptr::eq(root, outer) {
crud.outer_writes = targets;
continue;
}
match root {
LogicalPlan::Insert(i) => {
crud.create.extend(targets.iter().cloned());
if !i.on_conflict.is_empty() {
crud.update.extend(targets);
}
}
LogicalPlan::Update(_) | LogicalPlan::AlterTable(_) => crud.update.extend(targets),
LogicalPlan::Delete(_) | LogicalPlan::Drop(_) => crud.delete.extend(targets),
LogicalPlan::CreateTableAs(_) | LogicalPlan::CreateView(_) => {
crud.create.extend(targets)
}
LogicalPlan::Merge(_) => crud.create.extend(targets),
LogicalPlan::Scan(_)
| LogicalPlan::Filter(_)
| LogicalPlan::Join(_)
| LogicalPlan::Aggregate(_)
| LogicalPlan::Projection(_)
| LogicalPlan::Sort(_)
| LogicalPlan::SetOp(_)
| LogicalPlan::SubqueryAlias(_)
| LogicalPlan::TableFunction(_)
| LogicalPlan::With(_)
| LogicalPlan::CteRef(_)
| LogicalPlan::Values(_)
| LogicalPlan::Empty => {}
}
}
Some(crud)
}