1 parent
51f7b1d8b5
commit
151172f81a
1 file changed
+110
-30
+110
-30
@@ -8023,7 +8023,7 @@ pub fn postgres_object_source_sql(
|
||||
kind: &db::ObjectSourceKind,
|
||||
signature: Option<&str>,
|
||||
) -> String {
|
||||
postgres_object_source_sql_inner(schema, name, kind, signature, true, false)
|
||||
postgres_object_source_sql_inner(schema, name, kind, signature, true, false, true)
|
||||
}
|
||||
|
||||
fn postgres_trigger_object_source_sql(schema: &str, name: &str, relation_name: Option<&str>) -> String {
|
||||
@@ -8050,7 +8050,7 @@ fn opengauss_object_source_sql(
|
||||
kind: &db::ObjectSourceKind,
|
||||
signature: Option<&str>,
|
||||
) -> String {
|
||||
postgres_object_source_sql_inner(schema, name, kind, signature, true, true)
|
||||
postgres_object_source_sql_inner(schema, name, kind, signature, true, true, false)
|
||||
}
|
||||
|
||||
fn opengauss_sequence_object_source_sql(schema: &str, name: &str, include_cache: bool) -> String {
|
||||
@@ -8093,15 +8093,6 @@ fn opengauss_sequence_object_source_sql(schema: &str, name: &str, include_cache:
|
||||
)
|
||||
}
|
||||
|
||||
fn postgres_object_source_sql_without_relispopulated(
|
||||
schema: &str,
|
||||
name: &str,
|
||||
kind: &db::ObjectSourceKind,
|
||||
signature: Option<&str>,
|
||||
) -> String {
|
||||
postgres_object_source_sql_inner(schema, name, kind, signature, false, false)
|
||||
}
|
||||
|
||||
fn postgres_function_object_source_sql_without_prokind(
|
||||
schema: &str,
|
||||
name: &str,
|
||||
@@ -8155,6 +8146,7 @@ fn postgres_object_source_sql_inner(
|
||||
signature: Option<&str>,
|
||||
include_relispopulated: bool,
|
||||
unwrap_opengauss_record: bool,
|
||||
isolate_view_search_path: bool,
|
||||
) -> String {
|
||||
match kind {
|
||||
db::ObjectSourceKind::View | db::ObjectSourceKind::MaterializedView => {
|
||||
@@ -8163,20 +8155,35 @@ fn postgres_object_source_sql_inner(
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let materialized_viewdef = "regexp_replace(pg_get_viewdef(c.oid, 0), ';[[:space:]]*$', '')";
|
||||
let viewdef = if isolate_view_search_path {
|
||||
"pg_catalog.pg_get_viewdef(c.oid, 0)"
|
||||
} else {
|
||||
"pg_get_viewdef(c.oid, 0)"
|
||||
};
|
||||
let regexp_replace = if isolate_view_search_path { "pg_catalog.regexp_replace" } else { "regexp_replace" };
|
||||
let format_fn = if isolate_view_search_path { "pg_catalog.format" } else { "format" };
|
||||
let materialized_viewdef = format!("{regexp_replace}({viewdef}, ';[[: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} \
|
||||
ELSE {format_fn}('CREATE MATERIALIZED VIEW %I.%I AS ', n.nspname, c.relname) || {materialized_viewdef}{materialized_populated_clause} \
|
||||
END"
|
||||
);
|
||||
let search_path_cte = if isolate_view_search_path {
|
||||
"WITH dbx_search_path AS (SELECT pg_catalog.set_config('search_path', '', true) AS applied) "
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let search_path_join = if isolate_view_search_path { " CROSS JOIN dbx_search_path path_guard" } else { "" };
|
||||
let search_path_guard = if isolate_view_search_path { " AND path_guard.applied IS NOT NULL" } else { "" };
|
||||
format!(
|
||||
"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) \
|
||||
"{search_path_cte}SELECT CASE WHEN c.relkind = 'm' THEN {} \
|
||||
ELSE {format_fn}('CREATE OR REPLACE VIEW %I.%I AS ', n.nspname, c.relname) || {viewdef} \
|
||||
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') \
|
||||
{search_path_join} \
|
||||
WHERE n.nspname = {} AND c.relname = {} AND c.relkind IN ('v','m'){search_path_guard} \
|
||||
ORDER BY c.oid LIMIT 1",
|
||||
materialized_source_expr,
|
||||
sql_string(schema),
|
||||
@@ -8353,15 +8360,32 @@ pub(crate) fn mysql_object_source_ddl_column_index(kind: &db::ObjectSourceKind)
|
||||
}
|
||||
}
|
||||
|
||||
fn postgres_view_source_fallback_sql_inner(schema: &str, name: &str, isolate_search_path: bool) -> String {
|
||||
if isolate_search_path {
|
||||
format!(
|
||||
"WITH dbx_search_path AS (SELECT pg_catalog.set_config('search_path', '', true) AS applied) \
|
||||
SELECT v.definition \
|
||||
FROM pg_catalog.pg_views v \
|
||||
CROSS JOIN dbx_search_path path_guard \
|
||||
WHERE v.schemaname = {} AND v.viewname = {} AND path_guard.applied IS NOT NULL \
|
||||
LIMIT 1",
|
||||
sql_string(schema),
|
||||
sql_string(name)
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"SELECT definition \
|
||||
FROM pg_catalog.pg_views \
|
||||
WHERE schemaname = {} AND viewname = {} \
|
||||
LIMIT 1",
|
||||
sql_string(schema),
|
||||
sql_string(name)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn postgres_view_source_fallback_sql(schema: &str, name: &str) -> String {
|
||||
format!(
|
||||
"SELECT definition \
|
||||
FROM pg_catalog.pg_views \
|
||||
WHERE schemaname = {} AND viewname = {} \
|
||||
LIMIT 1",
|
||||
sql_string(schema),
|
||||
sql_string(name)
|
||||
)
|
||||
postgres_view_source_fallback_sql_inner(schema, name, true)
|
||||
}
|
||||
|
||||
fn first_string_cell(result: db::QueryResult) -> Result<String, String> {
|
||||
@@ -8740,6 +8764,9 @@ async fn get_object_source_once(
|
||||
}
|
||||
PoolKind::Postgres(pool) => {
|
||||
let unwrap_opengauss_record = db_config.as_ref().is_some_and(is_opengauss_family_config);
|
||||
let isolate_view_search_path = postgres_view_source_uses_isolated_search_path(
|
||||
db_config.as_ref().map(|config| &config.db_type),
|
||||
);
|
||||
postgres_object_source(
|
||||
pool,
|
||||
schema,
|
||||
@@ -8748,6 +8775,7 @@ async fn get_object_source_once(
|
||||
signature,
|
||||
relation_name,
|
||||
unwrap_opengauss_record,
|
||||
isolate_view_search_path,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
@@ -9113,6 +9141,10 @@ async fn db2_column_comments(
|
||||
comments
|
||||
}
|
||||
|
||||
fn postgres_view_source_uses_isolated_search_path(database_type: Option<&DatabaseType>) -> bool {
|
||||
database_type == Some(&DatabaseType::Postgres)
|
||||
}
|
||||
|
||||
async fn postgres_object_source(
|
||||
pool: &deadpool_postgres::Pool,
|
||||
schema: &str,
|
||||
@@ -9121,13 +9153,16 @@ async fn postgres_object_source(
|
||||
signature: Option<&str>,
|
||||
relation_name: Option<&str>,
|
||||
unwrap_opengauss_record: bool,
|
||||
isolate_view_search_path: bool,
|
||||
) -> Result<String, String> {
|
||||
let sql = if matches!(object_type, db::ObjectSourceKind::Trigger) {
|
||||
postgres_trigger_object_source_sql(schema, name, relation_name)
|
||||
} else if unwrap_opengauss_record {
|
||||
opengauss_object_source_sql(schema, name, object_type, signature)
|
||||
} else {
|
||||
} else if isolate_view_search_path {
|
||||
postgres_object_source_sql(schema, name, object_type, signature)
|
||||
} else {
|
||||
postgres_object_source_sql_inner(schema, name, object_type, signature, true, false, false)
|
||||
};
|
||||
match db::postgres::execute_query(pool, &sql).await.and_then(first_string_cell) {
|
||||
Ok(source) => Ok(source),
|
||||
@@ -9135,7 +9170,15 @@ async fn postgres_object_source(
|
||||
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, signature);
|
||||
let fallback_sql = postgres_object_source_sql_inner(
|
||||
schema,
|
||||
name,
|
||||
object_type,
|
||||
signature,
|
||||
false,
|
||||
false,
|
||||
isolate_view_search_path,
|
||||
);
|
||||
db::postgres::execute_query(pool, &fallback_sql)
|
||||
.await
|
||||
.and_then(first_string_cell)
|
||||
@@ -9178,7 +9221,7 @@ async fn postgres_object_source(
|
||||
.map_err(|fallback_err| format!("{primary_err}; prokind fallback failed: {fallback_err}"))
|
||||
}
|
||||
Err(primary_err) if matches!(object_type, db::ObjectSourceKind::View) => {
|
||||
let fallback_sql = postgres_view_source_fallback_sql(schema, name);
|
||||
let fallback_sql = postgres_view_source_fallback_sql_inner(schema, name, isolate_view_search_path);
|
||||
db::postgres::execute_query(pool, &fallback_sql)
|
||||
.await
|
||||
.and_then(first_string_cell)
|
||||
@@ -9253,6 +9296,11 @@ mod object_source_tests {
|
||||
fn builds_postgres_object_source_sql_for_views_and_functions() {
|
||||
let view_sql = postgres_object_source_sql("public", "active_users", &ObjectSourceKind::View, None);
|
||||
|
||||
assert!(view_sql
|
||||
.starts_with("WITH dbx_search_path AS (SELECT pg_catalog.set_config('search_path', '', true) AS applied)"));
|
||||
assert!(view_sql.contains("pg_catalog.pg_get_viewdef(c.oid, 0)"));
|
||||
assert!(view_sql.contains("CROSS JOIN dbx_search_path path_guard"));
|
||||
assert!(view_sql.contains("path_guard.applied IS NOT NULL"));
|
||||
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"));
|
||||
@@ -9268,6 +9316,29 @@ mod object_source_tests {
|
||||
postgres_object_source_sql("public", "recalc_score", &ObjectSourceKind::Function, Some("integer, integer")),
|
||||
"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' AND pg_get_function_identity_arguments(p.oid) = 'integer, integer' ORDER BY p.oid LIMIT 1"
|
||||
);
|
||||
|
||||
let opengauss_view_sql = opengauss_object_source_sql("public", "active_users", &ObjectSourceKind::View, None);
|
||||
assert!(!opengauss_view_sql.contains("set_config('search_path'"));
|
||||
|
||||
let compatible_view_sql = postgres_object_source_sql_inner(
|
||||
"public",
|
||||
"active_users",
|
||||
&ObjectSourceKind::View,
|
||||
None,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
assert!(!compatible_view_sql.contains("set_config('search_path'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn isolates_view_search_path_only_for_native_postgres() {
|
||||
assert!(postgres_view_source_uses_isolated_search_path(Some(&DatabaseType::Postgres)));
|
||||
assert!(!postgres_view_source_uses_isolated_search_path(Some(&DatabaseType::Redshift)));
|
||||
assert!(!postgres_view_source_uses_isolated_search_path(Some(&DatabaseType::OpenGauss)));
|
||||
assert!(!postgres_view_source_uses_isolated_search_path(Some(&DatabaseType::Kingbase)));
|
||||
assert!(!postgres_view_source_uses_isolated_search_path(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -9283,15 +9354,20 @@ mod object_source_tests {
|
||||
|
||||
#[test]
|
||||
fn builds_postgres_object_source_sql_without_relispopulated_for_legacy_catalogs() {
|
||||
let sql = postgres_object_source_sql_without_relispopulated(
|
||||
let sql = postgres_object_source_sql_inner(
|
||||
"public",
|
||||
"active_users",
|
||||
&ObjectSourceKind::MaterializedView,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
|
||||
assert!(sql.contains("CREATE MATERIALIZED VIEW"));
|
||||
assert!(sql.contains("pg_get_viewdef(c.oid, 0)"));
|
||||
assert!(sql.contains("pg_catalog.pg_get_viewdef(c.oid, 0)"));
|
||||
assert!(sql.contains("pg_catalog.set_config('search_path', '', true)"));
|
||||
assert!(sql.contains("path_guard.applied IS NOT NULL"));
|
||||
assert!(!sql.contains("relispopulated"));
|
||||
}
|
||||
|
||||
@@ -9428,7 +9504,7 @@ mod object_source_tests {
|
||||
)
|
||||
);
|
||||
assert!(sql.contains(
|
||||
"THEN regexp_replace(pg_get_viewdef(c.oid, 0), ';[[:space:]]*$', '') ELSE format('CREATE MATERIALIZED VIEW"
|
||||
"THEN pg_catalog.regexp_replace(pg_catalog.pg_get_viewdef(c.oid, 0), ';[[:space:]]*$', '') ELSE pg_catalog.format('CREATE MATERIALIZED VIEW"
|
||||
));
|
||||
}
|
||||
|
||||
@@ -9461,6 +9537,10 @@ mod object_source_tests {
|
||||
fn builds_postgres_view_source_fallback_sql_from_pg_views() {
|
||||
assert_eq!(
|
||||
postgres_view_source_fallback_sql("tenant's schema", "active users"),
|
||||
"WITH dbx_search_path AS (SELECT pg_catalog.set_config('search_path', '', true) AS applied) SELECT v.definition FROM pg_catalog.pg_views v CROSS JOIN dbx_search_path path_guard WHERE v.schemaname = 'tenant''s schema' AND v.viewname = 'active users' AND path_guard.applied IS NOT NULL LIMIT 1"
|
||||
);
|
||||
assert_eq!(
|
||||
postgres_view_source_fallback_sql_inner("tenant's schema", "active users", false),
|
||||
"SELECT definition FROM pg_catalog.pg_views WHERE schemaname = 'tenant''s schema' AND viewname = 'active users' LIMIT 1"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user