Skip to content

Commit 9a1cd68

Browse files
authored
Merge pull request #20 from fmguerreiro/feat/alter-table-attach-detach-partition
feat: parse ALTER TABLE ATTACH/DETACH PARTITION for PostgreSQL
2 parents a6211ec + 898e884 commit 9a1cd68

6 files changed

Lines changed: 272 additions & 1 deletion

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
[package]
1919
name = "pgmold-sqlparser"
2020
description = "Fork of sqlparser with additional PostgreSQL features (PARTITION OF, SECURITY DEFINER/INVOKER, SET params, EXCLUDE, TEXT SEARCH, AGGREGATE, FOREIGN TABLE/FDW, PUBLICATION, SUBSCRIPTION, ALTER DOMAIN/TRIGGER/EXTENSION, CAST, CONVERSION, LANGUAGE, RULE, STATISTICS, ACCESS METHOD, EVENT TRIGGER, TRANSFORM, SECURITY LABEL, USER MAPPING, TABLESPACE)"
21-
version = "0.60.11"
21+
version = "0.60.12"
2222
authors = ["Filipe Guerreiro <filipe.m.guerreiro@gmail.com>"]
2323
homepage = "https://github.com/fmguerreiro/datafusion-sqlparser-rs"
2424
documentation = "https://docs.rs/pgmold-sqlparser/"

