iridium_core 0.1.12

SQL Server-compatible Rust engine core for Iridium SQL
Documentation
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceSpan {
    pub start_line: usize,
    pub start_col: usize,
    pub end_line: usize,
    pub end_col: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatementSlice {
    pub index: usize,
    pub sql: String,
    pub normalized_sql: String,
    pub span: SourceSpan,
}

pub fn split_sql_statements(sql: &str) -> Vec<StatementSlice> {
    let mut out = Vec::new();
    let mut buf = String::new();
    let mut in_string = false;
    let mut paren_depth = 0usize;
    let mut block_depth = 0usize;
    let mut line = 1usize;
    let mut col = 1usize;
    let mut start_line = 1usize;
    let mut start_col = 1usize;
    let chars: Vec<char> = sql.chars().collect();
    let mut idx = 0usize;

    while idx < chars.len() {
        let ch = chars[idx];
        if buf.is_empty() && ch.is_whitespace() {
            advance_pos(ch, &mut line, &mut col);
            idx += 1;
            continue;
        }
        if buf.is_empty() {
            start_line = line;
            start_col = col;
        }

        if !in_string {
            if ch == '\'' {
                in_string = true;
            } else if ch == '(' {
                paren_depth += 1;
            } else if ch == ')' {
                paren_depth = paren_depth.saturating_sub(1);
            } else if idx + 4 < chars.len()
                && chars[idx..idx + 5]
                    .iter()
                    .collect::<String>()
                    .to_uppercase()
                    == "BEGIN"
            {
                block_depth += 1;
            } else if idx + 2 < chars.len()
                && chars[idx..idx + 3]
                    .iter()
                    .collect::<String>()
                    .to_uppercase()
                    == "END"
            {
                block_depth = block_depth.saturating_sub(1);
            } else if ch == ';' && paren_depth == 0 && block_depth == 0 {
                push_slice(&mut out, &mut buf, start_line, start_col, line, col);
                advance_pos(ch, &mut line, &mut col);
                idx += 1;
                continue;
            } else if idx + 1 < chars.len()
                && chars[idx..idx + 2]
                    .iter()
                    .collect::<String>()
                    .to_uppercase()
                    == "GO"
                && (idx + 2 == chars.len()
                    || chars[idx + 2].is_whitespace()
                    || chars[idx + 2] == ';')
            {
                push_slice(&mut out, &mut buf, start_line, start_col, line, col);
                idx += 2;
                col += 2;
                continue;
            }
        } else if ch == '\'' {
            if idx + 1 < chars.len() && chars[idx + 1] == '\'' {
                buf.push('\'');
                buf.push('\'');
                idx += 2;
                col += 2;
                continue;
            } else {
                in_string = false;
            }
        }

        buf.push(ch);
        advance_pos(ch, &mut line, &mut col);
        idx += 1;
    }

    if !buf.trim().is_empty() {
        push_slice(&mut out, &mut buf, start_line, start_col, line, col);
    }

    out
}

fn advance_pos(ch: char, line: &mut usize, col: &mut usize) {
    if ch == '\n' {
        *line += 1;
        *col = 1;
    } else {
        *col += 1;
    }
}

fn push_slice(
    out: &mut Vec<StatementSlice>,
    buf: &mut String,
    start_line: usize,
    start_col: usize,
    end_line: usize,
    end_col: usize,
) {
    let sql = buf.trim().to_string();
    if !sql.is_empty() {
        out.push(StatementSlice {
            index: out.len(),
            normalized_sql: sql.to_uppercase(),
            sql,
            span: SourceSpan {
                start_line,
                start_col,
                end_line,
                end_col,
            },
        });
    }
    buf.clear();
}