Skip to content

Commit 322b21d

Browse files
committed
자동 포멧팅 기능 개선
1 parent ca7baa2 commit 322b21d

77 files changed

Lines changed: 3442 additions & 143 deletions

Some content is hidden

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

crates/tns-thin/src/exec.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ pub enum OracleVectorValue {
157157
pub struct ColumnMetadata {
158158
pub name: String,
159159
pub column_type: OracleColumnType,
160+
pub precision: i8,
161+
pub scale: i8,
160162
pub charset_form: u8,
161163
pub ora_type_num: u8,
162164
pub buffer_size: u32,

crates/tns-thin/src/session.rs

Lines changed: 290 additions & 42 deletions
Large diffs are not rendered by default.

crates/tns-thin/tests/live_tns.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -990,13 +990,21 @@ fn fetch_interval_columns_decodes_vendor_formats() {
990990
OracleColumnType::IntervalDaySecond,
991991
]
992992
);
993+
assert_eq!(
994+
result
995+
.columns
996+
.iter()
997+
.map(|column| (column.precision, column.scale))
998+
.collect::<Vec<_>>(),
999+
vec![(9, 0), (9, 0), (9, 9), (9, 9)]
1000+
);
9931001
assert_eq!(
9941002
rows_to_strings(&result.result.rows),
9951003
vec![vec![
996-
"+2021-10".to_string(),
997-
"-05-03".to_string(),
998-
"+02 12:23:34.456000".to_string(),
999-
"-00 10:20:30.456789".to_string(),
1004+
"+000002021-10".to_string(),
1005+
"-000000005-03".to_string(),
1006+
"+000000002 12:23:34.456000000".to_string(),
1007+
"-000000000 10:20:30.456789000".to_string(),
10001008
]]
10011009
);
10021010
}

src/sql_text.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,8 @@ pub const MYSQL_SQL_KEYWORDS: &[&str] = &[
11841184
"OVERLAPS",
11851185
"PACK_KEYS",
11861186
"PAGE_CHECKSUM",
1187+
"PAGE_COMPRESSED",
1188+
"PAGE_COMPRESSION_LEVEL",
11871189
"PARSER",
11881190
"PARSE_VCOL_EXPR",
11891191
"PARTIAL",
@@ -2075,14 +2077,25 @@ pub(crate) fn is_mysql_dash_comment_start(bytes: &[u8], idx: usize) -> bool {
20752077
match bytes.get(idx + 2) {
20762078
None => true,
20772079
Some(byte) if byte.is_ascii_whitespace() || byte.is_ascii_control() => true,
2078-
// A dash ruler (`--------…` to end of line) is a comment banner, not a
2079-
// chain of minus operators. MySQL's `-- ` rule alone would open the
2080-
// comment at the *last* two dashes and re-render everything before it as
2081-
// operators, so re-formatting kept peeling the ruler apart.
2082-
Some(b'-') => bytes[idx + 2..]
2083-
.iter()
2084-
.take_while(|byte| **byte != b'\n' && **byte != b'\r')
2085-
.all(|byte| *byte == b'-' || byte.is_ascii_whitespace()),
2080+
// A dash ruler is a comment banner, not a chain of minus operators. The
2081+
// `-- ` rule applied to the first two dashes alone would open the comment
2082+
// at the *last* two dashes of the run and re-render everything before it
2083+
// as operators, so re-formatting kept peeling the ruler apart.
2084+
//
2085+
// The `-- ` rule is therefore applied to the whole dash *run*: whatever
2086+
// follows the run must be whitespace or end-of-line. That accepts both a
2087+
// bare ruler and a titled banner (`----- banner -----`), while leaving
2088+
// `---x` and `1---1` as operators, which is what the server computes.
2089+
Some(b'-') => {
2090+
let run_end = bytes[idx..]
2091+
.iter()
2092+
.position(|byte| *byte != b'-')
2093+
.map_or(bytes.len(), |offset| idx + offset);
2094+
match bytes.get(run_end) {
2095+
None => true,
2096+
Some(byte) => byte.is_ascii_whitespace() || byte.is_ascii_control(),
2097+
}
2098+
}
20862099
Some(_) => false,
20872100
}
20882101
}

src/ui/builtin_signatures.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7528,6 +7528,7 @@ pub const MARIADB_FUNCTIONS: &[&str] = &[
75287528
"UUID_SHORT",
75297529
"UUID_V4",
75307530
"UUID_V7",
7531+
"VALUE",
75317532
"VALUES",
75327533
"VARIANCE",
75337534
"VAR_POP",
@@ -10744,6 +10745,13 @@ const MARIADB_SIGNATURES: &[BuiltinSignature] = &[
1074410745
],
1074510746
argument_separator_keywords: &[],
1074610747
},
10748+
BuiltinSignature {
10749+
name: "VALUE",
10750+
syntaxes: &[
10751+
"VALUE(col_name)",
10752+
],
10753+
argument_separator_keywords: &[],
10754+
},
1074710755
BuiltinSignature {
1074810756
name: "VALUES",
1074910757
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, 464);
3434
assert_catalog(DatabaseType::MySQL, MYSQL_FUNCTIONS, 408);
35-
assert_catalog(DatabaseType::MariaDB, MARIADB_FUNCTIONS, 475);
35+
assert_catalog(DatabaseType::MariaDB, MARIADB_FUNCTIONS, 476);
3636
}
3737

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

