pub mod aggregate_unmanaged_state_function;
pub mod builtin_provider_requires_pg_17;
pub mod closed_world_references;
pub mod column_position_drift;
pub mod column_references_unmanaged_collation;
pub mod composite_attribute_collision;
pub mod compression_change_not_retroactive;
pub mod domain_check_references_unmanaged_type;
pub mod enum_value_collision;
pub mod extension_references_unmanaged_schema;
pub mod extension_version_unpinned;
pub mod force_rls_without_policies;
pub mod function_references_unmanaged_schema;
pub mod grant_references_unknown_role;
pub mod grants_to_unmanaged_role;
pub mod managed_schemas_match;
pub mod mv_no_unique_index;
pub mod no_duplicate_qnames;
pub mod nondeterministic_collation_requires_pg_12;
pub mod partition_references_unmanaged_parent;
pub mod pl_pgsql_dynamic_sql;
pub mod procedure_contains_commit;
pub mod publication_captures_unmanaged_table;
pub mod publication_feature_requires_pg_version;
pub mod publication_row_filter_references_unmanaged_column;
pub mod range_type_references_unmanaged_subtype;
pub mod revoke_from_owner;
pub mod role_loses_superuser;
pub mod role_membership_cycle;
pub mod storage_downgrade_not_retroactive;
pub mod subscription_feature_requires_pg_version;
pub mod subscription_password_in_source;
pub mod subscription_references_undeclared_publication;
pub mod table_access_method_change;
pub mod tablespace_location_drift;
pub mod trigger_references_unmanaged_function;
pub mod trigger_references_unmanaged_table;
pub mod type_shadows_table;
pub mod unmanaged_collation;
pub mod unmanaged_event_trigger;
pub mod unmanaged_publication;
pub mod unmanaged_reloption;
pub mod unmanaged_statistic;
pub mod unmanaged_subscription;
pub mod view_body_references_unmanaged_schema;
pub mod view_shadows_table;
use crate::lint::finding::{Finding, Severity};
use std::fmt::Display;
pub const BUILTIN_SCHEMAS: &[&str] = &["pg_catalog", "information_schema"];
pub fn check_unmanaged_objects<T, K, F>(
target: &[T],
source: &[T],
key: F,
rule_id: &'static str,
noun: &str,
) -> Vec<Finding>
where
K: PartialEq + Display,
F: Fn(&T) -> &K,
{
target
.iter()
.filter(|t| !source.iter().any(|s| key(s) == key(t)))
.map(|t| Finding {
rule: rule_id,
severity: Severity::Warning,
message: format!(
"{noun} {}: catalog has a {noun} not declared in source",
key(t),
),
location: None,
})
.collect()
}
pub fn extract_qualified_refs(text: &str) -> Vec<(String, String)> {
let mut result = Vec::new();
let bytes = text.as_bytes();
let len = bytes.len();
let mut i = 0;
while i < len {
if !is_id_start(bytes[i]) {
i += 1;
continue;
}
let start = i;
while i < len && is_id_char(bytes[i]) {
i += 1;
}
let first = &text[start..i];
if i < len && bytes[i] == b'.' {
i += 1; if i < len && is_id_start(bytes[i]) {
let start2 = i;
while i < len && is_id_char(bytes[i]) {
i += 1;
}
let second = &text[start2..i];
result.push((first.to_string(), second.to_string()));
}
}
}
result
}
pub const fn is_id_start(b: u8) -> bool {
b.is_ascii_alphabetic() || b == b'_'
}
pub const fn is_id_char(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_'
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extract_qualified_refs_basic() {
let refs = extract_qualified_refs("value > 0 and external.validate_int(value)");
assert!(
refs.contains(&("external".to_string(), "validate_int".to_string())),
"should extract external.validate_int: {refs:?}",
);
}
#[test]
fn extract_qualified_refs_empty_text() {
let refs = extract_qualified_refs("value > 0");
assert!(
refs.is_empty(),
"no qualified refs in simple expression: {refs:?}",
);
}
}