Skip to content

Commit 0612129

Browse files
committed
자동 포멧팅 기능 개선
1 parent 344a0c5 commit 0612129

4 files changed

Lines changed: 160 additions & 26 deletions

File tree

src/ui/sql_editor/intellisense/completion.rs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4424,22 +4424,27 @@ impl SqlEditorWidget {
44244424
Some(snapshot.preferred_db_type),
44254425
)
44264426
};
4427-
let bounded_query_column_suggestions =
4428-
if Self::cursor_is_at_plsql_keyword_only_slot_for_context(
4427+
// This fallback infers columns from the first bounded query
4428+
// relation and has no qualifier awareness. Using it for
4429+
// `<alias>.` can therefore leak a different relation's
4430+
// columns ahead of the resolved alias columns.
4431+
let bounded_query_column_suggestions = if qualifier.is_some()
4432+
|| Self::cursor_is_at_plsql_keyword_only_slot_for_context(
44294433
deep_ctx,
44304434
!snapshot.prefix.is_empty(),
44314435
Some(snapshot.preferred_db_type),
4432-
) {
4433-
Vec::new()
4434-
} else {
4435-
Self::oracle_bounded_query_column_suggestions(
4436-
&mut data,
4437-
&snapshot.signature_scan_text,
4438-
&snapshot.text_after_cursor,
4439-
&snapshot.prefix,
4440-
Some(snapshot.preferred_db_type),
4441-
)
4442-
};
4436+
)
4437+
{
4438+
Vec::new()
4439+
} else {
4440+
Self::oracle_bounded_query_column_suggestions(
4441+
&mut data,
4442+
&snapshot.signature_scan_text,
4443+
&snapshot.text_after_cursor,
4444+
&snapshot.prefix,
4445+
Some(snapshot.preferred_db_type),
4446+
)
4447+
};
44434448
for candidate in Self::oracle_bounded_clustering_column_suggestions(
44444449
&mut data,
44454450
&snapshot.signature_scan_text,
@@ -4471,7 +4476,8 @@ impl SqlEditorWidget {
44714476
Self::push_unique_completion_name(&mut suggestions, &candidate);
44724477
}
44734478
suggestions
4474-
} else if completion_db_type_is_mariadb(Some(snapshot.preferred_db_type))
4479+
} else if qualifier.is_none()
4480+
&& completion_db_type_is_mariadb(Some(snapshot.preferred_db_type))
44754481
&& Self::plsql_grammar_is_available(
44764482
deep_ctx.statement_tokens.as_ref(),
44774483
deep_ctx.cursor_token_len,

src/ui/sql_editor/intellisense/context.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,6 +1768,14 @@ impl SqlEditorWidget {
17681768
)
17691769
}
17701770

1771+
fn should_trigger_qualified_dot_immediately(
1772+
ch: char,
1773+
qualifier: Option<&str>,
1774+
compose_state: i32,
1775+
) -> bool {
1776+
ch == '.' && qualifier.is_some() && compose_state == 0
1777+
}
1778+
17711779
fn has_min_intellisense_prefix(word: &str) -> bool {
17721780
word.chars().next().is_some()
17731781
}