src/ui/intellisense.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ const ORACLE_BUILTIN_PACKAGES: &[&str] = &[
5151
"DBMS_MVIEW",
5252
"DBMS_ALERT",
5353
"DBMS_PIPE",
54+
"DBMS_XMLGEN",
55+
"DBMS_ILM",
56+
"UTL_CALL_STACK",
5457
"UTL_FILE",
5558
"UTL_RAW",
5659
"UTL_HTTP",

src/ui/sql_editor/execution.rs

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14252,9 +14252,41 @@ impl SqlEditorWidget {
1425214252
),
1425314253
OracleValue::Lob(_) => Some("[LOB]".to_string()),
1425414254
OracleValue::Cursor(_) => Some("[CURSOR]".to_string()),
14255-
OracleValue::Object(_) => Some("[OBJECT]".to_string()),
14256-
OracleValue::Array(_) => Some("[ARRAY]".to_string()),
14257-
OracleValue::IndexedArray(_) => Some("[INDEXED ARRAY]".to_string()),
14255+
OracleValue::Object(_) | OracleValue::Array(_) | OracleValue::IndexedArray(_) => {
14256+
serde_json::to_string(&Self::oracle_thin_value_json(value)).ok()
14257+
}
14258+
}
14259+
}
14260+
14261+
fn oracle_thin_value_json(value: &OracleValue) -> serde_json::Value {
14262+
match value {
14263+
OracleValue::Null => serde_json::Value::Null,
14264+
OracleValue::Number(value) => value
14265+
.parse::<serde_json::Number>()
14266+
.map(serde_json::Value::Number)
14267+
.unwrap_or_else(|_| serde_json::Value::String(value.clone())),
14268+
OracleValue::Boolean(value) => serde_json::Value::Bool(*value),
14269+
OracleValue::Text(value) => serde_json::Value::String(value.clone()),
14270+
OracleValue::Object(attributes) => {
14271+
let mut object = serde_json::Map::new();
14272+
for (name, value) in attributes {
14273+
object.insert(name.clone(), Self::oracle_thin_value_json(value));
14274+
}
14275+
serde_json::Value::Object(object)
14276+
}
14277+
OracleValue::Array(values) => {
14278+
serde_json::Value::Array(values.iter().map(Self::oracle_thin_value_json).collect())
14279+
}
14280+
OracleValue::IndexedArray(values) => {
14281+
let mut object = serde_json::Map::new();
14282+
for (index, value) in values {
14283+
object.insert(index.to_string(), Self::oracle_thin_value_json(value));
14284+
}
14285+
serde_json::Value::Object(object)
14286+
}
14287+
_ => {
14288+
serde_json::Value::String(Self::oracle_thin_string_cell(value).unwrap_or_default())
14289+
}
1425814290
}
1425914291
}
1426014292

@@ -21251,6 +21283,42 @@ impl SqlEditorWidget {
2125121283
}
2125221284
}
2125321285

