Skip to content

Commit 3426fcf

Browse files
so far. clone not required
1 parent 26e82c7 commit 3426fcf

File tree

13 files changed

+235
-9
lines changed

13 files changed

+235
-9
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/pgt_schema_cache/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ serde.workspace = true
2121
serde_json.workspace = true
2222
sqlx.workspace = true
2323
tokio.workspace = true
24+
strum = { workspace = true }
2425

2526
[dev-dependencies]
2627
pgt_test_utils.workspace = true

crates/pgt_schema_cache/src/columns.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ impl From<char> for ColumnClassKind {
3737
}
3838
}
3939

40-
#[derive(Debug, Clone, PartialEq, Eq)]
40+
#[derive(Debug, PartialEq, Eq)]
4141
pub struct Column {
4242
pub name: String,
4343

crates/pgt_schema_cache/src/functions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ impl From<Option<JsonValue>> for FunctionArgs {
5858
}
5959
}
6060

61-
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
61+
#[derive(Debug, Default, Serialize, Deserialize)]
6262
pub struct Function {
6363
/// The Id (`oid`).
6464
pub id: i64,

crates/pgt_schema_cache/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod policies;
88
mod schema_cache;
99
mod schemas;
1010
mod tables;
11+
mod triggers;
1112
mod types;
1213
mod versions;
1314

@@ -16,3 +17,4 @@ pub use functions::{Behavior, Function, FunctionArg, FunctionArgs};
1617
pub use schema_cache::SchemaCache;
1718
pub use schemas::Schema;
1819
pub use tables::{ReplicaIdentity, Table};
20+
pub use triggers::{Trigger, TriggerAffected, TriggerEvent};

crates/pgt_schema_cache/src/policies.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ impl From<PolicyQueried> for Policy {
5454
}
5555
}
5656

