1use crate::sql::codec::{bind_any, json_to_any_value, row_to_json};
26use crate::sql::introspect::ColumnInfo;
27use axum::http::StatusCode;
28use larust_core::AppError;
29use serde_json::Value as Json;
30
31fn internal<E: std::error::Error + Send + Sync + 'static>(error: E) -> AppError {
32 AppError::Internal(Box::new(error))
33}
34
35fn column_kind<'a>(columns: &'a [ColumnInfo], name: &str) -> sqlx::any::AnyTypeInfoKind {
36 columns
37 .iter()
38 .find(|c| c.name == name)
39 .map(|c: &'a ColumnInfo| c.kind)
40 .unwrap_or(sqlx::any::AnyTypeInfoKind::Text)
41}
42
43fn require_known_pk_columns(columns: &[ColumnInfo], pk: &[(String, Json)]) -> Result<(), AppError> {
57 for (name, _) in pk {
58 if !columns.iter().any(|c| &c.name == name) {
59 return Err(AppError::Http {
60 status: StatusCode::BAD_REQUEST,
61 message: format!("unknown primary key column: {name}"),
62 });
63 }
64 }
65 Ok(())
66}
67
68pub async fn insert_row(
74 table: &str,
75 columns: &[ColumnInfo],
76 values: &[(String, Json)],
77) -> Result<(), AppError> {
78 let backend = larust_orm::backend();
79 let present: Vec<&(String, Json)> = values
80 .iter()
81 .filter(|(name, _)| columns.iter().any(|c| &c.name == name))
82 .collect();
83 if present.is_empty() {
84 return Ok(());
85 }
86
87 let column_list = present
88 .iter()
89 .map(|(name, _)| format!("\"{name}\""))
90 .collect::<Vec<_>>()
91 .join(", ");
92 let placeholders = (1..=present.len())
93 .map(|n| larust_orm::placeholder(backend, n))
94 .collect::<Vec<_>>()
95 .join(", ");
96 let sql = format!("INSERT INTO \"{table}\" ({column_list}) VALUES ({placeholders})");
97
98 let pool = larust_orm::pool()?;
99 let mut query = sqlx::query(&sql);
100 for (name, value) in &present {
101 query = bind_any(query, json_to_any_value(value, column_kind(columns, name)));
102 }
103 query.execute(pool).await.map_err(internal)?;
104 Ok(())
105}
106
107pub async fn update_row(
112 table: &str,
113 columns: &[ColumnInfo],
114 pk: &[(String, Json)],
115 values: &[(String, Json)],
116) -> Result<(), AppError> {
117 require_known_pk_columns(columns, pk)?;
118 let backend = larust_orm::backend();
119 let settable: Vec<&(String, Json)> = values
120 .iter()
121 .filter(|(name, _)| {
122 columns.iter().any(|c| &c.name == name)
123 && !pk.iter().any(|(pk_name, _)| pk_name == name)
124 })
125 .collect();
126 if settable.is_empty() {
127 return Ok(());
128 }
129
130 let mut n = 0usize;
131 let set_clause = settable
132 .iter()
133 .map(|(name, _)| {
134 n += 1;
135 format!("\"{name}\" = {}", larust_orm::placeholder(backend, n))
136 })
137 .collect::<Vec<_>>()
138 .join(", ");
139 let where_clause = pk
140 .iter()
141 .map(|(name, _)| {
142 n += 1;
143 format!("\"{name}\" = {}", larust_orm::placeholder(backend, n))
144 })
145 .collect::<Vec<_>>()
146 .join(" AND ");
147 let sql = format!("UPDATE \"{table}\" SET {set_clause} WHERE {where_clause}");
148
149 let pool = larust_orm::pool()?;
150 let mut query = sqlx::query(&sql);
151 for (name, value) in &settable {
152 query = bind_any(query, json_to_any_value(value, column_kind(columns, name)));
153 }
154 for (name, value) in pk {
155 query = bind_any(query, json_to_any_value(value, column_kind(columns, name)));
156 }
157 query.execute(pool).await.map_err(internal)?;
158 Ok(())
159}
160
161pub async fn delete_row(
164 table: &str,
165 columns: &[ColumnInfo],
166 pk: &[(String, Json)],
167) -> Result<(), AppError> {
168 require_known_pk_columns(columns, pk)?;
169 let backend = larust_orm::backend();
170 let mut n = 0usize;
171 let where_clause = pk
172 .iter()
173 .map(|(name, _)| {
174 n += 1;
175 format!("\"{name}\" = {}", larust_orm::placeholder(backend, n))
176 })
177 .collect::<Vec<_>>()
178 .join(" AND ");
179 let sql = format!("DELETE FROM \"{table}\" WHERE {where_clause}");
180
181 let pool = larust_orm::pool()?;
182 let mut query = sqlx::query(&sql);
183 for (name, value) in pk {
184 query = bind_any(query, json_to_any_value(value, column_kind(columns, name)));
185 }
186 query.execute(pool).await.map_err(internal)?;
187 Ok(())
188}
189
190pub async fn fetch_row(
196 table: &str,
197 columns: &[ColumnInfo],
198 pk: &[(String, Json)],
199) -> Result<Option<Json>, AppError> {
200 require_known_pk_columns(columns, pk)?;
201 let backend = larust_orm::backend();
202 let mut n = 0usize;
203 let where_clause = pk
204 .iter()
205 .map(|(name, _)| {
206 n += 1;
207 format!("\"{name}\" = {}", larust_orm::placeholder(backend, n))
208 })
209 .collect::<Vec<_>>()
210 .join(" AND ");
211 let sql = format!("SELECT * FROM \"{table}\" WHERE {where_clause}");
212
213 let pool = larust_orm::pool()?;
214 let mut query = sqlx::query(&sql);
215 for (name, value) in pk {
216 query = bind_any(query, json_to_any_value(value, column_kind(columns, name)));
217 }
218 let row = query.fetch_optional(pool).await.map_err(internal)?;
219 Ok(row.as_ref().map(row_to_json))
220}
221
222pub async fn run_raw(sql: &str) -> Result<Vec<Json>, AppError> {
229 let pool = larust_orm::pool()?;
230 let rows = sqlx::query(sql).fetch_all(pool).await.map_err(internal)?;
231 Ok(rows.iter().map(row_to_json).collect())
232}
233
234pub async fn run_script(sql: &str) -> Result<(), AppError> {
245 let pool = larust_orm::pool()?;
246 sqlx::raw_sql(sql).execute(pool).await.map_err(internal)?;
247 Ok(())
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 fn id_column() -> ColumnInfo {
255 ColumnInfo {
256 name: "id".to_string(),
257 not_null: true,
258 kind: sqlx::any::AnyTypeInfoKind::BigInt,
259 }
260 }
261
262 #[test]
263 fn require_known_pk_columns_accepts_a_real_column() {
264 let columns = [id_column()];
265 let pk = [("id".to_string(), Json::from(1))];
266 assert!(require_known_pk_columns(&columns, &pk).is_ok());
267 }
268
269 #[test]
270 fn require_known_pk_columns_rejects_an_unknown_column_name() {
271 let columns = [id_column()];
275 let pk = [(r#"id" OR "1"="1"#.to_string(), Json::from(1))];
276 let error = require_known_pk_columns(&columns, &pk).unwrap_err();
277 assert!(matches!(
278 error,
279 AppError::Http {
280 status: StatusCode::BAD_REQUEST,
281 ..
282 }
283 ));
284 }
285}