ironflow_ops_postgres/admin/
connections.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 ActiveConnectionsOutput {
15 pub count: i64,
17}
18
19pub struct ActiveConnections {
33 pool: PgPool,
34}
35
36impl ActiveConnections {
37 pub fn new(pool: PgPool) -> Self {
39 Self { pool }
40 }
41
42 pub async fn run(
48 &self,
49 _ctx: &OperationContext,
50 ) -> Result<ActiveConnectionsOutput, OperationError> {
51 let row = sqlx::query(
52 "SELECT count(*) AS cnt FROM pg_stat_activity \
53 WHERE datname = current_database()",
54 )
55 .fetch_one(&self.pool)
56 .await
57 .map_err(pg_error)?;
58 let count: i64 = row.try_get("cnt").map_err(pg_error)?;
59 Ok(ActiveConnectionsOutput { count })
60 }
61}
62
63#[async_trait]
64impl Operation for ActiveConnections {
65 fn kind(&self) -> &str {
66 "postgres"
67 }
68 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
69 to_value(&self.run(ctx).await?)
70 }
71}
72
73impl TypedOperation for ActiveConnections {
74 type Output = ActiveConnectionsOutput;
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct RunningQueryInfo {
80 pub pid: i32,
82 pub query: String,
84 pub state: String,
86 pub duration: String,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct RunningQueriesOutput {
93 pub queries: Vec<RunningQueryInfo>,
95}
96
97pub struct RunningQueries {
111 pool: PgPool,
112}
113
114impl RunningQueries {
115 pub fn new(pool: PgPool) -> Self {
117 Self { pool }
118 }
119
120 pub async fn run(
126 &self,
127 _ctx: &OperationContext,
128 ) -> Result<RunningQueriesOutput, OperationError> {
129 let rows = sqlx::query(
130 "SELECT pid, query, state, \
131 extract(epoch from (now() - query_start))::bigint AS duration_secs \
132 FROM pg_stat_activity \
133 WHERE datname = current_database() AND state = 'active' \
134 ORDER BY query_start",
135 )
136 .fetch_all(&self.pool)
137 .await
138 .map_err(pg_error)?;
139 let queries = rows
140 .iter()
141 .map(|r| {
142 let secs: i64 = r.try_get("duration_secs").unwrap_or(0);
143 Ok(RunningQueryInfo {
144 pid: r.try_get::<i32, _>("pid").map_err(pg_error)?,
145 query: r.try_get::<String, _>("query").map_err(pg_error)?,
146 state: r.try_get::<String, _>("state").map_err(pg_error)?,
147 duration: format!("{secs}s"),
148 })
149 })
150 .collect::<Result<Vec<_>, OperationError>>()?;
151 Ok(RunningQueriesOutput { queries })
152 }
153}
154
155#[async_trait]
156impl Operation for RunningQueries {
157 fn kind(&self) -> &str {
158 "postgres"
159 }
160 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
161 to_value(&self.run(ctx).await?)
162 }
163}
164
165impl TypedOperation for RunningQueries {
166 type Output = RunningQueriesOutput;
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[tokio::test]
174 async fn active_connections_kind() {
175 let pool = PgPool::connect_lazy("postgres://localhost/test").unwrap();
176 let op = ActiveConnections::new(pool);
177 assert_eq!(op.kind(), "postgres");
178 }
179
180 #[tokio::test]
181 async fn running_queries_kind() {
182 let pool = PgPool::connect_lazy("postgres://localhost/test").unwrap();
183 let op = RunningQueries::new(pool);
184 assert_eq!(op.kind(), "postgres");
185 }
186}