use super::*;
fn shapes(table: &Table) -> Vec<(&str, &str, bool, bool)> {
table
.columns
.iter()
.map(|column| {
(
column.sql_name.as_str(),
column.rust_type.as_str(),
column.has_default,
column.is_generated,
)
})
.collect()
}
#[test]
fn alter_column_forms_that_leave_the_model_unchanged_are_dropped() {
let table = one_table(
"CREATE TABLE notes (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
body TEXT NOT NULL
);
ALTER TABLE notes ALTER COLUMN body SET STATISTICS 100;
ALTER TABLE notes ALTER body SET STORAGE EXTERNAL, ALTER COLUMN body SET COMPRESSION lz4;
ALTER TABLE notes ALTER COLUMN body SET (n_distinct = 100), ALTER COLUMN body RESET (n_distinct);
ALTER TABLE notes ALTER COLUMN id SET GENERATED ALWAYS RESTART WITH 100;
ALTER TABLE notes ALTER COLUMN id RESTART, ALTER COLUMN id SET INCREMENT BY 2;
ALTER TABLE ONLY notes RESET (fillfactor), SET (fillfactor = 70), CLUSTER ON notes_pkey;
ALTER TABLE notes SET WITHOUT CLUSTER, SET TABLESPACE fast, SET LOGGED, SET UNLOGGED;
ALTER TABLE notes FORCE ROW LEVEL SECURITY, NO FORCE ROW LEVEL SECURITY;
ALTER TABLE notes REPLICA IDENTITY FULL, OWNER TO app, VALIDATE CONSTRAINT notes_pkey;
ALTER TABLE notes DISABLE TRIGGER ALL, ENABLE TRIGGER ALL;
ALTER TABLE IF EXISTS notes ALTER CONSTRAINT notes_fk DEFERRABLE;",
);
assert_eq!(
shapes(&table),
[("id", "i64", true, true), ("body", "String", false, false),]
);
}
#[test]
fn drop_identity_and_drop_expression_make_the_column_a_plain_one() {
let table = one_table(
"CREATE TABLE notes (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
body TEXT NOT NULL,
upper_body TEXT GENERATED ALWAYS AS (upper(body)) STORED
);
ALTER TABLE notes ALTER COLUMN id DROP IDENTITY IF EXISTS, ADD COLUMN title TEXT;
ALTER TABLE notes ALTER COLUMN upper_body DROP EXPRESSION;",
);
assert_eq!(
shapes(&table),
[
("id", "i64", false, false),
("body", "String", false, false),
("upper_body", "Option<String>", false, false),
("title", "Option<String>", false, false),
]
);
}
#[test]
fn alter_column_type_with_a_collation_changes_the_type() {
let table = one_table(
"CREATE TABLE notes (id BIGINT PRIMARY KEY, code INTEGER, body VARCHAR(10));
ALTER TABLE notes ALTER COLUMN code TYPE TEXT COLLATE \"C\" USING code::text;
ALTER TABLE notes ALTER COLUMN body SET DATA TYPE TEXT COLLATE pg_catalog.\"default\";",
);
assert_eq!(
shapes(&table)[1..],
[
("code", "Option<String>", false, false),
("body", "Option<String>", false, false),
]
);
}
#[test]
fn an_unknown_clause_next_to_a_recognised_one_still_fails_the_table() {
let parsed = parse_files(&[
(
"0001.up.sql",
"CREATE TABLE notes (id BIGINT PRIMARY KEY, body TEXT);",
),
(
"0002.up.sql",
"-- move\nALTER TABLE notes ALTER COLUMN body SET STATISTICS 10, SET SCHEMA archive;",
),
]);
assert!(parsed.schema.tables.is_empty());
assert_eq!(parsed.failures.len(), 1);
assert_eq!(parsed.failures[0].line, Some(2));
assert!(
parsed.failures[0]
.reason
.starts_with("the SQL parser cannot read this ALTER TABLE"),
"{}",
parsed.failures[0].reason
);
}
#[test]
fn begin_atomic_bodies_and_other_function_like_statements_are_skipped() {
let parsed = parse(
"CREATE FUNCTION add_one(n INTEGER) RETURNS INTEGER LANGUAGE sql
BEGIN ATOMIC
SELECT CASE WHEN n IS NULL THEN 0 ELSE n END + 1;
SELECT 1;
END;
CREATE OR REPLACE PROCEDURE touch() LANGUAGE sql BEGIN ATOMIC SELECT 1; END;
DO $$ BEGIN RAISE NOTICE 'hi'; END $$;
ALTER SEQUENCE notes_seq OWNED BY NONE;
ALTER VIEW recent_notes ALTER COLUMN id SET DEFAULT 1;
CREATE TABLE notes (id BIGINT PRIMARY KEY);",
);
assert!(parsed.failures.is_empty(), "{:?}", parsed.failures);
assert_eq!(parsed.schema.tables.len(), 1);
}
#[test]
fn an_unreadable_create_table_still_stops_the_run_at_its_line() {
let error = parse_schema(
"CREATE FUNCTION f() RETURNS INTEGER LANGUAGE sql BEGIN ATOMIC SELECT 1; END;\n\
CREATE TABEL notes (id BIGINT);",
)
.err()
.expect("the run stops");
assert!(
format!("{error:#}").starts_with("line 2: invalid PostgreSQL SQL syntax"),
"{error:#}"
);
}
fn constraint_names(table: &Table) -> Vec<&str> {
table
.key_constraints
.iter()
.map(|constraint| constraint.name.as_str())
.collect()
}
#[test]
fn renames_free_names_for_default_constraint_names() {
let parsed = parse(
"CREATE TABLE t (id BIGINT PRIMARY KEY, a TEXT, b TEXT, c TEXT, d TEXT);
CREATE INDEX t_a_key ON t (id);
CREATE SEQUENCE t_b_key;
CREATE VIEW t_c_key AS SELECT 1;
CREATE MATERIALIZED VIEW t_d_key AS SELECT 1;
ALTER INDEX t_a_key RENAME TO t_a_idx;
ALTER SEQUENCE IF EXISTS t_b_key RENAME TO t_b_seq;
ALTER VIEW t_c_key RENAME TO t_c_view;
ALTER MATERIALIZED VIEW t_d_key RENAME TO t_d_view;
ALTER TABLE t ADD UNIQUE (a), ADD UNIQUE (b), ADD UNIQUE (c), ADD UNIQUE (d);",
);
assert!(parsed.failures.is_empty(), "{:?}", parsed.failures);
assert_eq!(
constraint_names(&parsed.schema.tables[0]),
["t_pkey", "t_a_key", "t_b_key", "t_c_key", "t_d_key"]
);
}
#[test]
fn renaming_a_key_constraint_or_its_index_renames_the_constraint() {
let table = one_table(
"CREATE TABLE t (id BIGINT PRIMARY KEY, a TEXT UNIQUE, b TEXT);
ALTER INDEX t_pkey RENAME TO t_primary;
ALTER TABLE t RENAME CONSTRAINT t_a_key TO t_a_unique;
ALTER TABLE t RENAME CONSTRAINT some_check TO other_check;
ALTER INDEX IF EXISTS missing RENAME TO still_missing;
ALTER TABLE t ADD UNIQUE (a, b), ADD UNIQUE (a);",
);
assert_eq!(
constraint_names(&table),
["t_primary", "t_a_unique", "t_a_b_key", "t_a_key"]
);
}