ironflow_ops_postgres/schema/
columns.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 ColumnInfo {
15 pub name: String,
17 pub data_type: String,
19 pub is_nullable: bool,
21 pub column_default: Option<String>,
23 pub ordinal_position: i32,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ListColumnsOutput {
30 pub columns: Vec<ColumnInfo>,
32}
33
34pub struct ListColumns {
48 pool: PgPool,
49 schema: String,
50 table: String,
51}
52
53impl ListColumns {
54 pub fn new(pool: PgPool, schema: impl Into<String>, table: impl Into<String>) -> Self {
56 Self {
57 pool,
58 schema: schema.into(),
59 table: table.into(),
60 }
61 }
62
63 pub async fn run(&self, _ctx: &OperationContext) -> Result<ListColumnsOutput, OperationError> {
69 let rows = sqlx::query(
70 "SELECT column_name, data_type, is_nullable, column_default, ordinal_position \
71 FROM information_schema.columns \
72 WHERE table_schema = $1 AND table_name = $2 \
73 ORDER BY ordinal_position",
74 )
75 .bind(&self.schema)
76 .bind(&self.table)
77 .fetch_all(&self.pool)
78 .await
79 .map_err(pg_error)?;
80 let columns = rows
81 .iter()
82 .map(|r| {
83 Ok(ColumnInfo {
84 name: r.try_get::<String, _>("column_name").map_err(pg_error)?,
85 data_type: r.try_get::<String, _>("data_type").map_err(pg_error)?,
86 is_nullable: r.try_get::<String, _>("is_nullable").map_err(pg_error)? == "YES",
87 column_default: r
88 .try_get::<Option<String>, _>("column_default")
89 .map_err(pg_error)?,
90 ordinal_position: r.try_get::<i32, _>("ordinal_position").map_err(pg_error)?,
91 })
92 })
93 .collect::<Result<Vec<_>, OperationError>>()?;
94 Ok(ListColumnsOutput { columns })
95 }
96}
97
98#[async_trait]
99impl Operation for ListColumns {
100 fn kind(&self) -> &str {
101 "postgres"
102 }
103 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
104 to_value(&self.run(ctx).await?)
105 }
106 fn input(&self) -> Option<Value> {
107 Some(serde_json::json!({ "schema": self.schema, "table": self.table }))
108 }
109}
110
111impl TypedOperation for ListColumns {
112 type Output = ListColumnsOutput;
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[tokio::test]
120 async fn kind() {
121 let pool = PgPool::connect_lazy("postgres://localhost/test").unwrap();
122 let op = ListColumns::new(pool, "public", "users");
123 assert_eq!(op.kind(), "postgres");
124 }
125}