src/ast/ddl.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,28 @@ pub enum AlterTableOperation {
246246
/// Partition expression to detach.
247247
partition: Partition,
248248
},
249+
/// `ATTACH PARTITION <partition_name> { FOR VALUES <partition_bound_spec> | DEFAULT }`
250+
///
251+
/// PostgreSQL-specific operation for declarative partitioning.
252+
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-altertable.html)
253+
AttachPartitionOf {
254+
/// Name of the partition table to attach.
255+
partition_name: ObjectName,
256+
/// Partition bound specification, or DEFAULT.
257+
partition_bound: ForValues,
258+
},
259+
/// `DETACH PARTITION <partition_name> [ CONCURRENTLY | FINALIZE ]`
260+
///
261+
/// PostgreSQL-specific operation for declarative partitioning.
262+
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-altertable.html)
263+
DetachPartitionOf {
264+
/// Name of the partition table to detach.
265+
partition_name: ObjectName,
266+
/// Whether to detach concurrently (non-blocking two-phase detach).
267+
concurrently: bool,
268+
/// Whether to finalize a previously started concurrent detach.
269+
finalize: bool,
270+
},
249271
/// `FREEZE PARTITION <partition_expr>`
250272
/// Note: this is a ClickHouse-specific operation, please refer to
251273
/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#freeze-partition)
@@ -896,6 +918,26 @@ impl fmt::Display for AlterTableOperation {
896918
AlterTableOperation::DetachPartition { partition } => {
897919
write!(f, "DETACH {partition}")
898920
}
921+
AlterTableOperation::AttachPartitionOf {
922+
partition_name,
923+
partition_bound,
924+
} => {
925+
write!(f, "ATTACH PARTITION {partition_name} {partition_bound}")
926+
}
927+
AlterTableOperation::DetachPartitionOf {
928+
partition_name,
929+
concurrently,
930+
finalize,
931+
} => {
932+
write!(f, "DETACH PARTITION {partition_name}")?;
933+
if *concurrently {
934+
write!(f, " CONCURRENTLY")?;
935+
}
936+
if *finalize {
937+
write!(f, " FINALIZE")?;
938+
}
939+
Ok(())
940+
}
899941
AlterTableOperation::EnableAlwaysRule { name } => {
900942
write!(f, "ENABLE ALWAYS RULE {name}")
901943
}

src/ast/spans.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,15 @@ impl Spanned for AlterTableOperation {
11581158
} => union_spans(column_names.iter().map(|i| i.span)),
11591159
AlterTableOperation::AttachPartition { partition } => partition.span(),
11601160
AlterTableOperation::DetachPartition { partition } => partition.span(),
1161+
AlterTableOperation::AttachPartitionOf {
1162+
partition_name,
1163+
partition_bound: _,
1164+
} => partition_name.span(),
1165+
AlterTableOperation::DetachPartitionOf {
1166+
partition_name,
1167+
concurrently: _,
1168+
finalize: _,
1169+
} => partition_name.span(),
11611170
AlterTableOperation::FreezePartition {
11621171
partition,
11631172
with_name,

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ define_keywords!(
429429
FILL,
430430
FILTER,
431431
FINAL,
432+
FINALIZE,
432433
FIRST,
433434
FIRST_VALUE,
434435
FIXEDSTRING,

src/parser/mod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10898,6 +10898,26 @@ impl<'a> Parser<'a> {
1089810898
{
1089910899
let new_owner = self.parse_owner()?;
1090010900
AlterTableOperation::OwnerTo { new_owner }
10901+
} else if dialect_of!(self is PostgreSqlDialect)
10902+
&& self.parse_keywords(&[Keyword::ATTACH, Keyword::PARTITION])
10903+
{
10904+
let partition_name = self.parse_object_name(false)?;
10905+
let partition_bound = self.parse_partition_for_values()?;
10906+
AlterTableOperation::AttachPartitionOf {
10907+
partition_name,
10908+
partition_bound,
10909+
}
10910+
} else if dialect_of!(self is PostgreSqlDialect)
10911+
&& self.parse_keywords(&[Keyword::DETACH, Keyword::PARTITION])
10912+
{
10913+
let partition_name = self.parse_object_name(false)?;
10914+
let concurrently = self.parse_keyword(Keyword::CONCURRENTLY);
10915+
let finalize = self.parse_keyword(Keyword::FINALIZE);
10916+
AlterTableOperation::DetachPartitionOf {
10917+
partition_name,
10918+
concurrently,
10919+
finalize,
10920+
}
1090110921
} else if dialect_of!(self is ClickHouseDialect|GenericDialect)
1090210922
&& self.parse_keyword(Keyword::ATTACH)
1090310923
{

tests/sqlparser_postgres.rs

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10241,3 +10241,202 @@ fn parse_create_tablespace_with_options() {
1024110241
other => panic!("unexpected option: {other:?}"),
1024210242
}
1024310243
}
10244+
10245+
#[test]
10246+
fn alter_table_attach_partition_range() {
10247+
let sql = "ALTER TABLE ONLY public.payment ATTACH PARTITION public.payment_p2022_01 FOR VALUES FROM ('2022-01-01 00:00:00+00') TO ('2022-02-01 00:00:00+00')";
10248+
let dialect = PostgreSqlDialect {};
10249+
let statements = sqlparser::parser::Parser::parse_sql(&dialect, sql).unwrap();
10250+
assert_eq!(1, statements.len());
10251+
match &statements[0] {
10252+
Statement::AlterTable { operations, .. } => {
10253+
assert_eq!(1, operations.len());
10254+
match &operations[0] {
10255+
AlterTableOperation::AttachPartitionOf {
10256+
partition_name,
10257+
partition_bound,
10258+
} => {
10259+
assert_eq!("public.payment_p2022_01", partition_name.to_string());
10260+
match partition_bound {
10261+
ForValues::From { from, to } => {
10262+
assert_eq!(1, from.len());
10263+
assert_eq!(1, to.len());
10264+
assert_eq!(
10265+
"'2022-01-01 00:00:00+00'",
10266+
from[0].to_string()
10267+
);
10268+
assert_eq!(
10269+
"'2022-02-01 00:00:00+00'",
10270+
to[0].to_string()
10271+
);
10272+
}
10273+
_ => panic!("Expected ForValues::From"),
10274+
}
10275+
}
10276+
_ => panic!("Expected AttachPartitionOf"),
10277+
}
10278+
}
10279+
_ => panic!("Expected AlterTable"),
10280+
}
10281+
}
10282+
10283+
#[test]
10284+
fn alter_table_attach_partition_list() {
10285+
let sql = "ALTER TABLE cities ATTACH PARTITION cities_ab FOR VALUES IN ('a', 'b')";
10286+
let dialect = PostgreSqlDialect {};
10287+
let statements = sqlparser::parser::Parser::parse_sql(&dialect, sql).unwrap();
10288+
assert_eq!(1, statements.len());
10289+
match &statements[0] {
10290+
Statement::AlterTable { operations, .. } => {
10291+
assert_eq!(1, operations.len());
10292+
match &operations[0] {
10293+
AlterTableOperation::AttachPartitionOf {
10294+
partition_name,
10295+
partition_bound,
10296+
} => {
10297+
assert_eq!("cities_ab", partition_name.to_string());
10298+
match partition_bound {
10299+
ForValues::In(values) => {
10300+
assert_eq!(2, values.len());
10301+
}
10302+
_ => panic!("Expected ForValues::In"),
10303+
}
10304+
}
10305+
_ => panic!("Expected AttachPartitionOf"),
10306+
}
10307+
}
10308+
_ => panic!("Expected AlterTable"),
10309+
}
10310+
}
10311+
10312+
#[test]
10313+
fn alter_table_attach_partition_hash() {
10314+
let sql = "ALTER TABLE orders ATTACH PARTITION orders_p1 FOR VALUES WITH (MODULUS 4, REMAINDER 0)";
10315+
let dialect = PostgreSqlDialect {};
10316+
let statements = sqlparser::parser::Parser::parse_sql(&dialect, sql).unwrap();
10317+
assert_eq!(1, statements.len());
10318+
match &statements[0] {
10319+
Statement::AlterTable { operations, .. } => {
10320+
assert_eq!(1, operations.len());
10321+
match &operations[0] {
10322+
AlterTableOperation::AttachPartitionOf {
10323+
partition_name,
10324+
partition_bound,
10325+
} => {
10326+
assert_eq!("orders_p1", partition_name.to_string());
10327+
match partition_bound {
10328+
ForValues::With { modulus, remainder } => {
10329+
assert_eq!(4, *modulus);
10330+
assert_eq!(0, *remainder);
10331+
}
10332+
_ => panic!("Expected ForValues::With"),
10333+
}
10334+
}
10335+
_ => panic!("Expected AttachPartitionOf"),
10336+
}
10337+
}
10338+
_ => panic!("Expected AlterTable"),
10339+
}
10340+
}
10341+
10342+
#[test]
10343+
fn alter_table_attach_partition_default() {
10344+
let sql = "ALTER TABLE cities ATTACH PARTITION cities_default DEFAULT";
10345+
let dialect = PostgreSqlDialect {};
10346+
let statements = sqlparser::parser::Parser::parse_sql(&dialect, sql).unwrap();
10347+
assert_eq!(1, statements.len());
10348+
match &statements[0] {
10349+
Statement::AlterTable { operations, .. } => {
10350+
assert_eq!(1, operations.len());
10351+
match &operations[0] {
10352+
AlterTableOperation::AttachPartitionOf {
10353+
partition_name,
10354+
partition_bound,
10355+
} => {
10356+
assert_eq!("cities_default", partition_name.to_string());
10357+
assert_eq!(ForValues::Default, *partition_bound);
10358+
}
10359+
_ => panic!("Expected AttachPartitionOf"),
10360+
}
10361+
}
10362+
_ => panic!("Expected AlterTable"),
10363+
}
10364+
}
10365+
10366+
#[test]
10367+
fn alter_table_detach_partition_plain() {
10368+
let sql = "ALTER TABLE measurement DETACH PARTITION measurement_y2021m01";
10369+
let dialect = PostgreSqlDialect {};
10370+
let statements = sqlparser::parser::Parser::parse_sql(&dialect, sql).unwrap();
10371+
assert_eq!(1, statements.len());
10372+
match &statements[0] {
10373+
Statement::AlterTable { operations, .. } => {
10374+
assert_eq!(1, operations.len());
10375+
match &operations[0] {
10376+
AlterTableOperation::DetachPartitionOf {
10377+
partition_name,
10378+
concurrently,
10379+
finalize,
10380+
} => {
10381+
assert_eq!("measurement_y2021m01", partition_name.to_string());
10382+
assert!(!concurrently);
10383+
assert!(!finalize);
10384+
}
10385+
_ => panic!("Expected DetachPartitionOf"),
10386+
}
10387+
}
10388+
_ => panic!("Expected AlterTable"),
10389+
}
10390+
}
10391+
10392+
#[test]
10393+
fn alter_table_detach_partition_concurrently() {
10394+
let sql = "ALTER TABLE measurement DETACH PARTITION measurement_y2021m01 CONCURRENTLY";
10395+
let dialect = PostgreSqlDialect {};
10396+
let statements = sqlparser::parser::Parser::parse_sql(&dialect, sql).unwrap();
10397+
assert_eq!(1, statements.len());
10398+
match &statements[0] {
10399+
Statement::AlterTable { operations, .. } => {
10400+
assert_eq!(1, operations.len());
10401+
match &operations[0] {
10402+
AlterTableOperation::DetachPartitionOf {
10403+
partition_name,
10404+
concurrently,
10405+
finalize,
10406+
} => {
10407+
assert_eq!("measurement_y2021m01", partition_name.to_string());
10408+
assert!(concurrently);
10409+
assert!(!finalize);
10410+
}
10411+
_ => panic!("Expected DetachPartitionOf"),
10412+
}
10413+
}
10414+
_ => panic!("Expected AlterTable"),
10415+
}
10416+
}
10417+
10418+
#[test]
10419+
fn alter_table_detach_partition_finalize() {
10420+
let sql = "ALTER TABLE measurement DETACH PARTITION measurement_y2021m01 FINALIZE";
10421+
let dialect = PostgreSqlDialect {};
10422+
let statements = sqlparser::parser::Parser::parse_sql(&dialect, sql).unwrap();
10423+
assert_eq!(1, statements.len());
10424+
match &statements[0] {
10425+
Statement::AlterTable { operations, .. } => {
10426+
assert_eq!(1, operations.len());
10427+
match &operations[0] {
10428+
AlterTableOperation::DetachPartitionOf {
10429+
partition_name,
10430+
concurrently,
10431+
finalize,
10432+
} => {
10433+
assert_eq!("measurement_y2021m01", partition_name.to_string());
10434+
assert!(!concurrently);
10435+
assert!(finalize);
10436+
}
10437+
_ => panic!("Expected DetachPartitionOf"),
10438+
}
10439+
}
10440+
_ => panic!("Expected AlterTable"),
10441+
}
10442+
}

0 commit comments

Comments
 (0)