fix(postgres): handle legacy materialized view source
This commit is contained in:
parent
fbcf3591d0
commit
fe03f740b7
|
|
@ -3036,16 +3036,42 @@ pub fn sqlserver_object_source_sql(schema: &str, name: &str, kind: &db::ObjectSo
|
|||
}
|
||||
|
||||
pub fn postgres_object_source_sql(schema: &str, name: &str, kind: &db::ObjectSourceKind) -> String {
|
||||
postgres_object_source_sql_inner(schema, name, kind, true)
|
||||
}
|
||||
|
||||
fn postgres_object_source_sql_without_relispopulated(schema: &str, name: &str, kind: &db::ObjectSourceKind) -> String {
|
||||
postgres_object_source_sql_inner(schema, name, kind, false)
|
||||
}
|
||||
|
||||
fn postgres_object_source_sql_inner(
|
||||
schema: &str,
|
||||
name: &str,
|
||||
kind: &db::ObjectSourceKind,
|
||||
include_relispopulated: bool,
|
||||
) -> String {
|
||||
match kind {
|
||||
db::ObjectSourceKind::View | db::ObjectSourceKind::MaterializedView => {
|
||||
let materialized_populated_clause = if include_relispopulated {
|
||||
" || CASE WHEN c.relispopulated THEN ' WITH DATA' ELSE ' WITH NO DATA' END"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let materialized_viewdef = "regexp_replace(pg_get_viewdef(c.oid, 0), ';[[:space:]]*$', '')";
|
||||
let materialized_source_expr = format!(
|
||||
"CASE WHEN {materialized_viewdef} ~* '^[[:space:]]*CREATE[[:space:]]+(OR[[:space:]]+REPLACE[[:space:]]+)?MATERIALIZED[[:space:]]+VIEW[[:space:]]+' \
|
||||
THEN {materialized_viewdef} \
|
||||
ELSE format('CREATE MATERIALIZED VIEW %I.%I AS ', n.nspname, c.relname) || {materialized_viewdef}{materialized_populated_clause} \
|
||||
END"
|
||||
);
|
||||
format!(
|
||||
"SELECT CASE WHEN c.relkind = 'm' THEN format('CREATE MATERIALIZED VIEW %I.%I AS ', n.nspname, c.relname) || regexp_replace(pg_get_viewdef(c.oid, 0), ';[[:space:]]*$', '') || CASE WHEN c.relispopulated THEN ' WITH DATA' ELSE ' WITH NO DATA' END \
|
||||
"SELECT CASE WHEN c.relkind = 'm' THEN {} \
|
||||
ELSE format('CREATE OR REPLACE VIEW %I.%I AS ', n.nspname, c.relname) || pg_get_viewdef(c.oid, 0) \
|
||||
END \
|
||||
FROM pg_catalog.pg_class c \
|
||||
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace \
|
||||
WHERE n.nspname = {} AND c.relname = {} AND c.relkind IN ('v','m') \
|
||||
ORDER BY c.oid LIMIT 1",
|
||||
materialized_source_expr,
|
||||
sql_string(schema),
|
||||
sql_string(name)
|
||||
)
|
||||
|
|
@ -3372,6 +3398,16 @@ async fn postgres_object_source(
|
|||
let sql = postgres_object_source_sql(schema, name, object_type);
|
||||
match db::postgres::execute_query(pool, &sql).await.and_then(first_string_cell) {
|
||||
Ok(source) => Ok(source),
|
||||
Err(primary_err)
|
||||
if postgres_missing_relispopulated_error(&primary_err)
|
||||
&& matches!(object_type, db::ObjectSourceKind::View | db::ObjectSourceKind::MaterializedView) =>
|
||||
{
|
||||
let fallback_sql = postgres_object_source_sql_without_relispopulated(schema, name, object_type);
|
||||
db::postgres::execute_query(pool, &fallback_sql)
|
||||
.await
|
||||
.and_then(first_string_cell)
|
||||
.map_err(|fallback_err| format!("{primary_err}; relispopulated fallback failed: {fallback_err}"))
|
||||
}
|
||||
Err(primary_err) if matches!(object_type, db::ObjectSourceKind::View) => {
|
||||
let fallback_sql = postgres_view_source_fallback_sql(schema, name);
|
||||
db::postgres::execute_query(pool, &fallback_sql)
|
||||
|
|
@ -3383,6 +3419,14 @@ async fn postgres_object_source(
|
|||
}
|
||||
}
|
||||
|
||||
fn postgres_missing_relispopulated_error(err: &str) -> bool {
|
||||
let lower = err.to_ascii_lowercase();
|
||||
lower.contains("does not exist")
|
||||
&& (lower.contains("column c.relispopulated")
|
||||
|| lower.contains("column \"c\".\"relispopulated\"")
|
||||
|| lower.contains("column \"relispopulated\""))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod object_source_tests {
|
||||
use super::*;
|
||||
|
|
@ -3398,16 +3442,53 @@ mod object_source_tests {
|
|||
|
||||
#[test]
|
||||
fn builds_postgres_object_source_sql_for_views_and_functions() {
|
||||
assert_eq!(
|
||||
postgres_object_source_sql("public", "active_users", &ObjectSourceKind::View),
|
||||
"SELECT CASE WHEN c.relkind = 'm' THEN format('CREATE MATERIALIZED VIEW %I.%I AS ', n.nspname, c.relname) || regexp_replace(pg_get_viewdef(c.oid, 0), ';[[:space:]]*$', '') || CASE WHEN c.relispopulated THEN ' WITH DATA' ELSE ' WITH NO DATA' END ELSE format('CREATE OR REPLACE VIEW %I.%I AS ', n.nspname, c.relname) || pg_get_viewdef(c.oid, 0) END FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'public' AND c.relname = 'active_users' AND c.relkind IN ('v','m') ORDER BY c.oid LIMIT 1"
|
||||
);
|
||||
let view_sql = postgres_object_source_sql("public", "active_users", &ObjectSourceKind::View);
|
||||
|
||||
assert!(view_sql.contains("CREATE MATERIALIZED VIEW"));
|
||||
assert!(view_sql.contains("CREATE OR REPLACE VIEW"));
|
||||
assert!(view_sql.contains("CASE WHEN c.relispopulated THEN ' WITH DATA' ELSE ' WITH NO DATA' END"));
|
||||
assert!(view_sql.contains("n.nspname = 'public'"));
|
||||
assert!(view_sql.contains("c.relname = 'active_users'"));
|
||||
|
||||
assert_eq!(
|
||||
postgres_object_source_sql("public", "recalc_score", &ObjectSourceKind::Function),
|
||||
"SELECT pg_get_functiondef(p.oid) FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = 'public' AND p.proname = 'recalc_score' AND p.prokind = 'f' ORDER BY p.oid LIMIT 1"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_postgres_object_source_sql_without_relispopulated_for_legacy_catalogs() {
|
||||
let sql = postgres_object_source_sql_without_relispopulated(
|
||||
"public",
|
||||
"active_users",
|
||||
&ObjectSourceKind::MaterializedView,
|
||||
);
|
||||
|
||||
assert!(sql.contains("CREATE MATERIALIZED VIEW"));
|
||||
assert!(sql.contains("pg_get_viewdef(c.oid, 0)"));
|
||||
assert!(!sql.contains("relispopulated"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_legacy_materialized_viewdef_when_it_already_contains_create_statement() {
|
||||
let sql = postgres_object_source_sql("public", "active_users", &ObjectSourceKind::MaterializedView);
|
||||
|
||||
assert!(
|
||||
sql.contains(
|
||||
"~* '^[[:space:]]*CREATE[[:space:]]+(OR[[:space:]]+REPLACE[[:space:]]+)?MATERIALIZED[[:space:]]+VIEW[[:space:]]+'"
|
||||
)
|
||||
);
|
||||
assert!(sql.contains(
|
||||
"THEN regexp_replace(pg_get_viewdef(c.oid, 0), ';[[:space:]]*$', '') ELSE format('CREATE MATERIALIZED VIEW"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_legacy_postgres_relispopulated_errors() {
|
||||
assert!(postgres_missing_relispopulated_error("ERROR: column c.relispopulated does not exist"));
|
||||
assert!(!postgres_missing_relispopulated_error("ERROR: relation public.relispopulated does not exist"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_postgres_view_source_sql_without_regclass_cast() {
|
||||
let sql = postgres_object_source_sql("tenant's schema", "active users", &ObjectSourceKind::View);
|
||||
|
|
|
|||
Loading…
Reference in New Issue