57-
#[derive(Debug, Clone, PartialEq, Eq)]
57+
#[derive(Debug, PartialEq, Eq)]
5858
pub struct Policy {
5959
name: String,
6060
table_name: String,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- we need to join tables from the pg_catalog since "TRUNCATE" triggers are
2+
-- not available in the information_schema.trigger table.
3+
select
4+
t.tgname as "name!",
5+
c.relname as "table_name!",
6+
p.proname as "proc_name!",
7+
n.nspname as "schema_name!",
8+
t.tgtype as "details_bitmask!"
9+
from
10+
pg_catalog.pg_trigger t
11+
left join pg_catalog.pg_proc p on t.tgfoid = p.oid
12+
left join pg_catalog.pg_class c on t.tgrelid = c.oid
13+
left join pg_catalog.pg_namespace n on c.relnamespace = n.oid
14+
where
15+
-- triggers enforcing constraints (e.g. unique fields) should not be included.
16+
t.tgisinternal = false and
17+
t.tgconstraint = 0;

crates/pgt_schema_cache/src/schema_cache.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use sqlx::postgres::PgPool;
22

3+
use crate::Trigger;
34
use crate::columns::Column;
45
use crate::functions::Function;
56
use crate::policies::Policy;
@@ -8,7 +9,7 @@ use crate::tables::Table;
89
use crate::types::PostgresType;
910
use crate::versions::Version;
1011

11-
#[derive(Debug, Clone, Default)]
12+
#[derive(Debug, Default)]
1213
pub struct SchemaCache {
1314
pub schemas: Vec<Schema>,
1415
pub tables: Vec<Table>,
@@ -17,18 +18,20 @@ pub struct SchemaCache {
1718
pub versions: Vec<Version>,
1819
pub columns: Vec<Column>,
1920
pub policies: Vec<Policy>,
21+
pub triggers: Vec<Trigger>,
2022
}
2123

2224
impl SchemaCache {
2325
pub async fn load(pool: &PgPool) -> Result<SchemaCache, sqlx::Error> {
24-
let (schemas, tables, functions, types, versions, columns, policies) = futures_util::try_join!(
26+
let (schemas, tables, functions, types, versions, columns, policies, triggers) = futures_util::try_join!(
2527
Schema::load(pool),
2628
Table::load(pool),
2729
Function::load(pool),
2830
PostgresType::load(pool),
2931
Version::load(pool),
3032
Column::load(pool),
3133
Policy::load(pool),
34+
Trigger::load(pool),
3235
)?;
3336

3437
Ok(SchemaCache {
@@ -39,6 +42,7 @@ impl SchemaCache {
3942
versions,
4043
columns,
4144
policies,
45+
triggers,
4246
})
4347
}
4448

crates/pgt_schema_cache/src/schemas.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use sqlx::PgPool;
22

33
use crate::schema_cache::SchemaCacheItem;
44

5-
#[derive(Debug, Clone, Default)]
5+
#[derive(Debug, Default)]
66
pub struct Schema {
77
pub id: i64,
88
pub name: String,

crates/pgt_schema_cache/src/tables.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl From<String> for ReplicaIdentity {
2323
}
2424
}
2525

26-
#[derive(Debug, Clone, Default, PartialEq, Eq)]
26+
#[derive(Debug, Default, PartialEq, Eq)]
2727
pub struct Table {
2828
pub id: i64,
2929
pub schema: String,
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
use crate::schema_cache::SchemaCacheItem;
2+
use strum::{EnumIter, IntoEnumIterator};
3+
4+
#[derive(Debug, PartialEq, Eq)]
5+
pub enum TriggerAffected {
6+
Row,
7+
Statement,
8+
}
9+
10+
impl From<i16> for TriggerAffected {
11+
fn from(value: i16) -> Self {
12+
let is_row = 0b0000_0001;
13+
if value & is_row == is_row {
14+
Self::Row
15+
} else {
16+
Self::Statement
17+
}
18+
}
19+
}
20+
21+
#[derive(Debug, PartialEq, Eq, EnumIter)]
22+
pub enum TriggerEvent {
23+
Insert,
24+
Delete,
25+
Update,
26+
Truncate,
27+
}
28+
29+
struct TriggerEvents(Vec<TriggerEvent>);
30+
31+
impl From<i16> for TriggerEvents {
32+
fn from(value: i16) -> Self {
33+
Self(
34+
TriggerEvent::iter()
35+
.filter(|variant| {
36+
#[rustfmt::skip]
37+
let mask = match variant {
38+
TriggerEvent::Insert => 0b0000_0100,
39+
TriggerEvent::Delete => 0b0000_1000,
40+
TriggerEvent::Update => 0b0001_0000,
41+
TriggerEvent::Truncate => 0b0010_0000,
42+
};
43+
mask & value == mask
44+
})
45+
.collect(),
46+
)
47+
}
48+
}
49+
50+
#[derive(Debug, PartialEq, Eq, EnumIter)]
51+
pub enum TriggerTiming {
52+
Before,
53+
After,
54+
Instead,
55+
}
56+
57+
impl TryFrom<i16> for TriggerTiming {
58+
type Error = ();
59+
fn try_from(value: i16) -> Result<Self, ()> {
60+
TriggerTiming::iter()
61+
.find(|variant| {
62+
#[rustfmt::skip]
63+
let mask = match variant {
64+
TriggerTiming::Instead => 0b0100_0000,
65+
TriggerTiming::Before => 0b0000_0010,
66+
TriggerTiming::After => 0b0000_0000, // before/after share same bit
67+
};
68+
mask & value == mask
69+
})
70+
.ok_or(())
71+
}
72+
}
73+
74+
pub struct TriggerQueried {
75+
name: String,
76+
table_name: String,
77+
schema_name: String,
78+
proc_name: String,
79+
details_bitmask: i16,
80+
}
81+
82+
#[derive(Debug, PartialEq, Eq)]
83+
pub struct Trigger {
84+
name: String,
85+
table_name: String,
86+
schema_name: String,
87+
affected: TriggerAffected,
88+
timing: TriggerTiming,
89+
events: Vec<TriggerEvent>,
90+
}
91+
92+
impl From<TriggerQueried> for Trigger {
93+
fn from(value: TriggerQueried) -> Self {
94+
Self {
95+
name: value.name,
96+
table_name: value.table_name,
97+
schema_name: value.schema_name,
98+
affected: value.details_bitmask.into(),
99+
timing: value.details_bitmask.try_into().unwrap(),
100+
events: TriggerEvents::from(value.details_bitmask).0,
101+
}
102+
}
103+
}
104+
105+
impl SchemaCacheItem for Trigger {
106+
type Item = Trigger;
107+
108+
async fn load(pool: &sqlx::PgPool) -> Result<Vec<Self::Item>, sqlx::Error> {
109+
let results = sqlx::query_file_as!(TriggerQueried, "src/queries/triggers.sql")
110+
.fetch_all(pool)
111+
.await?;
112+
113+
Ok(results.into_iter().map(|r| r.into()).collect())
114+
}
115+
}
116+
117+
#[cfg(test)]
118+
mod tests {
119+
use pgt_test_utils::test_database::get_new_test_db;
120+
use sqlx::Executor;
121+
122+
use crate::{
123+
SchemaCache,
124+
triggers::{TriggerAffected, TriggerEvent, TriggerTiming},
125+
};
126+
127+
#[tokio::test]
128+
async fn loads_triggers() {
129+
let test_db = get_new_test_db().await;
130+
131+
let setup = r#"
132+
create table public.users (
133+
id serial primary key,
134+
name text
135+
);
136+
137+
create or replace function public.log_user_insert()
138+
returns trigger as $$
139+
begin
140+
-- dummy body
141+
return new;
142+
end;
143+
$$ language plpgsql;
144+
145+
create trigger trg_users_insert
146+
before insert on public.users
147+
for each row
148+
execute function public.log_user_insert();
149+
150+
create trigger trg_users_update
151+
after update on public.users
152+
for each statement
153+
execute function public.log_user_insert();
154+
155+
create trigger trg_users_delete
156+
before delete on public.users
157+
for each row
158+
execute function public.log_user_insert();
159+
"#;
160+
161+
test_db
162+
.execute(setup)
163+
.await
164+
.expect("Failed to setup test database");
165+
166+
let cache = SchemaCache::load(&test_db)
167+
.await
168+
.expect("Failed to load Schema Cache");
169+
170+
let triggers: Vec<_> = cache
171+
.triggers
172+
.iter()
173+
.filter(|t| t.table_name == "users")
174+
.collect();
175+
assert_eq!(triggers.len(), 3);
176+
177+
let insert_trigger = triggers
178+
.iter()
179+
.find(|t| t.name == "trg_users_insert")
180+
.unwrap();
181+
assert_eq!(insert_trigger.timing, TriggerTiming::Before);
182+
assert_eq!(insert_trigger.affected, TriggerAffected::Row);
183+
assert!(insert_trigger.events.contains(&TriggerEvent::Insert));
184+
185+
let update_trigger = triggers
186+
.iter()
187+
.find(|t| t.name == "trg_users_update")
188+
.unwrap();
189+
assert_eq!(update_trigger.timing, TriggerTiming::After);
190+
assert_eq!(update_trigger.affected, TriggerAffected::Statement);
191+
assert!(update_trigger.events.contains(&TriggerEvent::Update));
192+
193+
let delete_trigger = triggers
194+
.iter()
195+
.find(|t| t.name == "trg_users_delete")
196+
.unwrap();
197+
assert_eq!(delete_trigger.timing, TriggerTiming::Before);
198+
assert_eq!(delete_trigger.affected, TriggerAffected::Row);
199+
assert!(delete_trigger.events.contains(&TriggerEvent::Delete));
200+
}
201+
}

crates/pgt_schema_cache/src/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ impl From<Option<JsonValue>> for Enums {
3636
}
3737
}
3838

39-
#[derive(Debug, Clone, Default)]
39+
#[derive(Debug, Default)]
4040
pub struct PostgresType {
4141
pub id: i64,
4242
pub name: String,

crates/pgt_schema_cache/src/versions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use sqlx::PgPool;
22

33
use crate::schema_cache::SchemaCacheItem;
44

5-
#[derive(Debug, Clone, Default)]
5+
#[derive(Debug, Default)]
66
pub struct Version {
77
pub version: Option<String>,
88
pub version_num: Option<i64>,

0 commit comments

Comments
 (0)