Skip to content

Commit cc4edab

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

26 files changed

Lines changed: 3431 additions & 427 deletions

src/db/query/mysql_executor.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2166,7 +2166,7 @@ impl MysqlObjectBrowser {
21662166
pub fn get_schemas(conn: &mut Conn) -> Result<Vec<String>, MysqlError> {
21672167
let rows: Vec<String> = conn.query(
21682168
"SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA \
2169-
WHERE SCHEMA_NAME NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys') \
2169+
WHERE SCHEMA_NAME NOT IN ('information_schema', 'mysql', 'performance_schema') \
21702170
ORDER BY SCHEMA_NAME",
21712171
)?;
21722172
Ok(rows)
@@ -2182,20 +2182,20 @@ impl MysqlObjectBrowser {
21822182
ELSE TABLE_TYPE \
21832183
END AS OBJECT_TYPE \
21842184
FROM INFORMATION_SCHEMA.TABLES \
2185-
WHERE TABLE_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys') \
2185+
WHERE TABLE_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema') \
21862186
AND TABLE_TYPE IN ('BASE TABLE', 'VIEW', 'SEQUENCE') \
21872187
UNION ALL \
21882188
SELECT ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE \
21892189
FROM INFORMATION_SCHEMA.ROUTINES \
2190-
WHERE ROUTINE_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys') \
2190+
WHERE ROUTINE_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema') \
21912191
UNION ALL \
21922192
SELECT TRIGGER_SCHEMA, TRIGGER_NAME, 'TRIGGER' \
21932193
FROM INFORMATION_SCHEMA.TRIGGERS \
2194-
WHERE TRIGGER_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys') \
2194+
WHERE TRIGGER_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema') \
21952195
UNION ALL \
21962196
SELECT EVENT_SCHEMA, EVENT_NAME, 'EVENT' \
21972197
FROM INFORMATION_SCHEMA.EVENTS \
2198-
WHERE EVENT_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys') \
2198+
WHERE EVENT_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema') \
21992199
ORDER BY 1, 2, 3",
22002200
)?;
22012201

@@ -2214,7 +2214,7 @@ impl MysqlObjectBrowser {
22142214
) -> Result<HashMap<String, Vec<String>>, MysqlError> {
22152215
let rows: Vec<(String, String)> = conn.query(
22162216
"SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES \
2217-
WHERE TABLE_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys') \
2217+
WHERE TABLE_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema') \
22182218
AND TABLE_TYPE IN ('BASE TABLE', 'VIEW') \
22192219
ORDER BY 1, 2",
22202220
)?;

src/db/query/query_tests.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7807,6 +7807,28 @@ fn test_set_null_command_parsed() {
78077807
);
78087808
}
78097809

7810+
#[test]
7811+
fn sqlplus_quoted_empty_and_whitespace_text_arguments_round_trip_semantically() {
7812+
let sql = "SET NULL ''\nSET COLSEP ' '\nSET NULL 'can''t render'";
7813+
let items = QueryExecutor::split_script_items(sql);
7814+
7815+
assert!(matches!(
7816+
items.first(),
7817+
Some(ScriptItem::ToolCommand(ToolCommand::SetNull { null_text }))
7818+
if null_text.is_empty()
7819+
));
7820+
assert!(matches!(
7821+
items.get(1),
7822+
Some(ScriptItem::ToolCommand(ToolCommand::SetColSep { separator }))
7823+
if separator == " "
7824+
));
7825+
assert!(matches!(
7826+
items.get(2),
7827+
Some(ScriptItem::ToolCommand(ToolCommand::SetNull { null_text }))
7828+
if null_text == "can't render"
7829+
));
7830+
}
7831+
78107832
#[test]
78117833
fn test_spool_file_command_parsed() {
78127834
let sql = "SPOOL output.log";

src/db/query/script.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11497,7 +11497,7 @@ impl QueryExecutor {
1149711497
};
1149811498
}
1149911499

11500-
let separator = rest.trim_matches('\'').trim_matches('"').to_string();
11500+
let separator = Self::parse_sqlplus_text_argument(rest);
1150111501
ToolCommand::SetColSep { separator }
1150211502
}
1150311503

@@ -11511,10 +11511,26 @@ impl QueryExecutor {
1151111511
};
1151211512
}
1151311513

11514-
let null_text = rest.trim_matches('\'').trim_matches('"').to_string();
11514+
let null_text = Self::parse_sqlplus_text_argument(rest);
1151511515
ToolCommand::SetNull { null_text }
1151611516
}
1151711517

