1use quarb::{AstAdapter, NodeId, Value};
25use quarb_relational::{RelationalModel, RowSpec, TableSpec};
26use tokio_postgres::types::Type;
27use tokio_postgres::{Client, NoTls, Row};
28
29#[derive(Debug, thiserror::Error)]
31pub enum PostgresError {
32 #[error("postgres: {0}")]
33 Postgres(#[from] tokio_postgres::Error),
34 #[error("pushdown plan: {0}")]
35 Plan(String),
36 #[error("postgres runtime: {0}")]
37 Runtime(#[from] std::io::Error),
38}
39
40pub struct PostgresAdapter {
42 model: RelationalModel,
43}
44
45fn native_type(data_type: &str) -> bool {
48 matches!(
49 data_type,
50 "boolean"
51 | "smallint"
52 | "integer"
53 | "bigint"
54 | "real"
55 | "double precision"
56 | "text"
57 | "character varying"
58 | "character"
59 )
60}
61
62fn cell(row: &Row, i: usize) -> Result<Value, tokio_postgres::Error> {
63 let ty = row.columns()[i].type_();
64 let value = match *ty {
65 Type::BOOL => row
66 .get::<_, Option<bool>>(i)
67 .map_or(Value::Null, Value::Bool),
68 Type::INT2 => row
69 .get::<_, Option<i16>>(i)
70 .map_or(Value::Null, |n| Value::Int(n as i64)),
71 Type::INT4 => row
72 .get::<_, Option<i32>>(i)
73 .map_or(Value::Null, |n| Value::Int(n as i64)),
74 Type::INT8 => row.get::<_, Option<i64>>(i).map_or(Value::Null, Value::Int),
75 Type::FLOAT4 => row
76 .get::<_, Option<f32>>(i)
77 .map_or(Value::Null, |f| Value::Float(f as f64)),
78 Type::FLOAT8 => row
79 .get::<_, Option<f64>>(i)
80 .map_or(Value::Null, Value::Float),
81 _ => row
89 .try_get::<_, Option<String>>(i)?
90 .map_or(Value::Null, Value::Str),
91 };
92 Ok(value)
93}
94
95impl PostgresAdapter {
96 pub fn connect(config: &str) -> Result<Self, PostgresError> {
101 Self::connect_impl(config, None)
102 }
103
104 pub fn connect_filtered(
108 config: &str,
109 table: &str,
110 where_sql: &str,
111 ) -> Result<Self, PostgresError> {
112 Self::connect_impl(config, Some((table.to_string(), where_sql.to_string())))
113 }
114
115 fn connect_impl(config: &str, filter: Option<(String, String)>) -> Result<Self, PostgresError> {
116 let rt = tokio::runtime::Builder::new_current_thread()
117 .enable_all()
118 .build()?;
119 let (client, specs, types) = rt.block_on(async {
123 let (client, connection) = tokio_postgres::connect(config, NoTls).await?;
124 tokio::spawn(connection);
125 let (specs, types) = Self::introspect(&client).await?;
126 Ok::<_, tokio_postgres::Error>((client, specs, types))
127 })?;
128 let model = RelationalModel::lazy(
129 specs,
130 Box::new(move |t, spec| {
131 let w = filter
132 .as_ref()
133 .filter(|(tn, _)| *tn == spec.name)
134 .map(|(_, w)| w.as_str());
135 rt.block_on(Self::fetch_rows(&client, spec, &types[t], w))
136 .map_err(|e| e.to_string())
137 }),
138 );
139 Ok(PostgresAdapter { model })
140 }
141
142 async fn introspect(
145 client: &Client,
146 ) -> Result<(Vec<TableSpec>, Vec<Vec<String>>), tokio_postgres::Error> {
147 let names: Vec<String> = client
148 .query(
149 "SELECT table_name FROM information_schema.tables \
150 WHERE table_schema = 'public' AND table_type = 'BASE TABLE' \
151 ORDER BY table_name",
152 &[],
153 )
154 .await?
155 .iter()
156 .map(|r| r.get(0))
157 .collect();
158
159 let mut specs = Vec::new();
160 let mut all_types = Vec::new();
161 for name in names {
162 let cols = client
164 .query(
165 "SELECT column_name, data_type \
166 FROM information_schema.columns \
167 WHERE table_schema = 'public' AND table_name = $1 \
168 ORDER BY ordinal_position",
169 &[&name],
170 )
171 .await?;
172 let columns: Vec<String> = cols.iter().map(|r| r.get(0)).collect();
173 let types: Vec<String> = cols.iter().map(|r| r.get(1)).collect();
174
175 let pk_rows = client
177 .query(
178 "SELECT kcu.column_name \
179 FROM information_schema.table_constraints tc \
180 JOIN information_schema.key_column_usage kcu \
181 ON tc.constraint_name = kcu.constraint_name \
182 AND tc.table_schema = kcu.table_schema \
183 AND tc.table_name = kcu.table_name \
184 WHERE tc.table_schema = 'public' AND tc.table_name = $1 \
185 AND tc.constraint_type = 'PRIMARY KEY' \
186 ORDER BY kcu.ordinal_position",
187 &[&name],
188 )
189 .await?;
190 let pk = (pk_rows.len() == 1).then(|| {
191 let col: String = pk_rows[0].get(0);
192 columns.iter().position(|c| *c == col)
193 });
194 let pk = pk.flatten();
195
196 let fk_rows = client
205 .query(
206 "SELECT a.attname, cf.relname, af.attname \
207 FROM pg_catalog.pg_constraint con \
208 JOIN pg_catalog.pg_class c ON c.oid = con.conrelid \
209 JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace \
210 JOIN pg_catalog.pg_class cf ON cf.oid = con.confrelid \
211 JOIN LATERAL unnest(con.conkey, con.confkey) \
212 WITH ORDINALITY AS k(conkey, confkey, ord) ON true \
213 JOIN pg_catalog.pg_attribute a \
214 ON a.attrelid = con.conrelid AND a.attnum = k.conkey \
215 JOIN pg_catalog.pg_attribute af \
216 ON af.attrelid = con.confrelid AND af.attnum = k.confkey \
217 WHERE con.contype = 'f' AND n.nspname = 'public' \
218 AND c.relname = $1 \
219 ORDER BY con.oid, k.ord",
220 &[&name],
221 )
222 .await?;
223 let mut fks = Vec::new();
224 for r in &fk_rows {
225 let from: String = r.get(0);
226 let target: String = r.get(1);
227 let to: String = r.get(2);
228 if let Some(idx) = columns.iter().position(|c| *c == from) {
229 fks.push((idx, target, to));
230 }
231 }
232
233 specs.push(TableSpec {
234 name,
235 columns,
236 pk,
237 fks,
238 });
239 all_types.push(types);
240 }
241 Ok((specs, all_types))
242 }
243
244 async fn fetch_rows(
247 client: &Client,
248 spec: &TableSpec,
249 types: &[String],
250 where_sql: Option<&str>,
251 ) -> Result<Vec<RowSpec>, tokio_postgres::Error> {
252 let select: Vec<String> = spec
253 .columns
254 .iter()
255 .zip(types)
256 .map(|(c, t)| {
257 if native_type(t) {
258 format!("\"{c}\"")
259 } else {
260 format!("\"{c}\"::text")
261 }
262 })
263 .collect();
264 let order = match spec.pk {
270 Some(i) => format!(" ORDER BY \"{}\".\"{}\"", spec.name, spec.columns[i]),
271 None => String::new(),
272 };
273 let filter = match where_sql {
274 Some(w) => format!(" WHERE {w}"),
275 None => String::new(),
276 };
277 let rows = client
278 .query(
279 &format!(
280 "SELECT {} FROM \"{}\"{filter}{order}",
281 select.join(", "),
282 spec.name
283 ),
284 &[],
285 )
286 .await?;
287 rows.iter()
288 .enumerate()
289 .map(|(i, r)| {
290 let values = (0..spec.columns.len())
291 .map(|c| cell(r, c))
292 .collect::<Result<Vec<_>, _>>()?;
293 Ok(RowSpec {
294 rowid: i as i64 + 1,
295 values,
296 })
297 })
298 .collect()
299 }
300
301 pub fn prefetch(&self, table: &str) -> Result<(), String> {
306 self.model.prefetch(table)
307 }
308
309 pub fn locator(&self, node: NodeId) -> String {
310 self.model.locator(node)
311 }
312}
313
314pub fn raw_query(
318 config: &str,
319 sql: &str,
320 order_table: Option<&str>,
321 join_left: Option<(&str, &[String])>,
322) -> Result<(Vec<String>, Vec<Vec<Value>>), PostgresError> {
323 if join_left.is_some() {
327 return Err(PostgresError::Plan(
328 "witness-JOIN uniqueness not verified by this driver".into(),
329 ));
330 }
331
332 let rt = tokio::runtime::Builder::new_current_thread()
333 .enable_all()
334 .build()?;
335 rt.block_on(async {
336 let (client, connection) = tokio_postgres::connect(config, NoTls).await?;
337 tokio::spawn(connection);
338 let sql = match order_table {
339 Some(t) => {
340 let (specs, _) = PostgresAdapter::introspect(&client).await?;
341 let key = specs
342 .into_iter()
343 .find(|s| s.name == t)
344 .and_then(|s| s.pk.map(|i| s.columns[i].clone()));
345 match key {
346 Some(k) => format!("{sql} ORDER BY \"{t}\".\"{k}\""),
347 None => sql.to_string(),
348 }
349 }
350 None => sql.to_string(),
351 };
352 quarb_relational::record_executed(&sql);
356 let rows = client.query(&sql, &[]).await?;
357 let cols: Vec<String> = rows
358 .first()
359 .map(|r| r.columns().iter().map(|c| c.name().to_string()).collect())
360 .unwrap_or_default();
361 let out = rows
362 .iter()
363 .map(|r| {
364 (0..r.columns().len())
365 .map(|i| cell(r, i))
366 .collect::<Result<Vec<_>, _>>()
367 })
368 .collect::<Result<Vec<_>, _>>()?;
369 Ok((cols, out))
370 })
371}
372
373impl AstAdapter for PostgresAdapter {
374 fn root(&self) -> NodeId {
375 self.model.root()
376 }
377 fn children(&self, node: NodeId) -> Vec<NodeId> {
378 self.model.children(node)
379 }
380 fn name(&self, node: NodeId) -> Option<String> {
381 self.model.name(node)
382 }
383 fn parent(&self, node: NodeId) -> Option<NodeId> {
384 self.model.parent(node)
385 }
386 fn property(&self, node: NodeId, name: &str) -> Option<Value> {
387 self.model.property(node, name)
388 }
389 fn default_value(&self, node: NodeId) -> Option<Value> {
390 self.model.default_value(node)
391 }
392 fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
393 self.model.metadata(node, key)
394 }
395 fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
396 self.model.resolve(node, property, hint)
397 }
398 fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
399 self.model.links(node)
400 }
401 fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
402 self.model.backlinks(node)
403 }
404}