src/ui/sql_editor/intellisense/runtime.rs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1670,18 +1670,42 @@ impl SqlEditorWidget {
16701670
qualifier.as_deref(),
16711671
)
16721672
{
1673-
Self::schedule_keyup_intellisense_debounce(
1674-
&intellisense_runtime_for_handle,
1675-
cursor_pos,
1676-
buffer_len,
1677-
ed,
1678-
&buffer_for_handle,
1679-
&text_shadow_for_handle,
1680-
&intellisense_data_for_handle,
1681-
&intellisense_popup_for_handle,
1682-
&column_sender_for_handle,
1683-
&connection_for_handle,
1684-
);
1673+
if Self::should_trigger_qualified_dot_immediately(
1674+
ch,
1675+
qualifier.as_deref(),
1676+
app::compose_state(),
1677+
) {
1678+
Self::invalidate_keyup_debounce(
1679+
&intellisense_runtime_for_handle,
1680+
);
1681+
Self::clear_intellisense_ui_state(
1682+
&intellisense_popup_for_handle,
1683+
&intellisense_runtime_for_handle,
1684+
);
1685+
Self::trigger_intellisense(
1686+
ed,
1687+
&buffer_for_handle,
1688+
&text_shadow_for_handle,
1689+
&intellisense_data_for_handle,
1690+
&intellisense_popup_for_handle,
1691+
&column_sender_for_handle,
1692+
&connection_for_handle,
1693+
&intellisense_runtime_for_handle,
1694+
);
1695+
} else {
1696+
Self::schedule_keyup_intellisense_debounce(
1697+
&intellisense_runtime_for_handle,
1698+
cursor_pos,
1699+
buffer_len,
1700+
ed,
1701+
&buffer_for_handle,
1702+
&text_shadow_for_handle,
1703+
&intellisense_data_for_handle,
1704+
&intellisense_popup_for_handle,
1705+
&column_sender_for_handle,
1706+
&connection_for_handle,
1707+
);
1708+
}
16851709
} else {
16861710
Self::request_intellisense_popup_hide(
16871711
&intellisense_popup_for_handle,

src/ui/sql_editor/intellisense/tests.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11643,6 +11643,29 @@ fn fast_path_reanalyzes_when_prefix_crosses_qualifier_dot() {
1164311643
);
1164411644
}
1164511645

11646+
#[test]
11647+
fn qualified_dot_triggers_immediately_only_outside_ime_composition() {
11648+
assert!(
11649+
SqlEditorWidget::should_trigger_qualified_dot_immediately('.', Some("d"), 0),
11650+
"a completed qualifier dot must bypass the popup debounce"
11651+
);
11652+
assert!(!SqlEditorWidget::should_trigger_qualified_dot_immediately(
11653+
'.',
11654+
None,
11655+
0
11656+
));
11657+
assert!(!SqlEditorWidget::should_trigger_qualified_dot_immediately(
11658+
'd',
11659+
Some("d"),
11660+
0
11661+
));
11662+
assert!(!SqlEditorWidget::should_trigger_qualified_dot_immediately(
11663+
'.',
11664+
Some("d"),
11665+
1
11666+
));
11667+
}
11668+
1164611669
#[test]
1164711670
fn condition_comparison_suffix_ignores_bracket_identifier_dots() {
1164811671
assert_eq!(
@@ -91100,6 +91123,79 @@ fn qualified_alias_column_slot_yields_relation_columns() {
9110091123
}
9110191124
}
9110291125

91126+
#[test]
91127+
fn qualified_alias_production_completion_does_not_leak_other_relation_columns() {
91128+
use crate::db::DatabaseType::{MariaDB, MySQL, Oracle};
91129+
91130+
let contains = |values: &[String], needle: &str| {
91131+
values
91132+
.iter()
91133+
.any(|value| value.eq_ignore_ascii_case(needle))
91134+
};
91135+
let sql_slots = [
91136+
"SELECT d.| FROM emp e JOIN dept d ON e.deptno = d.deptno",
91137+
"SELECT * FROM emp e JOIN dept d ON d.|",
91138+
"SELECT * FROM emp e JOIN dept d ON e.deptno = d.deptno WHERE d.|",
91139+
];
91140+
91141+
for db_type in [Oracle, MariaDB, MySQL] {
91142+
for sql in sql_slots {
91143+
let suggestions = query_completion_suggestions_impl(sql, db_type, true);
91144+
for expected in ["DEPTNO", "DNAME"] {
91145+
assert!(
91146+
contains(&suggestions, expected),
91147+
"target alias column `{expected}` missing at `{sql}` {db_type:?}: {suggestions:?}"
91148+
);
91149+
}
91150+
for leaked in ["EMPNO", "ENAME", "SAL"] {
91151+
assert!(
91152+
!contains(&suggestions, leaked),
91153+
"unrelated first-relation column `{leaked}` leaked at `{sql}` {db_type:?}: {suggestions:?}"
91154+
);
91155+
}
91156+
}
91157+
91158+
let unresolved_sql =
91159+
"SELECT missing.| FROM emp e JOIN dept d ON e.deptno = d.deptno";
91160+
let unresolved =
91161+
query_completion_suggestions_impl(unresolved_sql, db_type, true);
91162+
for leaked in ["EMPNO", "ENAME", "DEPTNO", "DNAME", "SAL"] {
91163+
assert!(
91164+
!contains(&unresolved, leaked),
91165+
"unresolved qualifier leaked `{leaked}` at `{unresolved_sql}` {db_type:?}: {unresolved:?}"
91166+
);
91167+
}
91168+
}
91169+
}
91170+
91171+
#[test]
91172+
fn mariadb_oracle_mode_qualified_alias_does_not_use_unqualified_query_fallback() {
91173+
use crate::db::DatabaseType::MariaDB;
91174+
91175+
let sql = "DECLARE v_name VARCHAR2(100); BEGIN \
91176+
SELECT d.| INTO v_name FROM emp e JOIN dept d ON e.deptno = d.deptno; \
91177+
END;";
91178+
let suggestions = query_completion_suggestions_impl(sql, MariaDB, true);
91179+
let contains = |needle: &str| {
91180+
suggestions
91181+
.iter()
91182+
.any(|value| value.eq_ignore_ascii_case(needle))
91183+
};
91184+
91185+
for expected in ["DEPTNO", "DNAME"] {
91186+
assert!(
91187+
contains(expected),
91188+
"target alias column `{expected}` missing at `{sql}`: {suggestions:?}"
91189+
);
91190+
}
91191+
for leaked in ["EMPNO", "ENAME", "SAL"] {
91192+
assert!(
91193+
!contains(leaked),
91194+
"unqualified query fallback leaked `{leaked}` at `{sql}`: {suggestions:?}"
91195+
);
91196+
}
91197+
}
91198+
9110391199
#[test]
9110491200
fn mysql_alias_and_where_columns_work_for_lowercase_uncataloged_table() {
9110591201
use crate::db::DatabaseType::MySQL;

0 commit comments

Comments
 (0)