11518+
fn parse_sqlplus_text_argument(raw: &str) -> String {
11519+
if let Some(inner) = raw
11520+
.strip_prefix('\'')
11521+
.and_then(|value| value.strip_suffix('\''))
11522+
{
11523+
return inner.replace("''", "'");
11524+
}
11525+
if let Some(inner) = raw
11526+
.strip_prefix('"')
11527+
.and_then(|value| value.strip_suffix('"'))
11528+
{
11529+
return inner.replace("\"\"", "\"");
11530+
}
11531+
raw.to_string()
11532+
}
11533+
1151811534
fn parse_script_command(raw: &str) -> ToolCommand {
1151911535
let trimmed = raw.trim();
1152011536
let (relative_to_caller, command_label, path) = if trimmed.starts_with("@@") {

src/ui/sql_editor/format_sweep_tests.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4653,6 +4653,98 @@ fn mysql_declare_condition_for_clause_stays_inline() {
46534653
);
46544654
}
46554655

4656+
#[test]
4657+
fn wave11_mysql_insert_scalar_function_keeps_select_list_frame() {
4658+
let source = "SELECT ELT(2, 'alpha', 'beta') AS picked,\n INSERT('abcdef', 2, 3, 'XY') AS spliced,\n SUBSTRING_INDEX('a.b.c', '.', -2) AS tail_parts,\n BIT_COUNT(255) AS bits_set;";
4659+
let run = format_sweep_run(source, DatabaseType::MySQL);
4660+
4661+
assert!(
4662+
run.issues.is_empty(),
4663+
"INSERT(...) must remain a scalar SELECT item: {:#?}\n{}",
4664+
run.issues,
4665+
run.formatted
4666+
);
4667+
}
4668+
4669+
#[test]
4670+
fn wave11_mariadb_identified_via_or_keeps_authentication_chain_structural() {
4671+
let source = "CREATE OR REPLACE USER demo@localhost\n IDENTIFIED VIA mysql_native_password USING PASSWORD('secret')\n OR unix_socket\n WITH MAX_QUERIES_PER_HOUR 120;";
4672+
let run = format_sweep_run(source, DatabaseType::MariaDB);
4673+
4674+
assert!(
4675+
run.issues.is_empty(),
4676+
"authentication-plugin OR must not become a condition connector: {:#?}\n{}",
4677+
run.issues,
4678+
run.formatted
4679+
);
4680+
}
4681+
4682+
#[test]
4683+
fn wave11_oracle_create_schema_formats_embedded_schema_elements_independently() {
4684+
let source = "CREATE SCHEMA AUTHORIZATION system\n CREATE TABLE demo_ledger (\n entry_id NUMBER PRIMARY KEY,\n amount NUMBER(9, 2))\n CREATE VIEW demo_ledger_v AS\n SELECT entry_id, amount FROM demo_ledger WHERE amount >= 0\n GRANT SELECT ON demo_ledger TO PUBLIC;";
4685+
let run = format_sweep_run(source, DatabaseType::Oracle);
4686+
4687+
assert!(
4688+
run.issues.is_empty(),
4689+
"CREATE SCHEMA elements must remain one stable executable statement: {:#?}\n{}",
4690+
run.issues,
4691+
run.formatted
4692+
);
4693+
assert!(
4694+
run.formatted.contains("\n CREATE TABLE demo_ledger")
4695+
&& run.formatted.contains("\n CREATE VIEW demo_ledger_v")
4696+
&& run
4697+
.formatted
4698+
.contains("\n GRANT SELECT ON demo_ledger TO PUBLIC;"),
4699+
"schema elements must be sibling children of CREATE SCHEMA:\n{}",
4700+
run.formatted
4701+
);
4702+
}
4703+
4704+
#[test]
4705+
fn wave11_oracle_dml_hint_target_clause_is_whitespace_independent() {
4706+
let source = "DELETE /*+ FULL(d) */ FROM demo_ledger d\nWHERE d.amount < 0;\nINSERT /*+ APPEND_VALUES */ INTO demo_ledger (entry_id, amount)\nVALUES (9, 9);";
4707+
let run = format_sweep_run(source, DatabaseType::Oracle);
4708+
4709+
assert!(
4710+
run.issues.is_empty(),
4711+
"DML hints must not make target-clause indentation source-dependent: {:#?}\n{}",
4712+
run.issues,
4713+
run.formatted
4714+
);
4715+
assert!(
4716+
run.formatted
4717+
.contains("DELETE /*+ FULL(d) */\nFROM demo_ledger d"),
4718+
"FROM must use DELETE's owner depth after an inline hint:\n{}",
4719+
run.formatted
4720+
);
4721+
assert!(
4722+
run.formatted
4723+
.contains("INSERT /*+ APPEND_VALUES */\nINTO demo_ledger"),
4724+
"INTO must use INSERT's owner depth after an inline hint:\n{}",
4725+
run.formatted
4726+
);
4727+
}
4728+
4729+
#[test]
4730+
fn wave11_oracle_sqlplus_empty_and_whitespace_values_remain_executable() {
4731+
let source =
4732+
"SET NULL ''\nSET COLSEP ' '\nSET TAB ON\nSELECT 'still executable' AS status FROM dual;";
4733+
let run = format_sweep_run(source, DatabaseType::Oracle);
4734+
4735+
assert!(
4736+
run.issues.is_empty(),
4737+
"quoted SQL*Plus values must survive formatting: {:#?}\n{}",
4738+
run.issues,
4739+
run.formatted
4740+
);
4741+
assert!(
4742+
run.formatted.contains("SET NULL ''") && run.formatted.contains("SET COLSEP ' '"),
4743+
"empty and whitespace-only values require their quotes after formatting:\n{}",
4744+
run.formatted
4745+
);
4746+
}
4747+
46564748
#[test]
46574749
fn fetch_first_comment_continuation_stays_one_deeper_after_match_recognize_close() {
46584750
let source = "CREATE OR REPLACE PROCEDURE p IS r SYS_REFCURSOR; BEGIN OPEN r FOR SELECT * FROM e MATCH_RECOGNIZE (PARTITION BY d ORDER BY rn MEASURES FIRST (n) AS s ONE ROW PER MATCH PATTERN (A B+) DEFINE B AS B.sal > PREV (B.sal)) FETCH FIRST /* BV */ 20 ROWS ONLY; END p;";

0 commit comments

Comments
 (0)