1use std::collections::HashMap;
119use std::sync::Arc;
120
121use serde_json::Value;
122use tokio::io::{AsyncReadExt, AsyncWriteExt};
123use tokio::net::{TcpListener, TcpStream};
124
125use crate::db::Db;
126
127const OID_BOOL: i32 = 16;
129const OID_INT8: i32 = 20;
130const OID_FLOAT8: i32 = 701;
131const OID_TEXT: i32 = 25;
132
133const PROTO_V3: i32 = 196_608; const SSL_REQUEST: i32 = 80_877_103;
135const GSS_REQUEST: i32 = 80_877_104;
136const CANCEL_REQUEST: i32 = 80_877_102;
137
138pub trait DbResolver: Send + Sync + 'static {
144 fn resolve(&self, name: &str) -> Option<Arc<Db>>;
152 fn token(&self) -> Option<String> {
154 None
155 }
156}
157
158struct Out(Vec<u8>);
161
162impl Out {
163 fn msg(tag: u8) -> Self {
164 Out(vec![tag, 0, 0, 0, 0])
166 }
167 fn i16(&mut self, v: i16) { self.0.extend_from_slice(&v.to_be_bytes()); }
168 fn i32(&mut self, v: i32) { self.0.extend_from_slice(&v.to_be_bytes()); }
169 fn cstr(&mut self, s: &str) {
170 self.0.extend_from_slice(s.replace('\0', "").as_bytes());
173 self.0.push(0);
174 }
175 fn bytes(&mut self, b: &[u8]) { self.0.extend_from_slice(b); }
176 fn finish(mut self) -> Vec<u8> {
178 let len = (self.0.len() - 1) as i32;
179 self.0[1..5].copy_from_slice(&len.to_be_bytes());
180 self.0
181 }
182}
183
184fn err_msg(code: &str, message: &str) -> Vec<u8> {
185 let mut m = Out::msg(b'E');
186 m.bytes(b"S"); m.cstr("ERROR");
187 m.bytes(b"C"); m.cstr(code);
188 m.bytes(b"M"); m.cstr(message);
189 m.0.push(0);
190 m.finish()
191}
192
193fn ready() -> Vec<u8> {
194 let mut m = Out::msg(b'Z');
195 m.bytes(b"I"); m.finish()
197}
198
199fn command_complete(tag: &str) -> Vec<u8> {
200 let mut m = Out::msg(b'C');
201 m.cstr(tag);
202 m.finish()
203}
204
205#[derive(Debug, PartialEq, Clone)]
215pub struct Col {
216 pub src: String,
217 pub out: String,
218}
219
220impl Col {
221 fn same(name: &str) -> Self {
222 Col { src: name.to_string(), out: name.to_string() }
223 }
224 fn renamed(src: &str, out: &str) -> Self {
225 Col { src: src.to_string(), out: out.to_string() }
226 }
227}
228
229#[derive(Debug, PartialEq)]
247pub enum Stmt {
248 Query { nql: String, project: Vec<Col> },
250 Insert { coll: String, rows: Vec<InsertRow>, returning: Vec<Col> },
252 Update { coll: String, set: Vec<(String, Value)>, nql: String, returning: Vec<Col> },
254 Delete { coll: String, nql: String, returning: Vec<Col> },
256 Canned { cols: Vec<String>, row: Vec<String> },
258 Ok(&'static str),
260}
261
262#[derive(Debug, PartialEq, Clone)]
265pub struct InsertRow {
266 pub id: Option<String>,
268 pub doc: serde_json::Map<String, Value>,
269 pub caused_by: Vec<String>,
272 pub valid_from: Option<String>,
273 pub valid_to: Option<String>,
274}
275
276fn normalise(sql: &str) -> String {
279 let mut out = String::with_capacity(sql.len());
280 let mut chars = sql.chars().peekable();
281 let mut in_s = false;
282 while let Some(c) = chars.next() {
283 if in_s {
284 out.push(c);
285 if c == '\'' { in_s = false; }
286 continue;
287 }
288 match c {
289 '\'' => { in_s = true; out.push(c); }
290 '-' if chars.peek() == Some(&'-') => {
291 for n in chars.by_ref() { if n == '\n' { break; } }
293 out.push(' ');
294 }
295 '/' if chars.peek() == Some(&'*') => {
296 chars.next();
297 let mut prev = ' ';
298 while let Some(n) = chars.next() {
299 if prev == '*' && n == '/' { break; }
300 prev = n;
301 }
302 out.push(' ');
303 }
304 _ => out.push(c),
305 }
306 }
307 out.split_whitespace().collect::<Vec<_>>().join(" ")
308}
309
310fn sql_literals_to_nql(s: &str) -> String {
317 let mut out = String::with_capacity(s.len());
318 let mut it = s.chars().peekable();
319 while let Some(c) = it.next() {
320 match c {
321 '\'' => {
322 out.push('"');
323 while let Some(ch) = it.next() {
324 if ch == '\'' {
325 if it.peek() == Some(&'\'') {
326 it.next();
327 out.push('\''); } else {
329 break;
330 }
331 } else if ch == '"' {
332 out.push('\\');
335 out.push('"');
336 } else {
337 out.push(ch);
338 }
339 }
340 out.push('"');
341 }
342 '<' if it.peek() == Some(&'>') => { it.next(); out.push_str("!="); }
343 _ => out.push(c),
344 }
345 }
346 out
347}
348
349fn strip_prefix_ci(s: &str, prefix: &str) -> Option<String> {
350 if s.len() >= prefix.len() && s[..prefix.len()].eq_ignore_ascii_case(prefix) {
351 Some(s[prefix.len()..].trim_start().to_string())
352 } else {
353 None
354 }
355}
356
357fn find_kw(s: &str, kw: &str) -> Option<usize> {
360 let bytes = s.as_bytes();
361 let k = kw.as_bytes();
362 let mut depth = 0i32;
363 let mut in_s = false;
364 let mut in_d = false;
365 let mut i = 0usize;
366 while i < bytes.len() {
367 let c = bytes[i];
368 if in_s { if c == b'\'' { in_s = false; } i += 1; continue; }
369 if in_d { if c == b'"' { in_d = false; } i += 1; continue; }
370 match c {
371 b'\'' => { in_s = true; i += 1; continue; }
372 b'"' => { in_d = true; i += 1; continue; }
373 b'(' => { depth += 1; i += 1; continue; }
374 b')' => { depth -= 1; i += 1; continue; }
375 _ => {}
376 }
377 if depth == 0 && i + k.len() <= bytes.len()
378 && bytes[i..i + k.len()].eq_ignore_ascii_case(k)
379 {
380 let before_ok = i == 0 || !(bytes[i - 1] as char).is_alphanumeric() && bytes[i - 1] != b'_';
381 let after = i + k.len();
382 let after_ok = after >= bytes.len()
383 || !(bytes[after] as char).is_alphanumeric() && bytes[after] != b'_';
384 if before_ok && after_ok {
385 return Some(i);
386 }
387 }
388 i += 1;
389 }
390 None
391}
392
393fn split_top(s: &str, sep: char) -> Vec<String> {
397 let mut out = vec![];
398 let mut cur = String::new();
399 let mut depth = 0i32;
400 let mut in_s = false;
401 let mut it = s.chars().peekable();
402 while let Some(c) = it.next() {
403 if in_s {
404 cur.push(c);
405 if c == '\'' {
406 if it.peek() == Some(&'\'') { cur.push(it.next().unwrap()); } else { in_s = false; }
408 }
409 continue;
410 }
411 match c {
412 '\'' => { in_s = true; cur.push(c); }
413 '(' => { depth += 1; cur.push(c); }
414 ')' => { depth -= 1; cur.push(c); }
415 x if x == sep && depth == 0 => { out.push(cur.trim().to_string()); cur.clear(); }
416 _ => cur.push(c),
417 }
418 }
419 if !cur.trim().is_empty() { out.push(cur.trim().to_string()); }
420 out
421}
422
423fn sql_value(raw: &str) -> Result<Value, String> {
429 let t = raw.trim();
430 if t.is_empty() {
431 return Err("empty value".into());
432 }
433 let up = t.to_uppercase();
434 if up == "NULL" { return Ok(Value::Null); }
435 if up == "TRUE" { return Ok(Value::Bool(true)); }
436 if up == "FALSE" { return Ok(Value::Bool(false)); }
437 if t.starts_with('\'') && t.ends_with('\'') && t.len() >= 2 {
438 let inner = &t[1..t.len() - 1];
440 return Ok(Value::String(inner.replace("''", "'")));
441 }
442 if let Ok(i) = t.parse::<i64>() { return Ok(Value::from(i)); }
443 if let Ok(f) = t.parse::<f64>() { return Ok(Value::from(f)); }
444 Err(format!(
445 "cannot use {:?} as a value — this endpoint accepts string literals, \
446 numbers, TRUE/FALSE and NULL. Expressions, casts and function calls \
447 are not evaluated, because storing an unevaluated expression as text \
448 would be worse than refusing it", t))
449}
450
451fn split_returning(tail: &str) -> (String, Vec<Col>) {
453 let tu = tail.to_uppercase();
454 match find_kw(&tu, "RETURNING") {
455 None => (tail.to_string(), vec![]),
456 Some(at) => {
457 let head = tail[..at].trim().to_string();
458 let list = tail[at + "RETURNING".len()..].trim();
459 if list == "*" {
460 return (head, vec![]); }
462 let cols = split_top(list, ',')
463 .into_iter()
464 .map(|p| {
465 let raw = p.split_whitespace().next().unwrap_or(&p).to_string();
466 let name = raw.rsplit('.').next().unwrap_or(&raw).trim_matches('"').to_string();
467 Col::same(&name)
468 })
469 .collect();
470 (head, cols)
471 }
472 }
473}
474
475fn take_reserved(doc: &mut serde_json::Map<String, Value>) -> (Option<String>, Vec<String>, Option<String>, Option<String>) {
477 let id = doc.remove("_id").or_else(|| doc.remove("id"))
478 .and_then(|v| match v {
479 Value::String(s) => Some(s),
480 Value::Null => None,
481 other => Some(other.to_string()), });
483 let caused_by = match doc.remove("_caused_by") {
484 Some(Value::String(s)) => vec![s],
485 Some(Value::Array(a)) => a.into_iter()
486 .filter_map(|v| v.as_str().map(str::to_string)).collect(),
487 _ => vec![],
488 };
489 let vf = doc.remove("_valid_from").and_then(|v| v.as_str().map(str::to_string));
490 let vt = doc.remove("_valid_to").and_then(|v| v.as_str().map(str::to_string));
491 (id, caused_by, vf, vt)
492}
493
494fn translate_insert(sql: &str) -> Result<Stmt, String> {
496 let rest = strip_prefix_ci(sql, "INSERT")
497 .and_then(|r| strip_prefix_ci(&r, "INTO"))
498 .ok_or("expected INSERT INTO")?;
499 let ru = rest.to_uppercase();
503 let values_at = find_kw(&ru, "VALUES").ok_or(
504 "expected VALUES — `INSERT … SELECT` is not supported on this endpoint")?;
505 let head = rest[..values_at].trim().to_string();
506 let open = head.find('(').ok_or(
507 "INSERT needs an explicit column list — `INSERT INTO t (a, b) VALUES (…)`. \
508 NEDB is schemaless, so there is no declared column order to infer from")?;
509 let coll = head[..open].trim().trim_matches('"');
510 let coll = coll.rsplit('.').next().unwrap_or(coll).to_string();
511 if coll.is_empty() {
512 return Err("expected a collection name after INSERT INTO".into());
513 }
514 let close = head.rfind(')').ok_or("unterminated column list")?;
515 if close < open {
516 return Err("malformed column list".into());
517 }
518 let tail_from_values = rest[values_at..].to_string();
519 let cols: Vec<String> = split_top(&head[open + 1..close], ',')
520 .into_iter()
521 .map(|c| c.trim().trim_matches('"').to_string())
522 .collect();
523 if cols.is_empty() {
524 return Err("the column list is empty".into());
525 }
526
527 let after = strip_prefix_ci(&tail_from_values, "VALUES")
528 .ok_or("expected VALUES after the column list")?;
529 let (values_part, returning) = split_returning(&after);
530
531 let mut rows = vec![];
532 for group in split_top(&values_part, ',') {
533 let g = group.trim();
534 if !(g.starts_with('(') && g.ends_with(')')) {
535 return Err(format!("expected a parenthesised row of values, got {:?}", g));
536 }
537 let vals = split_top(&g[1..g.len() - 1], ',');
538 if vals.len() != cols.len() {
539 return Err(format!(
540 "{} values for {} columns — every row must match the column list",
541 vals.len(), cols.len()));
542 }
543 let mut doc = serde_json::Map::new();
544 for (c, v) in cols.iter().zip(vals.iter()) {
545 doc.insert(c.clone(), sql_value(v)?);
546 }
547 let (id, caused_by, valid_from, valid_to) = take_reserved(&mut doc);
548 rows.push(InsertRow { id, doc, caused_by, valid_from, valid_to });
549 }
550 if rows.is_empty() {
551 return Err("INSERT with no rows".into());
552 }
553 Ok(Stmt::Insert { coll, rows, returning })
554}
555
556fn translate_update(sql: &str) -> Result<Stmt, String> {
558 let rest = strip_prefix_ci(sql, "UPDATE").ok_or("expected UPDATE")?;
559 let ru = rest.to_uppercase();
560 let set_at = find_kw(&ru, "SET").ok_or("expected SET in UPDATE")?;
561 let coll = rest[..set_at].trim().trim_matches('"');
562 let coll = coll.rsplit('.').next().unwrap_or(coll).to_string();
563 if coll.is_empty() {
564 return Err("expected a collection name after UPDATE".into());
565 }
566 let after_set = rest[set_at + 3..].trim().to_string();
567 let (after_set, returning) = split_returning(&after_set);
568
569 let au = after_set.to_uppercase();
571 let (assigns_raw, where_raw) = match find_kw(&au, "WHERE") {
572 Some(at) => (after_set[..at].to_string(), after_set[at..].to_string()),
573 None => (after_set.clone(), String::new()),
574 };
575
576 let mut set = vec![];
577 for a in split_top(&assigns_raw, ',') {
578 let eq = a.find('=').ok_or(format!("expected `col = value` in SET, got {:?}", a))?;
579 let col = a[..eq].trim().trim_matches('"').to_string();
580 if col.is_empty() {
581 return Err("empty column name in SET".into());
582 }
583 set.push((col, sql_value(&a[eq + 1..])?));
584 }
585 if set.is_empty() {
586 return Err("UPDATE with no assignments".into());
587 }
588 let nql = format!("FROM {} {}", coll, sql_literals_to_nql(where_raw.trim()))
591 .trim().to_string();
592 Ok(Stmt::Update { coll, set, nql, returning })
593}
594
595fn translate_delete(sql: &str) -> Result<Stmt, String> {
597 let rest = strip_prefix_ci(sql, "DELETE")
598 .and_then(|r| strip_prefix_ci(&r, "FROM"))
599 .ok_or("expected DELETE FROM")?;
600 let (rest, returning) = split_returning(&rest);
601 let end = rest.find(' ').unwrap_or(rest.len());
602 let coll = rest[..end].trim().trim_matches('"');
603 let coll = coll.rsplit('.').next().unwrap_or(coll).to_string();
604 if coll.is_empty() {
605 return Err("expected a collection name after DELETE FROM".into());
606 }
607 let where_raw = rest[end..].trim();
608 let nql = format!("FROM {} {}", coll, sql_literals_to_nql(where_raw))
609 .trim().to_string();
610 Ok(Stmt::Delete { coll, nql, returning })
611}
612
613pub fn translate(sql_raw: &str) -> Result<Stmt, String> {
615 let sql = normalise(sql_raw);
616 let sql = sql.trim().trim_end_matches(';').trim();
617 if sql.is_empty() {
618 return Ok(Stmt::Ok(""));
619 }
620 let upper = sql.to_uppercase();
621
622 if upper.starts_with("SET ") || upper.starts_with("BEGIN") || upper.starts_with("COMMIT")
627 || upper.starts_with("ROLLBACK") || upper.starts_with("DISCARD")
628 || upper.starts_with("LISTEN ") || upper.starts_with("UNLISTEN ")
629 {
630 return Ok(Stmt::Ok(if upper.starts_with("SET") { "SET" } else { "OK" }));
632 }
633 if upper.starts_with("SHOW ") {
634 let name = sql[5..].trim().to_lowercase();
635 let val = match name.as_str() {
636 "transaction_isolation" | "default_transaction_isolation" => "read committed",
637 "server_version" => SERVER_VERSION,
638 "server_encoding" | "client_encoding" => "UTF8",
639 "standard_conforming_strings" => "on",
640 "is_superuser" => "off",
641 _ => "",
642 };
643 return Ok(Stmt::Canned { cols: vec![name], row: vec![val.to_string()] });
644 }
645 if upper == "SELECT VERSION()" {
646 return Ok(Stmt::Canned {
647 cols: vec!["version".into()],
648 row: vec![full_version_string()],
649 });
650 }
651 if upper == "SELECT 1" || upper == "SELECT 1;" {
652 return Ok(Stmt::Canned { cols: vec!["?column?".into()], row: vec!["1".into()] });
653 }
654 if upper.starts_with("SELECT CURRENT_SCHEMA") {
655 return Ok(Stmt::Canned { cols: vec!["current_schema".into()], row: vec!["public".into()] });
656 }
657 if upper.starts_with("SELECT CURRENT_DATABASE") {
658 return Ok(Stmt::Canned { cols: vec!["current_database".into()], row: vec!["nedb".into()] });
659 }
660 if upper.starts_with("SELECT CURRENT_USER") || upper.starts_with("SELECT USER") {
661 return Ok(Stmt::Canned { cols: vec!["current_user".into()], row: vec!["nedb".into()] });
662 }
663
664 if upper.starts_with("INSERT") { return translate_insert(sql); }
668 if upper.starts_with("UPDATE") { return translate_update(sql); }
669 if upper.starts_with("DELETE") { return translate_delete(sql); }
670
671 for (kw, why) in [
673 ("CREATE", "DDL is not supported — collections are created implicitly by the first write to them, because NEDB is schemaless"),
674 ("ALTER", "DDL is not supported — there is no schema to alter"),
675 ("DROP", "DDL is not supported; drop a database with DELETE /v1/databases/<db>"),
676 ("TRUNCATE", "not supported, and not an oversight: NEDB is append-only so that history cannot be discarded. That is the product"),
677 ("COPY", "not supported; use GET /v1/databases/<db>/since for bulk export"),
678 ("GRANT", "there is no SQL-level privilege system; auth is the bearer token"),
679 ("REVOKE", "there is no SQL-level privilege system; auth is the bearer token"),
680 ] {
681 if upper.starts_with(kw) {
682 return Err(format!("{} is not supported — {}", kw, why));
683 }
684 }
685 if !upper.starts_with("SELECT") {
686 return Err(format!(
687 "only SELECT, INSERT, UPDATE and DELETE are supported on the Postgres \
688 endpoint (got {:?})",
689 sql.split_whitespace().next().unwrap_or("")
690 ));
691 }
692 for (kw, why) in [
693 (" JOIN ", "JOIN is not supported — NQL is single-collection; join in your client or model the relation with LINK/TRAVERSE"),
694 (" UNION ", "UNION is not supported"),
695 (" INTERSECT ", "INTERSECT is not supported"),
696 (" EXCEPT ", "EXCEPT is not supported"),
697 (" OVER (", "window functions are not supported"),
698 ("DISTINCT ", "DISTINCT is not supported — GROUP BY <col> gives the distinct values with counts"),
699 ] {
700 if upper.contains(kw) {
701 return Err(why.to_string());
702 }
703 }
704 if find_kw(&upper, "FROM").is_none() {
705 return Err("SELECT without FROM is not supported on this endpoint".into());
706 }
707
708 let after_select = strip_prefix_ci(sql, "SELECT").ok_or("expected SELECT")?;
710 let from_at = find_kw(&after_select.to_uppercase(), "FROM")
711 .ok_or("expected FROM after the select list")?;
712 let projection = after_select[..from_at].trim().to_string();
713 let rest = after_select[from_at + 4..].trim().to_string();
714 if rest.is_empty() {
715 return Err("expected a collection name after FROM".into());
716 }
717 if rest.starts_with('(') {
720 return Err("subqueries in FROM are not supported".into());
721 }
722 let coll_end = rest.find(' ').unwrap_or(rest.len());
723 let coll = &rest[..coll_end];
724 if coll.contains(',') {
725 return Err("selecting from more than one collection is not supported (no JOIN)".into());
726 }
727 let bare = coll.rsplit('.').next().unwrap_or(coll).trim_matches('"');
734 let qualified = coll
735 .split('.')
736 .map(|p| p.trim_matches('"'))
737 .collect::<Vec<_>>()
738 .join(".");
739 let coll = if qualified.starts_with("information_schema.") {
740 qualified.as_str()
741 } else {
742 bare
743 };
744 let tail = rest[coll_end..].trim();
745
746 let pu = projection.to_uppercase();
748 let mut agg_clause = String::new();
749 let mut project: Vec<Col> = vec![];
750
751 if projection == "*" {
752 } else if pu.starts_with("COUNT(") {
754 agg_clause = " COUNT".to_string();
757 project.push(Col::same("count"));
758 } else if let Some(agg) = ["SUM", "AVG", "MIN", "MAX"]
759 .iter()
760 .find(|a| pu.starts_with(&format!("{}(", a)))
761 {
762 let inner = projection[agg.len() + 1..]
763 .trim_end_matches(')')
764 .trim()
765 .to_string();
766 if inner.is_empty() || inner == "*" {
767 return Err(format!("{}() needs a column", agg));
768 }
769 agg_clause = format!(" {} {}", agg, inner);
770 project.push(Col::renamed(
772 &format!("{}_{}", agg.to_lowercase(), inner),
773 &agg.to_lowercase(),
774 ));
775 } else {
776 for part in projection.split(',') {
777 let p = part.trim();
778 if p.is_empty() {
779 return Err("empty column in the select list".into());
780 }
781 if p.contains('(') {
782 return Err(format!(
783 "expressions in the select list are not supported ({:?}) — \
784 supported: *, a column list, COUNT(*), or SUM/AVG/MIN/MAX(col)", p));
785 }
786 let raw = p.split_whitespace().next().unwrap_or(p);
788 let name = raw.rsplit('.').next().unwrap_or(raw).trim_matches('"');
789 project.push(Col::same(name));
790 }
791 }
792
793 let mut tail = tail.to_string();
800 let tu = tail.to_uppercase();
801 if let Some(at) = find_kw(&tu, "AS OF SYSTEM TIME") {
802 let before = tail[..at].to_string();
803 let after = tail[at + "AS OF SYSTEM TIME".len()..].trim_start().to_string();
804 let end = after.find(' ').unwrap_or(after.len());
806 let seq = after[..end].trim().trim_matches('\'').trim_matches('"').to_string();
807 if seq.parse::<u64>().is_err() {
808 return Err(format!(
809 "AS OF SYSTEM TIME takes a NEDB sequence number here, not a timestamp (got {:?}). \
810 NEDB's history is sequence-addressed and never garbage-collected, so a seq is \
811 exact where a wall-clock time would be approximate", seq));
812 }
813 tail = format!("{} AS OF {} {}", before.trim(), seq, after[end..].trim())
814 .trim()
815 .to_string();
816 }
817
818 let tu_all = tail.to_uppercase();
826 if let Some(gb_at) = find_kw(&tu_all, "GROUP BY") {
827 let after = tail[gb_at + "GROUP BY".len()..].trim_start();
828 let key_end = after.find(|c: char| c == ' ' || c == ',').unwrap_or(after.len());
829 let group_key = after[..key_end].trim().trim_matches('"').to_string();
830 let is_agg = !agg_clause.is_empty();
831 for c in &project {
832 let ok = c.src == group_key
833 || c.src == "count"
834 || (is_agg && c.out == agg_clause.trim().split(' ').next()
835 .unwrap_or("").to_lowercase());
836 if !ok {
837 return Err(format!(
838 "column {:?} must appear in the GROUP BY clause or be used in an \
839 aggregate function — a grouped row carries the group key, `count`, \
840 and the aggregate, nothing else",
841 c.src));
842 }
843 }
844 }
845
846 let tail = sql_literals_to_nql(&tail);
847 let nql = format!("FROM {}{}{}", coll,
848 if agg_clause.is_empty() { String::new() } else { agg_clause },
849 if tail.is_empty() { String::new() } else { format!(" {}", tail) });
850
851 Ok(Stmt::Query { nql: nql.trim().to_string(), project })
852}
853
854const SERVER_VERSION: &str = "15.0";
855
856pub fn version_string() -> String {
858 full_version_string()
859}
860
861fn full_version_string() -> String {
862 format!(
863 "PostgreSQL {} (NEDB {}) — tamper-evident, append-only, permanent \
864 history. SELECT + INSERT/UPDATE/DELETE; an UPDATE is a new version, \
865 so prior values stay readable with AS OF SYSTEM TIME.",
866 SERVER_VERSION,
867 env!("CARGO_PKG_VERSION")
868 )
869}
870
871fn columns_for(rows: &[Value], project: &[Col]) -> Vec<Col> {
879 if !project.is_empty() {
880 return project.to_vec();
881 }
882 let mut plain: Vec<String> = vec![];
883 let mut meta: Vec<String> = vec![];
884 for r in rows {
885 if let Value::Object(m) = r {
886 for k in m.keys() {
887 let target = if k.starts_with('_') { &mut meta } else { &mut plain };
888 if !target.contains(k) {
889 target.push(k.clone());
890 }
891 }
892 }
893 }
894 plain.sort();
895 meta.sort();
896 plain.extend(meta);
897 plain.into_iter().map(|k| Col::same(&k)).collect()
898}
899
900fn oid_of_value(v: &Value) -> Option<i32> {
902 match v {
903 Value::Null => None,
904 Value::Bool(_) => Some(OID_BOOL),
905 Value::Number(n) => Some(if n.is_i64() || n.is_u64() { OID_INT8 } else { OID_FLOAT8 }),
906 Value::String(_) => Some(OID_TEXT),
907 _ => Some(OID_TEXT),
909 }
910}
911
912fn unify_oid(a: i32, b: i32) -> i32 {
919 if a == b {
920 return a;
921 }
922 match (a, b) {
923 (OID_INT8, OID_FLOAT8) | (OID_FLOAT8, OID_INT8) => OID_FLOAT8,
924 _ => OID_TEXT,
925 }
926}
927
928pub fn oid_for_column(rows: &[Value], col: &str) -> i32 {
940 oid_for(rows, col)
941}
942
943fn oid_for(rows: &[Value], col: &str) -> i32 {
944 let mut acc: Option<i32> = None;
945 for r in rows {
946 if let Some(o) = r.get(col).and_then(oid_of_value) {
947 acc = Some(match acc {
948 None => o,
949 Some(prev) => unify_oid(prev, o),
950 });
951 if acc == Some(OID_TEXT) {
952 break; }
954 }
955 }
956 acc.unwrap_or(OID_TEXT)
957}
958
959fn cell(v: Option<&Value>) -> Option<String> {
961 match v {
962 None | Some(Value::Null) => None, Some(Value::String(s)) => Some(s.clone()),
964 Some(Value::Bool(b)) => Some(if *b { "t".into() } else { "f".into() }),
965 Some(other) => Some(other.to_string()),
966 }
967}
968
969fn cell_binary(v: Option<&Value>, oid: i32) -> Result<Option<Vec<u8>>, String> {
981 let v = match v {
982 None | Some(Value::Null) => return Ok(None),
983 Some(v) => v,
984 };
985 let as_f64 = |n: &serde_json::Number| n.as_f64()
986 .ok_or_else(|| "a number too large to send as float8".to_string());
987 Ok(Some(match (oid, v) {
988 (OID_BOOL, Value::Bool(b)) => vec![u8::from(*b)],
989 (OID_INT2, Value::Number(n)) => {
990 let i = n.as_i64().ok_or("not an integer")?;
991 i16::try_from(i).map_err(|_| format!("{} does not fit in int2", i))?
992 .to_be_bytes().to_vec()
993 }
994 (OID_INT4, Value::Number(n)) => {
995 let i = n.as_i64().ok_or("not an integer")?;
996 i32::try_from(i).map_err(|_| format!("{} does not fit in int4", i))?
997 .to_be_bytes().to_vec()
998 }
999 (OID_INT8, Value::Number(n)) => {
1000 n.as_i64().ok_or("not an integer")?.to_be_bytes().to_vec()
1001 }
1002 (OID_FLOAT4, Value::Number(n)) => (as_f64(n)? as f32).to_be_bytes().to_vec(),
1003 (OID_FLOAT8, Value::Number(n)) => as_f64(n)?.to_be_bytes().to_vec(),
1004 (OID_TEXT | OID_VARCHAR | OID_NAME | OID_UNKNOWN | OID_JSON, _) => {
1006 cell(Some(v)).unwrap_or_default().into_bytes()
1007 }
1008 (OID_JSONB, _) => {
1010 let mut b = vec![1u8];
1011 b.extend_from_slice(cell(Some(v)).unwrap_or_default().as_bytes());
1012 b
1013 }
1014 (oid, val) => {
1015 let kind = match val {
1016 Value::Bool(_) => "a boolean",
1017 Value::Number(_) => "a number",
1018 Value::String(_) => "a string",
1019 Value::Array(_) => "an array",
1020 _ => "an object",
1021 };
1022 return Err(format!(
1023 "cannot send {} in binary format as type OID {} — the field holds \
1024 more than one type across documents, so it cannot be described \
1025 by a single Postgres type. Select it with a text cast, or use a \
1026 text-format client",
1027 kind, oid
1028 ));
1029 }
1030 }))
1031}
1032
1033fn row_description_fmt(cols: &[Col], oids: &[i32], fmts: &[i16]) -> Vec<u8> {
1035 let mut m = Out::msg(b'T');
1036 m.i16(cols.len() as i16);
1037 for (i, c) in cols.iter().enumerate() {
1038 m.cstr(&c.out);
1039 m.i32(0); m.i16((i + 1) as i16); m.i32(oids.get(i).copied().unwrap_or(OID_TEXT));
1042 m.i16(-1); m.i32(-1); m.i16(fmts.get(i).copied().unwrap_or(0));
1045 }
1046 m.finish()
1047}
1048
1049fn row_description(cols: &[Col], oids: &[i32]) -> Vec<u8> {
1050 row_description_fmt(cols, oids, &[])
1051}
1052
1053fn data_row_bytes(vals: &[Option<Vec<u8>>]) -> Vec<u8> {
1054 let mut m = Out::msg(b'D');
1055 m.i16(vals.len() as i16);
1056 for v in vals {
1057 match v {
1058 None => m.i32(-1),
1059 Some(b) => {
1060 m.i32(b.len() as i32);
1061 m.bytes(b);
1062 }
1063 }
1064 }
1065 m.finish()
1066}
1067
1068fn data_row(vals: &[Option<String>]) -> Vec<u8> {
1069 let owned: Vec<Option<Vec<u8>>> =
1070 vals.iter().map(|v| v.as_ref().map(|s| s.as_bytes().to_vec())).collect();
1071 data_row_bytes(&owned)
1072}
1073
1074pub fn encode_rows(rows: &[Value], project: &[Col]) -> Vec<u8> {
1084 let cols = columns_for(rows, project);
1085 let oids: Vec<i32> = cols.iter().map(|c| oid_for(rows, &c.src)).collect();
1086 let mut out = row_description(&cols, &oids);
1087 for r in rows {
1088 let vals: Vec<Option<String>> = cols.iter().map(|c| cell(r.get(&c.src))).collect();
1089 out.extend_from_slice(&data_row(&vals));
1090 }
1091 out
1092}
1093
1094pub fn encode_result(rows: &[Value], project: &[Col]) -> Vec<u8> {
1096 let mut out = encode_rows(rows, project);
1097 out.extend_from_slice(&command_complete(&format!("SELECT {}", rows.len())));
1098 out
1099}
1100
1101const OID_INT2: i32 = 21;
1128const OID_INT4: i32 = 23;
1129const OID_OID: i32 = 26;
1130const OID_FLOAT4: i32 = 700;
1131const OID_VARCHAR: i32 = 1043;
1132const OID_NAME: i32 = 19;
1133const OID_UNKNOWN: i32 = 705;
1134const OID_JSON: i32 = 114;
1135const OID_JSONB: i32 = 3802;
1136
1137fn param_count(sql: &str) -> usize {
1143 let b = sql.as_bytes();
1144 let mut i = 0usize;
1145 let mut in_s = false;
1146 let mut max = 0usize;
1147 while i < b.len() {
1148 let c = b[i];
1149 if in_s {
1150 if c == b'\'' {
1151 in_s = false;
1152 }
1153 i += 1;
1154 continue;
1155 }
1156 if c == b'\'' {
1157 in_s = true;
1158 i += 1;
1159 continue;
1160 }
1161 if c == b'$' && i + 1 < b.len() && b[i + 1].is_ascii_digit() {
1162 let mut j = i + 1;
1163 let mut n = 0usize;
1164 while j < b.len() && b[j].is_ascii_digit() {
1165 n = n * 10 + (b[j] - b'0') as usize;
1166 j += 1;
1167 }
1168 max = max.max(n);
1169 i = j;
1170 continue;
1171 }
1172 i += 1;
1173 }
1174 max
1175}
1176
1177fn infer_field_oid(db: Option<&Arc<Db>>, coll: &str, field: &str) -> i32 {
1186 match field {
1190 "_seq" => return OID_INT8,
1191 "_id" | "_hash" | "_prev" | "_collection" | "_valid_from" | "_valid_to" => return OID_TEXT,
1192 _ => {}
1193 }
1194 let db = match db {
1195 Some(db) => db,
1196 None => return OID_TEXT,
1197 };
1198 if coll.is_empty() || field.is_empty() {
1199 return OID_TEXT;
1200 }
1201 let rows = match crate::nql::query(db, &format!("FROM {} LIMIT {}", coll, TYPE_SAMPLE)) {
1202 Ok((rows, _)) => rows,
1203 Err(_) => return OID_TEXT,
1204 };
1205 oid_for(&rows, field)
1209}
1210
1211fn aggregate_oid(src: &str, db: Option<&Arc<Db>>, coll: &str) -> Option<i32> {
1223 if src == "count" {
1224 return Some(OID_INT8);
1225 }
1226 for (prefix, fixed) in [
1227 ("count_", Some(OID_INT8)),
1228 ("avg_", Some(OID_FLOAT8)),
1229 ("sum_", None),
1230 ("min_", None),
1231 ("max_", None),
1232 ] {
1233 if let Some(field) = src.strip_prefix(prefix) {
1234 return Some(match fixed {
1235 Some(oid) => oid,
1236 None => match infer_field_oid(db, coll, field) {
1239 OID_INT8 => OID_INT8,
1240 OID_FLOAT8 => OID_FLOAT8,
1241 other => other,
1244 },
1245 });
1246 }
1247 }
1248 None
1249}
1250
1251const TYPE_SAMPLE: usize = 200;
1257
1258fn stmt_collection(sql: &str) -> String {
1260 let s = normalise(sql);
1261 let up = s.to_uppercase();
1262 let after = if let Some(at) = find_kw(&up, "FROM") {
1263 &s[at + 4..]
1264 } else if let Some(rest) = strip_prefix_ci(&s, "UPDATE") {
1265 return rest
1266 .split_whitespace()
1267 .next()
1268 .unwrap_or("")
1269 .rsplit('.')
1270 .next()
1271 .unwrap_or("")
1272 .trim_matches('"')
1273 .to_string();
1274 } else if let Some(rest) = strip_prefix_ci(&s, "INSERT INTO") {
1275 return rest
1276 .split(|c: char| c.is_whitespace() || c == '(')
1277 .find(|t| !t.is_empty())
1278 .unwrap_or("")
1279 .rsplit('.')
1280 .next()
1281 .unwrap_or("")
1282 .trim_matches('"')
1283 .to_string();
1284 } else {
1285 return String::new();
1286 };
1287 after
1288 .trim()
1289 .split(|c: char| c.is_whitespace())
1290 .find(|t| !t.is_empty())
1291 .unwrap_or("")
1292 .rsplit('.')
1293 .next()
1294 .unwrap_or("")
1295 .trim_matches('"')
1296 .to_string()
1297}
1298
1299fn param_fields(sql: &str, n_params: usize) -> Vec<Option<String>> {
1310 let s = normalise(sql);
1311 let mut out = vec![None; n_params];
1312
1313 let up = s.to_uppercase();
1316 if up.starts_with("INSERT") {
1317 if let (Some(open), Some(vals_at)) = (s.find('('), find_kw(&up, "VALUES")) {
1318 if open < vals_at {
1319 if let Some(close) = s[open..vals_at].rfind(')') {
1320 let cols: Vec<String> = split_top(&s[open + 1..open + close], ',')
1321 .into_iter()
1322 .map(|c| c.trim().trim_matches('"').to_string())
1323 .collect();
1324 let tail = &s[vals_at..];
1326 let mut seen = 0usize;
1327 let b = tail.as_bytes();
1328 let mut i = 0usize;
1329 let mut in_s = false;
1330 while i < b.len() {
1331 if in_s {
1332 if b[i] == b'\'' { in_s = false; }
1333 i += 1;
1334 continue;
1335 }
1336 if b[i] == b'\'' { in_s = true; i += 1; continue; }
1337 if b[i] == b'$' && i + 1 < b.len() && b[i + 1].is_ascii_digit() {
1338 let mut j = i + 1;
1339 let mut num = 0usize;
1340 while j < b.len() && b[j].is_ascii_digit() {
1341 num = num * 10 + (b[j] - b'0') as usize;
1342 j += 1;
1343 }
1344 if num >= 1 && num <= n_params {
1345 if let Some(c) = cols.get(seen % cols.len().max(1)) {
1346 out[num - 1] = Some(c.clone());
1347 }
1348 }
1349 seen += 1;
1350 i = j;
1351 continue;
1352 }
1353 i += 1;
1354 }
1355 return out;
1356 }
1357 }
1358 }
1359 }
1360
1361 let b = s.as_bytes();
1363 let mut i = 0usize;
1364 let mut in_s = false;
1365 while i < b.len() {
1366 if in_s {
1367 if b[i] == b'\'' { in_s = false; }
1368 i += 1;
1369 continue;
1370 }
1371 if b[i] == b'\'' { in_s = true; i += 1; continue; }
1372 if b[i] == b'$' && i + 1 < b.len() && b[i + 1].is_ascii_digit() {
1373 let mut j = i + 1;
1374 let mut num = 0usize;
1375 while j < b.len() && b[j].is_ascii_digit() {
1376 num = num * 10 + (b[j] - b'0') as usize;
1377 j += 1;
1378 }
1379 if num >= 1 && num <= n_params {
1380 let left = &s[..i];
1381 let trimmed = left.trim_end_matches(|c: char| {
1384 c.is_whitespace() || "=<>!+-*/%(,".contains(c)
1385 });
1386 let mut tok = trimmed
1389 .rsplit(|c: char| c.is_whitespace() || c == '(' || c == ',')
1390 .find(|t| !t.is_empty())
1391 .unwrap_or("")
1392 .trim_matches('"');
1393 let mut before = trimmed;
1394 for _ in 0..4 {
1395 let upper_tok = tok.to_uppercase();
1396 if upper_tok.starts_with('$')
1402 || matches!(upper_tok.as_str(),
1403 "LIKE" | "ILIKE" | "IN" | "BETWEEN" | "AND" | "OR" | "NOT" | "IS") {
1404 before = before[..before.len() - tok.len()].trim_end_matches(|c: char| {
1405 c.is_whitespace() || "=<>!(,".contains(c)
1406 });
1407 tok = before
1408 .rsplit(|c: char| c.is_whitespace() || c == '(' || c == ',')
1409 .find(|t| !t.is_empty())
1410 .unwrap_or("")
1411 .trim_matches('"');
1412 } else {
1413 break;
1414 }
1415 }
1416 if !tok.is_empty()
1417 && tok.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '.')
1418 && !tok.chars().next().map(|c| c.is_ascii_digit()).unwrap_or(true)
1419 {
1420 out[num - 1] = Some(tok.rsplit('.').next().unwrap_or(tok).to_string());
1421 }
1422 }
1423 i = j;
1424 continue;
1425 }
1426 i += 1;
1427 }
1428 out
1429}
1430
1431fn clause_param_oids(sql: &str, n_params: usize) -> Vec<Option<i32>> {
1441 let s = normalise(sql);
1442 let mut out = vec![None; n_params];
1443 let b = s.as_bytes();
1444 let mut i = 0usize;
1445 let mut in_s = false;
1446 while i < b.len() {
1447 if in_s {
1448 if b[i] == b'\'' { in_s = false; }
1449 i += 1;
1450 continue;
1451 }
1452 if b[i] == b'\'' { in_s = true; i += 1; continue; }
1453 if b[i] == b'$' && i + 1 < b.len() && b[i + 1].is_ascii_digit() {
1454 let mut j = i + 1;
1455 let mut num = 0usize;
1456 while j < b.len() && b[j].is_ascii_digit() {
1457 num = num * 10 + (b[j] - b'0') as usize;
1458 j += 1;
1459 }
1460 if num >= 1 && num <= n_params {
1461 let left = s[..i].trim_end().to_uppercase();
1462 out[num - 1] = if left.ends_with("VALID AS OF") {
1465 Some(OID_TEXT)
1466 } else if left.ends_with("AS OF SYSTEM TIME")
1467 || left.ends_with("FOR SYSTEM_TIME AS OF")
1468 || left.ends_with("AS OF")
1469 || left.ends_with("LIMIT")
1470 || left.ends_with("OFFSET")
1471 {
1472 Some(OID_INT8)
1473 } else {
1474 None
1475 };
1476 }
1477 i = j;
1478 continue;
1479 }
1480 i += 1;
1481 }
1482 out
1483}
1484
1485fn infer_param_oids(sql: &str, declared: &[i32], db: Option<&Arc<Db>>) -> Vec<i32> {
1491 let n = param_count(sql).max(declared.len());
1492 if n == 0 {
1493 return vec![];
1494 }
1495 let coll = stmt_collection(sql);
1496 let fields = param_fields(sql, n);
1497 let clauses = clause_param_oids(sql, n);
1498 (0..n)
1499 .map(|i| match declared.get(i) {
1500 Some(&oid) if oid != 0 => oid,
1501 _ => match clauses[i] {
1504 Some(oid) => oid,
1505 None => match &fields[i] {
1506 Some(f) => infer_field_oid(db, &coll, f),
1507 None => OID_TEXT,
1508 },
1509 },
1510 })
1511 .collect()
1512}
1513
1514fn decode_param(raw: Option<&[u8]>, oid: i32, format: i16) -> Result<Option<String>, String> {
1520 let bytes = match raw {
1521 None => return Ok(None),
1522 Some(b) => b,
1523 };
1524 let quote = |s: &str| format!("'{}'", s.replace('\'', "''"));
1525
1526 if format == 0 {
1527 let s = String::from_utf8_lossy(bytes).to_string();
1528 return Ok(Some(match oid {
1529 OID_BOOL => {
1530 let t = matches!(s.as_str(), "t" | "true" | "TRUE" | "1" | "yes" | "on");
1531 if t { "TRUE".into() } else { "FALSE".into() }
1532 }
1533 OID_INT2 | OID_INT4 | OID_INT8 | OID_OID | OID_FLOAT4 | OID_FLOAT8 => {
1534 if s.parse::<f64>().is_ok() { s } else { quote(&s) }
1538 }
1539 _ => quote(&s),
1544 }));
1545 }
1546 if format != 1 {
1547 return Err(format!("unsupported parameter format code {}", format));
1548 }
1549
1550 let need = |n: usize| -> Result<(), String> {
1552 if bytes.len() == n {
1553 Ok(())
1554 } else {
1555 Err(format!(
1556 "binary parameter of type OID {} should be {} bytes, got {}",
1557 oid, n, bytes.len()
1558 ))
1559 }
1560 };
1561 Ok(Some(match oid {
1562 OID_BOOL => {
1563 need(1)?;
1564 if bytes[0] != 0 { "TRUE".into() } else { "FALSE".into() }
1565 }
1566 OID_INT2 => {
1567 need(2)?;
1568 i16::from_be_bytes([bytes[0], bytes[1]]).to_string()
1569 }
1570 OID_INT4 => {
1571 need(4)?;
1572 i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]).to_string()
1573 }
1574 OID_OID => {
1575 need(4)?;
1576 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]).to_string()
1577 }
1578 OID_INT8 => {
1579 need(8)?;
1580 i64::from_be_bytes(bytes[..8].try_into().unwrap()).to_string()
1581 }
1582 OID_FLOAT4 => {
1583 need(4)?;
1584 let f = f32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1585 fmt_float(f as f64)
1586 }
1587 OID_FLOAT8 => {
1588 need(8)?;
1589 fmt_float(f64::from_be_bytes(bytes[..8].try_into().unwrap()))
1590 }
1591 OID_TEXT | OID_VARCHAR | OID_NAME | OID_UNKNOWN | OID_JSON | 0 => {
1592 quote(&String::from_utf8_lossy(bytes))
1593 }
1594 OID_JSONB => {
1595 let body = if bytes.first() == Some(&1) { &bytes[1..] } else { bytes };
1597 quote(&String::from_utf8_lossy(body))
1598 }
1599 other => {
1600 return Err(format!(
1601 "parameter type OID {} is not supported in binary format — \
1602 the supported set is bool, int2/int4/int8, float4/float8, \
1603 text/varchar/json/jsonb. Send it as text, or cast it in the \
1604 statement",
1605 other
1606 ))
1607 }
1608 }))
1609}
1610
1611fn fmt_float(f: f64) -> String {
1613 if f.is_nan() {
1614 "'NaN'".into()
1615 } else if f.is_infinite() {
1616 if f > 0.0 { "'Infinity'".into() } else { "'-Infinity'".into() }
1617 } else if f.fract() == 0.0 && f.abs() < 1e15 {
1618 format!("{:.0}", f)
1619 } else {
1620 f.to_string()
1621 }
1622}
1623
1624fn substitute_params(sql: &str, params: &[Option<String>]) -> Result<String, String> {
1632 let b = sql.as_bytes();
1633 let mut out = String::with_capacity(sql.len() + 16);
1634 let mut i = 0usize;
1635 let mut in_s = false;
1636 while i < b.len() {
1637 let c = b[i];
1638 if in_s {
1639 out.push(c as char);
1640 if c == b'\'' { in_s = false; }
1641 i += 1;
1642 continue;
1643 }
1644 if c == b'\'' {
1645 in_s = true;
1646 out.push('\'');
1647 i += 1;
1648 continue;
1649 }
1650 if c == b'$' && i + 1 < b.len() && b[i + 1].is_ascii_digit() {
1651 let mut j = i + 1;
1652 let mut n = 0usize;
1653 while j < b.len() && b[j].is_ascii_digit() {
1654 n = n * 10 + (b[j] - b'0') as usize;
1655 j += 1;
1656 }
1657 match params.get(n.wrapping_sub(1)) {
1658 Some(Some(lit)) => out.push_str(lit),
1659 Some(None) => out.push_str("NULL"),
1660 None => {
1661 return Err(format!(
1662 "bind message supplies {} parameter(s) but the statement uses ${}",
1663 params.len(), n
1664 ))
1665 }
1666 }
1667 i = j;
1668 continue;
1669 }
1670 out.push(c as char);
1671 i += 1;
1672 }
1673 Ok(out)
1674}
1675
1676struct Prepared {
1678 sql: String,
1679 param_oids: Vec<i32>,
1682 out_shape: Option<Option<(Vec<Col>, Vec<i32>)>>,
1690}
1691
1692fn prepared_shape<'a>(
1694 p: &'a mut Prepared,
1695 db: Option<&Arc<Db>>,
1696) -> &'a Option<(Vec<Col>, Vec<i32>)> {
1697 if p.out_shape.is_none() {
1698 p.out_shape = Some(describe_shape(&p.sql, db, p.param_oids.len()));
1699 }
1700 p.out_shape.as_ref().expect("just filled")
1701}
1702
1703struct Portal {
1705 sql: String,
1706 result: Option<PortalResult>,
1712 frozen: Option<Vec<Col>>,
1722 formats: Vec<i16>,
1724 declared: Option<(Vec<Col>, Vec<i32>)>,
1731}
1732
1733impl Portal {
1734 fn format_of(&self, i: usize) -> i16 {
1737 match self.formats.len() {
1738 0 => 0,
1739 1 => self.formats[0],
1740 _ => self.formats.get(i).copied().unwrap_or(0),
1741 }
1742 }
1743 fn shape(&self, r: &PortalResult) -> (Vec<Col>, Vec<i32>) {
1745 match &self.declared {
1746 Some((cols, oids)) if self.formats.iter().any(|f| *f == 1) => {
1747 (cols.clone(), oids.clone())
1748 }
1749 _ => {
1750 let cols = columns_for(&r.rows, &r.project);
1751 let oids = cols.iter().map(|c| oid_for(&r.rows, &c.src)).collect();
1752 (cols, oids)
1753 }
1754 }
1755 }
1756}
1757
1758struct PortalResult {
1759 rows: Vec<Value>,
1760 project: Vec<Col>,
1761 has_rows: bool,
1762 tag: String,
1763 tag_counts_rows: bool,
1764 sent: usize,
1766}
1767
1768fn parse_complete() -> Vec<u8> { Out::msg(b'1').finish() }
1769fn bind_complete() -> Vec<u8> { Out::msg(b'2').finish() }
1770fn close_complete() -> Vec<u8> { Out::msg(b'3').finish() }
1771fn no_data() -> Vec<u8> { Out::msg(b'n').finish() }
1772fn portal_suspended() -> Vec<u8> { Out::msg(b's').finish() }
1773
1774fn parameter_description(oids: &[i32]) -> Vec<u8> {
1775 let mut m = Out::msg(b't');
1776 m.i16(oids.len() as i16);
1777 for o in oids {
1778 m.i32(*o);
1779 }
1780 m.finish()
1781}
1782
1783fn take_cstr(body: &[u8], at: &mut usize) -> String {
1785 let start = *at;
1786 while *at < body.len() && body[*at] != 0 {
1787 *at += 1;
1788 }
1789 let s = String::from_utf8_lossy(&body[start..*at]).to_string();
1790 if *at < body.len() {
1791 *at += 1; }
1793 s
1794}
1795
1796fn take_i16(body: &[u8], at: &mut usize) -> Result<i16, String> {
1797 if *at + 2 > body.len() {
1798 return Err("truncated message".into());
1799 }
1800 let v = i16::from_be_bytes([body[*at], body[*at + 1]]);
1801 *at += 2;
1802 Ok(v)
1803}
1804
1805fn take_i32(body: &[u8], at: &mut usize) -> Result<i32, String> {
1806 if *at + 4 > body.len() {
1807 return Err("truncated message".into());
1808 }
1809 let v = i32::from_be_bytes([body[*at], body[*at + 1], body[*at + 2], body[*at + 3]]);
1810 *at += 4;
1811 Ok(v)
1812}
1813
1814fn sample_columns(db: Option<&Arc<Db>>, coll: &str) -> Vec<Col> {
1820 let db = match db {
1821 Some(db) => db,
1822 None => return vec![],
1823 };
1824 let rows = match crate::nql::query(db, &format!("FROM {} LIMIT 25", coll)) {
1825 Ok((rows, _)) => rows,
1826 Err(_) => return vec![],
1827 };
1828 let mut names: Vec<String> = vec![];
1829 for r in &rows {
1830 if let Value::Object(m) = r {
1831 for k in m.keys() {
1832 if !names.iter().any(|n| n == k) {
1833 names.push(k.clone());
1834 }
1835 }
1836 }
1837 }
1838 names.sort();
1839 names.iter().map(|n| Col::same(n)).collect()
1840}
1841
1842fn describe_shape(
1850 sql: &str,
1851 db: Option<&Arc<Db>>,
1852 n_params: usize,
1853) -> Option<(Vec<Col>, Vec<i32>)> {
1854 let probe = probe_sql(sql, n_params);
1855 let stmt = translate(&probe).ok()?;
1856 let coll = stmt_collection(sql);
1857
1858 let cols = match stmt {
1859 Stmt::Ok(_) => return None,
1860 Stmt::Canned { cols, .. } => cols.iter().map(|c| Col::same(c)).collect(),
1861 Stmt::Query { project, .. } => {
1862 if project.is_empty() { sample_columns(db, &coll) } else { project }
1863 }
1864 Stmt::Insert { returning, .. } | Stmt::Update { returning, .. } | Stmt::Delete { returning, .. } => {
1865 if !wants_returning(sql) {
1866 return None;
1867 }
1868 if returning.is_empty() { sample_columns(db, &coll) } else { returning }
1869 }
1870 };
1871 if cols.is_empty() {
1872 return None;
1876 }
1877 let oids = cols
1878 .iter()
1879 .map(|c| {
1880 aggregate_oid(&c.src, db, &coll)
1881 .unwrap_or_else(|| infer_field_oid(db, &coll, &c.src))
1882 })
1883 .collect();
1884 Some((cols, oids))
1885}
1886
1887fn probe_sql(sql: &str, n_params: usize) -> String {
1895 let stub: Vec<Option<String>> = vec![Some("0".to_string()); n_params];
1896 substitute_params(sql, &stub).unwrap_or_else(|_| sql.to_string())
1897}
1898
1899fn ensure_executed(
1901 portal: &mut Portal,
1902 db_name: &str,
1903 db: Option<&Arc<Db>>,
1904 read_only: bool,
1905) -> Result<(), Vec<u8>> {
1906 if portal.result.is_some() {
1907 return Ok(());
1908 }
1909 let ex = execute_stmt(&portal.sql, db_name, db, read_only)?;
1910 let project = if let Some(f) = &portal.frozen {
1913 f.clone()
1914 } else {
1915 let p = if ex.project.is_empty() {
1916 columns_for(&ex.rows, &[])
1917 } else {
1918 ex.project.clone()
1919 };
1920 portal.frozen = Some(p.clone());
1921 p
1922 };
1923 portal.result = Some(PortalResult {
1924 rows: ex.rows,
1925 project,
1926 has_rows: ex.has_rows,
1927 tag: ex.tag,
1928 tag_counts_rows: ex.tag_counts_rows,
1929 sent: 0,
1930 });
1931 Ok(())
1932}
1933
1934async fn read_exact(sock: &mut TcpStream, n: usize) -> std::io::Result<Vec<u8>> {
1937 let mut buf = vec![0u8; n];
1938 sock.read_exact(&mut buf).await?;
1939 Ok(buf)
1940}
1941
1942async fn read_i32(sock: &mut TcpStream) -> std::io::Result<i32> {
1943 let b = read_exact(sock, 4).await?;
1944 Ok(i32::from_be_bytes([b[0], b[1], b[2], b[3]]))
1945}
1946
1947fn parse_startup_params(body: &[u8]) -> HashMap<String, String> {
1948 let mut out = HashMap::new();
1949 let mut parts = body.split(|b| *b == 0).map(|s| String::from_utf8_lossy(s).to_string());
1950 while let (Some(k), Some(v)) = (parts.next(), parts.next()) {
1951 if k.is_empty() {
1952 break;
1953 }
1954 out.insert(k, v);
1955 }
1956 out
1957}
1958
1959async fn handle(mut sock: TcpStream, resolver: Arc<dyn DbResolver>, read_only: bool) -> std::io::Result<()> {
1961 let params = loop {
1963 let len = read_i32(&mut sock).await?;
1964 if len < 8 || len > 1 << 20 {
1965 return Ok(()); }
1967 let code = read_i32(&mut sock).await?;
1968 let body = read_exact(&mut sock, (len - 8) as usize).await?;
1969 match code {
1970 SSL_REQUEST | GSS_REQUEST => {
1971 sock.write_all(b"N").await?;
1973 continue;
1974 }
1975 CANCEL_REQUEST => return Ok(()), PROTO_V3 => break parse_startup_params(&body),
1977 other => {
1978 let major = other >> 16;
1979 sock.write_all(&err_msg(
1980 "0A000",
1981 &format!("unsupported frontend protocol {}.{} — this endpoint speaks 3.0",
1982 major, other & 0xffff),
1983 )).await?;
1984 return Ok(());
1985 }
1986 }
1987 };
1988
1989 let db_name = params.get("database").cloned().unwrap_or_default();
1990
1991 let resolved: Option<Arc<Db>> = {
1997 let r = Arc::clone(&resolver);
1998 let name = db_name.clone();
1999 tokio::task::spawn_blocking(move || r.resolve(&name))
2000 .await
2001 .unwrap_or(None)
2002 };
2003
2004 if let Some(expected) = resolver.token() {
2006 let mut m = Out::msg(b'R');
2008 m.i32(3);
2009 sock.write_all(&m.finish()).await?;
2010
2011 let tag = read_exact(&mut sock, 1).await?;
2012 if tag[0] != b'p' {
2013 sock.write_all(&err_msg("28000", "expected a password message")).await?;
2014 return Ok(());
2015 }
2016 let len = read_i32(&mut sock).await?;
2017 if len < 4 || len > 1 << 16 {
2018 return Ok(());
2019 }
2020 let body = read_exact(&mut sock, (len - 4) as usize).await?;
2021 let supplied = String::from_utf8_lossy(&body).trim_end_matches('\0').to_string();
2022 let ok = supplied.len() == expected.len()
2024 && supplied.bytes().zip(expected.bytes()).fold(0u8, |a, (x, y)| a | (x ^ y)) == 0;
2025 if !ok {
2026 sock.write_all(&err_msg("28P01", "password authentication failed")).await?;
2027 return Ok(());
2028 }
2029 }
2030
2031 let mut m = Out::msg(b'R');
2032 m.i32(0); sock.write_all(&m.finish()).await?;
2034
2035 for (k, v) in [
2036 ("server_version", SERVER_VERSION),
2037 ("server_encoding", "UTF8"),
2038 ("client_encoding", "UTF8"),
2039 ("DateStyle", "ISO, MDY"),
2040 ("integer_datetimes", "on"),
2041 ("standard_conforming_strings", "on"),
2042 ("application_name", "nedbd"),
2043 ] {
2044 let mut p = Out::msg(b'S');
2045 p.cstr(k);
2046 p.cstr(v);
2047 sock.write_all(&p.finish()).await?;
2048 }
2049 let mut k = Out::msg(b'K');
2050 k.i32(std::process::id() as i32);
2051 k.i32(0);
2052 sock.write_all(&k.finish()).await?;
2053 sock.write_all(&ready()).await?;
2054
2055 let mut prepared: HashMap<String, Prepared> = HashMap::new();
2061 let mut portals: HashMap<String, Portal> = HashMap::new();
2062 let mut failed = false;
2066
2067 loop {
2068 let mut tag = [0u8; 1];
2069 if sock.read_exact(&mut tag).await.is_err() {
2070 return Ok(()); }
2072 let len = read_i32(&mut sock).await?;
2073 if len < 4 || len > 64 << 20 {
2074 return Ok(());
2075 }
2076 let body = read_exact(&mut sock, (len - 4) as usize).await?;
2077
2078 if failed && tag[0] != b'S' && tag[0] != b'X' {
2080 continue;
2081 }
2082
2083 match tag[0] {
2084 b'X' => return Ok(()), b'Q' => {
2087 let sql = String::from_utf8_lossy(&body).trim_end_matches('\0').to_string();
2088 let out = run_simple_query(&sql, &db_name, resolved.as_ref(), read_only);
2089 sock.write_all(&out).await?;
2090 sock.write_all(&ready()).await?;
2091 portals.remove("");
2093 }
2094
2095 b'P' => {
2097 let mut at = 0usize;
2098 let name = take_cstr(&body, &mut at);
2099 let sql = take_cstr(&body, &mut at);
2100 let n = take_i16(&body, &mut at).unwrap_or(0).max(0) as usize;
2101 let mut declared = Vec::with_capacity(n);
2102 let mut bad = false;
2103 for _ in 0..n {
2104 match take_i32(&body, &mut at) {
2105 Ok(o) => declared.push(o),
2106 Err(_) => { bad = true; break; }
2107 }
2108 }
2109 if bad {
2110 sock.write_all(&err_msg("08P01", "malformed Parse message")).await?;
2111 failed = true;
2112 continue;
2113 }
2114 if let Err(why) = translate(&probe_sql(&sql, param_count(&sql))) {
2118 sock.write_all(&err_msg("0A000", &why)).await?;
2119 failed = true;
2120 continue;
2121 }
2122 let param_oids = infer_param_oids(&sql, &declared, resolved.as_ref());
2123 prepared.insert(name, Prepared { sql, param_oids, out_shape: None });
2124 sock.write_all(&parse_complete()).await?;
2125 }
2126
2127 b'B' => {
2129 let mut at = 0usize;
2130 let portal_name = take_cstr(&body, &mut at);
2131 let stmt_name = take_cstr(&body, &mut at);
2132 if !prepared.contains_key(&stmt_name) {
2133 sock.write_all(&err_msg("26000", &format!(
2134 "prepared statement {:?} does not exist", stmt_name))).await?;
2135 failed = true;
2136 continue;
2137 }
2138 let p = &prepared[&stmt_name];
2139 let mut want_formats: Vec<i16> = vec![];
2140 let res: Result<String, String> = (|| {
2141 let nfmt = take_i16(&body, &mut at)? .max(0) as usize;
2142 let mut fmts = Vec::with_capacity(nfmt);
2143 for _ in 0..nfmt {
2144 fmts.push(take_i16(&body, &mut at)?);
2145 }
2146 let nparam = take_i16(&body, &mut at)?.max(0) as usize;
2147 let mut vals: Vec<Option<String>> = Vec::with_capacity(nparam);
2148 for i in 0..nparam {
2149 let l = take_i32(&body, &mut at)?;
2150 let raw: Option<Vec<u8>> = if l < 0 {
2151 None
2152 } else {
2153 let l = l as usize;
2154 if at + l > body.len() {
2155 return Err("truncated Bind parameter".into());
2156 }
2157 let v = body[at..at + l].to_vec();
2158 at += l;
2159 Some(v)
2160 };
2161 let f = match fmts.len() {
2164 0 => 0,
2165 1 => fmts[0],
2166 _ => *fmts.get(i).unwrap_or(&0),
2167 };
2168 let oid = *p.param_oids.get(i).unwrap_or(&OID_TEXT);
2169 vals.push(decode_param(raw.as_deref(), oid, f)?);
2170 }
2171 let nres = take_i16(&body, &mut at)?.max(0) as usize;
2176 for _ in 0..nres {
2177 let f = take_i16(&body, &mut at)?;
2178 if f != 0 && f != 1 {
2179 return Err(format!("unknown result format code {}", f));
2180 }
2181 want_formats.push(f);
2182 }
2183 substitute_params(&p.sql, &vals)
2184 })();
2185 match res {
2186 Ok(sql) => {
2187 let declared = if want_formats.iter().any(|f| *f == 1) {
2190 let p = prepared.get_mut(&stmt_name).expect("checked above");
2191 prepared_shape(p, resolved.as_ref()).clone()
2192 } else {
2193 None
2194 };
2195 portals.insert(portal_name, Portal {
2196 sql, result: None, frozen: None,
2197 formats: want_formats, declared,
2198 });
2199 sock.write_all(&bind_complete()).await?;
2200 }
2201 Err(why) => {
2202 sock.write_all(&err_msg("08P01", &why)).await?;
2203 failed = true;
2204 }
2205 }
2206 }
2207
2208 b'D' => {
2210 let kind = body.first().copied().unwrap_or(b'S');
2211 let mut at = 1usize;
2212 let name = take_cstr(&body, &mut at);
2213 if kind == b'S' {
2214 if !prepared.contains_key(&name) {
2215 sock.write_all(&err_msg("26000", &format!(
2216 "prepared statement {:?} does not exist", name))).await?;
2217 failed = true;
2218 continue;
2219 }
2220 let p = prepared.get_mut(&name).expect("checked above");
2221 let oids = p.param_oids.clone();
2222 sock.write_all(¶meter_description(&oids)).await?;
2225 let out = match prepared_shape(p, resolved.as_ref()) {
2229 Some((cols, col_oids)) => row_description(cols, col_oids),
2230 None => no_data(),
2231 };
2232 sock.write_all(&out).await?;
2233 } else {
2234 let portal = match portals.get_mut(&name) {
2235 Some(p) => p,
2236 None => {
2237 sock.write_all(&err_msg("34000", &format!(
2238 "portal {:?} does not exist", name))).await?;
2239 failed = true;
2240 continue;
2241 }
2242 };
2243 match ensure_executed(portal, &db_name, resolved.as_ref(), read_only) {
2248 Err(encoded) => {
2249 sock.write_all(&encoded).await?;
2250 failed = true;
2251 }
2252 Ok(()) => {
2253 let r = portal.result.as_ref().expect("just executed");
2254 if !r.has_rows {
2255 sock.write_all(&no_data()).await?;
2256 } else {
2257 let (cols, oids) = portal.shape(r);
2258 let fmts: Vec<i16> =
2259 (0..cols.len()).map(|i| portal.format_of(i)).collect();
2260 sock.write_all(&row_description_fmt(&cols, &oids, &fmts)).await?;
2261 }
2262 }
2263 }
2264 }
2265 }
2266
2267 b'E' => {
2269 let mut at = 0usize;
2270 let name = take_cstr(&body, &mut at);
2271 let max_rows = take_i32(&body, &mut at).unwrap_or(0);
2272 let portal = match portals.get_mut(&name) {
2273 Some(p) => p,
2274 None => {
2275 sock.write_all(&err_msg("34000", &format!(
2276 "portal {:?} does not exist", name))).await?;
2277 failed = true;
2278 continue;
2279 }
2280 };
2281 if let Err(encoded) = ensure_executed(portal, &db_name, resolved.as_ref(), read_only) {
2282 sock.write_all(&encoded).await?;
2283 failed = true;
2284 continue;
2285 }
2286 let r = portal.result.as_ref().expect("just executed");
2287 if !r.has_rows {
2288 let tag = r.tag.clone();
2289 sock.write_all(&command_complete(&tag)).await?;
2290 continue;
2291 }
2292 let (cols, oids) = portal.shape(r);
2293 let limit = if max_rows > 0 {
2294 (r.sent + max_rows as usize).min(r.rows.len())
2295 } else {
2296 r.rows.len()
2297 };
2298 let mut encoded: Vec<Vec<u8>> = Vec::with_capacity(limit - r.sent);
2303 let mut fail: Option<String> = None;
2304 for row in &r.rows[r.sent..limit] {
2305 let mut vals: Vec<Option<Vec<u8>>> = Vec::with_capacity(cols.len());
2306 for (i, c) in cols.iter().enumerate() {
2307 let v = row.get(&c.src);
2308 let got = if portal.format_of(i) == 1 {
2309 cell_binary(v, oids.get(i).copied().unwrap_or(OID_TEXT))
2310 .map_err(|e| format!("column {:?}: {}", c.out, e))
2311 } else {
2312 Ok(cell(v).map(|s| s.into_bytes()))
2313 };
2314 match got {
2315 Ok(b) => vals.push(b),
2316 Err(e) => { fail = Some(e); break; }
2317 }
2318 }
2319 if fail.is_some() {
2320 break;
2321 }
2322 encoded.push(data_row_bytes(&vals));
2323 }
2324 if let Some(why) = fail {
2325 sock.write_all(&err_msg("22P03", &why)).await?;
2326 failed = true;
2327 continue;
2328 }
2329 let mut out = vec![];
2330 for e in &encoded {
2331 out.extend_from_slice(e);
2332 }
2333 let r = portal.result.as_mut().expect("just executed");
2334 r.sent = limit;
2335 if max_rows > 0 && r.sent < r.rows.len() {
2339 out.extend_from_slice(&portal_suspended());
2340 } else {
2341 let tag = if r.tag_counts_rows {
2342 format!("{} {}", r.tag, r.sent)
2343 } else {
2344 r.tag.clone()
2345 };
2346 out.extend_from_slice(&command_complete(&tag));
2347 }
2348 sock.write_all(&out).await?;
2349 }
2350
2351 b'C' => {
2353 let kind = body.first().copied().unwrap_or(b'S');
2354 let mut at = 1usize;
2355 let name = take_cstr(&body, &mut at);
2356 if kind == b'S' {
2357 prepared.remove(&name);
2358 } else {
2359 portals.remove(&name);
2360 }
2361 sock.write_all(&close_complete()).await?;
2364 }
2365
2366 b'H' => {}
2370
2371 b'S' => {
2372 failed = false;
2373 sock.write_all(&ready()).await?;
2374 }
2375
2376 other => {
2377 sock.write_all(&err_msg(
2378 "08P01",
2379 &format!("unexpected frontend message {:?}", other as char),
2380 )).await?;
2381 failed = true;
2382 }
2383 }
2384 }
2385}
2386
2387const READ_ONLY_MSG: &str =
2388 "this endpoint is running read-only (NEDBD_PG_READ_ONLY=1). Writes are \
2389 implemented but disabled on this server — unset the flag to allow them.";
2390
2391fn no_db(db_name: &str) -> Vec<u8> {
2392 err_msg("3D000", &format!(
2393 "database {:?} is not open on this server — create it first \
2394 (POST /v1/databases), or connect with -d <name>", db_name))
2395}
2396
2397fn try_catalog_select(
2410 sql: &str,
2411 db: Option<&Arc<Db>>,
2412) -> Result<Option<(Executed, crate::sqlplan::Plan)>, Vec<u8>> {
2413 let sel = match crate::sqlselect::parse(sql) {
2414 Ok(sel) => sel,
2415 Err(why) => {
2416 if mentions_catalog(sql) {
2425 return Err(err_msg("0A000", &format!(
2426 "this catalogue query uses SQL this endpoint does not \
2427 implement: {}", why)));
2428 }
2429 return Ok(None);
2430 }
2431 };
2432
2433 let mut touched: Vec<String> = vec![];
2435 if let Some(f) = &sel.from {
2436 touched.push(f.name.clone());
2437 }
2438 for j in &sel.joins {
2439 touched.push(j.table.name.clone());
2440 }
2441 let catalog_name = |n: &str| -> String {
2442 let joined: Vec<&str> = n.split('.').collect();
2446 if joined.len() >= 2 && joined[joined.len() - 2] == "information_schema" {
2447 format!("information_schema.{}", joined[joined.len() - 1])
2448 } else {
2449 joined[joined.len() - 1].to_string()
2450 }
2451 };
2452 if !touched.iter().any(|t| crate::pgcatalog::is_catalog(&catalog_name(t))) {
2453 return Ok(None);
2454 }
2455
2456 if select_has_aggregate(&sel) {
2460 return Ok(None);
2461 }
2462
2463 let resolve = |name: &str| -> anyhow::Result<Option<Box<dyn crate::sqlselect::Relation>>> {
2464 let cname = catalog_name(name);
2465 if let Some(rows) = crate::pgcatalog::rows(&cname, db) {
2466 return Ok(Some(crate::sqlselect::from_vec(rows)));
2470 }
2471 match db {
2480 Some(db) => match crate::nql::query(db, &format!("FROM {}", cname)) {
2481 Ok((rows, _)) => Ok(Some(crate::sqlselect::from_vec(rows))),
2482 Err(_) => Ok(None),
2483 },
2484 None => Ok(None),
2485 }
2486 };
2487
2488 let (cols, rows, plan) = crate::sqlselect::execute_explain(
2489 &sel,
2490 &resolve,
2491 crate::sqljoin::JoinExec::Auto,
2492 )
2493 .map_err(|e| err_msg("42601", &e.to_string()))?;
2494
2495 Ok(Some((
2496 Executed {
2497 rows,
2498 project: cols
2502 .iter()
2503 .map(|c| Col::renamed(&c.key, &c.name))
2504 .collect(),
2505 has_rows: true,
2506 tag: "SELECT".into(),
2507 tag_counts_rows: true,
2508 },
2509 plan,
2510 )))
2511}
2512
2513fn strip_explain(sql: &str) -> Option<&str> {
2520 let t = sql.trim().trim_end_matches(';').trim();
2521 let mut rest = t.strip_prefix("EXPLAIN").or_else(|| t.strip_prefix("explain"))?;
2522 if !rest.starts_with(char::is_whitespace) {
2524 return None;
2525 }
2526 rest = rest.trim_start();
2527 loop {
2528 let low = rest.to_lowercase();
2529 if let Some(r) = low.strip_prefix("analyze").or_else(|| low.strip_prefix("analyse")) {
2530 if r.starts_with(char::is_whitespace) || r.is_empty() {
2531 rest = rest[rest.len() - r.len()..].trim_start();
2532 continue;
2533 }
2534 }
2535 if let Some(r) = low.strip_prefix("verbose") {
2536 if r.starts_with(char::is_whitespace) || r.is_empty() {
2537 rest = rest[rest.len() - r.len()..].trim_start();
2538 continue;
2539 }
2540 }
2541 break;
2542 }
2543 Some(rest)
2544}
2545
2546fn plan_result(lines: Vec<String>) -> Executed {
2549 Executed {
2550 rows: lines
2551 .into_iter()
2552 .map(|l| serde_json::json!({ "QUERY PLAN": l }))
2553 .collect(),
2554 project: vec![Col::same("QUERY PLAN")],
2555 has_rows: true,
2556 tag: "EXPLAIN".into(),
2557 tag_counts_rows: false,
2558 }
2559}
2560
2561fn mentions_catalog(sql: &str) -> bool {
2568 let low = sql.to_lowercase();
2569 low.contains("pg_catalog.")
2570 || low.contains("information_schema.")
2571 || low.contains("from pg_")
2572 || low.contains("join pg_")
2573}
2574
2575fn select_has_aggregate(sel: &crate::sqlselect::Select) -> bool {
2577 fn walk(e: &crate::sqlselect::Expr) -> bool {
2578 use crate::sqlselect::Expr as E;
2579 match e {
2580 E::Func { name, args } => {
2581 matches!(name.as_str(),
2582 "count" | "sum" | "avg" | "min" | "max" | "array_agg"
2583 | "string_agg" | "bool_and" | "bool_or")
2584 || args.iter().any(walk)
2585 }
2586 E::Binary { left, right, .. } => walk(left) || walk(right),
2587 E::Unary { expr, .. } | E::Cast { expr, .. } => walk(expr),
2588 E::IsNull { expr, .. } => walk(expr),
2589 E::InList { expr, list, .. } => walk(expr) || list.iter().any(walk),
2590 E::Case { operand, whens, else_ } => {
2591 operand.as_deref().map(walk).unwrap_or(false)
2592 || whens.iter().any(|(c, t)| walk(c) || walk(t))
2593 || else_.as_deref().map(walk).unwrap_or(false)
2594 }
2595 _ => false,
2596 }
2597 }
2598 sel.items.iter().any(|i| walk(&i.expr))
2599}
2600
2601fn catalog_target(nql: &str) -> Option<String> {
2606 let coll = crate::nql::parse(nql).ok()?.coll;
2607 if crate::pgcatalog::is_catalog(&coll) {
2608 Some(coll)
2609 } else {
2610 None
2611 }
2612}
2613
2614fn wants_returning(sql: &str) -> bool {
2618 find_kw(&sql.to_uppercase(), "RETURNING").is_some()
2619}
2620
2621fn next_row_id() -> String {
2623 use std::sync::atomic::{AtomicU64, Ordering};
2624 static N: AtomicU64 = AtomicU64::new(0);
2625 let n = N.fetch_add(1, Ordering::Relaxed);
2626 let ts = std::time::SystemTime::now()
2627 .duration_since(std::time::UNIX_EPOCH)
2628 .map(|d| d.as_micros())
2629 .unwrap_or(0);
2630 format!("r{}{}", ts, n)
2631}
2632
2633pub struct Executed {
2641 pub rows: Vec<Value>,
2643 pub project: Vec<Col>,
2645 pub has_rows: bool,
2649 pub tag: String,
2652 pub tag_counts_rows: bool,
2654}
2655
2656impl Executed {
2657 fn nothing(tag: &str) -> Self {
2658 Executed { rows: vec![], project: vec![], has_rows: false, tag: tag.to_string(), tag_counts_rows: false }
2659 }
2660 fn tag_for(&self, sent: usize) -> String {
2662 if self.tag_counts_rows { format!("{} {}", self.tag, sent) } else { self.tag.clone() }
2663 }
2664}
2665
2666fn execute_stmt(
2672 stmt_sql: &str,
2673 db_name: &str,
2674 db: Option<&Arc<Db>>,
2675 read_only: bool,
2676) -> Result<Executed, Vec<u8>> {
2677 if let Some(inner) = strip_explain(stmt_sql) {
2686 if let Some((_, plan)) = try_catalog_select(inner, db)? {
2687 return Ok(plan_result(plan.render()));
2688 }
2689 let mut lines = vec![];
2690 match translate(inner) {
2691 Ok(_) => {
2692 lines.push(
2693 "NQL path — this statement is translated to NQL and \
2694 executed by the storage engine, not by the SQL evaluator."
2695 .to_string(),
2696 );
2697 lines.push(
2698 "No plan is reported, because the SQL evaluator is not \
2699 what runs it. Reporting one would describe a pipeline \
2700 that never executed."
2701 .to_string(),
2702 );
2703 lines.push(
2704 "The SQL evaluator (joins, CASE, scalar functions, a \
2705 hash-join planner) currently serves catalogue queries."
2706 .to_string(),
2707 );
2708 }
2709 Err(why) => lines.push(format!("cannot be executed: {why}")),
2710 }
2711 return Ok(plan_result(lines));
2712 }
2713
2714 if let Some((done, _plan)) = try_catalog_select(stmt_sql, db)? {
2715 return Ok(done);
2716 }
2717
2718 let stmt = translate(stmt_sql).map_err(|why| err_msg("0A000", &why))?;
2719
2720 macro_rules! need_db {
2723 () => {
2724 match db {
2725 Some(db) => db,
2726 None => return Err(no_db(db_name)),
2727 }
2728 };
2729 }
2730 macro_rules! need_write {
2731 () => {
2732 if read_only {
2733 return Err(err_msg("25006", READ_ONLY_MSG));
2734 }
2735 };
2736 }
2737
2738 match stmt {
2739 Stmt::Ok(tag) => Ok(Executed::nothing(if tag.is_empty() { "SELECT 0" } else { tag })),
2740
2741 Stmt::Canned { cols, row } => {
2742 let mut obj = serde_json::Map::new();
2745 for (c, v) in cols.iter().zip(row.iter()) {
2746 obj.insert(c.clone(), Value::String(v.clone()));
2747 }
2748 Ok(Executed {
2749 rows: vec![Value::Object(obj)],
2750 project: cols.iter().map(|c| Col::same(c)).collect(),
2751 has_rows: true,
2752 tag: "SELECT".into(),
2753 tag_counts_rows: true,
2754 })
2755 }
2756
2757 Stmt::Query { nql, project } => {
2758 if let Some(coll) = catalog_target(&nql) {
2768 let rows = crate::pgcatalog::rows(&coll, db)
2769 .expect("catalog_target only returns names pgcatalog serves");
2770 let rows = crate::nql::query_rows(rows, &nql)
2771 .map_err(|e| err_msg("42601", &e.to_string()))?;
2772 return Ok(Executed {
2773 rows, project, has_rows: true,
2774 tag: "SELECT".into(), tag_counts_rows: true,
2775 });
2776 }
2777 let db = need_db!();
2778 let (rows, _) = crate::nql::query(db, &nql).map_err(|e| {
2779 err_msg("42601", &format!("{} (translated to NQL: {})", e, nql))
2780 })?;
2781 Ok(Executed { rows, project, has_rows: true, tag: "SELECT".into(), tag_counts_rows: true })
2782 }
2783
2784 Stmt::Insert { coll, rows, returning } => {
2785 let db = need_db!();
2786 need_write!();
2787 let mut written: Vec<Value> = vec![];
2788 for (i, r) in rows.iter().enumerate() {
2789 let id = match &r.id {
2793 Some(id) => id.clone(),
2794 None => format!("{}-{}", next_row_id(), i),
2795 };
2796 let node = db
2797 .put(&coll, &id, Value::Object(r.doc.clone()),
2798 r.caused_by.clone(), r.valid_from.clone(), r.valid_to.clone())
2799 .map_err(|e| err_msg("XX000", &format!("INSERT failed: {}", e)))?;
2800 written.push(crate::nql::node_to_json(&node));
2801 }
2802 let n = written.len();
2803 let has_rows = wants_returning(stmt_sql);
2804 Ok(Executed {
2805 rows: if has_rows { written } else { vec![] },
2806 project: returning,
2807 has_rows,
2808 tag: format!("INSERT 0 {}", n),
2810 tag_counts_rows: false,
2811 })
2812 }
2813
2814 Stmt::Update { coll, set, nql, returning } => {
2815 let db = need_db!();
2816 need_write!();
2817 let (matched, _) = crate::nql::query(db, &nql).map_err(|e| {
2820 err_msg("42601", &format!("{} (translated to NQL: {})", e, nql))
2821 })?;
2822 let mut written: Vec<Value> = vec![];
2823 for row in &matched {
2824 let id = match row.get("_id").and_then(|v| v.as_str()) {
2825 Some(id) => id.to_string(),
2826 None => continue,
2827 };
2828 let mut doc = match db.get(&coll, &id) {
2832 Some(n) => match n.data {
2833 Value::Object(m) => m,
2834 _ => serde_json::Map::new(),
2835 },
2836 None => continue,
2837 };
2838 for (k, v) in &set {
2839 doc.insert(k.clone(), v.clone());
2840 }
2841 let node = db
2844 .put(&coll, &id, Value::Object(doc), vec![], None, None)
2845 .map_err(|e| err_msg("XX000", &format!("UPDATE failed: {}", e)))?;
2846 written.push(crate::nql::node_to_json(&node));
2847 }
2848 let n = written.len();
2849 let has_rows = wants_returning(stmt_sql);
2850 Ok(Executed {
2851 rows: if has_rows { written } else { vec![] },
2852 project: returning,
2853 has_rows,
2854 tag: format!("UPDATE {}", n),
2855 tag_counts_rows: false,
2856 })
2857 }
2858
2859 Stmt::Delete { coll, nql, returning } => {
2860 let db = need_db!();
2861 need_write!();
2862 let (matched, _) = crate::nql::query(db, &nql).map_err(|e| {
2863 err_msg("42601", &format!("{} (translated to NQL: {})", e, nql))
2864 })?;
2865 let returned = matched.clone();
2868 let mut n = 0usize;
2869 for row in &matched {
2870 if let Some(id) = row.get("_id").and_then(|v| v.as_str()) {
2871 match db.delete(&coll, id) {
2872 Ok(true) => n += 1,
2873 Ok(false) => {}
2874 Err(e) => return Err(err_msg("XX000", &format!("DELETE failed: {}", e))),
2875 }
2876 }
2877 }
2878 let has_rows = wants_returning(stmt_sql);
2879 Ok(Executed {
2880 rows: if has_rows { returned } else { vec![] },
2881 project: returning,
2882 has_rows,
2883 tag: format!("DELETE {}", n),
2884 tag_counts_rows: false,
2885 })
2886 }
2887 }
2888}
2889
2890fn run_simple_query(sql: &str, db_name: &str, db: Option<&Arc<Db>>, read_only: bool) -> Vec<u8> {
2892 let mut out = vec![];
2893 let statements = split_statements(sql);
2894 if statements.is_empty() {
2895 return Out::msg(b'I').finish();
2897 }
2898 for stmt_sql in statements {
2899 match execute_stmt(&stmt_sql, db_name, db, read_only) {
2900 Err(encoded) => {
2902 out.extend_from_slice(&encoded);
2903 return out;
2904 }
2905 Ok(ex) => {
2906 if ex.has_rows {
2907 out.extend_from_slice(&encode_rows(&ex.rows, &ex.project));
2908 }
2909 out.extend_from_slice(&command_complete(&ex.tag_for(ex.rows.len())));
2910 }
2911 }
2912 }
2913 out
2914}
2915
2916fn split_statements(sql: &str) -> Vec<String> {
2918 let mut out = vec![];
2919 let mut cur = String::new();
2920 let mut in_s = false;
2921 for c in sql.chars() {
2922 match c {
2923 '\'' => { in_s = !in_s; cur.push(c); }
2924 ';' if !in_s => {
2925 if !cur.trim().is_empty() { out.push(cur.clone()); }
2926 cur.clear();
2927 }
2928 _ => cur.push(c),
2929 }
2930 }
2931 if !cur.trim().is_empty() {
2932 out.push(cur);
2933 }
2934 out
2935}
2936
2937pub async fn run(host: &str, port: u16, resolver: Arc<dyn DbResolver>) -> anyhow::Result<()> {
2939 let read_only = std::env::var("NEDBD_PG_READ_ONLY")
2943 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
2944 .unwrap_or(false);
2945 let listener = TcpListener::bind((host, port)).await?;
2946 println!(" pgwire postgres endpoint on {}:{} — psql / DBeaver / psycopg ({})",
2947 host, port,
2948 if read_only { "SELECT only — read-only mode" } else { "SELECT + INSERT/UPDATE/DELETE" });
2949 loop {
2950 let (sock, _peer) = match listener.accept().await {
2951 Ok(v) => v,
2952 Err(e) => {
2953 eprintln!(" [pgwire] accept failed: {}", e);
2954 continue;
2955 }
2956 };
2957 let r = Arc::clone(&resolver);
2958 tokio::spawn(async move {
2959 let _ = sock.set_nodelay(true);
2960 if let Err(e) = handle(sock, r, read_only).await {
2961 if e.kind() != std::io::ErrorKind::UnexpectedEof
2963 && e.kind() != std::io::ErrorKind::ConnectionReset
2964 {
2965 eprintln!(" [pgwire] connection error: {}", e);
2966 }
2967 }
2968 });
2969 }
2970}
2971
2972#[cfg(test)]
2975mod explain_tests {
2976 use super::*;
2977
2978 #[test]
2979 fn a_bare_explain_is_stripped() {
2980 assert_eq!(strip_explain("EXPLAIN SELECT 1"), Some("SELECT 1"));
2981 assert_eq!(strip_explain("explain select 1"), Some("select 1"));
2982 assert_eq!(strip_explain(" EXPLAIN SELECT 1 ; "), Some("SELECT 1"));
2983 }
2984
2985 #[test]
2986 fn analyze_and_verbose_are_accepted_and_ignored() {
2987 assert_eq!(strip_explain("EXPLAIN ANALYZE SELECT 1"), Some("SELECT 1"));
2991 assert_eq!(strip_explain("EXPLAIN ANALYSE SELECT 1"), Some("SELECT 1"));
2992 assert_eq!(strip_explain("EXPLAIN VERBOSE SELECT 1"), Some("SELECT 1"));
2993 assert_eq!(strip_explain("EXPLAIN ANALYZE VERBOSE SELECT 1"), Some("SELECT 1"));
2994 assert_eq!(strip_explain("explain analyze verbose select 1"), Some("select 1"));
2995 }
2996
2997 #[test]
2998 fn a_word_merely_starting_with_explain_is_not_a_keyword() {
2999 assert_eq!(strip_explain("EXPLAINED SELECT 1"), None);
3000 assert_eq!(strip_explain("SELECT 1"), None);
3001 assert_eq!(strip_explain("SELECT explain FROM t"), None);
3002 }
3003
3004 #[test]
3005 fn a_column_named_analyze_is_not_eaten() {
3006 assert_eq!(strip_explain("EXPLAIN analyzed_view"), Some("analyzed_view"));
3009 }
3010
3011 #[test]
3012 fn the_plan_result_has_postgres_shape() {
3013 let e = plan_result(vec!["Seq Scan on t".into(), "note".into()]);
3014 assert_eq!(e.project.len(), 1);
3015 assert_eq!(e.project[0].out, "QUERY PLAN");
3016 assert_eq!(e.rows.len(), 2);
3017 assert_eq!(e.rows[0]["QUERY PLAN"], "Seq Scan on t");
3018 assert_eq!(e.tag, "EXPLAIN");
3019 assert!(!e.tag_counts_rows);
3021 }
3022}
3023
3024#[cfg(test)]
3025mod tests {
3026 use super::*;
3027 use serde_json::json;
3028
3029 fn q(sql: &str) -> String {
3030 match translate(sql) {
3031 Ok(Stmt::Query { nql, .. }) => nql,
3032 other => panic!("expected a query for {:?}, got {:?}", sql, other),
3033 }
3034 }
3035 fn proj(sql: &str) -> Vec<String> {
3037 match translate(sql) {
3038 Ok(Stmt::Query { project, .. }) => project.iter().map(|c| c.out.clone()).collect(),
3039 other => panic!("expected a query for {:?}, got {:?}", sql, other),
3040 }
3041 }
3042 fn proj_pairs(sql: &str) -> Vec<(String, String)> {
3044 match translate(sql) {
3045 Ok(Stmt::Query { project, .. }) =>
3046 project.iter().map(|c| (c.src.clone(), c.out.clone())).collect(),
3047 other => panic!("expected a query for {:?}, got {:?}", sql, other),
3048 }
3049 }
3050 fn names(cols: &[Col]) -> Vec<String> { cols.iter().map(|c| c.out.clone()).collect() }
3051
3052 #[test]
3053 fn select_star_becomes_bare_from() {
3054 assert_eq!(q("SELECT * FROM orders"), "FROM orders");
3055 assert_eq!(q("select * from orders;"), "FROM orders");
3056 assert_eq!(proj("SELECT * FROM orders"), Vec::<String>::new());
3057 }
3058
3059 #[test]
3060 fn a_column_list_becomes_a_projection_not_a_clause() {
3061 assert_eq!(q("SELECT status, total FROM orders"), "FROM orders");
3064 assert_eq!(proj("SELECT status, total FROM orders"), vec!["status", "total"]);
3065 }
3066
3067 #[test]
3068 fn aliases_and_qualified_names_reduce_to_the_field() {
3069 assert_eq!(proj("SELECT o.status AS s, o.total total FROM orders o"),
3070 vec!["status", "total"]);
3071 assert_eq!(q("SELECT * FROM public.orders"), "FROM orders");
3072 assert_eq!(q("SELECT * FROM \"orders\""), "FROM orders");
3073 }
3074
3075 #[test]
3076 fn where_clauses_pass_through_with_sql_literals_rewritten() {
3077 assert_eq!(q("SELECT * FROM orders WHERE status = 'paid'"),
3078 r#"FROM orders WHERE status = "paid""#);
3079 assert_eq!(q("SELECT * FROM orders WHERE status <> 'paid'"),
3080 r#"FROM orders WHERE status != "paid""#);
3081 assert_eq!(q("SELECT * FROM orders WHERE status IN ('paid','open')"),
3082 r#"FROM orders WHERE status IN ("paid","open")"#);
3083 }
3084
3085 #[test]
3088 fn a_doubled_sql_quote_is_one_literal_character() {
3089 assert_eq!(q("SELECT * FROM t WHERE name = 'it''s'"),
3090 r#"FROM t WHERE name = "it's""#);
3091 }
3092
3093 #[test]
3096 fn a_double_quote_inside_a_sql_literal_is_escaped_for_nql() {
3097 assert_eq!(q(r#"SELECT * FROM t WHERE name = 'say "hi"'"#),
3098 r#"FROM t WHERE name = "say \"hi\"""#);
3099 }
3100
3101 #[test]
3102 fn the_shared_clauses_are_handed_to_nql_unchanged() {
3103 assert_eq!(q("SELECT * FROM orders ORDER BY total DESC LIMIT 10 OFFSET 5"),
3104 "FROM orders ORDER BY total DESC LIMIT 10 OFFSET 5");
3105 assert_eq!(q("SELECT * FROM orders GROUP BY region"), "FROM orders GROUP BY region");
3106 assert_eq!(q("SELECT * FROM o WHERE total BETWEEN 1 AND 9 ORDER BY a, b DESC"),
3107 "FROM o WHERE total BETWEEN 1 AND 9 ORDER BY a, b DESC");
3108 }
3109
3110 #[test]
3117 fn an_aggregate_is_one_column_named_as_sql_names_it() {
3118 assert_eq!(proj_pairs("SELECT COUNT(*) FROM orders"),
3119 vec![("count".to_string(), "count".to_string())]);
3120 assert_eq!(proj_pairs("SELECT SUM(total) FROM orders"),
3121 vec![("sum_total".to_string(), "sum".to_string())]);
3122 assert_eq!(proj_pairs("SELECT avg(total) FROM orders"),
3123 vec![("avg_total".to_string(), "avg".to_string())]);
3124 assert_eq!(proj_pairs("SELECT MIN(total) FROM orders"),
3125 vec![("min_total".to_string(), "min".to_string())]);
3126 let rows = vec![json!({"count": 4, "sum_total": 420, "value": 420})];
3128 let p = vec![Col::renamed("sum_total", "sum")];
3129 let cols = columns_for(&rows, &p);
3130 assert_eq!(names(&cols), vec!["sum"], "one column, SQL's name");
3131 assert_eq!(cell(rows[0].get(&cols[0].src)), Some("420".to_string()));
3132 }
3133
3134 #[test]
3139 fn a_bare_column_with_group_by_is_refused_not_nulled() {
3140 let e = translate("SELECT region, total FROM orders GROUP BY region").unwrap_err();
3141 assert!(e.contains("must appear in the GROUP BY clause"), "{}", e);
3142 assert!(e.contains("total"), "the message names the offending column: {}", e);
3143
3144 assert!(translate("SELECT region FROM orders GROUP BY region").is_ok());
3146 assert!(translate("SELECT region, count FROM orders GROUP BY region").is_ok());
3147 assert!(translate("SELECT SUM(total) FROM orders GROUP BY region").is_ok());
3149 assert!(translate("SELECT * FROM orders GROUP BY region").is_ok());
3151 }
3152
3153 #[test]
3154 fn count_star_becomes_nql_count() {
3155 assert_eq!(q("SELECT COUNT(*) FROM orders"), "FROM orders COUNT");
3156 assert_eq!(q("SELECT count(*) FROM orders WHERE total > 5"),
3157 "FROM orders COUNT WHERE total > 5");
3158 }
3159
3160 #[test]
3161 fn aggregates_carry_their_target_column() {
3162 assert_eq!(q("SELECT SUM(total) FROM orders"), "FROM orders SUM total");
3163 assert_eq!(q("SELECT avg(total) FROM orders WHERE region = 'eu'"),
3164 r#"FROM orders AVG total WHERE region = "eu""#);
3165 assert!(translate("SELECT SUM(*) FROM orders").is_err());
3166 }
3167
3168 #[test]
3171 fn as_of_system_time_bridges_to_nql_as_of() {
3172 assert_eq!(q("SELECT * FROM orders AS OF SYSTEM TIME 42"),
3173 "FROM orders AS OF 42");
3174 assert_eq!(q("SELECT * FROM orders AS OF SYSTEM TIME 42 WHERE total > 1"),
3175 "FROM orders AS OF 42 WHERE total > 1");
3176 let e = translate("SELECT * FROM orders AS OF SYSTEM TIME '2026-01-01'").unwrap_err();
3178 assert!(e.contains("sequence number"), "{}", e);
3179 }
3180
3181 #[test]
3182 fn handshake_queries_are_answered_so_clients_can_connect() {
3183 assert!(matches!(translate("SELECT version()"), Ok(Stmt::Canned { .. })));
3184 assert!(matches!(translate("SHOW transaction_isolation"), Ok(Stmt::Canned { .. })));
3185 assert!(matches!(translate("SELECT current_schema()"), Ok(Stmt::Canned { .. })));
3186 assert!(matches!(translate("SET extra_float_digits = 3"), Ok(Stmt::Ok(_))));
3187 assert!(matches!(translate("BEGIN"), Ok(Stmt::Ok(_))));
3188 assert!(matches!(translate(""), Ok(Stmt::Ok(_))));
3189 }
3190
3191 #[test]
3194 fn unsupported_sql_is_refused_with_a_reason() {
3195 for (sql, expect) in [
3196 ("INSERT INTO t VALUES (1)", "explicit column list"),
3197 ("CREATE TABLE t (a int)", "DDL"),
3198 ("TRUNCATE t", "append-only"),
3199 ("GRANT ALL ON t TO x", "privilege system"),
3200 ("SELECT * FROM a JOIN b ON a.x = b.x", "JOIN is not supported"),
3201 ("SELECT * FROM a UNION SELECT * FROM b", "UNION"),
3202 ("SELECT DISTINCT region FROM orders", "GROUP BY"),
3203 ("SELECT * FROM (SELECT 1) x", "subqueries in FROM"),
3204 ("SELECT * FROM a, b", "more than one collection"),
3205 ("SELECT lower(status) FROM orders", "expressions in the select list"),
3206 ("VACUUM", "only SELECT"),
3207 ] {
3208 let e = translate(sql).unwrap_err();
3209 assert!(e.contains(expect), "for {:?} expected {:?} in {:?}", sql, expect, e);
3210 }
3211 }
3212
3213 fn ins(sql: &str) -> (String, Vec<InsertRow>, Vec<Col>) {
3221 match translate(sql) {
3222 Ok(Stmt::Insert { coll, rows, returning }) => (coll, rows, returning),
3223 other => panic!("expected INSERT for {:?}, got {:?}", sql, other),
3224 }
3225 }
3226
3227 #[test]
3228 fn insert_becomes_a_put_per_row() {
3229 let (coll, rows, ret) = ins("INSERT INTO orders (_id, status, total) VALUES ('o1', 'paid', 120)");
3230 assert_eq!(coll, "orders");
3231 assert_eq!(rows.len(), 1);
3232 assert_eq!(rows[0].id.as_deref(), Some("o1"));
3233 assert_eq!(rows[0].doc.get("status"), Some(&json!("paid")));
3234 assert_eq!(rows[0].doc.get("total"), Some(&json!(120)));
3235 assert!(!rows[0].doc.contains_key("_id"));
3237 assert!(ret.is_empty());
3238 }
3239
3240 #[test]
3241 fn a_multi_row_insert_yields_one_row_each() {
3242 let (_, rows, _) = ins(
3243 "INSERT INTO t (id, n) VALUES ('a', 1), ('b', 2), ('c', 3)");
3244 assert_eq!(rows.len(), 3);
3245 assert_eq!(rows[1].id.as_deref(), Some("b"));
3246 assert_eq!(rows[2].doc.get("n"), Some(&json!(3)));
3247 }
3248
3249 #[test]
3250 fn an_insert_without_an_id_column_lets_the_server_assign_one() {
3251 let (_, rows, _) = ins("INSERT INTO t (n) VALUES (1)");
3252 assert_eq!(rows[0].id, None, "the executor mints a unique key");
3253 assert_eq!(rows[0].doc.get("n"), Some(&json!(1)));
3254 }
3255
3256 #[test]
3259 fn insert_lifts_provenance_out_of_reserved_columns() {
3260 let (_, rows, _) = ins(
3261 "INSERT INTO audit (_id, _caused_by, _valid_from, kind) \
3262 VALUES ('e1', 'abc123', '2026-01-01', 'reprice')");
3263 assert_eq!(rows[0].caused_by, vec!["abc123".to_string()]);
3264 assert_eq!(rows[0].valid_from.as_deref(), Some("2026-01-01"));
3265 assert_eq!(rows[0].doc.get("kind"), Some(&json!("reprice")));
3266 for k in ["_id", "_caused_by", "_valid_from"] {
3268 assert!(!rows[0].doc.contains_key(k), "{} leaked into the doc", k);
3269 }
3270 }
3271
3272 #[test]
3273 fn insert_values_cover_the_scalar_types() {
3274 let (_, rows, _) = ins(
3275 "INSERT INTO t (s, i, f, b, n) VALUES ('x', 42, 1.5, TRUE, NULL)");
3276 assert_eq!(rows[0].doc.get("s"), Some(&json!("x")));
3277 assert_eq!(rows[0].doc.get("i"), Some(&json!(42)));
3278 assert_eq!(rows[0].doc.get("f"), Some(&json!(1.5)));
3279 assert_eq!(rows[0].doc.get("b"), Some(&json!(true)));
3280 assert_eq!(rows[0].doc.get("n"), Some(&Value::Null));
3281 }
3282
3283 #[test]
3286 fn insert_literals_survive_quotes_and_commas() {
3287 let (_, rows, _) = ins("INSERT INTO t (a, b) VALUES ('it''s', 'x,y')");
3288 assert_eq!(rows[0].doc.get("a"), Some(&json!("it's")));
3289 assert_eq!(rows[0].doc.get("b"), Some(&json!("x,y")));
3290 }
3291
3292 #[test]
3293 fn insert_refuses_what_it_cannot_store_faithfully() {
3294 assert!(translate("INSERT INTO t (a) VALUES (1 + 1)").is_err());
3296 assert!(translate("INSERT INTO t (a) VALUES (now())").is_err());
3297 let e = translate("INSERT INTO t (a, b) VALUES (1)").unwrap_err();
3299 assert!(e.contains("values for"), "{}", e);
3300 let e2 = translate("INSERT INTO t VALUES (1)").unwrap_err();
3302 assert!(e2.contains("explicit column list"), "{}", e2);
3303 }
3304
3305 #[test]
3306 fn update_finds_rows_with_the_full_predicate_surface() {
3307 match translate("UPDATE orders SET status = 'void' WHERE total < 50 AND region IN ('eu')") {
3308 Ok(Stmt::Update { coll, set, nql, .. }) => {
3309 assert_eq!(coll, "orders");
3310 assert_eq!(set, vec![("status".to_string(), json!("void"))]);
3311 assert_eq!(nql, r#"FROM orders WHERE total < 50 AND region IN ("eu")"#);
3313 }
3314 other => panic!("expected UPDATE, got {:?}", other),
3315 }
3316 }
3317
3318 #[test]
3319 fn update_without_where_targets_the_whole_collection() {
3320 match translate("UPDATE t SET a = 1") {
3322 Ok(Stmt::Update { nql, .. }) => assert_eq!(nql, "FROM t"),
3323 other => panic!("expected UPDATE, got {:?}", other),
3324 }
3325 }
3326
3327 #[test]
3328 fn update_handles_several_assignments() {
3329 match translate("UPDATE t SET a = 1, b = 'x,y', c = NULL WHERE id = 'k'") {
3330 Ok(Stmt::Update { set, .. }) => {
3331 assert_eq!(set.len(), 3);
3332 assert_eq!(set[1], ("b".to_string(), json!("x,y")));
3333 assert_eq!(set[2], ("c".to_string(), Value::Null));
3334 }
3335 other => panic!("expected UPDATE, got {:?}", other),
3336 }
3337 assert!(translate("UPDATE t SET").is_err());
3338 assert!(translate("UPDATE t SET a").is_err());
3339 }
3340
3341 #[test]
3342 fn delete_becomes_a_predicate_over_the_collection() {
3343 match translate("DELETE FROM orders WHERE status = 'void'") {
3344 Ok(Stmt::Delete { coll, nql, .. }) => {
3345 assert_eq!(coll, "orders");
3346 assert_eq!(nql, r#"FROM orders WHERE status = "void""#);
3347 }
3348 other => panic!("expected DELETE, got {:?}", other),
3349 }
3350 match translate("DELETE FROM t") {
3351 Ok(Stmt::Delete { nql, .. }) => assert_eq!(nql, "FROM t"),
3352 other => panic!("expected DELETE, got {:?}", other),
3353 }
3354 }
3355
3356 #[test]
3357 fn returning_is_parsed_off_every_write() {
3358 let (_, _, ret) = ins("INSERT INTO t (a) VALUES (1) RETURNING a, _id");
3359 assert_eq!(ret.iter().map(|c| c.out.clone()).collect::<Vec<_>>(), vec!["a", "_id"]);
3360 let (_, _, star) = ins("INSERT INTO t (a) VALUES (1) RETURNING *");
3363 assert!(star.is_empty());
3364 assert!(wants_returning("INSERT INTO t (a) VALUES (1) RETURNING *"));
3365 assert!(!wants_returning("INSERT INTO t (a) VALUES (1)"));
3366
3367 match translate("UPDATE t SET a = 1 WHERE id = 'k' RETURNING a") {
3368 Ok(Stmt::Update { nql, returning, .. }) => {
3369 assert_eq!(returning.len(), 1);
3370 assert!(!nql.to_uppercase().contains("RETURNING"), "{}", nql);
3372 }
3373 other => panic!("expected UPDATE, got {:?}", other),
3374 }
3375 match translate("DELETE FROM t WHERE id = 'k' RETURNING *") {
3376 Ok(Stmt::Delete { nql, .. }) =>
3377 assert!(!nql.to_uppercase().contains("RETURNING"), "{}", nql),
3378 other => panic!("expected DELETE, got {:?}", other),
3379 }
3380 }
3381
3382 #[test]
3383 fn a_keyword_inside_a_value_is_not_a_clause() {
3384 match translate("UPDATE t SET note = 'where returning from' WHERE id = 'k'") {
3385 Ok(Stmt::Update { set, nql, .. }) => {
3386 assert_eq!(set[0].1, json!("where returning from"));
3387 assert_eq!(nql, r#"FROM t WHERE id = "k""#);
3388 }
3389 other => panic!("expected UPDATE, got {:?}", other),
3390 }
3391 }
3392
3393 #[test]
3394 fn split_top_respects_quotes_and_nesting() {
3395 assert_eq!(split_top("a, b, c", ',').len(), 3);
3396 assert_eq!(split_top("(1, 2), (3, 4)", ',').len(), 2);
3397 assert_eq!(split_top("'a,b', c", ',').len(), 2);
3398 assert_eq!(split_top("'it''s, fine', c", ',').len(), 2);
3399 }
3400
3401 #[test]
3402 fn comments_and_whitespace_do_not_confuse_the_translator() {
3403 assert_eq!(q("SELECT *\n FROM orders -- trailing note\n"), "FROM orders");
3404 assert_eq!(q("SELECT /* inline */ * FROM orders"), "FROM orders");
3405 assert_eq!(q("SELECT * FROM t WHERE note = 'from here to JOIN'"),
3407 r#"FROM t WHERE note = "from here to JOIN""#);
3408 }
3409
3410 #[test]
3411 fn find_kw_ignores_quotes_parens_and_substrings() {
3412 assert_eq!(find_kw("SELECT A FROM B", "FROM"), Some(9));
3413 assert_eq!(find_kw("SELECT 'FROM' FROM B", "FROM"), Some(14));
3414 assert_eq!(find_kw("SELECT F(x FROM y) FROM B", "FROM"), Some(19));
3415 assert_eq!(find_kw("SELECT FROMAGE", "FROM"), None);
3416 assert_eq!(find_kw("SELECT X_FROM", "FROM"), None);
3417 }
3418
3419 #[test]
3422 fn provenance_columns_sort_after_the_users_own_fields() {
3423 let rows = vec![json!({"_id":"1","_hash":"ab","status":"paid","total":9})];
3424 assert_eq!(names(&columns_for(&rows, &[])),
3425 vec!["status", "total", "_hash", "_id"]);
3426 }
3427
3428 #[test]
3429 fn an_explicit_projection_sets_the_column_order() {
3430 let rows = vec![json!({"a":1,"b":2})];
3431 let p = vec![Col::same("b"), Col::same("a")];
3432 assert_eq!(names(&columns_for(&rows, &p)), vec!["b", "a"]);
3433 }
3434
3435 #[test]
3436 fn columns_are_the_union_across_sparse_rows() {
3437 let rows = vec![json!({"a":1}), json!({"b":2})];
3439 assert_eq!(names(&columns_for(&rows, &[])), vec!["a", "b"]);
3440 }
3441
3442 #[test]
3443 fn type_oids_follow_the_first_non_null_value() {
3444 let rows = vec![json!({"i":1,"f":1.5,"b":true,"s":"x","n":null})];
3445 assert_eq!(oid_for(&rows, "i"), OID_INT8);
3446 assert_eq!(oid_for(&rows, "f"), OID_FLOAT8);
3447 assert_eq!(oid_for(&rows, "b"), OID_BOOL);
3448 assert_eq!(oid_for(&rows, "s"), OID_TEXT);
3449 assert_eq!(oid_for(&rows, "n"), OID_TEXT);
3451 assert_eq!(oid_for(&rows, "absent"), OID_TEXT);
3452 }
3453
3454 #[test]
3455 fn a_column_that_is_null_in_the_first_row_still_gets_its_type() {
3456 let rows = vec![json!({"v": null}), json!({"v": 7})];
3457 assert_eq!(oid_for(&rows, "v"), OID_INT8);
3458 }
3459
3460 #[test]
3461 fn cells_render_in_postgres_text_format() {
3462 assert_eq!(cell(Some(&json!("x"))), Some("x".to_string()));
3463 assert_eq!(cell(Some(&json!(true))), Some("t".to_string()));
3464 assert_eq!(cell(Some(&json!(false))), Some("f".to_string()));
3465 assert_eq!(cell(Some(&json!(42))), Some("42".to_string()));
3466 assert_eq!(cell(Some(&json!(null))), None);
3467 assert_eq!(cell(None), None);
3468 assert_eq!(cell(Some(&json!({"a":1}))), Some("{\"a\":1}".to_string()));
3470 }
3471
3472 #[test]
3475 fn message_framing_length_excludes_the_tag() {
3476 let mut m = Out::msg(b'Z');
3477 m.bytes(b"I");
3478 let bytes = m.finish();
3479 assert_eq!(bytes[0], b'Z');
3480 assert_eq!(i32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]), 5);
3481 assert_eq!(bytes.len(), 6);
3482 }
3483
3484 #[test]
3485 fn a_result_set_encodes_as_description_then_rows_then_complete() {
3486 let rows = vec![json!({"a": 1}), json!({"a": 2})];
3487 let out = encode_result(&rows, &[]);
3488 assert_eq!(out[0], b'T');
3489 let tags: Vec<u8> = {
3490 let mut t = vec![];
3492 let mut i = 0usize;
3493 while i < out.len() {
3494 t.push(out[i]);
3495 let len = i32::from_be_bytes([out[i+1], out[i+2], out[i+3], out[i+4]]) as usize;
3496 i += 1 + len;
3497 }
3498 t
3499 };
3500 assert_eq!(tags, vec![b'T', b'D', b'D', b'C'],
3501 "one description, one row each, one completion");
3502 }
3503
3504 #[test]
3509 fn a_write_with_returning_emits_exactly_one_command_complete() {
3510 let rows = vec![json!({"_id": "o1", "total": 9})];
3511 let mut out = encode_rows(&rows, &[Col::same("_id")]);
3512 out.extend_from_slice(&command_complete("INSERT 0 1"));
3513 let mut tags = vec![];
3514 let mut i = 0usize;
3515 while i < out.len() {
3516 tags.push(out[i]);
3517 let len = i32::from_be_bytes([out[i+1], out[i+2], out[i+3], out[i+4]]) as usize;
3518 i += 1 + len;
3519 }
3520 assert_eq!(tags, vec![b'T', b'D', b'C'], "one description, one row, ONE tag");
3521 assert_eq!(tags.iter().filter(|t| **t == b'C').count(), 1);
3522 assert!(!encode_rows(&rows, &[]).contains(&b'C')
3524 || encode_rows(&rows, &[]).iter().filter(|b| **b == b'C').count() > 0);
3525 let bare = encode_rows(&rows, &[Col::same("_id")]);
3526 let mut bare_tags = vec![];
3527 let mut j = 0usize;
3528 while j < bare.len() {
3529 bare_tags.push(bare[j]);
3530 let len = i32::from_be_bytes([bare[j+1], bare[j+2], bare[j+3], bare[j+4]]) as usize;
3531 j += 1 + len;
3532 }
3533 assert_eq!(bare_tags, vec![b'T', b'D'], "encode_rows never appends a tag");
3534 }
3535
3536 #[test]
3537 fn an_empty_result_still_sends_a_description() {
3538 let out = encode_result(&[], &[Col::same("a")]);
3539 assert_eq!(out[0], b'T', "clients need the shape even with no rows");
3540 }
3541
3542 #[test]
3543 fn statements_split_on_top_level_semicolons_only() {
3544 assert_eq!(split_statements("SELECT 1; SELECT 2").len(), 2);
3545 assert_eq!(split_statements("SELECT ';'").len(), 1);
3546 assert_eq!(split_statements("SELECT 1;").len(), 1);
3547 assert_eq!(split_statements(" ").len(), 0);
3548 }
3549
3550 #[test]
3551 fn an_error_names_its_sqlstate() {
3552 let e = String::from_utf8_lossy(&err_msg("0A000", "x")).to_string();
3553 assert!(e.contains("ERROR"));
3554 assert!(e.contains("0A000"));
3555 }
3556
3557 #[test]
3560 fn placeholders_are_counted_outside_string_literals() {
3561 assert_eq!(param_count("SELECT a FROM t WHERE b = $1 AND c = $2"), 2);
3562 assert_eq!(param_count("SELECT a FROM t"), 0);
3563 assert_eq!(param_count("WHERE a = $2 OR b = $2 OR c = $1"), 2);
3565 assert_eq!(param_count("SELECT a FROM t WHERE b = '$1'"), 0,
3566 "a placeholder inside a literal is data, not a parameter");
3567 assert_eq!(param_count("WHERE a = $10 AND b = $1"), 10,
3568 "two-digit indexes must not be read as $1 followed by 0");
3569 }
3570
3571 #[test]
3572 fn parameters_are_spliced_as_literals() {
3573 let out = substitute_params("WHERE a = $1 AND b = $2 AND c = $3",
3574 &[Some("'x'".into()), Some("42".into()), None]).unwrap();
3575 assert_eq!(out, "WHERE a = 'x' AND b = 42 AND c = NULL");
3576 }
3577
3578 #[test]
3579 fn substitution_leaves_string_literals_alone() {
3580 let out = substitute_params("WHERE a = '$1' AND b = $1", &[Some("9".into())]).unwrap();
3581 assert_eq!(out, "WHERE a = '$1' AND b = 9");
3582 }
3583
3584 #[test]
3585 fn too_few_parameters_is_an_error_not_a_silent_null() {
3586 let e = substitute_params("WHERE a = $2", &[Some("1".into())]).unwrap_err();
3589 assert!(e.contains("$2"), "{}", e);
3590 }
3591
3592 #[test]
3593 fn a_quote_in_a_parameter_cannot_escape_its_literal() {
3594 let lit = decode_param(Some(b"it's"), OID_TEXT, 0).unwrap().unwrap();
3595 assert_eq!(lit, "'it''s'");
3596 let out = substitute_params("WHERE a = $1", &[Some(lit)]).unwrap();
3598 assert_eq!(out, "WHERE a = 'it''s'");
3599 }
3600
3601 #[test]
3602 fn binary_parameters_decode_in_every_width_psycopg_sends() {
3603 assert_eq!(decode_param(Some(&[0x00, 0x2a]), OID_INT2, 1).unwrap().unwrap(), "42");
3606 assert_eq!(decode_param(Some(&[0, 0, 0, 7]), OID_INT4, 1).unwrap().unwrap(), "7");
3607 assert_eq!(
3608 decode_param(Some(&[0, 0, 0, 0, 0, 0, 0, 9]), OID_INT8, 1).unwrap().unwrap(), "9");
3609 assert_eq!(
3610 decode_param(Some(&0x400c_0000_0000_0000u64.to_be_bytes()), OID_FLOAT8, 1)
3611 .unwrap().unwrap(), "3.5");
3612 assert_eq!(decode_param(Some(&[1]), OID_BOOL, 1).unwrap().unwrap(), "TRUE");
3613 assert_eq!(decode_param(Some(&[0]), OID_BOOL, 1).unwrap().unwrap(), "FALSE");
3614 }
3615
3616 #[test]
3617 fn a_negative_binary_integer_keeps_its_sign() {
3618 assert_eq!(decode_param(Some(&(-5i32).to_be_bytes()), OID_INT4, 1).unwrap().unwrap(), "-5");
3619 assert_eq!(decode_param(Some(&(-5i16).to_be_bytes()), OID_INT2, 1).unwrap().unwrap(), "-5");
3620 }
3621
3622 #[test]
3623 fn a_binary_parameter_of_the_wrong_width_is_refused() {
3624 let e = decode_param(Some(&[0x2a]), OID_INT4, 1).unwrap_err();
3627 assert!(e.contains("4 bytes"), "{}", e);
3628 }
3629
3630 #[test]
3631 fn an_unspecified_text_parameter_is_treated_as_a_string() {
3632 assert_eq!(decode_param(Some(b"hello"), 0, 0).unwrap().unwrap(), "'hello'");
3635 }
3636
3637 #[test]
3638 fn a_null_parameter_decodes_to_none_in_every_format() {
3639 assert_eq!(decode_param(None, OID_TEXT, 0).unwrap(), None);
3640 assert_eq!(decode_param(None, OID_INT8, 1).unwrap(), None);
3641 }
3642
3643 #[test]
3644 fn an_unsupported_binary_type_says_so_by_name() {
3645 let e = decode_param(Some(&[0u8; 8]), 1114, 1).unwrap_err();
3646 assert!(e.contains("1114"), "{}", e);
3647 assert!(e.contains("text"), "the error should point at the way out: {}", e);
3648 }
3649
3650 #[test]
3651 fn a_text_number_that_is_not_a_number_gets_quoted() {
3652 assert_eq!(decode_param(Some(b"oops"), OID_INT8, 0).unwrap().unwrap(), "'oops'");
3655 }
3656
3657 #[test]
3658 fn a_client_declared_type_is_believed_over_inference() {
3659 let oids = infer_param_oids("SELECT a FROM t WHERE b = $1 AND c = $2", &[OID_INT4, 0], None);
3662 assert_eq!(oids, vec![OID_INT4, OID_TEXT]);
3663 }
3664
3665 #[test]
3666 fn parameter_arity_is_taken_from_the_sql_when_the_client_declares_none() {
3667 let oids = infer_param_oids("SELECT a FROM t WHERE b = $1 AND c = $2", &[], None);
3670 assert_eq!(oids.len(), 2);
3671 }
3672
3673 #[test]
3674 fn the_field_behind_each_placeholder_is_identified() {
3675 assert_eq!(
3676 param_fields("SELECT a FROM t WHERE qty > $1 AND status = $2", 2),
3677 vec![Some("qty".to_string()), Some("status".to_string())]);
3678 }
3679
3680 #[test]
3681 fn word_operators_do_not_hide_the_field() {
3682 assert_eq!(param_fields("SELECT a FROM t WHERE name LIKE $1", 1),
3683 vec![Some("name".to_string())]);
3684 assert_eq!(param_fields("SELECT a FROM t WHERE qty BETWEEN $1 AND $2", 2),
3685 vec![Some("qty".to_string()), Some("qty".to_string())]);
3686 assert_eq!(param_fields("SELECT a FROM t WHERE region IN ($1, $2)", 2),
3687 vec![Some("region".to_string()), Some("region".to_string())]);
3688 }
3689
3690 #[test]
3691 fn a_clause_position_types_from_the_grammar_not_from_a_column() {
3692 assert_eq!(
3696 infer_param_oids("SELECT a FROM t AS OF SYSTEM TIME $1 WHERE b = $2", &[], None),
3697 vec![OID_INT8, OID_TEXT]);
3698 assert_eq!(infer_param_oids("SELECT a FROM t AS OF $1", &[], None), vec![OID_INT8]);
3699 assert_eq!(
3702 infer_param_oids("SELECT a FROM t VALID AS OF $1", &[], None), vec![OID_TEXT]);
3703 assert_eq!(
3704 infer_param_oids("SELECT a FROM t LIMIT $1 OFFSET $2", &[], None),
3705 vec![OID_INT8, OID_INT8]);
3706 }
3707
3708 #[test]
3709 fn an_aggregate_column_types_from_what_the_aggregate_means() {
3710 assert_eq!(aggregate_oid("count", None, "t"), Some(OID_INT8));
3714 assert_eq!(aggregate_oid("avg_fee", None, "t"), Some(OID_FLOAT8),
3715 "an average is fractional even over integers");
3716 assert_eq!(aggregate_oid("max__seq", None, "t"), Some(OID_INT8));
3719 assert_eq!(aggregate_oid("total", None, "t"), None, "not an aggregate");
3720 }
3721
3722 #[test]
3723 fn the_parse_probe_uses_a_literal_that_every_clause_accepts() {
3724 let probe = probe_sql("SELECT a FROM t AS OF SYSTEM TIME $1 WHERE b = $2", 2);
3728 assert!(!probe.contains("NULL"), "{}", probe);
3729 assert!(translate(&probe).is_ok(), "the probe must parse: {}", probe);
3730 }
3731
3732 #[test]
3733 fn a_column_with_mixed_types_across_documents_is_advertised_as_text() {
3734 let rows = vec![json!({"x": 3}), json!({"x": "n/a"})];
3738 assert_eq!(oid_for(&rows, "x"), OID_TEXT);
3739 let rows = vec![json!({"x": 3}), json!({"x": 1.5})];
3741 assert_eq!(oid_for(&rows, "x"), OID_FLOAT8);
3742 let rows = vec![json!({"x": Value::Null}), json!({"x": 7})];
3744 assert_eq!(oid_for(&rows, "x"), OID_INT8);
3745 }
3746
3747 #[test]
3748 fn binary_output_encodes_each_advertised_type() {
3749 assert_eq!(cell_binary(Some(&json!(true)), OID_BOOL).unwrap().unwrap(), vec![1]);
3750 assert_eq!(cell_binary(Some(&json!(42)), OID_INT8).unwrap().unwrap(),
3751 42i64.to_be_bytes().to_vec());
3752 assert_eq!(cell_binary(Some(&json!(3.5)), OID_FLOAT8).unwrap().unwrap(),
3753 3.5f64.to_be_bytes().to_vec());
3754 assert_eq!(cell_binary(Some(&json!("hi")), OID_TEXT).unwrap().unwrap(), b"hi".to_vec());
3756 assert_eq!(cell_binary(Some(&Value::Null), OID_INT8).unwrap(), None);
3757 assert_eq!(cell(Some(&json!(true))).unwrap(), "t");
3759 }
3760
3761 #[test]
3762 fn a_value_that_does_not_fit_its_advertised_binary_type_is_refused() {
3763 let e = cell_binary(Some(&json!("nope")), OID_INT8).unwrap_err();
3768 assert!(e.contains("a string"), "{}", e);
3769 assert!(e.contains("more than one type"), "the error should explain WHY: {}", e);
3770 }
3771
3772 #[test]
3773 fn a_row_description_carries_the_requested_format_per_column() {
3774 let cols = [Col::same("a"), Col::same("b")];
3775 let m = row_description_fmt(&cols, &[OID_INT8, OID_TEXT], &[1, 0]);
3776 assert_eq!(m[0], b'T');
3777 assert_eq!(m[m.len() - 1], 0, "the last column was requested as text");
3779 }
3780
3781 #[test]
3782 fn a_qualified_column_resolves_to_its_bare_name() {
3783 assert_eq!(param_fields("SELECT a FROM t WHERE t.qty = $1", 1),
3784 vec![Some("qty".to_string())]);
3785 }
3786
3787 #[test]
3788 fn insert_placeholders_map_positionally_to_the_column_list() {
3789 assert_eq!(
3790 param_fields("INSERT INTO t (_id, qty, status) VALUES ($1, $2, $3)", 3),
3791 vec![Some("_id".to_string()), Some("qty".to_string()), Some("status".to_string())]);
3792 }
3793
3794 #[test]
3795 fn a_set_clause_placeholder_finds_its_column() {
3796 assert_eq!(param_fields("UPDATE t SET status = $1 WHERE _id = $2", 2),
3797 vec![Some("status".to_string()), Some("_id".to_string())]);
3798 }
3799
3800 #[test]
3801 fn the_target_collection_is_found_for_every_statement_kind() {
3802 assert_eq!(stmt_collection("SELECT a FROM inv WHERE b = $1"), "inv");
3803 assert_eq!(stmt_collection("UPDATE inv SET a = $1"), "inv");
3804 assert_eq!(stmt_collection("DELETE FROM inv WHERE a = $1"), "inv");
3805 assert_eq!(stmt_collection("INSERT INTO inv (a) VALUES ($1)"), "inv");
3806 assert_eq!(stmt_collection("SELECT a FROM public.inv"), "inv");
3808 assert_eq!(stmt_collection("INSERT INTO inv(a) VALUES ($1)"), "inv");
3809 }
3810
3811 #[test]
3812 fn engine_metadata_fields_type_without_touching_storage() {
3813 assert_eq!(infer_field_oid(None, "t", "_seq"), OID_INT8);
3814 assert_eq!(infer_field_oid(None, "t", "_id"), OID_TEXT);
3815 }
3816
3817 #[test]
3818 fn the_protocol_acknowledgements_are_single_empty_messages() {
3819 for (m, tag) in [
3821 (parse_complete(), b'1'), (bind_complete(), b'2'),
3822 (close_complete(), b'3'), (no_data(), b'n'), (portal_suspended(), b's'),
3823 ] {
3824 assert_eq!(m.len(), 5, "{:?}", tag as char);
3825 assert_eq!(m[0], tag);
3826 assert_eq!(i32::from_be_bytes([m[1], m[2], m[3], m[4]]), 4);
3827 }
3828 }
3829
3830 #[test]
3831 fn parameter_description_reports_its_arity_and_types() {
3832 let m = parameter_description(&[OID_TEXT, OID_INT8]);
3833 assert_eq!(m[0], b't');
3834 assert_eq!(i16::from_be_bytes([m[5], m[6]]), 2);
3835 assert_eq!(i32::from_be_bytes([m[7], m[8], m[9], m[10]]), OID_TEXT);
3836 assert_eq!(i32::from_be_bytes([m[11], m[12], m[13], m[14]]), OID_INT8);
3837 }
3838
3839 #[test]
3840 fn a_cstring_is_taken_without_its_terminator() {
3841 let body = b"one\0two\0".to_vec();
3842 let mut at = 0usize;
3843 assert_eq!(take_cstr(&body, &mut at), "one");
3844 assert_eq!(take_cstr(&body, &mut at), "two");
3845 assert_eq!(at, body.len());
3846 }
3847
3848 #[test]
3849 fn truncated_integers_are_reported_rather_than_read_past_the_end() {
3850 let body = vec![0u8, 1];
3851 let mut at = 0usize;
3852 assert!(take_i32(&body, &mut at).is_err());
3853 let mut at = 0usize;
3854 assert!(take_i16(&body, &mut at).is_ok());
3855 }
3856
3857 #[test]
3858 fn a_binary_result_format_request_is_refused_rather_than_faked() {
3859 let out = encode_rows(&[], &[Col::same("a")]);
3862 let desc_format = &out[out.len() - 2..];
3863 assert_eq!(i16::from_be_bytes([desc_format[0], desc_format[1]]), 0,
3864 "every column is advertised as text format");
3865 }
3866
3867 #[test]
3868 fn a_float_parameter_does_not_render_as_rust_infinity() {
3869 assert_eq!(fmt_float(f64::INFINITY), "'Infinity'");
3870 assert_eq!(fmt_float(f64::NEG_INFINITY), "'-Infinity'");
3871 assert_eq!(fmt_float(f64::NAN), "'NaN'");
3872 assert_eq!(fmt_float(3.0), "3", "a whole float should not gain a .0 tail");
3873 assert_eq!(fmt_float(3.5), "3.5");
3874 }
3875}