sal_postgresclient/
rhai.rs

1//! Rhai wrappers for PostgreSQL client module functions
2//!
3//! This module provides Rhai wrappers for the functions in the PostgreSQL client module.
4
5use crate::{
6    create_database, execute, execute_sql, get_postgres_client, install_postgres,
7    is_postgres_running, query_one, reset, PostgresInstallerConfig,
8};
9use postgres::types::ToSql;
10use rhai::{Array, Engine, EvalAltResult, Map};
11use sal_virt::nerdctl::Container;
12
13/// Register PostgreSQL client module functions with the Rhai engine
14///
15/// # Arguments
16///
17/// * `engine` - The Rhai engine to register the functions with
18///
19/// # Returns
20///
21/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
22pub fn register_postgresclient_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
23    // Register PostgreSQL connection functions
24    engine.register_fn("pg_connect", pg_connect);
25    engine.register_fn("pg_ping", pg_ping);
26    engine.register_fn("pg_reset", pg_reset);
27
28    // Register basic query functions
29    engine.register_fn("pg_execute", pg_execute);
30    engine.register_fn("pg_query", pg_query);
31    engine.register_fn("pg_query_one", pg_query_one);
32
33    // Register installer functions
34    engine.register_fn("pg_install", pg_install);
35    engine.register_fn("pg_create_database", pg_create_database);
36    engine.register_fn("pg_execute_sql", pg_execute_sql);
37    engine.register_fn("pg_is_running", pg_is_running);
38
39    // Builder pattern functions will be implemented in a future update
40
41    Ok(())
42}
43
44/// Connect to PostgreSQL using environment variables
45///
46/// # Returns
47///
48/// * `Result<bool, Box<EvalAltResult>>` - true if successful, error otherwise
49pub fn pg_connect() -> Result<bool, Box<EvalAltResult>> {
50    match get_postgres_client() {
51        Ok(_) => Ok(true),
52        Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
53            format!("PostgreSQL error: {}", e).into(),
54            rhai::Position::NONE,
55        ))),
56    }
57}
58
59/// Ping the PostgreSQL server
60///
61/// # Returns
62///
63/// * `Result<bool, Box<EvalAltResult>>` - true if successful, error otherwise
64pub fn pg_ping() -> Result<bool, Box<EvalAltResult>> {
65    match get_postgres_client() {
66        Ok(client) => match client.ping() {
67            Ok(result) => Ok(result),
68            Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
69                format!("PostgreSQL error: {}", e).into(),
70                rhai::Position::NONE,
71            ))),
72        },
73        Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
74            format!("PostgreSQL error: {}", e).into(),
75            rhai::Position::NONE,
76        ))),
77    }
78}
79
80/// Reset the PostgreSQL client connection
81///
82/// # Returns
83///
84/// * `Result<bool, Box<EvalAltResult>>` - true if successful, error otherwise
85pub fn pg_reset() -> Result<bool, Box<EvalAltResult>> {
86    match reset() {
87        Ok(_) => Ok(true),
88        Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
89            format!("PostgreSQL error: {}", e).into(),
90            rhai::Position::NONE,
91        ))),
92    }
93}
94
95/// Execute a query on the PostgreSQL connection
96///
97/// # Arguments
98///
99/// * `query` - The query to execute
100///
101/// # Returns
102///
103/// * `Result<i64, Box<EvalAltResult>>` - The number of rows affected if successful, error otherwise
104pub fn pg_execute(query: &str) -> Result<i64, Box<EvalAltResult>> {
105    // We can't directly pass dynamic parameters from Rhai to PostgreSQL
106    // So we'll only support parameterless queries for now
107    let params: &[&(dyn ToSql + Sync)] = &[];
108
109    match execute(query, params) {
110        Ok(rows) => Ok(rows as i64),
111        Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
112            format!("PostgreSQL error: {}", e).into(),
113            rhai::Position::NONE,
114        ))),
115    }
116}
117
118/// Execute a query on the PostgreSQL connection and return the rows
119///
120/// # Arguments
121///
122/// * `query` - The query to execute
123///
124/// # Returns
125///
126/// * `Result<Array, Box<EvalAltResult>>` - The rows if successful, error otherwise
127pub fn pg_query(query_str: &str) -> Result<Array, Box<EvalAltResult>> {
128    // We can't directly pass dynamic parameters from Rhai to PostgreSQL
129    // So we'll only support parameterless queries for now
130    let params: &[&(dyn ToSql + Sync)] = &[];
131
132    match crate::query(query_str, params) {
133        Ok(rows) => {
134            let mut result = Array::new();
135            for row in rows {
136                let mut map = Map::new();
137                for column in row.columns() {
138                    let name = column.name();
139                    // We'll convert all values to strings for simplicity
140                    let value: Option<String> = row.get(name);
141                    if let Some(val) = value {
142                        map.insert(name.into(), val.into());
143                    } else {
144                        map.insert(name.into(), rhai::Dynamic::UNIT);
145                    }
146                }
147                result.push(map.into());
148            }
149            Ok(result)
150        }
151        Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
152            format!("PostgreSQL error: {}", e).into(),
153            rhai::Position::NONE,
154        ))),
155    }
156}
157
158/// Execute a query on the PostgreSQL connection and return a single row
159///
160/// # Arguments
161///
162/// * `query` - The query to execute
163///
164/// # Returns
165///
166/// * `Result<Map, Box<EvalAltResult>>` - The row if successful, error otherwise
167pub fn pg_query_one(query: &str) -> Result<Map, Box<EvalAltResult>> {
168    // We can't directly pass dynamic parameters from Rhai to PostgreSQL
169    // So we'll only support parameterless queries for now
170    let params: &[&(dyn ToSql + Sync)] = &[];
171
172    match query_one(query, params) {
173        Ok(row) => {
174            let mut map = Map::new();
175            for column in row.columns() {
176                let name = column.name();
177                // We'll convert all values to strings for simplicity
178                let value: Option<String> = row.get(name);
179                if let Some(val) = value {
180                    map.insert(name.into(), val.into());
181                } else {
182                    map.insert(name.into(), rhai::Dynamic::UNIT);
183                }
184            }
185            Ok(map)
186        }
187        Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
188            format!("PostgreSQL error: {}", e).into(),
189            rhai::Position::NONE,
190        ))),
191    }
192}
193
194/// Install PostgreSQL using nerdctl
195///
196/// # Arguments
197///
198/// * `container_name` - Name for the PostgreSQL container
199/// * `version` - PostgreSQL version to install (e.g., "latest", "15", "14")
200/// * `port` - Port to expose PostgreSQL on
201/// * `username` - Username for PostgreSQL
202/// * `password` - Password for PostgreSQL
203///
204/// # Returns
205///
206/// * `Result<bool, Box<EvalAltResult>>` - true if successful, error otherwise
207pub fn pg_install(
208    container_name: &str,
209    version: &str,
210    port: i64,
211    username: &str,
212    password: &str,
213) -> Result<bool, Box<EvalAltResult>> {
214    // Create the installer configuration
215    let config = PostgresInstallerConfig::new()
216        .container_name(container_name)
217        .version(version)
218        .port(port as u16)
219        .username(username)
220        .password(password);
221
222    // Install PostgreSQL
223    match install_postgres(config) {
224        Ok(_) => Ok(true),
225        Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
226            format!("PostgreSQL installer error: {}", e).into(),
227            rhai::Position::NONE,
228        ))),
229    }
230}
231
232/// Create a new database in PostgreSQL
233///
234/// # Arguments
235///
236/// * `container_name` - Name of the PostgreSQL container
237/// * `db_name` - Database name to create
238///
239/// # Returns
240///
241/// * `Result<bool, Box<EvalAltResult>>` - true if successful, error otherwise
242pub fn pg_create_database(container_name: &str, db_name: &str) -> Result<bool, Box<EvalAltResult>> {
243    // Create a container reference
244    let container = Container {
245        name: container_name.to_string(),
246        container_id: Some(container_name.to_string()), // Use name as ID for simplicity
247        image: None,
248        config: std::collections::HashMap::new(),
249        ports: Vec::new(),
250        volumes: Vec::new(),
251        env_vars: std::collections::HashMap::new(),
252        network: None,
253        network_aliases: Vec::new(),
254        cpu_limit: None,
255        memory_limit: None,
256        memory_swap_limit: None,
257        cpu_shares: None,
258        restart_policy: None,
259        health_check: None,
260        detach: false,
261        snapshotter: None,
262    };
263
264    // Create the database
265    match create_database(&container, db_name) {
266        Ok(_) => Ok(true),
267        Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
268            format!("PostgreSQL error: {}", e).into(),
269            rhai::Position::NONE,
270        ))),
271    }
272}
273
274/// Execute a SQL script in PostgreSQL
275///
276/// # Arguments
277///
278/// * `container_name` - Name of the PostgreSQL container
279/// * `db_name` - Database name
280/// * `sql` - SQL script to execute
281///
282/// # Returns
283///
284/// * `Result<String, Box<EvalAltResult>>` - Output of the command if successful, error otherwise
285pub fn pg_execute_sql(
286    container_name: &str,
287    db_name: &str,
288    sql: &str,
289) -> Result<String, Box<EvalAltResult>> {
290    // Create a container reference
291    let container = Container {
292        name: container_name.to_string(),
293        container_id: Some(container_name.to_string()), // Use name as ID for simplicity
294        image: None,
295        config: std::collections::HashMap::new(),
296        ports: Vec::new(),
297        volumes: Vec::new(),
298        env_vars: std::collections::HashMap::new(),
299        network: None,
300        network_aliases: Vec::new(),
301        cpu_limit: None,
302        memory_limit: None,
303        memory_swap_limit: None,
304        cpu_shares: None,
305        restart_policy: None,
306        health_check: None,
307        detach: false,
308        snapshotter: None,
309    };
310
311    // Execute the SQL script
312    match execute_sql(&container, db_name, sql) {
313        Ok(output) => Ok(output),
314        Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
315            format!("PostgreSQL error: {}", e).into(),
316            rhai::Position::NONE,
317        ))),
318    }
319}
320
321/// Check if PostgreSQL is running
322///
323/// # Arguments
324///
325/// * `container_name` - Name of the PostgreSQL container
326///
327/// # Returns
328///
329/// * `Result<bool, Box<EvalAltResult>>` - true if running, false otherwise, or error
330pub fn pg_is_running(container_name: &str) -> Result<bool, Box<EvalAltResult>> {
331    // Create a container reference
332    let container = Container {
333        name: container_name.to_string(),
334        container_id: Some(container_name.to_string()), // Use name as ID for simplicity
335        image: None,
336        config: std::collections::HashMap::new(),
337        ports: Vec::new(),
338        volumes: Vec::new(),
339        env_vars: std::collections::HashMap::new(),
340        network: None,
341        network_aliases: Vec::new(),
342        cpu_limit: None,
343        memory_limit: None,
344        memory_swap_limit: None,
345        cpu_shares: None,
346        restart_policy: None,
347        health_check: None,
348        detach: false,
349        snapshotter: None,
350    };
351
352    // Check if PostgreSQL is running
353    match is_postgres_running(&container) {
354        Ok(running) => Ok(running),
355        Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
356            format!("PostgreSQL error: {}", e).into(),
357            rhai::Position::NONE,
358        ))),
359    }
360}