From 3a5de1f9d8294edbf2710d1316bcb0a105729ee8 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Wed, 12 Aug 2026 03:50:33 +0000 Subject: [PATCH] fix(postgres): restore table DDL on PostgreSQL 9.x Closes #5964 --- crates/dbx-core/src/db/postgres.rs | 63 +++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/crates/dbx-core/src/db/postgres.rs b/crates/dbx-core/src/db/postgres.rs index 42b2f15574..8b94a94cbf 100644 --- a/crates/dbx-core/src/db/postgres.rs +++ b/crates/dbx-core/src/db/postgres.rs @@ -2745,7 +2745,11 @@ pub async fn get_table_partition_local_objects( fn postgres_table_partition_relation_sql() -> &'static str { "SELECT c.relkind::text, \ - COALESCE((pg_catalog.row_to_json(c)->>'relispartition')::boolean, false) AS is_partition \ + EXISTS ( \ + SELECT 1 FROM pg_catalog.pg_inherits i \ + WHERE i.inhrelid = c.oid \ + AND (SELECT parent.relkind FROM pg_catalog.pg_class parent WHERE parent.oid = i.inhparent) = 'p' \ + ) AS is_partition \ FROM pg_catalog.pg_class c \ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace \ WHERE n.nspname = $1 AND c.relname = $2 AND c.relkind IN ('r','p') \ @@ -7774,7 +7778,12 @@ mod tests { let info_sql = postgres_table_partition_info_sql(); let local_objects_sql = postgres_table_partition_local_objects_sql(); - assert!(relation_sql.contains("row_to_json(c)->>'relispartition'")); + assert!(!relation_sql.contains("row_to_json")); + assert!(!relation_sql.contains("relispartition")); + assert!(!relation_sql.contains("relpartbound")); + assert!(relation_sql.contains("pg_catalog.pg_inherits")); + assert!(relation_sql.contains("parent.oid = i.inhparent")); + assert!(relation_sql.contains(") = 'p'")); assert!(relation_sql.contains("c.relkind IN ('r','p')")); assert!(info_sql.contains("pg_catalog.pg_get_expr(c.relpartbound, c.oid, true)")); assert!(info_sql.contains("pg_catalog.pg_get_partkeydef(c.oid)")); @@ -7927,6 +7936,56 @@ mod tests { assert!(info.is_primary_key); } + #[tokio::test] + #[ignore = "requires DBX_TEST_POSTGRES_URL pointing at a writable PostgreSQL 9.x database"] + async fn postgres_legacy_table_ddl_uses_compatible_partition_probe() { + let url = std::env::var("DBX_TEST_POSTGRES_URL").expect("DBX_TEST_POSTGRES_URL"); + let pool = connect(&url, Duration::from_secs(5)).await.expect("connect postgres"); + let version = execute_query(&pool, "SHOW server_version_num").await.expect("query PostgreSQL version"); + let version_num = version.rows[0][0] + .as_str() + .expect("server_version_num should be text") + .parse::() + .expect("server_version_num should be numeric"); + assert!((90000..100000).contains(&version_num), "expected PostgreSQL 9.x, got {version_num}"); + + let schema = format!("dbx_legacy_ddl_{}", uuid::Uuid::new_v4().simple()); + let schema_ident = pg_quote_ident(&schema); + let parent = format!("{schema_ident}.parent"); + let child = format!("{schema_ident}.child"); + let function = format!("{schema_ident}.fill_child_name"); + let client = pool.get().await.expect("get postgres client"); + client + .batch_execute(&format!( + "CREATE SCHEMA {schema_ident}; \ + CREATE TABLE {parent} (id integer PRIMARY KEY); \ + CREATE TABLE {child} (parent_id integer REFERENCES {parent}(id), name text) INHERITS ({parent}); \ + ALTER TABLE {child} ADD PRIMARY KEY (id); \ + CREATE INDEX child_name_idx ON {child}(name); \ + CREATE FUNCTION {function}() RETURNS trigger LANGUAGE plpgsql AS $$BEGIN NEW.name := COALESCE(NEW.name, chr(120)); RETURN NEW; END$$; \ + CREATE TRIGGER child_bi BEFORE INSERT ON {child} FOR EACH ROW EXECUTE PROCEDURE {function}()" + )) + .await + .expect("create PostgreSQL 9.x DDL fixtures"); + drop(client); + + let partition_info = + get_table_partition_info(&pool, &schema, "child").await.expect("classify PostgreSQL 9.x inherited table"); + let ddl = crate::schema::pg_ddl(&pool, &schema, "child").await; + execute_query(&pool, &format!("DROP SCHEMA {schema_ident} CASCADE")) + .await + .expect("drop PostgreSQL 9.x DDL fixtures"); + let ddl = ddl.expect("generate PostgreSQL 9.x table DDL"); + + assert!(ddl.contains("CREATE TABLE"), "ddl: {ddl}"); + assert!(ddl.contains("PRIMARY KEY"), "ddl: {ddl}"); + assert!(ddl.contains("FOREIGN KEY"), "ddl: {ddl}"); + assert!(ddl.contains("CREATE INDEX \"child_name_idx\""), "ddl: {ddl}"); + assert!(ddl.contains("CREATE TRIGGER child_bi"), "ddl: {ddl}"); + assert_eq!(partition_info, PostgresTablePartitionInfo::default()); + assert!(!ddl.contains("PARTITION OF"), "ddl: {ddl}"); + } + #[tokio::test] #[ignore = "requires DBX_TEST_POSTGRES_URL pointing at a writable PostgreSQL database"] async fn postgres_partition_metadata_renders_replayable_children_and_subpartitions() {