use sql_dialect_fmt_parser::{parse, parse_with_dialect, Dialect, SyntaxKind};
use sql_dialect_fmt_test_support::parser::{
assert_has_node_kind, assert_parse_clean as clean, assert_parse_recovers as recovers,
};
fn count_nodes(sql: &str, kind: SyntaxKind) -> usize {
parse(sql)
.syntax()
.descendants()
.filter(|node| node.kind() == kind)
.count()
}
#[test]
fn alter_table_column_actions_parse_clean_and_expose_actions() {
for (sql, actions) in [
("ALTER TABLE t ADD COLUMN c INT", 1),
("ALTER TABLE t ADD COLUMN IF NOT EXISTS c NUMBER(10, 2)", 1),
("ALTER TABLE t DROP COLUMN c", 1),
("ALTER TABLE t DROP COLUMN a, b", 1), ("ALTER TABLE t RENAME COLUMN a TO b", 1),
("ALTER TABLE t RENAME TO t2", 1),
(
"ALTER TABLE db.sch.t ADD COLUMN a INT, ADD COLUMN b STRING",
2,
),
(
"ALTER TABLE t ADD COLUMN a INT, DROP COLUMN b, RENAME COLUMN c TO d",
3,
),
(
"ALTER TABLE t ALTER COLUMN c SET DEFAULT CURRENT_TIMESTAMP()",
1,
),
("ALTER TABLE t MODIFY COLUMN c SET NOT NULL", 1),
("ALTER TABLE IF EXISTS t DROP COLUMN c", 1),
("ALTER TABLE t SWAP WITH u", 1),
("ALTER TABLE t CLUSTER BY (a, b)", 1),
("ALTER TABLE t DROP CONSTRAINT pk CASCADE", 1),
] {
clean(sql);
assert_has_node_kind(sql, SyntaxKind::ALTER_STMT);
assert_has_node_kind(sql, SyntaxKind::NAME_REF);
assert_eq!(
count_nodes(sql, SyntaxKind::ALTER_ACTION),
actions,
"unexpected action count for {sql:?}"
);
}
}
#[test]
fn alter_table_governance_actions_split_on_action_commas_only() {
let sql = "ALTER TABLE t MODIFY COLUMN email SET MASKING POLICY p USING(email,tenant_id),\
MODIFY COLUMN email SET TAG g.c = 'restricted',SET TAG g.r = 'standard'";
clean(sql);
assert_eq!(count_nodes(sql, SyntaxKind::ALTER_ACTION), 3);
let search = "ALTER TABLE t ADD SEARCH OPTIMIZATION ON EQUALITY(a, b), SUBSTRING(c)";
clean(search);
assert_eq!(count_nodes(search, SyntaxKind::ALTER_ACTION), 1);
}
#[test]
fn alter_session_set_structures_property_pairs() {
let sql = "ALTER SESSION SET TIMEZONE = 'UTC', WEEK_START = 1, QUERY_TAG = 'etl'";
clean(sql);
assert_eq!(count_nodes(sql, SyntaxKind::ALTER_ACTION), 1);
assert_eq!(count_nodes(sql, SyntaxKind::OBJECT_PROPERTY), 3);
let unset = "ALTER SESSION UNSET QUERY_TAG, WEEK_START";
clean(unset);
assert_eq!(count_nodes(unset, SyntaxKind::ALTER_ACTION), 1);
}
#[test]
fn alter_warehouse_task_and_view_forms_parse_clean() {
for sql in [
"ALTER WAREHOUSE wh SET WAREHOUSE_SIZE = 'LARGE' AUTO_SUSPEND = 60",
"ALTER WAREHOUSE wh SUSPEND",
"ALTER WAREHOUSE IF EXISTS wh RESUME",
"ALTER TASK t RESUME",
"ALTER TASK ops.t SUSPEND",
"ALTER TASK t SET SCHEDULE = '5 minutes', SUSPEND_TASK_AFTER_NUM_FAILURES = 3",
"ALTER VIEW v RENAME TO v2",
"ALTER VIEW v SET SECURE",
"ALTER SCHEMA s RENAME TO s2",
"ALTER DATABASE d SET DATA_RETENTION_TIME_IN_DAYS = 7",
"ALTER MATERIALIZED VIEW mv RENAME TO mv2",
"ALTER DYNAMIC TABLE dt REFRESH",
] {
clean(sql);
assert_has_node_kind(sql, SyntaxKind::ALTER_STMT);
assert_has_node_kind(sql, SyntaxKind::ALTER_ACTION);
}
}
#[test]
fn alter_set_properties_are_object_properties() {
let sql = "ALTER WAREHOUSE wh SET WAREHOUSE_SIZE = 'LARGE' AUTO_SUSPEND = 60";
clean(sql);
assert_eq!(count_nodes(sql, SyntaxKind::OBJECT_PROPERTY), 2);
}
#[test]
fn case_insensitive_and_lowercase_spellings_parse_clean() {
clean("alter table t add column c int");
clean("Alter Session Set timezone = 'UTC'");
clean("alter task t resume");
clean("aLtEr TaBlE t ReNaMe To u");
}
#[test]
fn unmodeled_alter_kinds_stay_lenient_and_round_trip() {
for sql in [
"ALTER USER u SET PASSWORD = 'x' MUST_CHANGE_PASSWORD = TRUE",
"ALTER PIPE p REFRESH",
"ALTER STAGE s SET URL = 's3://bucket/'",
"ALTER ACCOUNT SET STATEMENT_TIMEOUT_IN_SECONDS = 60",
"ALTER FUNCTION f(INT) RENAME TO g",
] {
let parsed = clean(sql);
assert!(
!parsed
.syntax()
.descendants()
.any(|n| n.kind() == SyntaxKind::ALTER_ACTION),
"expected lenient flat run for {sql:?}"
);
}
}
#[test]
fn databricks_alter_table_forms_parse_clean() {
for sql in [
"ALTER TABLE t ADD COLUMNS (c1 INT, c2 STRING)",
"ALTER TABLE t SET TBLPROPERTIES ('delta.appendOnly' = 'true')",
"ALTER TABLE t RENAME TO u",
] {
let parsed = parse_with_dialect(sql, Dialect::Databricks);
assert_eq!(parsed.syntax().to_string(), sql, "round trip for {sql:?}");
assert!(
parsed.errors().is_empty(),
"unexpected errors for {sql:?}: {:?}",
parsed.errors()
);
assert!(parsed
.syntax()
.descendants()
.any(|n| n.kind() == SyntaxKind::ALTER_ACTION));
}
}
#[test]
fn alter_words_remain_usable_as_identifiers_elsewhere() {
clean("SELECT session, add, modify, rename, unset FROM t");
clean("SELECT suspend, resume, swap, column1, search, optimization FROM t");
clean("SELECT t.column FROM tbl t WHERE search > 1");
}
#[test]
fn alter_can_start_a_flow_chain() {
let sql = "ALTER TABLE t ADD COLUMN c int ->> SELECT * FROM $1";
clean(sql);
assert_has_node_kind(sql, SyntaxKind::FLOW_STMT);
assert_has_node_kind(sql, SyntaxKind::ALTER_ACTION);
}
#[test]
fn broken_and_partial_alter_round_trips_without_panic() {
for sql in [
"ALTER",
"ALTER TABLE",
"ALTER TABLE t",
"ALTER TABLE t ADD",
"ALTER TABLE t ADD COLUMN",
"ALTER TABLE t ADD COLUMN c INT,",
"ALTER TABLE t ADD COLUMN c INT, DROP",
"ALTER TABLE IF",
"ALTER SESSION SET",
"ALTER SESSION SET TIMEZONE =",
"ALTER SESSION SET TIMEZONE = 'UTC',",
"ALTER TASK t SET SCHEDULE = ",
"ALTER WAREHOUSE wh SET WAREHOUSE_SIZE =",
"ALTER TABLE t SWAP WITH",
"ALTER TABLE t CLUSTER BY (a,",
"ALTER TABLE t MODIFY COLUMN c SET MASKING POLICY p USING(",
"ALTER ,,, TABLE",
] {
recovers(sql);
}
}