///|
/// Errors raised by sqlglot.
pub(all) suberror SqlglotError {
/// Error raised when tokenizing fails. When available, `start` and `end` are the
/// offsets in the source SQL of the context snippet quoted in the message, i.e.
/// the snippet is `sql[start:end]`.
TokenError(String, start~ : Int?, end~ : Int?)
ParseError(String, Array[ParseErrorInfo])
UnsupportedError(String)
OptimizeError(String)
SchemaError(String)
ExecuteError(String)
ValueError(String)
}
///|
/// Details of a single parse error.
pub(all) struct ParseErrorInfo {
description : String
line : Int
col : Int
start_context : String
highlight : String
end_context : String
into_expression : String?
} derive(Debug)
///|
pub fn SqlglotError::message(self : SqlglotError) -> String {
match self {
TokenError(m, ..) => m
ParseError(m, _) => m
UnsupportedError(m) => m
OptimizeError(m) => m
SchemaError(m) => m
ExecuteError(m) => m
ValueError(m) => m
}
}
///|
pub impl Show for SqlglotError with fn output(self, logger) {
let name = match self {
TokenError(_, ..) => "TokenError"
ParseError(_, _) => "ParseError"
UnsupportedError(_) => "UnsupportedError"
OptimizeError(_) => "OptimizeError"
SchemaError(_) => "SchemaError"
ExecuteError(_) => "ExecuteError"
ValueError(_) => "ValueError"
}
logger.write_string(name)
logger.write_string(": ")
logger.write_string(self.message())
}
///|
pub(all) enum ErrorLevel {
Ignore
Warn
Raise
Immediate
} derive(Eq, Show)
///|
/// Highlight a SQL string using ANSI codes at the given (inclusive) positions.
pub fn highlight_sql(
sql : String,
positions : Array[(Int, Int)],
context_length? : Int = 100,
) -> (String, String, String, String) raise SqlglotError {
if positions.is_empty() {
raise ValueError("positions must contain at least one (start, end) tuple")
}
let chars = sql.to_array()
let n = chars.length()
fn sl(a : Int, b : Int) -> String {
let a = if a < 0 { 0 } else if a > n { n } else { a }
let b = if b < 0 { 0 } else if b > n { n } else { b }
if a >= b {
""
} else {
String::from_array(chars[a:b])
}
}
let mut start_context = ""
let mut end_context = ""
let mut first_highlight_start = 0
let parts = StringBuilder()
let mut previous_part_end = 0
let sorted = positions.copy()
sorted.sort_by((a, b) => a.0 - b.0)
if sorted[0].0 > 0 {
first_highlight_start = sorted[0].0
let s0 = first_highlight_start - context_length
start_context = sl(if s0 < 0 { 0 } else { s0 }, first_highlight_start)
parts.write_string(start_context)
previous_part_end = first_highlight_start
}
for p in sorted {
let (start, end) = p
let highlight_start = if start > previous_part_end {
start
} else {
previous_part_end
}
let highlight_end = end + 1
if highlight_start >= highlight_end {
continue
}
if highlight_start > previous_part_end {
parts.write_string(sl(previous_part_end, highlight_start))
}
parts.write_string("\u{1b}[4m")
parts.write_string(sl(highlight_start, highlight_end))
parts.write_string("\u{1b}[0m")
previous_part_end = highlight_end
}
if previous_part_end < n {
end_context = sl(previous_part_end, previous_part_end + context_length)
parts.write_string(end_context)
}
let highlight = sl(first_highlight_start, previous_part_end)
(parts.to_string(), start_context, highlight, end_context)
}
///|
pub fn concat_messages(errors : Array[String], maximum : Int) -> String {
let msg = []
for i, e in errors {
if i >= maximum {
break
}
msg.push(e)
}
let remaining = errors.length() - maximum
if remaining > 0 {
msg.push("... and \{remaining} more")
}
msg.join("\n\n")
}