Skip to content

Commit f62fa55

Browse files
committed
자동 포멧팅 기능 개선
1 parent ce61139 commit f62fa55

60 files changed

Lines changed: 3496 additions & 175 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/sql_parser_engine/engine.rs

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,14 +1390,99 @@ impl SqlParserEngine {
13901390
let mut scratch_chars = std::mem::take(&mut self.scratch_chars);
13911391
scratch_chars.clear();
13921392
scratch_chars.extend(text.chars());
1393-
let mut on_symbol = |_: &[char], _: usize, _: char, _: Option<char>| {};
1394-
let mut on_statement_boundary = |_: &[char], _: usize| {};
1395-
self.process_chars_with_observer(
1396-
&scratch_chars,
1397-
&mut on_symbol,
1398-
&mut on_statement_boundary,
1399-
&mut on_lexical_span,
1400-
);
1393+
let mut line_start = 0usize;
1394+
1395+
while line_start < scratch_chars.len() {
1396+
let line_end = scratch_chars[line_start..]
1397+
.iter()
1398+
.position(|ch| *ch == '\n')
1399+
.map_or(scratch_chars.len(), |offset| line_start + offset);
1400+
let chunk_end = usize::from(line_end < scratch_chars.len())
1401+
.saturating_add(line_end)
1402+
.min(scratch_chars.len());
1403+
let line: String = scratch_chars[line_start..line_end].iter().collect();
1404+
let line_started_with_empty_current = self.current_is_empty();
1405+
let current_has_only_sql_comments =
1406+
next_meaningful_word(self.current.as_str(), 0).is_none();
1407+
let current_is_effectively_empty =
1408+
line_started_with_empty_current || current_has_only_sql_comments;
1409+
let line_started_in_with_waiting_main_query =
1410+
self.state.in_with_plsql_declaration()
1411+
&& self.state.with_clause_waiting_main_query()
1412+
&& self.state.block_depth() == 0
1413+
&& self.state.paren_depth() == 0;
1414+
self.state
1415+
.close_foreign_module_source_on_terminator_line(&line);
1416+
let line_boundary_action = self
1417+
.state
1418+
.line_boundary_action_for_line(&line, current_is_effectively_empty);
1419+
if self.apply_line_boundary_action(line_boundary_action) {
1420+
line_start = chunk_end;
1421+
continue;
1422+
}
1423+
let line_starts_at_statement_boundary = self.state.is_idle()
1424+
&& self.state.block_depth() == 0
1425+
&& self.state.paren_depth() == 0
1426+
&& !self.state.in_with_plsql_declaration()
1427+
&& current_is_effectively_empty;
1428+
let sqlplus_remark_line = sql_text::is_sqlplus_remark_comment_line(&line);
1429+
let auto_terminated_tool_command = sqlplus_remark_line
1430+
|| (sql_text::is_auto_terminated_tool_command(&line)
1431+
&& !self.line_is_mysql_server_statement(&line));
1432+
1433+
if (line_starts_at_statement_boundary
1434+
|| (line_started_in_with_waiting_main_query && self.state.is_idle()))
1435+
&& auto_terminated_tool_command
1436+
{
1437+
let is_comment_like_tool_line = sql_text::first_meaningful_word(&line)
1438+
.is_some_and(|word| word.eq_ignore_ascii_case("PROMPT"));
1439+
if is_comment_like_tool_line {
1440+
on_lexical_span(
1441+
&scratch_chars,
1442+
line_start,
1443+
line_end,
1444+
LexicalKind::LineComment,
1445+
);
1446+
}
1447+
self.append_current_str(&line);
1448+
if chunk_end > line_end {
1449+
self.append_current_char('\n');
1450+
}
1451+
self.finish_current_statement();
1452+
line_start = chunk_end;
1453+
continue;
1454+
}
1455+
1456+
let mut on_symbol = |_: &[char], _: usize, _: char, _: Option<char>| {};
1457+
let mut on_statement_boundary = |_: &[char], _: usize| {};
1458+
let mut offset_lexical_span =
1459+
|_: &[char], start: usize, end: usize, kind: LexicalKind| {
1460+
on_lexical_span(
1461+
&scratch_chars,
1462+
line_start.saturating_add(start),
1463+
line_start.saturating_add(end),
1464+
kind,
1465+
);
1466+
};
1467+
self.process_chars_with_observer(
1468+
&scratch_chars[line_start..chunk_end],
1469+
&mut on_symbol,
1470+
&mut on_statement_boundary,
1471+
&mut offset_lexical_span,
1472+
);
1473+
self.state.clear_skip_next_end_label_token();
1474+
1475+
if (line_started_with_empty_current || line_started_in_with_waiting_main_query)
1476+
&& self.state.is_idle()
1477+
&& self.state.block_depth() == 0
1478+
&& self.state.paren_depth() == 0
1479+
&& auto_terminated_tool_command
1480+
{
1481+
self.finish_current_statement();
1482+
}
1483+
1484+
line_start = chunk_end;
1485+
}
14011486
self.scratch_chars = scratch_chars;
14021487
}
14031488

