use super::tokens::*;
use super::{codes, NocaseMode, Warning};
use std::collections::{HashMap, HashSet};
pub struct Ctx {
pub schema_map: HashMap<String, String>,
warnings: Vec<Warning>,
seen: HashSet<(String, String)>,
stmt_codes: HashSet<&'static str>,
pub cur_line: usize,
pub cur_col: usize,
pub temp_tables: HashSet<String>,
pub variables: HashSet<String>,
pub pre_statements: Vec<Vec<Tok>>,
pub in_ddl: bool,
pub target_version: u16,
pub nocase: bool,
pub nocase_mode: NocaseMode,
pub float_division: bool,
pub mssql_math: bool,
pub types: HashMap<String, String>,
pub var_types: HashMap<String, String>,
}
impl Ctx {
pub fn new(schema_map: HashMap<String, String>) -> Self {
Self {
schema_map, warnings: vec![], seen: HashSet::new(), stmt_codes: HashSet::new(), cur_line: 0, cur_col: 0,
temp_tables: HashSet::new(), variables: HashSet::new(), pre_statements: vec![], in_ddl: false, target_version: 2019,
nocase: true, nocase_mode: NocaseMode::Rewrite, float_division: true, mssql_math: false, types: HashMap::new(), var_types: HashMap::new(),
}
}
pub fn warn(&mut self, msg: impl Into<String>) {
self.warn_code(codes::GENERIC, msg);
}
pub fn warn_code(&mut self, code: &'static str, msg: impl Into<String>) {
let (l, c) = (self.cur_line, self.cur_col);
self.push_warning(code, l, c, msg.into());
}
pub fn warn_at(&mut self, code: &'static str, tok: &Tok, msg: impl Into<String>) {
let (l, c) = if tok.has_pos() { (tok.line, tok.col) } else { (self.cur_line, self.cur_col) };
self.push_warning(code, l, c, msg.into());
}
pub fn warn_stmt_once(&mut self, code: &'static str, tok: &Tok, msg: impl Into<String>) {
if self.stmt_codes.insert(code) { self.warn_at(code, tok, msg); }
}
fn push_warning(&mut self, code: &'static str, line: usize, col: usize, message: String) {
if self.seen.insert((code.to_string(), message.clone())) {
self.warnings.push(Warning { code, line, col, message });
}
}
pub fn set_pos(&mut self, tok: &Tok) {
if tok.has_pos() { self.cur_line = tok.line; self.cur_col = tok.col; }
}
pub fn begin_statement(&mut self, first: Option<&Tok>) {
self.stmt_codes.clear();
if let Some(t) = first { self.set_pos(t); }
}
pub fn take_warnings(&mut self) -> Vec<Warning> {
self.seen.clear();
std::mem::take(&mut self.warnings)
}
}
pub const NOT_FUNCS: &[&str] = &[
"IN", "EXISTS", "VALUES", "AND", "OR", "NOT", "ON", "WHERE", "FROM", "JOIN", "OVER", "AS", "WITH", "SELECT", "THEN",
"ELSE", "WHEN", "BY", "HAVING", "SET", "INTO", "UNION", "ALL", "DISTINCT", "TOP", "BETWEEN", "LIKE", "ILIKE", "IS",
"NULL", "CASE", "END", "ANY", "SOME", "INSERT", "UPDATE", "DELETE", "TABLE", "VIEW", "INDEX", "RETURNING", "LIMIT",
"OFFSET", "USING", "LATERAL", "APPLY", "PARTITION", "FETCH", "NEXT", "ROWS", "ONLY", "BEGIN", "WHILE", "DECLARE",
"PRINT", "EXEC", "EXECUTE", "RETURN", "ESCAPE", "COLLATE", "GROUP", "ORDER", "WINDOW", "QUALIFY", "SAMPLE", "CROSS",
"INNER", "LEFT", "RIGHT", "FULL", "OUTER", "NATURAL", "ASOF", "POSITIONAL", "SEMI", "ANTI", "DEFAULT", "CHECK",
"REFERENCES", "PRIMARY", "KEY", "UNIQUE", "FOREIGN", "CONSTRAINT", "ARRAY", "STRUCT", "MAP", "ROW", "INTERVAL",
"FILTER", "WITHIN", "EXCLUDE", "REPLACE", "TABLESAMPLE", "MERGE", "MATCHED", "OUTPUT", "OPTION", "PIVOT", "UNPIVOT",
"EXCEPT", "INTERSECT", "CREATE", "ALTER", "DROP", "TRUNCATE", "GRANT", "REVOKE", "COMMIT", "ROLLBACK", "TRANSACTION",
"SCHEMA", "DATABASE", "SEQUENCE", "TYPE", "FUNCTION", "PROCEDURE", "PROC", "TRIGGER", "ADD", "COLUMN", "RENAME", "TO",
"WHERE", "LOOP", "DO", "ELSEIF", "RAISE", "CAST_AS_NEVER",
];
pub trait Rules {
fn func(&self, name: &str, raw_args: &[Vec<Tok>], args: &[Vec<Tok>], ctx: &mut Ctx) -> Option<Vec<Tok>>;
fn token(&self, tok: &Tok, prev: Option<&Tok>, next: Option<&Tok>, ctx: &mut Ctx) -> Vec<Tok>;
fn group(&self, toks: Vec<Tok>, ctx: &mut Ctx) -> Vec<Tok>;
fn select_segment(&self, toks: Vec<Tok>, ctx: &mut Ctx) -> Vec<Tok>;
}
pub fn rewrite(toks: &[Tok], rules: &dyn Rules, ctx: &mut Ctx) -> Vec<Tok> {
let mut out: Vec<Tok> = Vec::with_capacity(toks.len());
let mut i = 0;
while i < toks.len() {
let t = &toks[i];
if t.is_punct("(") {
let close = match_paren(toks, i);
let inner = &toks[i + 1..close.max(i + 1)];
let fname_idx = prev_sig(&out, out.len());
let mut is_func = fname_idx.map(|k| out[k].kind == Kind::Ident && !out[k].is_any_kw(NOT_FUNCS)).unwrap_or(false);
if is_func {
let mut k = fname_idx.unwrap();
loop {
match prev_sig(&out, k) {
Some(p) if out[p].is_punct(".") => match prev_sig(&out, p) { Some(q) => { k = q; } None => break },
Some(p) => { if out[p].is_any_kw(&["TABLE", "INTO", "FROM", "JOIN", "UPDATE", "VIEW", "INDEX", "REFERENCES", "AS", "SEQUENCE", "TYPE", "SCHEMA", "LATERAL", "USING"]) { is_func = false; } break; }
None => break,
}
}
}
if is_func {
let k = fname_idx.unwrap();
let name = out[k].text.clone();
let name_tok = out[k].clone();
out.truncate(k);
let raw_args: Vec<Vec<Tok>> = if inner.iter().all(|x| x.is_trivia()) { vec![] } else { split_top(inner, ",") };
let args: Vec<Vec<Tok>> = raw_args.iter().map(|a| rewrite_arg(a, rules, ctx)).collect();
ctx.set_pos(&name_tok);
match rules.func(&name.to_uppercase(), &raw_args, &args, ctx) {
Some(rep) => out.extend(rep),
None => {
out.push(Tok::ident(&name).at(name_tok.line, name_tok.col));
out.push(Tok::punct("("));
for (ai, a) in args.iter().enumerate() {
if ai > 0 { out.push(Tok::punct(",")); out.push(Tok::ws()); }
out.extend(a.iter().cloned());
}
out.push(Tok::punct(")"));
}
}
} else {
let body = rewrite(inner, rules, ctx);
let body = if contains_top_kw(&body, "SELECT") { rules.select_segment(body, ctx) } else { body };
out.push(Tok::punct("("));
out.extend(body);
out.push(Tok::punct(")"));
}
i = close + 1;
continue;
}
let prev = prev_sig(&out, out.len()).map(|k| out[k].clone());
let next = next_sig(toks, i + 1).map(|k| toks[k].clone());
ctx.set_pos(t);
out.extend(rules.token(t, prev.as_ref(), next.as_ref(), ctx));
i += 1;
}
let out = rules.group(out, ctx);
if contains_top_kw(&out, "SELECT") { rules.select_segment(out, ctx) } else { out }
}
fn rewrite_arg(a: &[Tok], rules: &dyn Rules, ctx: &mut Ctx) -> Vec<Tok> {
let t = trim(a);
rewrite(t, rules, ctx)
}
pub fn contains_top_kw(toks: &[Tok], kw: &str) -> bool {
let mut depth = 0i32;
for t in toks {
if t.is_punct("(") { depth += 1; } else if t.is_punct(")") { depth -= 1; }
if depth == 0 && t.is_kw(kw) { return true; }
}
false
}
pub fn find_top_kw(toks: &[Tok], kw: &str, from: usize) -> Option<usize> {
let mut depth = 0i32;
for (i, t) in toks.iter().enumerate().skip(from) {
if t.is_punct("(") { depth += 1; } else if t.is_punct(")") { depth -= 1; }
if depth == 0 && t.is_kw(kw) { return Some(i); }
}
None
}
pub fn find_all_top(toks: &[Tok], kws: &[&str]) -> Vec<usize> {
let mut depth = 0i32;
let mut out = vec![];
for (i, t) in toks.iter().enumerate() {
if t.is_punct("(") { depth += 1; } else if t.is_punct(")") { depth -= 1; }
if depth == 0 && t.is_any_kw(kws) { out.push(i); }
}
out
}
pub fn atom_start(toks: &[Tok], end: usize) -> usize {
let mut j = match prev_sig(toks, end) { Some(j) => j, None => return end };
loop {
if toks[j].is_punct(")") {
let mut depth = 0i32;
let mut k = j;
loop {
if toks[k].is_punct(")") { depth += 1; } else if toks[k].is_punct("(") { depth -= 1; if depth == 0 { break; } }
if k == 0 { break; }
k -= 1;
}
j = k;
if let Some(p) = prev_sig(toks, j) {
if toks[p].kind == Kind::Ident && !toks[p].is_any_kw(NOT_FUNCS) { j = p; } else { return j; }
} else { return j; }
} else if matches!(toks[j].kind, Kind::Ident | Kind::QIdent | Kind::Num | Kind::Str | Kind::Var | Kind::Temp) {
} else {
return j + 1;
}
match prev_sig(toks, j) {
Some(p) if toks[p].is_punct(".") => {
match prev_sig(toks, p) { Some(q) => { j = q; continue; } None => return j }
}
_ => return j,
}
}
}
pub fn atom_end(toks: &[Tok], start: usize) -> usize {
let mut j = match next_sig(toks, start) { Some(j) => j, None => return start };
if toks[j].is_op("-") || toks[j].is_op("+") {
j = match next_sig(toks, j + 1) { Some(k) => k, None => return j + 1 };
}
loop {
if toks[j].kind == Kind::Ident && !toks[j].is_any_kw(NOT_FUNCS) {
if let Some(n) = next_sig(toks, j + 1) {
if toks[n].is_punct("(") { j = match_paren(toks, n); }
}
} else if toks[j].is_punct("(") {
j = match_paren(toks, j);
} else if !matches!(toks[j].kind, Kind::QIdent | Kind::Num | Kind::Str | Kind::Var | Kind::Temp) {
return j;
}
match next_sig(toks, j + 1) {
Some(n) if toks[n].is_punct(".") => match next_sig(toks, n + 1) { Some(k) => { j = k; continue; } None => return n + 1 },
_ => return j + 1,
}
}
}
pub fn t(s: &str) -> Vec<Tok> {
tokenize(s, false)
}
pub fn t_br(s: &str) -> Vec<Tok> {
tokenize(s, true)
}
pub fn fmt(template: &str, args: &[&[Tok]]) -> Vec<Tok> {
let mut out = vec![];
let mut ai = 0;
for (i, part) in template.split("{}").enumerate() {
if i > 0 {
if let Some(a) = args.get(ai) { out.extend(a.iter().cloned()); }
ai += 1;
}
if !part.is_empty() { out.extend(t(part)); }
}
out
}
pub fn upper_text(toks: &[Tok]) -> String {
join(trim(toks)).to_uppercase()
}
pub fn as_str_lit(toks: &[Tok]) -> Option<String> {
let s = strip_trivia(toks);
if s.len() == 1 && s[0].kind == Kind::Str { Some(s[0].value()) } else { None }
}
pub fn as_ident(toks: &[Tok]) -> Option<String> {
let s = strip_trivia(toks);
if s.len() == 1 && matches!(s[0].kind, Kind::Ident | Kind::QIdent) { Some(s[0].value()) } else { None }
}
pub fn as_num(toks: &[Tok]) -> Option<String> {
let s = strip_trivia(toks);
if s.len() == 1 && s[0].kind == Kind::Num { Some(s[0].text.clone()) } else { None }
}
pub struct Stmt<'a> {
pub toks: &'a [Tok],
pub sig: Vec<usize>,
}
impl<'a> Stmt<'a> {
pub fn new(toks: &'a [Tok]) -> Self {
let sig = toks.iter().enumerate().filter(|(_, t)| !t.is_trivia()).map(|(i, _)| i).collect();
Stmt { toks, sig }
}
pub fn len(&self) -> usize { self.sig.len() }
pub fn is_empty(&self) -> bool { self.sig.is_empty() }
pub fn kw(&self, n: usize) -> String { self.sig.get(n).map(|&i| self.toks[i].text.to_uppercase()).unwrap_or_default() }
pub fn tok(&self, n: usize) -> Option<&'a Tok> { self.sig.get(n).map(|&i| &self.toks[i]) }
pub fn pos(&self, n: usize) -> usize { self.sig.get(n).copied().unwrap_or(self.toks.len()) }
pub fn from(&self, n: usize) -> &'a [Tok] { &self.toks[self.pos(n).min(self.toks.len())..] }
pub fn all(&self) -> Vec<Tok> { self.toks.to_vec() }
}
pub fn ident_end(toks: &[Tok], start: usize) -> usize {
let mut j = match next_sig(toks, start) { Some(j) => j, None => return start };
loop {
if !matches!(toks[j].kind, Kind::Ident | Kind::QIdent | Kind::Temp | Kind::Var) { return j; }
match next_sig(toks, j + 1) {
Some(n) if toks[n].is_punct(".") => match next_sig(toks, n + 1) { Some(k) => { j = k; } None => return n + 1 },
_ => return j + 1,
}
}
}