21286+
#[cfg(test)]
21287+
mod oracle_thin_value_render_tests {
21288+
use super::{OracleValue, SqlEditorWidget};
21289+
21290+
#[test]
21291+
fn decoded_oracle_collections_render_their_elements() {
21292+
let value = OracleValue::Array(vec![
21293+
OracleValue::Number("10".to_string()),
21294+
OracleValue::Null,
21295+
OracleValue::Text("thirty".to_string()),
21296+
OracleValue::Object(vec![(
21297+
"NESTED".to_string(),
21298+
OracleValue::Array(vec![OracleValue::Boolean(true)]),
21299+
)]),
21300+
]);
21301+
21302+
assert_eq!(
21303+
SqlEditorWidget::oracle_thin_string_cell(&value).as_deref(),
21304+
Some(r#"[10,null,"thirty",{"NESTED":[true]}]"#)
21305+
);
21306+
}
21307+
21308+
#[test]
21309+
fn decoded_oracle_indexed_arrays_keep_indexes_and_values() {
21310+
let value = OracleValue::IndexedArray(vec![
21311+
(5, OracleValue::Text("x".to_string())),
21312+
(9, OracleValue::Number("42".to_string())),
21313+
]);
21314+
21315+
assert_eq!(
21316+
SqlEditorWidget::oracle_thin_string_cell(&value).as_deref(),
21317+
Some(r#"{"5":"x","9":42}"#)
21318+
);
21319+
}
21320+
}
21321+
2125421322
#[cfg(test)]
2125521323
mod oracle_current_schema_statement_tests {
2125621324
use super::SqlEditorWidget;

src/ui/sql_editor/format_sweep_tests.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3561,7 +3561,7 @@ fn formatting_sweep_additional_non_parenthesized_child_lists_have_typed_frames()
35613561
let cases: &[(DatabaseType, &str, &[ListOwnerKind])] = &[
35623562
(
35633563
DatabaseType::Oracle,
3564-
"SELECT * FROM sales MATCH_RECOGNIZE (ORDER BY sale_id SUBSET ab = (A, B), cd = (C, D) PATTERN (A B C D) DEFINE A AS amount > 0); SELECT a, b FROM t FOR UPDATE OF a, b; CREATE OR REPLACE TRIGGER trg BEFORE UPDATE OF a, b ON t FOLLOWS trg_a, trg_b BEGIN NULL; END; GRANT SELECT, UPDATE ON t TO app_a, app_b; ALTER TABLE t ADD c NUMBER, ADD d NUMBER; LOCK TABLE t_a, t_b IN EXCLUSIVE MODE; FLASHBACK TABLE t_a, t_b TO SCN 1; BEGIN FOR i IN 1..2, REVERSE 5..6, 9..9 LOOP NULL; END LOOP; END;",
3564+
"SELECT * FROM sales MATCH_RECOGNIZE (ORDER BY sale_id SUBSET ab = (A, B), cd = (C, D) PATTERN (A B C D) DEFINE A AS amount > 0); SELECT a, b FROM t FOR UPDATE OF a, b; CREATE OR REPLACE TRIGGER trg BEFORE UPDATE OF a, b ON t FOLLOWS trg_a, trg_b BEGIN NULL; END; GRANT SELECT, UPDATE ON t TO app_a, app_b; ALTER TABLE t ADD c NUMBER, ADD d NUMBER; LOCK TABLE t_a, t_b IN EXCLUSIVE MODE; FLASHBACK TABLE t_a, t_b TO SCN 1; CREATE AUDIT POLICY pol ACTIONS SELECT ON t_a, UPDATE ON t_b; BEGIN FOR i IN 1..2, REVERSE 5..6, 9..9 LOOP NULL; END LOOP; END;",
35653565
&[
35663566
ListOwnerKind::Subset,
35673567
ListOwnerKind::ForUpdateColumns,
@@ -3572,6 +3572,7 @@ fn formatting_sweep_additional_non_parenthesized_child_lists_have_typed_frames()
35723572
ListOwnerKind::AlterActions,
35733573
ListOwnerKind::LockTables,
35743574
ListOwnerKind::FlashbackTargets,
3575+
ListOwnerKind::AuditActions,
35753576
ListOwnerKind::IterationControls,
35763577
],
35773578
),
@@ -3708,7 +3709,7 @@ fn formatting_sweep_list_owner_inventory_covers_every_typed_variant() {
37083709
),
37093710
(
37103711
DatabaseType::Oracle,
3711-
"ALTER TABLE t ADD a NUMBER, ADD b NUMBER; LOCK TABLE t_a, t_b IN EXCLUSIVE MODE; FLASHBACK TABLE t_a, t_b TO SCN 1; CREATE OR REPLACE TRIGGER trg_order BEFORE INSERT ON t FOLLOWS trg_a, trg_b BEGIN NULL; END; CREATE MATERIALIZED VIEW LOG ON t WITH PRIMARY KEY, ROWID INCLUDING NEW VALUES; BEGIN FOR i IN 1..2, REVERSE 5..6 LOOP NULL; END LOOP; END;",
3712+
"ALTER TABLE t ADD a NUMBER, ADD b NUMBER; LOCK TABLE t_a, t_b IN EXCLUSIVE MODE; FLASHBACK TABLE t_a, t_b TO SCN 1; CREATE AUDIT POLICY pol ACTIONS SELECT ON t_a, UPDATE ON t_b; CREATE OR REPLACE TRIGGER trg_order BEFORE INSERT ON t FOLLOWS trg_a, trg_b BEGIN NULL; END; CREATE MATERIALIZED VIEW LOG ON t WITH PRIMARY KEY, ROWID INCLUDING NEW VALUES; BEGIN FOR i IN 1..2, REVERSE 5..6 LOOP NULL; END LOOP; END;",
37123713
),
37133714
(
37143715
DatabaseType::Oracle,

src/ui/sql_editor/formatter.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2253,6 +2253,7 @@ pub(super) enum ListOwnerKind {
22532253
PropertyGraphProperties,
22542254
TriggerOrdering,
22552255
FlashbackTargets,
2256+
AuditActions,
22562257
}
22572258

22582259
impl ListOwnerKind {
@@ -2319,6 +2320,7 @@ impl ListOwnerKind {
23192320
Self::PropertyGraphProperties,
23202321
Self::TriggerOrdering,
23212322
Self::FlashbackTargets,
2323+
Self::AuditActions,
23222324
];
23232325

23242326
fn depth(self, header_indent: usize) -> usize {
@@ -7472,6 +7474,33 @@ impl SqlEditorWidget {
74727474
}
74737475
}
74747476

7477+
/// Whether `idx` sits inside a `CREATE|ALTER AUDIT POLICY` statement, and if
7478+
/// so whether its ACTIONS list has already been opened. Inside that list
7479+
/// every word names an audited action or its ON target, so SELECT / UPDATE /
7480+
/// ON own no clause there the way they do everywhere else.
7481+
fn oracle_audit_policy_position(tokens: &[SqlToken], idx: usize) -> Option<bool> {
7482+
let mut leading = tokens.iter().take(idx).filter_map(|token| match token {
7483+
SqlToken::Word(word) => Some(word.to_ascii_uppercase()),
7484+
_ => None,
7485+
});
7486+
if !matches!(leading.next().as_deref(), Some("CREATE") | Some("ALTER")) {
7487+
return None;
7488+
}
7489+
7490+
let mut saw_audit_policy = false;
7491+
let mut prev: Option<String> = None;
7492+
for word in leading {
7493+
if word == "ACTIONS" && saw_audit_policy {
7494+
return Some(true);
7495+
}
7496+
if word == "POLICY" && prev.as_deref() == Some("AUDIT") {
7497+
saw_audit_policy = true;
7498+
}
7499+
prev = Some(word);
7500+
}
7501+
saw_audit_policy.then_some(false)
7502+
}
7503+
74757504
fn is_multiset_set_operator_from_indices(
74767505
tokens: &[SqlToken],
74777506
prev_word_upper: Option<&str>,
@@ -12956,6 +12985,14 @@ impl SqlEditorWidget {
1295612985
&& Self::is_mysql_index_hint_phrase_word(tokens, idx, upper);
1295712986
let ddl_option_phrase_word = current_scope.paren_depth == 0
1295812987
&& Self::is_ddl_option_phrase_word(tokens, idx, upper);
12988+
// `HIERARCHY h (child CHILD OF parent JOIN KEY t.c REFERENCES
12989+
// parent)` in CREATE DIMENSION: `JOIN KEY` is a dimension
12990+
// phrase, not a join, and it lives inside the hierarchy parens
12991+
// so it cannot be gated on paren_depth.
12992+
let oracle_dimension_join_key =
12993+
!mysql_compatible && upper == "JOIN" && next_word_is("KEY");
12994+
let oracle_audit_policy_action_word = !mysql_compatible
12995+
&& matches!(Self::oracle_audit_policy_position(tokens, idx), Some(true));
1295912996
let keyword_suppresses_structural_handling = keyword_preserves_original_case
1296012997
|| treat_control_keyword_as_identifier
1296112998
|| follows_alias_control_keyword
@@ -12964,7 +13001,9 @@ impl SqlEditorWidget {
1296413001
|| oracle_iteration_collection_keyword
1296513002
|| oracle_type_method_order_modifier
1296613003
|| mysql_index_hint_phrase_word
12967-
|| ddl_option_phrase_word;
13004+
|| ddl_option_phrase_word
13005+
|| oracle_dimension_join_key
13006+
|| oracle_audit_policy_action_word;
1296813007
let model_bracket_member = construct_flag_active!(ModelActive)
1296913008
&& delimiter_frame_state.innermost_is_bracket();
1297013009
let on_duplicate_key_values_function =
@@ -16322,6 +16361,18 @@ impl SqlEditorWidget {
1632216361
ListOwnerKind::TriggerUpdateColumns,
1632316362
);
1632416363
}
16364+
if upper == "ACTIONS"
16365+
&& !mysql_compatible
16366+
&& matches!(Self::oracle_audit_policy_position(tokens, idx), Some(false))
16367+
{
16368+
format_stack.push_list_owner_kind_for_render(
16369+
current_scope,
16370+
ListOwnerKind::AuditActions,
16371+
idx,
16372+
tokens,
16373+
line_indent,
16374+
);
16375+
}
1632516376
if matches!(upper, "FOLLOWS" | "PRECEDES")
1632616377
&& format_stack.trigger_header_is_active()
1632716378
{

0 commit comments

Comments
 (0)