src/sql_parser_engine/tests.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,71 @@ fn lexical_spans_apply_mysql_escape_and_dash_comment_rules() {
6868
}));
6969
}
7070

71+
#[test]
72+
fn lexical_spans_treat_sqlplus_prompt_payload_as_opaque_line_text() {
73+
let sql = concat!(
74+
"PROMPT [w10] don't stop; keep going -- not a SQL comment\n",
75+
"SELECT 1 AS \"it's\", q'\"double quoted q\"' AS q_value FROM dual;\n",
76+
"SELECT \"SELECT\" + \"order\" FROM quoted_words;"
77+
);
78+
let spans = lexical_spans(sql, false);
79+
80+
let prompt_start = sql.find("PROMPT").expect("PROMPT line");
81+
let prompt_end = sql.find('\n').expect("PROMPT line end");
82+
assert!(spans.iter().any(|span| {
83+
span.kind == LexicalKind::LineComment
84+
&& span.start == prompt_start
85+
&& span.end == prompt_end
86+
}));
87+
88+
for quoted in ["\"it's\"", "\"SELECT\"", "\"order\""] {
89+
let start = sql.find(quoted).expect("quoted identifier");
90+
assert!(spans.iter().any(|span| {
91+
span.kind == LexicalKind::QuotedIdentifier
92+
&& span.start <= start
93+
&& start + quoted.len() <= span.end
94+
}));
95+
}
96+
97+
let q_quote = "q'\"double quoted q\"'";
98+
let q_quote_start = sql.find(q_quote).expect("q-quote");
99+
assert!(spans.iter().any(|span| {
100+
span.kind == LexicalKind::String
101+
&& span.start <= q_quote_start
102+
&& q_quote_start + q_quote.len() <= span.end
103+
}));
104+
}
105+
106+
#[test]
107+
fn lexical_spans_keep_oracle_final_hardcore_wave10_quotes_bounded() {
108+
let sql = include_str!("../../test/final_hardcore.sql");
109+
let spans = lexical_spans(sql, false);
110+
111+
let prompt_apostrophe = sql.find("don't stop").expect("PROMPT apostrophe") + 3;
112+
assert_eq!(
113+
super::lexical_kind_at(&spans, prompt_apostrophe),
114+
Some(LexicalKind::LineComment),
115+
"PROMPT payload must not open a SQL string"
116+
);
117+
118+
for quoted in ["\"it's\"", "\"trailing space \"", "\"SELECT\"", "\"order\""] {
119+
let start = sql.find(quoted).expect("quoted identifier");
120+
assert_eq!(
121+
super::lexical_kind_at(&spans, start),
122+
Some(LexicalKind::QuotedIdentifier),
123+
"{quoted} must start its own quoted-identifier span"
124+
);
125+
assert!(
126+
spans.iter().any(|span| {
127+
span.kind == LexicalKind::QuotedIdentifier
128+
&& span.start == start
129+
&& span.end == start + quoted.len()
130+
}),
131+
"{quoted} must close without contaminating following SQL"
132+
);
133+
}
134+
}
135+
71136
#[test]
72137
fn pending_subprogram_begin_counter_does_not_underflow_on_malformed_nested_end() {
73138
let mut state = SplitState {

src/sql_text.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@ pub const ORACLE_SQL_KEYWORDS: &[&str] = &[
4949
"BETWEEN",
5050
"BFILE",
5151
"BINARY_DOUBLE",
52+
"BINARY_DOUBLE_INFINITY",
53+
"BINARY_DOUBLE_NAN",
5254
"BINARY_FLOAT",
55+
"BINARY_FLOAT_INFINITY",
56+
"BINARY_FLOAT_NAN",
5357
"BINARY_INTEGER",
5458
"BITMAP",
5559
"BLOB",

src/ui/builtin_signatures.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7459,6 +7459,7 @@ pub const MARIADB_FUNCTIONS: &[&str] = &[
74597459
"REVERSE",
74607460
"RIGHT",
74617461
"ROUND",
7462+
"ROWNUM",
74627463
"ROW_COUNT",
74637464
"ROW_NUMBER",
74647465
"RPAD",
@@ -9773,6 +9774,13 @@ const MARIADB_SIGNATURES: &[BuiltinSignature] = &[
97739774
],
97749775
argument_separator_keywords: &[],
97759776
},
9777+
BuiltinSignature {
9778+
name: "ROWNUM",
9779+
syntaxes: &[
9780+
"ROWNUM()",
9781+
],
9782+
argument_separator_keywords: &[],
9783+
},
97769784
BuiltinSignature {
97779785
name: "ROW_COUNT",
97789786
syntaxes: &[

src/ui/builtin_signatures_live_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ fn assert_catalog(db_type: DatabaseType, names: &[&str], expected_len: usize) {
3232
fn builtin_signature_catalogs_match_official_manual_indices() {
3333
assert_catalog(DatabaseType::Oracle, ORACLE_FUNCTIONS, 474);
3434
assert_catalog(DatabaseType::MySQL, MYSQL_FUNCTIONS, 408);
35-
assert_catalog(DatabaseType::MariaDB, MARIADB_FUNCTIONS, 477);
35+
assert_catalog(DatabaseType::MariaDB, MARIADB_FUNCTIONS, 478);
3636
}
3737

3838
#[derive(Clone, Copy, Debug, PartialEq, Eq)]

src/ui/sql_editor/format_sweep_tests.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2078,10 +2078,10 @@ COMMIT AND NO CHAIN;"#;
20782078

20792079
#[test]
20802080
fn formatting_sweep_mysql_xa_and_show_server_statements_are_stable() {
2081-
let source = r#"XA START 'sq-final-xa';
2082-
XA END 'sq-final-xa';
2083-
XA PREPARE 'sq-final-xa';
2084-
XA COMMIT 'sq-final-xa';
2081+
let source = r#"XA START 'sq-final-xa', 'branch-one', 1;
2082+
XA END 'sq-final-xa', 'branch-one', 1;
2083+
XA PREPARE 'sq-final-xa', 'branch-one', 1;
2084+
XA COMMIT 'sq-final-xa', 'branch-one', 1;
20852085
SHOW CREATE DATABASE sq_manual_final;
20862086
SHOW CREATE TABLE sq_manual_table;"#;
20872087

@@ -2093,7 +2093,17 @@ SHOW CREATE TABLE sq_manual_table;"#;
20932093
run.issues,
20942094
run.formatted
20952095
);
2096-
assert!(run.formatted.contains("XA START 'sq-final-xa';"));
2096+
assert!(
2097+
run.formatted.contains("XA START 'sq-final-xa',")
2098+
&& run.formatted.contains("'branch-one',")
2099+
&& run.formatted.contains("1;"),
2100+
"{db_type:?} XA three-part XID was not preserved:\n{}",
2101+
run.formatted
2102+
);
2103+
assert!(
2104+
run.managed_list_owner_kinds.contains(&ListOwnerKind::XaXid),
2105+
"{db_type:?} XA XID components must have a typed list owner"
2106+
);
20972107
assert!(run
20982108
.formatted
20992109
.contains("SHOW CREATE DATABASE sq_manual_final;"));
@@ -3775,7 +3785,7 @@ fn formatting_sweep_list_owner_inventory_covers_every_typed_variant() {
37753785
),
37763786
(
37773787
DatabaseType::MySQL,
3778-
"UPDATE t1, t2 SET t1.v = 1, t2.v = 2; ALTER TABLE t1 ADD a INT, ADD b INT; DROP TABLE IF EXISTS old_a, old_b; RENAME TABLE old_c TO new_c, old_d TO new_d; LOCK TABLES new_c READ, new_d WRITE; ANALYZE TABLE new_c, new_d; CREATE USER user_a IDENTIFIED BY 'x', user_b IDENTIFIED BY 'y';",
3788+
"UPDATE t1, t2 SET t1.v = 1, t2.v = 2; ALTER TABLE t1 ADD a INT, ADD b INT; DROP TABLE IF EXISTS old_a, old_b; RENAME TABLE old_c TO new_c, old_d TO new_d; LOCK TABLES new_c READ, new_d WRITE; ANALYZE TABLE new_c, new_d; CREATE USER user_a IDENTIFIED BY 'x', user_b IDENTIFIED BY 'y'; XA START 'gtrid', 'branch', 1;",
37793789
),
37803790
];
37813791

0 commit comments

Comments
 (0)