ironflow_ops_postgres/schema/
constraints.rs1use async_trait::async_trait;
4use ironflow_core::error::OperationError;
5use ironflow_core::operation::{Operation, OperationContext, TypedOperation};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use sqlx::{PgPool, Row};
9
10use crate::helpers::{pg_error, to_value};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ConstraintInfo {
15 pub name: String,
17 pub constraint_type: String,
19 pub columns: Vec<String>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ListConstraintsOutput {
26 pub constraints: Vec<ConstraintInfo>,
28}
29
30pub struct ListConstraints {
44 pool: PgPool,
45 schema: String,
46 table: String,
47}
48
49impl ListConstraints {
50 pub fn new(pool: PgPool, schema: impl Into<String>, table: impl Into<String>) -> Self {
52 Self {
53 pool,
54 schema: schema.into(),
55 table: table.into(),
56 }
57 }
58
59 pub async fn run(
65 &self,
66 _ctx: &OperationContext,
67 ) -> Result<ListConstraintsOutput, OperationError> {
68 let rows = sqlx::query(
69 "SELECT c.conname AS constraint_name, \
70 c.contype AS constraint_type, \
71 array_agg(a.attname ORDER BY u.ord) AS columns \
72 FROM pg_constraint c \
73 JOIN pg_class t ON t.oid = c.conrelid \
74 JOIN pg_namespace n ON n.oid = t.relnamespace \
75 CROSS JOIN LATERAL unnest(c.conkey) WITH ORDINALITY AS u(attnum, ord) \
76 JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = u.attnum \
77 WHERE n.nspname = $1 AND t.relname = $2 \
78 GROUP BY c.conname, c.contype \
79 ORDER BY c.conname",
80 )
81 .bind(&self.schema)
82 .bind(&self.table)
83 .fetch_all(&self.pool)
84 .await
85 .map_err(pg_error)?;
86 let constraints = rows
87 .iter()
88 .map(|r| {
89 let type_char: String = r
90 .try_get::<String, _>("constraint_type")
91 .map_err(pg_error)?;
92 let constraint_type = match type_char.as_str() {
93 "p" => "PRIMARY KEY",
94 "f" => "FOREIGN KEY",
95 "u" => "UNIQUE",
96 "c" => "CHECK",
97 "x" => "EXCLUSION",
98 other => other,
99 }
100 .to_string();
101 Ok(ConstraintInfo {
102 name: r
103 .try_get::<String, _>("constraint_name")
104 .map_err(pg_error)?,
105 constraint_type,
106 columns: r.try_get::<Vec<String>, _>("columns").map_err(pg_error)?,
107 })
108 })
109 .collect::<Result<Vec<_>, OperationError>>()?;
110 Ok(ListConstraintsOutput { constraints })
111 }
112}
113
114#[async_trait]
115impl Operation for ListConstraints {
116 fn kind(&self) -> &str {
117 "postgres"
118 }
119 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
120 to_value(&self.run(ctx).await?)
121 }
122 fn input(&self) -> Option<Value> {
123 Some(serde_json::json!({ "schema": self.schema, "table": self.table }))
124 }
125}
126
127impl TypedOperation for ListConstraints {
128 type Output = ListConstraintsOutput;
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[tokio::test]
136 async fn kind() {
137 let pool = PgPool::connect_lazy("postgres://localhost/test").unwrap();
138 let op = ListConstraints::new(pool, "public", "users");
139 assert_eq!(op.kind(), "postgres");
140 }
141}