sql_ddl_to_json_schema/
parser.rs1use serde_json::{json, Value};
4
5use crate::compact;
6use crate::grammar::parse_statement;
7use crate::json_schema;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum Error {
16 UnsupportedDialect(String),
18 Parse(String),
21 Format(String),
23}
24
25impl std::fmt::Display for Error {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 match self {
28 Error::UnsupportedDialect(d) => write!(
29 f,
30 "Unsupported SQL dialect given to parser: '{}'. Please provide 'mysql', 'mariadb' or none to use default.",
31 d
32 ),
33 Error::Parse(m) => write!(f, "{}", m),
34 Error::Format(m) => write!(f, "{}", m),
35 }
36 }
37}
38
39impl std::error::Error for Error {}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct JsonSchemaOptions {
44 pub use_ref: bool,
47}
48
49impl Default for JsonSchemaOptions {
50 fn default() -> Self {
51 JsonSchemaOptions { use_ref: true }
52 }
53}
54
55pub struct Parser {
69 statements: Vec<String>,
70 remains: String,
71 escaped: bool,
72 quoted: Option<char>,
73}
74
75impl Parser {
76 pub fn new(dialect: &str) -> Result<Self, Error> {
81 if dialect.is_empty() || dialect == "mysql" || dialect == "mariadb" {
82 Ok(Parser {
83 statements: Vec::new(),
84 remains: String::new(),
85 escaped: false,
86 quoted: None,
87 })
88 } else {
89 Err(Error::UnsupportedDialect(dialect.to_string()))
90 }
91 }
92
93 pub fn feed(&mut self, chunk: &str) -> &mut Self {
96 let units: Vec<u16> = chunk.encode_utf16().collect();
98 let mut parsed: Vec<u16> = Vec::with_capacity(units.len());
99 let mut last_statement_index = 0usize;
100
101 for (i, &unit) in units.iter().enumerate() {
102 parsed.push(unit);
103 let ch = char::from_u32(unit as u32);
104
105 if ch == Some('\\') {
106 self.escaped = !self.escaped;
107 } else {
108 if !self.escaped && is_quote_char(ch) {
109 let c = ch.unwrap();
110 match self.quoted {
111 Some(open) => {
112 if open == c {
113 self.quoted = None;
114 }
115 }
116 None => self.quoted = Some(c),
117 }
118 } else if ch == Some(';') && self.quoted.is_none() {
119 let slice = &parsed[last_statement_index..i + 1];
120 let statement = format!("{}{}", self.remains, utf16_to_string(slice));
121 self.statements.push(statement);
122 self.remains.clear();
123 last_statement_index = i + 1;
124 }
125 self.escaped = false;
126 }
127 }
128
129 let tail = &parsed[last_statement_index..];
130 self.remains.push_str(&utf16_to_string(tail));
131 self
132 }
133
134 pub fn parse(&mut self) -> Result<Value, Error> {
153 let mut line_count = 1usize;
154 let mut results = Vec::new();
155
156 let statements = std::mem::take(&mut self.statements);
157 for statement in statements {
158 if statement.is_empty() {
159 break;
160 }
161 match parse_statement(&statement) {
162 Ok(node) => {
163 line_count += count_line_breaks(&statement);
164 results.push(node);
165 }
166 Err(e) => {
167 let error_line = e.line;
168 let new_count = line_count + error_line - 1;
169 self.statements.clear();
171 self.remains.clear();
172 self.escaped = false;
173 self.quoted = None;
174 return Err(Error::Parse(format!(
175 "invalid syntax at line {}",
176 new_count
177 )));
178 }
179 }
180 }
181
182 self.remains.clear();
183 self.escaped = false;
184 self.quoted = None;
185
186 Ok(json!({ "id": "MAIN", "def": results }))
187 }
188
189 pub fn parse_compact(&mut self) -> Result<Vec<Value>, Error> {
194 let tree = self.parse()?;
195 compact_from_tree(&tree)
196 }
197
198 pub fn parse_json_schema(&mut self, options: JsonSchemaOptions) -> Result<Vec<Value>, Error> {
205 let tables = self.parse_compact()?;
206 Ok(json_schema_from_tables(&tables, options))
207 }
208}
209
210pub fn compact_from_tree(tree: &Value) -> Result<Vec<Value>, Error> {
215 compact::format(tree).map_err(Error::Format)
216}
217
218#[must_use]
223pub fn json_schema_from_tables(tables: &[Value], options: JsonSchemaOptions) -> Vec<Value> {
224 json_schema::format(tables, options)
225}
226
227fn is_quote_char(ch: Option<char>) -> bool {
229 matches!(ch, Some('"') | Some('\'') | Some('`'))
230}
231
232fn count_line_breaks(s: &str) -> usize {
234 let bytes = s.as_bytes();
235 let mut count = 0;
236 let mut i = 0;
237 while i < bytes.len() {
238 match bytes[i] {
239 b'\r' => {
240 if i + 1 < bytes.len() && bytes[i + 1] == b'\n' {
241 i += 2;
242 } else {
243 i += 1;
244 }
245 count += 1;
246 }
247 b'\n' => {
248 count += 1;
249 i += 1;
250 }
251 _ => i += 1,
252 }
253 }
254 count
255}
256
257fn utf16_to_string(units: &[u16]) -> String {
260 String::from_utf16_lossy(units)
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
271 fn tracks_escape_quote_and_statements() {
272 let mut p = Parser::new("mysql").unwrap();
273
274 p.feed("a");
276 assert!(!p.escaped);
277 assert_eq!(p.quoted, None);
278
279 p.feed("\\");
281 assert!(p.escaped);
282 assert_eq!(p.quoted, None);
283
284 p.feed("\\");
286 assert!(!p.escaped);
287 assert_eq!(p.quoted, None);
288
289 p.feed("\\");
290 assert!(p.escaped);
291 p.feed("n");
292 assert!(!p.escaped);
293 assert_eq!(p.quoted, None);
294
295 p.feed("\"");
297 assert!(!p.escaped);
298 assert_eq!(p.quoted, Some('"'));
299 p.feed("\"");
300 assert_eq!(p.quoted, None);
301 p.feed("\"a");
302 assert_eq!(p.quoted, Some('"'));
303 p.feed("a\"");
304 assert_eq!(p.quoted, None);
305
306 p.feed("'");
308 assert_eq!(p.quoted, Some('\''));
309 p.feed("'");
310 assert_eq!(p.quoted, None);
311 p.feed("'a");
312 assert_eq!(p.quoted, Some('\''));
313 p.feed("a'");
314 assert_eq!(p.quoted, None);
315
316 p.feed("`");
318 assert_eq!(p.quoted, Some('`'));
319 p.feed("`");
320 assert_eq!(p.quoted, None);
321 p.feed("`a");
322 assert_eq!(p.quoted, Some('`'));
323 p.feed("a`");
324 assert_eq!(p.quoted, None);
325
326 p.feed("`\\`");
328 assert!(!p.escaped);
329 assert_eq!(p.quoted, Some('`'));
330 p.feed("\\\\`");
331 assert_eq!(p.quoted, None);
332
333 p.feed("\"\\");
334 assert!(p.escaped);
335 assert_eq!(p.quoted, Some('"'));
336 p.feed("\"a`");
337 assert_eq!(p.quoted, Some('"'));
338 p.feed("'\\'");
339 assert_eq!(p.quoted, Some('"'));
340 p.feed("\"");
341 assert_eq!(p.quoted, None);
342
343 p.feed("\\;");
345 assert!(!p.escaped);
346 assert_eq!(p.quoted, None);
347 assert_eq!(p.statements.len(), 1);
348
349 p.feed("a;");
351 assert_eq!(p.statements.len(), 2);
352
353 p.feed("a\";");
355 assert_eq!(p.quoted, Some('"'));
356 assert_eq!(p.statements.len(), 2);
357
358 p.feed("a\";");
359 assert_eq!(p.quoted, None);
360 assert_eq!(p.statements.len(), 3);
361 }
362
363 #[test]
364 fn splits_on_utf16_code_units() {
365 let mut p = Parser::new("mysql").unwrap();
368 p.feed("USE \u{1F600};USE b;");
369 assert_eq!(p.statements.len(), 2);
370 }
371}