Skip to main content

dbmcp_sql_lineage/
extract.rs

1//! Entry point: parse a SQL statement and resolve its column lineage.
2
3use sqlparser::ast::Statement;
4use sqlparser::dialect::Dialect;
5use sqlparser::parser::Parser;
6
7use crate::error::LineageError;
8use crate::model::Lineage;
9use crate::{resolve, statement};
10
11/// Extracts column lineage from a single SQL statement.
12///
13/// Handles read queries (`SELECT`) and write statements (`INSERT`, `UPDATE`,
14/// `DELETE`, `CREATE TABLE AS SELECT`, `MERGE`); writes set [`Lineage::target`].
15/// Other statements (`SHOW`, `DESCRIBE`, `USE`, `EXPLAIN`) succeed empty.
16///
17/// # Errors
18/// Returns [`LineageError`] if the SQL fails to parse, contains more than one
19/// statement, or any projected column cannot be resolved to a concrete source
20/// (fail closed — no partial result).
21pub fn extract(sql: &str, dialect: &dyn Dialect) -> Result<Lineage, LineageError> {
22    let statements = Parser::parse_sql(dialect, sql).map_err(|e| LineageError::Parse(e.to_string()))?;
23
24    if statements.len() > 1 {
25        return Err(LineageError::MultiStatement);
26    }
27    let Some(stmt) = statements.first() else {
28        return Ok(Lineage::empty());
29    };
30
31    match stmt {
32        Statement::Query(query) => Ok(Lineage::from_columns(resolve::resolve(query)?)),
33        Statement::Insert(insert) => statement::extract_insert(insert),
34        Statement::Update(update) => statement::extract_update(update),
35        Statement::Delete(delete) => statement::extract_delete(delete),
36        Statement::CreateTable(ct) => statement::extract_create_table(ct),
37        Statement::Merge(merge) => statement::extract_merge(merge),
38        _ => Ok(Lineage::empty()),
39    }
40}