gaman_core/parsers/
error.rs1use crate::dialects::Dialect;
2use crate::parsers::segments::SqlSegment;
3use crate::states::SchemaValidationError;
4use sqlparser::parser::ParserError;
5
6#[derive(Debug, thiserror::Error)]
7pub enum ParseError {
8 #[error("{0}")]
10 SchemaValidation(#[from] SchemaValidationError),
11 #[error("{dialect} SQL parse error{location}: {message}")]
12 Parse {
13 dialect: &'static str,
14 message: String,
15 line: Option<usize>,
16 column: Option<usize>,
17 segment_ordinal: Option<usize>,
18 segment_start_line: Option<usize>,
19 segment_start_column: Option<usize>,
20 location: String,
21 },
22 #[error("unsupported {dialect} SQL statement '{statement}': {reason}")]
23 UnsupportedStatement {
24 dialect: &'static str,
25 statement: String,
26 reason: String,
27 },
28 #[error("{dialect} SQL segmentation error at line {line}, column {column}: {reason}")]
29 Segment {
30 dialect: &'static str,
31 line: usize,
32 column: usize,
33 reason: String,
34 },
35 #[error("unsupported SQL dialect '{0}'")]
36 UnsupportedDialect(String),
37 #[error("CREATE INDEX references unknown table '{table}'")]
38 UnknownTable { table: String },
39 #[error("CREATE TRIGGER references unknown table '{table}'")]
40 UnknownTriggerTable { table: String },
41 #[error("duplicate table '{0}'")]
42 DuplicateTable(String),
43 #[error("cannot read '{path}': {source}")]
44 Io {
45 path: String,
46 #[source]
47 source: std::io::Error,
48 },
49}
50
51impl ParseError {
52 pub(crate) fn parse_in_segment(
53 dialect: Dialect,
54 segment: &SqlSegment,
55 error: ParserError,
56 ) -> Self {
57 let message = error.to_string();
58 let (line, column) = extract_sqlparser_location(&message)
59 .map(|(line, column)| remap_segment_location(segment, line, column))
60 .unwrap_or((None, None));
61 let location = parse_location_suffix(line, column, segment);
62
63 Self::Parse {
64 dialect: dialect.as_str(),
65 message,
66 line,
67 column,
68 segment_ordinal: Some(segment.ordinal),
69 segment_start_line: Some(segment.start_line),
70 segment_start_column: Some(segment.start_column),
71 location,
72 }
73 }
74
75 pub(crate) fn unsupported(
76 dialect: Dialect,
77 statement: impl Into<String>,
78 reason: impl Into<String>,
79 ) -> Self {
80 Self::UnsupportedStatement {
81 dialect: dialect.as_str(),
82 statement: statement.into(),
83 reason: reason.into(),
84 }
85 }
86
87 pub(crate) fn segment(
88 dialect: Dialect,
89 line: usize,
90 column: usize,
91 reason: impl Into<String>,
92 ) -> Self {
93 Self::Segment {
94 dialect: dialect.as_str(),
95 line,
96 column,
97 reason: reason.into(),
98 }
99 }
100}
101
102fn extract_sqlparser_location(message: &str) -> Option<(usize, usize)> {
103 let (_, location) = message.rsplit_once(" at Line: ")?;
104 let (line, column) = location.split_once(", Column: ")?;
105 Some((line.parse().ok()?, column.parse().ok()?))
106}
107
108fn remap_segment_location(
109 segment: &SqlSegment,
110 line: usize,
111 column: usize,
112) -> (Option<usize>, Option<usize>) {
113 let source_line = segment.start_line + line.saturating_sub(1);
114 let source_column = if line == 1 {
115 segment.start_column + column.saturating_sub(1)
116 } else {
117 column
118 };
119 (Some(source_line), Some(source_column))
120}
121
122fn parse_location_suffix(
123 line: Option<usize>,
124 column: Option<usize>,
125 segment: &SqlSegment,
126) -> String {
127 match (line, column) {
128 (Some(line), Some(column)) => format!(" at line {line}, column {column}"),
129 _ => format!(
130 " near segment {} starting at line {}, column {}",
131 segment.ordinal, segment.start_line, segment.start_column
132 ),
133 }
134}