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 coll = coll.rsplit('.').next().unwrap_or(coll).trim_matches('"');
729 let tail = rest[coll_end..].trim();
730
731 let pu = projection.to_uppercase();
733 let mut agg_clause = String::new();
734 let mut project: Vec<Col> = vec![];
735
736 if projection == "*" {
737 } else if pu.starts_with("COUNT(") {
739 agg_clause = " COUNT".to_string();
742 project.push(Col::same("count"));
743 } else if let Some(agg) = ["SUM", "AVG", "MIN", "MAX"]
744 .iter()
745 .find(|a| pu.starts_with(&format!("{}(", a)))
746 {
747 let inner = projection[agg.len() + 1..]
748 .trim_end_matches(')')
749 .trim()
750 .to_string();
751 if inner.is_empty() || inner == "*" {
752 return Err(format!("{}() needs a column", agg));
753 }
754 agg_clause = format!(" {} {}", agg, inner);
755 project.push(Col::renamed(
757 &format!("{}_{}", agg.to_lowercase(), inner),
758 &agg.to_lowercase(),
759 ));
760 } else {
761 for part in projection.split(',') {
762 let p = part.trim();
763 if p.is_empty() {
764 return Err("empty column in the select list".into());
765 }
766 if p.contains('(') {
767 return Err(format!(
768 "expressions in the select list are not supported ({:?}) — \
769 supported: *, a column list, COUNT(*), or SUM/AVG/MIN/MAX(col)", p));
770 }
771 let raw = p.split_whitespace().next().unwrap_or(p);
773 let name = raw.rsplit('.').next().unwrap_or(raw).trim_matches('"');
774 project.push(Col::same(name));
775 }
776 }
777
778 let mut tail = tail.to_string();
785 let tu = tail.to_uppercase();
786 if let Some(at) = find_kw(&tu, "AS OF SYSTEM TIME") {
787 let before = tail[..at].to_string();
788 let after = tail[at + "AS OF SYSTEM TIME".len()..].trim_start().to_string();
789 let end = after.find(' ').unwrap_or(after.len());
791 let seq = after[..end].trim().trim_matches('\'').trim_matches('"').to_string();
792 if seq.parse::<u64>().is_err() {
793 return Err(format!(
794 "AS OF SYSTEM TIME takes a NEDB sequence number here, not a timestamp (got {:?}). \
795 NEDB's history is sequence-addressed and never garbage-collected, so a seq is \
796 exact where a wall-clock time would be approximate", seq));
797 }
798 tail = format!("{} AS OF {} {}", before.trim(), seq, after[end..].trim())
799 .trim()
800 .to_string();
801 }
802
803 let tu_all = tail.to_uppercase();
811 if let Some(gb_at) = find_kw(&tu_all, "GROUP BY") {
812 let after = tail[gb_at + "GROUP BY".len()..].trim_start();
813 let key_end = after.find(|c: char| c == ' ' || c == ',').unwrap_or(after.len());
814 let group_key = after[..key_end].trim().trim_matches('"').to_string();
815 let is_agg = !agg_clause.is_empty();
816 for c in &project {
817 let ok = c.src == group_key
818 || c.src == "count"
819 || (is_agg && c.out == agg_clause.trim().split(' ').next()
820 .unwrap_or("").to_lowercase());
821 if !ok {
822 return Err(format!(
823 "column {:?} must appear in the GROUP BY clause or be used in an \
824 aggregate function — a grouped row carries the group key, `count`, \
825 and the aggregate, nothing else",
826 c.src));
827 }
828 }
829 }
830
831 let tail = sql_literals_to_nql(&tail);
832 let nql = format!("FROM {}{}{}", coll,
833 if agg_clause.is_empty() { String::new() } else { agg_clause },
834 if tail.is_empty() { String::new() } else { format!(" {}", tail) });
835
836 Ok(Stmt::Query { nql: nql.trim().to_string(), project })
837}
838
839const SERVER_VERSION: &str = "15.0";
840
841fn full_version_string() -> String {
842 format!(
843 "PostgreSQL {} (NEDB {}) — tamper-evident, append-only, permanent \
844 history. SELECT + INSERT/UPDATE/DELETE; an UPDATE is a new version, \
845 so prior values stay readable with AS OF SYSTEM TIME.",
846 SERVER_VERSION,
847 env!("CARGO_PKG_VERSION")
848 )
849}
850
851fn columns_for(rows: &[Value], project: &[Col]) -> Vec<Col> {
859 if !project.is_empty() {
860 return project.to_vec();
861 }
862 let mut plain: Vec<String> = vec![];
863 let mut meta: Vec<String> = vec![];
864 for r in rows {
865 if let Value::Object(m) = r {
866 for k in m.keys() {
867 let target = if k.starts_with('_') { &mut meta } else { &mut plain };
868 if !target.contains(k) {
869 target.push(k.clone());
870 }
871 }
872 }
873 }
874 plain.sort();
875 meta.sort();
876 plain.extend(meta);
877 plain.into_iter().map(|k| Col::same(&k)).collect()
878}
879
880fn oid_of_value(v: &Value) -> Option<i32> {
882 match v {
883 Value::Null => None,
884 Value::Bool(_) => Some(OID_BOOL),
885 Value::Number(n) => Some(if n.is_i64() || n.is_u64() { OID_INT8 } else { OID_FLOAT8 }),
886 Value::String(_) => Some(OID_TEXT),
887 _ => Some(OID_TEXT),
889 }
890}
891
892fn unify_oid(a: i32, b: i32) -> i32 {
899 if a == b {
900 return a;
901 }
902 match (a, b) {
903 (OID_INT8, OID_FLOAT8) | (OID_FLOAT8, OID_INT8) => OID_FLOAT8,
904 _ => OID_TEXT,
905 }
906}
907
908fn oid_for(rows: &[Value], col: &str) -> i32 {
915 let mut acc: Option<i32> = None;
916 for r in rows {
917 if let Some(o) = r.get(col).and_then(oid_of_value) {
918 acc = Some(match acc {
919 None => o,
920 Some(prev) => unify_oid(prev, o),
921 });
922 if acc == Some(OID_TEXT) {
923 break; }
925 }
926 }
927 acc.unwrap_or(OID_TEXT)
928}
929
930fn cell(v: Option<&Value>) -> Option<String> {
932 match v {
933 None | Some(Value::Null) => None, Some(Value::String(s)) => Some(s.clone()),
935 Some(Value::Bool(b)) => Some(if *b { "t".into() } else { "f".into() }),
936 Some(other) => Some(other.to_string()),
937 }
938}
939
940fn cell_binary(v: Option<&Value>, oid: i32) -> Result<Option<Vec<u8>>, String> {
952 let v = match v {
953 None | Some(Value::Null) => return Ok(None),
954 Some(v) => v,
955 };
956 let as_f64 = |n: &serde_json::Number| n.as_f64()
957 .ok_or_else(|| "a number too large to send as float8".to_string());
958 Ok(Some(match (oid, v) {
959 (OID_BOOL, Value::Bool(b)) => vec![u8::from(*b)],
960 (OID_INT2, Value::Number(n)) => {
961 let i = n.as_i64().ok_or("not an integer")?;
962 i16::try_from(i).map_err(|_| format!("{} does not fit in int2", i))?
963 .to_be_bytes().to_vec()
964 }
965 (OID_INT4, Value::Number(n)) => {
966 let i = n.as_i64().ok_or("not an integer")?;
967 i32::try_from(i).map_err(|_| format!("{} does not fit in int4", i))?
968 .to_be_bytes().to_vec()
969 }
970 (OID_INT8, Value::Number(n)) => {
971 n.as_i64().ok_or("not an integer")?.to_be_bytes().to_vec()
972 }
973 (OID_FLOAT4, Value::Number(n)) => (as_f64(n)? as f32).to_be_bytes().to_vec(),
974 (OID_FLOAT8, Value::Number(n)) => as_f64(n)?.to_be_bytes().to_vec(),
975 (OID_TEXT | OID_VARCHAR | OID_NAME | OID_UNKNOWN | OID_JSON, _) => {
977 cell(Some(v)).unwrap_or_default().into_bytes()
978 }
979 (OID_JSONB, _) => {
981 let mut b = vec![1u8];
982 b.extend_from_slice(cell(Some(v)).unwrap_or_default().as_bytes());
983 b
984 }
985 (oid, val) => {
986 let kind = match val {
987 Value::Bool(_) => "a boolean",
988 Value::Number(_) => "a number",
989 Value::String(_) => "a string",
990 Value::Array(_) => "an array",
991 _ => "an object",
992 };
993 return Err(format!(
994 "cannot send {} in binary format as type OID {} — the field holds \
995 more than one type across documents, so it cannot be described \
996 by a single Postgres type. Select it with a text cast, or use a \
997 text-format client",
998 kind, oid
999 ));
1000 }
1001 }))
1002}
1003
1004fn row_description_fmt(cols: &[Col], oids: &[i32], fmts: &[i16]) -> Vec<u8> {
1006 let mut m = Out::msg(b'T');
1007 m.i16(cols.len() as i16);
1008 for (i, c) in cols.iter().enumerate() {
1009 m.cstr(&c.out);
1010 m.i32(0); m.i16((i + 1) as i16); m.i32(oids.get(i).copied().unwrap_or(OID_TEXT));
1013 m.i16(-1); m.i32(-1); m.i16(fmts.get(i).copied().unwrap_or(0));
1016 }
1017 m.finish()
1018}
1019
1020fn row_description(cols: &[Col], oids: &[i32]) -> Vec<u8> {
1021 row_description_fmt(cols, oids, &[])
1022}
1023
1024fn data_row_bytes(vals: &[Option<Vec<u8>>]) -> Vec<u8> {
1025 let mut m = Out::msg(b'D');
1026 m.i16(vals.len() as i16);
1027 for v in vals {
1028 match v {
1029 None => m.i32(-1),
1030 Some(b) => {
1031 m.i32(b.len() as i32);
1032 m.bytes(b);
1033 }
1034 }
1035 }
1036 m.finish()
1037}
1038
1039fn data_row(vals: &[Option<String>]) -> Vec<u8> {
1040 let owned: Vec<Option<Vec<u8>>> =
1041 vals.iter().map(|v| v.as_ref().map(|s| s.as_bytes().to_vec())).collect();
1042 data_row_bytes(&owned)
1043}
1044
1045pub fn encode_rows(rows: &[Value], project: &[Col]) -> Vec<u8> {
1055 let cols = columns_for(rows, project);
1056 let oids: Vec<i32> = cols.iter().map(|c| oid_for(rows, &c.src)).collect();
1057 let mut out = row_description(&cols, &oids);
1058 for r in rows {
1059 let vals: Vec<Option<String>> = cols.iter().map(|c| cell(r.get(&c.src))).collect();
1060 out.extend_from_slice(&data_row(&vals));
1061 }
1062 out
1063}
1064
1065pub fn encode_result(rows: &[Value], project: &[Col]) -> Vec<u8> {
1067 let mut out = encode_rows(rows, project);
1068 out.extend_from_slice(&command_complete(&format!("SELECT {}", rows.len())));
1069 out
1070}
1071
1072const OID_INT2: i32 = 21;
1099const OID_INT4: i32 = 23;
1100const OID_OID: i32 = 26;
1101const OID_FLOAT4: i32 = 700;
1102const OID_VARCHAR: i32 = 1043;
1103const OID_NAME: i32 = 19;
1104const OID_UNKNOWN: i32 = 705;
1105const OID_JSON: i32 = 114;
1106const OID_JSONB: i32 = 3802;
1107
1108fn param_count(sql: &str) -> usize {
1114 let b = sql.as_bytes();
1115 let mut i = 0usize;
1116 let mut in_s = false;
1117 let mut max = 0usize;
1118 while i < b.len() {
1119 let c = b[i];
1120 if in_s {
1121 if c == b'\'' {
1122 in_s = false;
1123 }
1124 i += 1;
1125 continue;
1126 }
1127 if c == b'\'' {
1128 in_s = true;
1129 i += 1;
1130 continue;
1131 }
1132 if c == b'$' && i + 1 < b.len() && b[i + 1].is_ascii_digit() {
1133 let mut j = i + 1;
1134 let mut n = 0usize;
1135 while j < b.len() && b[j].is_ascii_digit() {
1136 n = n * 10 + (b[j] - b'0') as usize;
1137 j += 1;
1138 }
1139 max = max.max(n);
1140 i = j;
1141 continue;
1142 }
1143 i += 1;
1144 }
1145 max
1146}
1147
1148fn infer_field_oid(db: Option<&Arc<Db>>, coll: &str, field: &str) -> i32 {
1157 match field {
1161 "_seq" => return OID_INT8,
1162 "_id" | "_hash" | "_prev" | "_collection" | "_valid_from" | "_valid_to" => return OID_TEXT,
1163 _ => {}
1164 }
1165 let db = match db {
1166 Some(db) => db,
1167 None => return OID_TEXT,
1168 };
1169 if coll.is_empty() || field.is_empty() {
1170 return OID_TEXT;
1171 }
1172 let rows = match crate::nql::query(db, &format!("FROM {} LIMIT {}", coll, TYPE_SAMPLE)) {
1173 Ok((rows, _)) => rows,
1174 Err(_) => return OID_TEXT,
1175 };
1176 oid_for(&rows, field)
1180}
1181
1182fn aggregate_oid(src: &str, db: Option<&Arc<Db>>, coll: &str) -> Option<i32> {
1194 if src == "count" {
1195 return Some(OID_INT8);
1196 }
1197 for (prefix, fixed) in [
1198 ("count_", Some(OID_INT8)),
1199 ("avg_", Some(OID_FLOAT8)),
1200 ("sum_", None),
1201 ("min_", None),
1202 ("max_", None),
1203 ] {
1204 if let Some(field) = src.strip_prefix(prefix) {
1205 return Some(match fixed {
1206 Some(oid) => oid,
1207 None => match infer_field_oid(db, coll, field) {
1210 OID_INT8 => OID_INT8,
1211 OID_FLOAT8 => OID_FLOAT8,
1212 other => other,
1215 },
1216 });
1217 }
1218 }
1219 None
1220}
1221
1222const TYPE_SAMPLE: usize = 200;
1228
1229fn stmt_collection(sql: &str) -> String {
1231 let s = normalise(sql);
1232 let up = s.to_uppercase();
1233 let after = if let Some(at) = find_kw(&up, "FROM") {
1234 &s[at + 4..]
1235 } else if let Some(rest) = strip_prefix_ci(&s, "UPDATE") {
1236 return rest
1237 .split_whitespace()
1238 .next()
1239 .unwrap_or("")
1240 .rsplit('.')
1241 .next()
1242 .unwrap_or("")
1243 .trim_matches('"')
1244 .to_string();
1245 } else if let Some(rest) = strip_prefix_ci(&s, "INSERT INTO") {
1246 return rest
1247 .split(|c: char| c.is_whitespace() || c == '(')
1248 .find(|t| !t.is_empty())
1249 .unwrap_or("")
1250 .rsplit('.')
1251 .next()
1252 .unwrap_or("")
1253 .trim_matches('"')
1254 .to_string();
1255 } else {
1256 return String::new();
1257 };
1258 after
1259 .trim()
1260 .split(|c: char| c.is_whitespace())
1261 .find(|t| !t.is_empty())
1262 .unwrap_or("")
1263 .rsplit('.')
1264 .next()
1265 .unwrap_or("")
1266 .trim_matches('"')
1267 .to_string()
1268}
1269
1270fn param_fields(sql: &str, n_params: usize) -> Vec<Option<String>> {
1281 let s = normalise(sql);
1282 let mut out = vec![None; n_params];
1283
1284 let up = s.to_uppercase();
1287 if up.starts_with("INSERT") {
1288 if let (Some(open), Some(vals_at)) = (s.find('('), find_kw(&up, "VALUES")) {
1289 if open < vals_at {
1290 if let Some(close) = s[open..vals_at].rfind(')') {
1291 let cols: Vec<String> = split_top(&s[open + 1..open + close], ',')
1292 .into_iter()
1293 .map(|c| c.trim().trim_matches('"').to_string())
1294 .collect();
1295 let tail = &s[vals_at..];
1297 let mut seen = 0usize;
1298 let b = tail.as_bytes();
1299 let mut i = 0usize;
1300 let mut in_s = false;
1301 while i < b.len() {
1302 if in_s {
1303 if b[i] == b'\'' { in_s = false; }
1304 i += 1;
1305 continue;
1306 }
1307 if b[i] == b'\'' { in_s = true; i += 1; continue; }
1308 if b[i] == b'$' && i + 1 < b.len() && b[i + 1].is_ascii_digit() {
1309 let mut j = i + 1;
1310 let mut num = 0usize;
1311 while j < b.len() && b[j].is_ascii_digit() {
1312 num = num * 10 + (b[j] - b'0') as usize;
1313 j += 1;
1314 }
1315 if num >= 1 && num <= n_params {
1316 if let Some(c) = cols.get(seen % cols.len().max(1)) {
1317 out[num - 1] = Some(c.clone());
1318 }
1319 }
1320 seen += 1;
1321 i = j;
1322 continue;
1323 }
1324 i += 1;
1325 }
1326 return out;
1327 }
1328 }
1329 }
1330 }
1331
1332 let b = s.as_bytes();
1334 let mut i = 0usize;
1335 let mut in_s = false;
1336 while i < b.len() {
1337 if in_s {
1338 if b[i] == b'\'' { in_s = false; }
1339 i += 1;
1340 continue;
1341 }
1342 if b[i] == b'\'' { in_s = true; i += 1; continue; }
1343 if b[i] == b'$' && i + 1 < b.len() && b[i + 1].is_ascii_digit() {
1344 let mut j = i + 1;
1345 let mut num = 0usize;
1346 while j < b.len() && b[j].is_ascii_digit() {
1347 num = num * 10 + (b[j] - b'0') as usize;
1348 j += 1;
1349 }
1350 if num >= 1 && num <= n_params {
1351 let left = &s[..i];
1352 let trimmed = left.trim_end_matches(|c: char| {
1355 c.is_whitespace() || "=<>!+-*/%(,".contains(c)
1356 });
1357 let mut tok = trimmed
1360 .rsplit(|c: char| c.is_whitespace() || c == '(' || c == ',')
1361 .find(|t| !t.is_empty())
1362 .unwrap_or("")
1363 .trim_matches('"');
1364 let mut before = trimmed;
1365 for _ in 0..4 {
1366 let upper_tok = tok.to_uppercase();
1367 if upper_tok.starts_with('$')
1373 || matches!(upper_tok.as_str(),
1374 "LIKE" | "ILIKE" | "IN" | "BETWEEN" | "AND" | "OR" | "NOT" | "IS") {
1375 before = before[..before.len() - tok.len()].trim_end_matches(|c: char| {
1376 c.is_whitespace() || "=<>!(,".contains(c)
1377 });
1378 tok = before
1379 .rsplit(|c: char| c.is_whitespace() || c == '(' || c == ',')
1380 .find(|t| !t.is_empty())
1381 .unwrap_or("")
1382 .trim_matches('"');
1383 } else {
1384 break;
1385 }
1386 }
1387 if !tok.is_empty()
1388 && tok.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '.')
1389 && !tok.chars().next().map(|c| c.is_ascii_digit()).unwrap_or(true)
1390 {
1391 out[num - 1] = Some(tok.rsplit('.').next().unwrap_or(tok).to_string());
1392 }
1393 }
1394 i = j;
1395 continue;
1396 }
1397 i += 1;
1398 }
1399 out
1400}
1401
1402fn clause_param_oids(sql: &str, n_params: usize) -> Vec<Option<i32>> {
1412 let s = normalise(sql);
1413 let mut out = vec![None; n_params];
1414 let b = s.as_bytes();
1415 let mut i = 0usize;
1416 let mut in_s = false;
1417 while i < b.len() {
1418 if in_s {
1419 if b[i] == b'\'' { in_s = false; }
1420 i += 1;
1421 continue;
1422 }
1423 if b[i] == b'\'' { in_s = true; i += 1; continue; }
1424 if b[i] == b'$' && i + 1 < b.len() && b[i + 1].is_ascii_digit() {
1425 let mut j = i + 1;
1426 let mut num = 0usize;
1427 while j < b.len() && b[j].is_ascii_digit() {
1428 num = num * 10 + (b[j] - b'0') as usize;
1429 j += 1;
1430 }
1431 if num >= 1 && num <= n_params {
1432 let left = s[..i].trim_end().to_uppercase();
1433 out[num - 1] = if left.ends_with("VALID AS OF") {
1436 Some(OID_TEXT)
1437 } else if left.ends_with("AS OF SYSTEM TIME")
1438 || left.ends_with("FOR SYSTEM_TIME AS OF")
1439 || left.ends_with("AS OF")
1440 || left.ends_with("LIMIT")
1441 || left.ends_with("OFFSET")
1442 {
1443 Some(OID_INT8)
1444 } else {
1445 None
1446 };
1447 }
1448 i = j;
1449 continue;
1450 }
1451 i += 1;
1452 }
1453 out
1454}
1455
1456fn infer_param_oids(sql: &str, declared: &[i32], db: Option<&Arc<Db>>) -> Vec<i32> {
1462 let n = param_count(sql).max(declared.len());
1463 if n == 0 {
1464 return vec![];
1465 }
1466 let coll = stmt_collection(sql);
1467 let fields = param_fields(sql, n);
1468 let clauses = clause_param_oids(sql, n);
1469 (0..n)
1470 .map(|i| match declared.get(i) {
1471 Some(&oid) if oid != 0 => oid,
1472 _ => match clauses[i] {
1475 Some(oid) => oid,
1476 None => match &fields[i] {
1477 Some(f) => infer_field_oid(db, &coll, f),
1478 None => OID_TEXT,
1479 },
1480 },
1481 })
1482 .collect()
1483}
1484
1485fn decode_param(raw: Option<&[u8]>, oid: i32, format: i16) -> Result<Option<String>, String> {
1491 let bytes = match raw {
1492 None => return Ok(None),
1493 Some(b) => b,
1494 };
1495 let quote = |s: &str| format!("'{}'", s.replace('\'', "''"));
1496
1497 if format == 0 {
1498 let s = String::from_utf8_lossy(bytes).to_string();
1499 return Ok(Some(match oid {
1500 OID_BOOL => {
1501 let t = matches!(s.as_str(), "t" | "true" | "TRUE" | "1" | "yes" | "on");
1502 if t { "TRUE".into() } else { "FALSE".into() }
1503 }
1504 OID_INT2 | OID_INT4 | OID_INT8 | OID_OID | OID_FLOAT4 | OID_FLOAT8 => {
1505 if s.parse::<f64>().is_ok() { s } else { quote(&s) }
1509 }
1510 _ => quote(&s),
1515 }));
1516 }
1517 if format != 1 {
1518 return Err(format!("unsupported parameter format code {}", format));
1519 }
1520
1521 let need = |n: usize| -> Result<(), String> {
1523 if bytes.len() == n {
1524 Ok(())
1525 } else {
1526 Err(format!(
1527 "binary parameter of type OID {} should be {} bytes, got {}",
1528 oid, n, bytes.len()
1529 ))
1530 }
1531 };
1532 Ok(Some(match oid {
1533 OID_BOOL => {
1534 need(1)?;
1535 if bytes[0] != 0 { "TRUE".into() } else { "FALSE".into() }
1536 }
1537 OID_INT2 => {
1538 need(2)?;
1539 i16::from_be_bytes([bytes[0], bytes[1]]).to_string()
1540 }
1541 OID_INT4 => {
1542 need(4)?;
1543 i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]).to_string()
1544 }
1545 OID_OID => {
1546 need(4)?;
1547 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]).to_string()
1548 }
1549 OID_INT8 => {
1550 need(8)?;
1551 i64::from_be_bytes(bytes[..8].try_into().unwrap()).to_string()
1552 }
1553 OID_FLOAT4 => {
1554 need(4)?;
1555 let f = f32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1556 fmt_float(f as f64)
1557 }
1558 OID_FLOAT8 => {
1559 need(8)?;
1560 fmt_float(f64::from_be_bytes(bytes[..8].try_into().unwrap()))
1561 }
1562 OID_TEXT | OID_VARCHAR | OID_NAME | OID_UNKNOWN | OID_JSON | 0 => {
1563 quote(&String::from_utf8_lossy(bytes))
1564 }
1565 OID_JSONB => {
1566 let body = if bytes.first() == Some(&1) { &bytes[1..] } else { bytes };
1568 quote(&String::from_utf8_lossy(body))
1569 }
1570 other => {
1571 return Err(format!(
1572 "parameter type OID {} is not supported in binary format — \
1573 the supported set is bool, int2/int4/int8, float4/float8, \
1574 text/varchar/json/jsonb. Send it as text, or cast it in the \
1575 statement",
1576 other
1577 ))
1578 }
1579 }))
1580}
1581
1582fn fmt_float(f: f64) -> String {
1584 if f.is_nan() {
1585 "'NaN'".into()
1586 } else if f.is_infinite() {
1587 if f > 0.0 { "'Infinity'".into() } else { "'-Infinity'".into() }
1588 } else if f.fract() == 0.0 && f.abs() < 1e15 {
1589 format!("{:.0}", f)
1590 } else {
1591 f.to_string()
1592 }
1593}
1594
1595fn substitute_params(sql: &str, params: &[Option<String>]) -> Result<String, String> {
1603 let b = sql.as_bytes();
1604 let mut out = String::with_capacity(sql.len() + 16);
1605 let mut i = 0usize;
1606 let mut in_s = false;
1607 while i < b.len() {
1608 let c = b[i];
1609 if in_s {
1610 out.push(c as char);
1611 if c == b'\'' { in_s = false; }
1612 i += 1;
1613 continue;
1614 }
1615 if c == b'\'' {
1616 in_s = true;
1617 out.push('\'');
1618 i += 1;
1619 continue;
1620 }
1621 if c == b'$' && i + 1 < b.len() && b[i + 1].is_ascii_digit() {
1622 let mut j = i + 1;
1623 let mut n = 0usize;
1624 while j < b.len() && b[j].is_ascii_digit() {
1625 n = n * 10 + (b[j] - b'0') as usize;
1626 j += 1;
1627 }
1628 match params.get(n.wrapping_sub(1)) {
1629 Some(Some(lit)) => out.push_str(lit),
1630 Some(None) => out.push_str("NULL"),
1631 None => {
1632 return Err(format!(
1633 "bind message supplies {} parameter(s) but the statement uses ${}",
1634 params.len(), n
1635 ))
1636 }
1637 }
1638 i = j;
1639 continue;
1640 }
1641 out.push(c as char);
1642 i += 1;
1643 }
1644 Ok(out)
1645}
1646
1647struct Prepared {
1649 sql: String,
1650 param_oids: Vec<i32>,
1653 out_shape: Option<Option<(Vec<Col>, Vec<i32>)>>,
1661}
1662
1663fn prepared_shape<'a>(
1665 p: &'a mut Prepared,
1666 db: Option<&Arc<Db>>,
1667) -> &'a Option<(Vec<Col>, Vec<i32>)> {
1668 if p.out_shape.is_none() {
1669 p.out_shape = Some(describe_shape(&p.sql, db, p.param_oids.len()));
1670 }
1671 p.out_shape.as_ref().expect("just filled")
1672}
1673
1674struct Portal {
1676 sql: String,
1677 result: Option<PortalResult>,
1683 frozen: Option<Vec<Col>>,
1693 formats: Vec<i16>,
1695 declared: Option<(Vec<Col>, Vec<i32>)>,
1702}
1703
1704impl Portal {
1705 fn format_of(&self, i: usize) -> i16 {
1708 match self.formats.len() {
1709 0 => 0,
1710 1 => self.formats[0],
1711 _ => self.formats.get(i).copied().unwrap_or(0),
1712 }
1713 }
1714 fn shape(&self, r: &PortalResult) -> (Vec<Col>, Vec<i32>) {
1716 match &self.declared {
1717 Some((cols, oids)) if self.formats.iter().any(|f| *f == 1) => {
1718 (cols.clone(), oids.clone())
1719 }
1720 _ => {
1721 let cols = columns_for(&r.rows, &r.project);
1722 let oids = cols.iter().map(|c| oid_for(&r.rows, &c.src)).collect();
1723 (cols, oids)
1724 }
1725 }
1726 }
1727}
1728
1729struct PortalResult {
1730 rows: Vec<Value>,
1731 project: Vec<Col>,
1732 has_rows: bool,
1733 tag: String,
1734 tag_counts_rows: bool,
1735 sent: usize,
1737}
1738
1739fn parse_complete() -> Vec<u8> { Out::msg(b'1').finish() }
1740fn bind_complete() -> Vec<u8> { Out::msg(b'2').finish() }
1741fn close_complete() -> Vec<u8> { Out::msg(b'3').finish() }
1742fn no_data() -> Vec<u8> { Out::msg(b'n').finish() }
1743fn portal_suspended() -> Vec<u8> { Out::msg(b's').finish() }
1744
1745fn parameter_description(oids: &[i32]) -> Vec<u8> {
1746 let mut m = Out::msg(b't');
1747 m.i16(oids.len() as i16);
1748 for o in oids {
1749 m.i32(*o);
1750 }
1751 m.finish()
1752}
1753
1754fn take_cstr(body: &[u8], at: &mut usize) -> String {
1756 let start = *at;
1757 while *at < body.len() && body[*at] != 0 {
1758 *at += 1;
1759 }
1760 let s = String::from_utf8_lossy(&body[start..*at]).to_string();
1761 if *at < body.len() {
1762 *at += 1; }
1764 s
1765}
1766
1767fn take_i16(body: &[u8], at: &mut usize) -> Result<i16, String> {
1768 if *at + 2 > body.len() {
1769 return Err("truncated message".into());
1770 }
1771 let v = i16::from_be_bytes([body[*at], body[*at + 1]]);
1772 *at += 2;
1773 Ok(v)
1774}
1775
1776fn take_i32(body: &[u8], at: &mut usize) -> Result<i32, String> {
1777 if *at + 4 > body.len() {
1778 return Err("truncated message".into());
1779 }
1780 let v = i32::from_be_bytes([body[*at], body[*at + 1], body[*at + 2], body[*at + 3]]);
1781 *at += 4;
1782 Ok(v)
1783}
1784
1785fn sample_columns(db: Option<&Arc<Db>>, coll: &str) -> Vec<Col> {
1791 let db = match db {
1792 Some(db) => db,
1793 None => return vec![],
1794 };
1795 let rows = match crate::nql::query(db, &format!("FROM {} LIMIT 25", coll)) {
1796 Ok((rows, _)) => rows,
1797 Err(_) => return vec![],
1798 };
1799 let mut names: Vec<String> = vec![];
1800 for r in &rows {
1801 if let Value::Object(m) = r {
1802 for k in m.keys() {
1803 if !names.iter().any(|n| n == k) {
1804 names.push(k.clone());
1805 }
1806 }
1807 }
1808 }
1809 names.sort();
1810 names.iter().map(|n| Col::same(n)).collect()
1811}
1812
1813fn describe_shape(
1821 sql: &str,
1822 db: Option<&Arc<Db>>,
1823 n_params: usize,
1824) -> Option<(Vec<Col>, Vec<i32>)> {
1825 let probe = probe_sql(sql, n_params);
1826 let stmt = translate(&probe).ok()?;
1827 let coll = stmt_collection(sql);
1828
1829 let cols = match stmt {
1830 Stmt::Ok(_) => return None,
1831 Stmt::Canned { cols, .. } => cols.iter().map(|c| Col::same(c)).collect(),
1832 Stmt::Query { project, .. } => {
1833 if project.is_empty() { sample_columns(db, &coll) } else { project }
1834 }
1835 Stmt::Insert { returning, .. } | Stmt::Update { returning, .. } | Stmt::Delete { returning, .. } => {
1836 if !wants_returning(sql) {
1837 return None;
1838 }
1839 if returning.is_empty() { sample_columns(db, &coll) } else { returning }
1840 }
1841 };
1842 if cols.is_empty() {
1843 return None;
1847 }
1848 let oids = cols
1849 .iter()
1850 .map(|c| {
1851 aggregate_oid(&c.src, db, &coll)
1852 .unwrap_or_else(|| infer_field_oid(db, &coll, &c.src))
1853 })
1854 .collect();
1855 Some((cols, oids))
1856}
1857
1858fn probe_sql(sql: &str, n_params: usize) -> String {
1866 let stub: Vec<Option<String>> = vec![Some("0".to_string()); n_params];
1867 substitute_params(sql, &stub).unwrap_or_else(|_| sql.to_string())
1868}
1869
1870fn ensure_executed(
1872 portal: &mut Portal,
1873 db_name: &str,
1874 db: Option<&Arc<Db>>,
1875 read_only: bool,
1876) -> Result<(), Vec<u8>> {
1877 if portal.result.is_some() {
1878 return Ok(());
1879 }
1880 let ex = execute_stmt(&portal.sql, db_name, db, read_only)?;
1881 let project = if let Some(f) = &portal.frozen {
1884 f.clone()
1885 } else {
1886 let p = if ex.project.is_empty() {
1887 columns_for(&ex.rows, &[])
1888 } else {
1889 ex.project.clone()
1890 };
1891 portal.frozen = Some(p.clone());
1892 p
1893 };
1894 portal.result = Some(PortalResult {
1895 rows: ex.rows,
1896 project,
1897 has_rows: ex.has_rows,
1898 tag: ex.tag,
1899 tag_counts_rows: ex.tag_counts_rows,
1900 sent: 0,
1901 });
1902 Ok(())
1903}
1904
1905async fn read_exact(sock: &mut TcpStream, n: usize) -> std::io::Result<Vec<u8>> {
1908 let mut buf = vec![0u8; n];
1909 sock.read_exact(&mut buf).await?;
1910 Ok(buf)
1911}
1912
1913async fn read_i32(sock: &mut TcpStream) -> std::io::Result<i32> {
1914 let b = read_exact(sock, 4).await?;
1915 Ok(i32::from_be_bytes([b[0], b[1], b[2], b[3]]))
1916}
1917
1918fn parse_startup_params(body: &[u8]) -> HashMap<String, String> {
1919 let mut out = HashMap::new();
1920 let mut parts = body.split(|b| *b == 0).map(|s| String::from_utf8_lossy(s).to_string());
1921 while let (Some(k), Some(v)) = (parts.next(), parts.next()) {
1922 if k.is_empty() {
1923 break;
1924 }
1925 out.insert(k, v);
1926 }
1927 out
1928}
1929
1930async fn handle(mut sock: TcpStream, resolver: Arc<dyn DbResolver>, read_only: bool) -> std::io::Result<()> {
1932 let params = loop {
1934 let len = read_i32(&mut sock).await?;
1935 if len < 8 || len > 1 << 20 {
1936 return Ok(()); }
1938 let code = read_i32(&mut sock).await?;
1939 let body = read_exact(&mut sock, (len - 8) as usize).await?;
1940 match code {
1941 SSL_REQUEST | GSS_REQUEST => {
1942 sock.write_all(b"N").await?;
1944 continue;
1945 }
1946 CANCEL_REQUEST => return Ok(()), PROTO_V3 => break parse_startup_params(&body),
1948 other => {
1949 let major = other >> 16;
1950 sock.write_all(&err_msg(
1951 "0A000",
1952 &format!("unsupported frontend protocol {}.{} — this endpoint speaks 3.0",
1953 major, other & 0xffff),
1954 )).await?;
1955 return Ok(());
1956 }
1957 }
1958 };
1959
1960 let db_name = params.get("database").cloned().unwrap_or_default();
1961
1962 let resolved: Option<Arc<Db>> = {
1968 let r = Arc::clone(&resolver);
1969 let name = db_name.clone();
1970 tokio::task::spawn_blocking(move || r.resolve(&name))
1971 .await
1972 .unwrap_or(None)
1973 };
1974
1975 if let Some(expected) = resolver.token() {
1977 let mut m = Out::msg(b'R');
1979 m.i32(3);
1980 sock.write_all(&m.finish()).await?;
1981
1982 let tag = read_exact(&mut sock, 1).await?;
1983 if tag[0] != b'p' {
1984 sock.write_all(&err_msg("28000", "expected a password message")).await?;
1985 return Ok(());
1986 }
1987 let len = read_i32(&mut sock).await?;
1988 if len < 4 || len > 1 << 16 {
1989 return Ok(());
1990 }
1991 let body = read_exact(&mut sock, (len - 4) as usize).await?;
1992 let supplied = String::from_utf8_lossy(&body).trim_end_matches('\0').to_string();
1993 let ok = supplied.len() == expected.len()
1995 && supplied.bytes().zip(expected.bytes()).fold(0u8, |a, (x, y)| a | (x ^ y)) == 0;
1996 if !ok {
1997 sock.write_all(&err_msg("28P01", "password authentication failed")).await?;
1998 return Ok(());
1999 }
2000 }
2001
2002 let mut m = Out::msg(b'R');
2003 m.i32(0); sock.write_all(&m.finish()).await?;
2005
2006 for (k, v) in [
2007 ("server_version", SERVER_VERSION),
2008 ("server_encoding", "UTF8"),
2009 ("client_encoding", "UTF8"),
2010 ("DateStyle", "ISO, MDY"),
2011 ("integer_datetimes", "on"),
2012 ("standard_conforming_strings", "on"),
2013 ("application_name", "nedbd"),
2014 ] {
2015 let mut p = Out::msg(b'S');
2016 p.cstr(k);
2017 p.cstr(v);
2018 sock.write_all(&p.finish()).await?;
2019 }
2020 let mut k = Out::msg(b'K');
2021 k.i32(std::process::id() as i32);
2022 k.i32(0);
2023 sock.write_all(&k.finish()).await?;
2024 sock.write_all(&ready()).await?;
2025
2026 let mut prepared: HashMap<String, Prepared> = HashMap::new();
2032 let mut portals: HashMap<String, Portal> = HashMap::new();
2033 let mut failed = false;
2037
2038 loop {
2039 let mut tag = [0u8; 1];
2040 if sock.read_exact(&mut tag).await.is_err() {
2041 return Ok(()); }
2043 let len = read_i32(&mut sock).await?;
2044 if len < 4 || len > 64 << 20 {
2045 return Ok(());
2046 }
2047 let body = read_exact(&mut sock, (len - 4) as usize).await?;
2048
2049 if failed && tag[0] != b'S' && tag[0] != b'X' {
2051 continue;
2052 }
2053
2054 match tag[0] {
2055 b'X' => return Ok(()), b'Q' => {
2058 let sql = String::from_utf8_lossy(&body).trim_end_matches('\0').to_string();
2059 let out = run_simple_query(&sql, &db_name, resolved.as_ref(), read_only);
2060 sock.write_all(&out).await?;
2061 sock.write_all(&ready()).await?;
2062 portals.remove("");
2064 }
2065
2066 b'P' => {
2068 let mut at = 0usize;
2069 let name = take_cstr(&body, &mut at);
2070 let sql = take_cstr(&body, &mut at);
2071 let n = take_i16(&body, &mut at).unwrap_or(0).max(0) as usize;
2072 let mut declared = Vec::with_capacity(n);
2073 let mut bad = false;
2074 for _ in 0..n {
2075 match take_i32(&body, &mut at) {
2076 Ok(o) => declared.push(o),
2077 Err(_) => { bad = true; break; }
2078 }
2079 }
2080 if bad {
2081 sock.write_all(&err_msg("08P01", "malformed Parse message")).await?;
2082 failed = true;
2083 continue;
2084 }
2085 if let Err(why) = translate(&probe_sql(&sql, param_count(&sql))) {
2089 sock.write_all(&err_msg("0A000", &why)).await?;
2090 failed = true;
2091 continue;
2092 }
2093 let param_oids = infer_param_oids(&sql, &declared, resolved.as_ref());
2094 prepared.insert(name, Prepared { sql, param_oids, out_shape: None });
2095 sock.write_all(&parse_complete()).await?;
2096 }
2097
2098 b'B' => {
2100 let mut at = 0usize;
2101 let portal_name = take_cstr(&body, &mut at);
2102 let stmt_name = take_cstr(&body, &mut at);
2103 if !prepared.contains_key(&stmt_name) {
2104 sock.write_all(&err_msg("26000", &format!(
2105 "prepared statement {:?} does not exist", stmt_name))).await?;
2106 failed = true;
2107 continue;
2108 }
2109 let p = &prepared[&stmt_name];
2110 let mut want_formats: Vec<i16> = vec![];
2111 let res: Result<String, String> = (|| {
2112 let nfmt = take_i16(&body, &mut at)? .max(0) as usize;
2113 let mut fmts = Vec::with_capacity(nfmt);
2114 for _ in 0..nfmt {
2115 fmts.push(take_i16(&body, &mut at)?);
2116 }
2117 let nparam = take_i16(&body, &mut at)?.max(0) as usize;
2118 let mut vals: Vec<Option<String>> = Vec::with_capacity(nparam);
2119 for i in 0..nparam {
2120 let l = take_i32(&body, &mut at)?;
2121 let raw: Option<Vec<u8>> = if l < 0 {
2122 None
2123 } else {
2124 let l = l as usize;
2125 if at + l > body.len() {
2126 return Err("truncated Bind parameter".into());
2127 }
2128 let v = body[at..at + l].to_vec();
2129 at += l;
2130 Some(v)
2131 };
2132 let f = match fmts.len() {
2135 0 => 0,
2136 1 => fmts[0],
2137 _ => *fmts.get(i).unwrap_or(&0),
2138 };
2139 let oid = *p.param_oids.get(i).unwrap_or(&OID_TEXT);
2140 vals.push(decode_param(raw.as_deref(), oid, f)?);
2141 }
2142 let nres = take_i16(&body, &mut at)?.max(0) as usize;
2147 for _ in 0..nres {
2148 let f = take_i16(&body, &mut at)?;
2149 if f != 0 && f != 1 {
2150 return Err(format!("unknown result format code {}", f));
2151 }
2152 want_formats.push(f);
2153 }
2154 substitute_params(&p.sql, &vals)
2155 })();
2156 match res {
2157 Ok(sql) => {
2158 let declared = if want_formats.iter().any(|f| *f == 1) {
2161 let p = prepared.get_mut(&stmt_name).expect("checked above");
2162 prepared_shape(p, resolved.as_ref()).clone()
2163 } else {
2164 None
2165 };
2166 portals.insert(portal_name, Portal {
2167 sql, result: None, frozen: None,
2168 formats: want_formats, declared,
2169 });
2170 sock.write_all(&bind_complete()).await?;
2171 }
2172 Err(why) => {
2173 sock.write_all(&err_msg("08P01", &why)).await?;
2174 failed = true;
2175 }
2176 }
2177 }
2178
2179 b'D' => {
2181 let kind = body.first().copied().unwrap_or(b'S');
2182 let mut at = 1usize;
2183 let name = take_cstr(&body, &mut at);
2184 if kind == b'S' {
2185 if !prepared.contains_key(&name) {
2186 sock.write_all(&err_msg("26000", &format!(
2187 "prepared statement {:?} does not exist", name))).await?;
2188 failed = true;
2189 continue;
2190 }
2191 let p = prepared.get_mut(&name).expect("checked above");
2192 let oids = p.param_oids.clone();
2193 sock.write_all(¶meter_description(&oids)).await?;
2196 let out = match prepared_shape(p, resolved.as_ref()) {
2200 Some((cols, col_oids)) => row_description(cols, col_oids),
2201 None => no_data(),
2202 };
2203 sock.write_all(&out).await?;
2204 } else {
2205 let portal = match portals.get_mut(&name) {
2206 Some(p) => p,
2207 None => {
2208 sock.write_all(&err_msg("34000", &format!(
2209 "portal {:?} does not exist", name))).await?;
2210 failed = true;
2211 continue;
2212 }
2213 };
2214 match ensure_executed(portal, &db_name, resolved.as_ref(), read_only) {
2219 Err(encoded) => {
2220 sock.write_all(&encoded).await?;
2221 failed = true;
2222 }
2223 Ok(()) => {
2224 let r = portal.result.as_ref().expect("just executed");
2225 if !r.has_rows {
2226 sock.write_all(&no_data()).await?;
2227 } else {
2228 let (cols, oids) = portal.shape(r);
2229 let fmts: Vec<i16> =
2230 (0..cols.len()).map(|i| portal.format_of(i)).collect();
2231 sock.write_all(&row_description_fmt(&cols, &oids, &fmts)).await?;
2232 }
2233 }
2234 }
2235 }
2236 }
2237
2238 b'E' => {
2240 let mut at = 0usize;
2241 let name = take_cstr(&body, &mut at);
2242 let max_rows = take_i32(&body, &mut at).unwrap_or(0);
2243 let portal = match portals.get_mut(&name) {
2244 Some(p) => p,
2245 None => {
2246 sock.write_all(&err_msg("34000", &format!(
2247 "portal {:?} does not exist", name))).await?;
2248 failed = true;
2249 continue;
2250 }
2251 };
2252 if let Err(encoded) = ensure_executed(portal, &db_name, resolved.as_ref(), read_only) {
2253 sock.write_all(&encoded).await?;
2254 failed = true;
2255 continue;
2256 }
2257 let r = portal.result.as_ref().expect("just executed");
2258 if !r.has_rows {
2259 let tag = r.tag.clone();
2260 sock.write_all(&command_complete(&tag)).await?;
2261 continue;
2262 }
2263 let (cols, oids) = portal.shape(r);
2264 let limit = if max_rows > 0 {
2265 (r.sent + max_rows as usize).min(r.rows.len())
2266 } else {
2267 r.rows.len()
2268 };
2269 let mut encoded: Vec<Vec<u8>> = Vec::with_capacity(limit - r.sent);
2274 let mut fail: Option<String> = None;
2275 for row in &r.rows[r.sent..limit] {
2276 let mut vals: Vec<Option<Vec<u8>>> = Vec::with_capacity(cols.len());
2277 for (i, c) in cols.iter().enumerate() {
2278 let v = row.get(&c.src);
2279 let got = if portal.format_of(i) == 1 {
2280 cell_binary(v, oids.get(i).copied().unwrap_or(OID_TEXT))
2281 .map_err(|e| format!("column {:?}: {}", c.out, e))
2282 } else {
2283 Ok(cell(v).map(|s| s.into_bytes()))
2284 };
2285 match got {
2286 Ok(b) => vals.push(b),
2287 Err(e) => { fail = Some(e); break; }
2288 }
2289 }
2290 if fail.is_some() {
2291 break;
2292 }
2293 encoded.push(data_row_bytes(&vals));
2294 }
2295 if let Some(why) = fail {
2296 sock.write_all(&err_msg("22P03", &why)).await?;
2297 failed = true;
2298 continue;
2299 }
2300 let mut out = vec![];
2301 for e in &encoded {
2302 out.extend_from_slice(e);
2303 }
2304 let r = portal.result.as_mut().expect("just executed");
2305 r.sent = limit;
2306 if max_rows > 0 && r.sent < r.rows.len() {
2310 out.extend_from_slice(&portal_suspended());
2311 } else {
2312 let tag = if r.tag_counts_rows {
2313 format!("{} {}", r.tag, r.sent)
2314 } else {
2315 r.tag.clone()
2316 };
2317 out.extend_from_slice(&command_complete(&tag));
2318 }
2319 sock.write_all(&out).await?;
2320 }
2321
2322 b'C' => {
2324 let kind = body.first().copied().unwrap_or(b'S');
2325 let mut at = 1usize;
2326 let name = take_cstr(&body, &mut at);
2327 if kind == b'S' {
2328 prepared.remove(&name);
2329 } else {
2330 portals.remove(&name);
2331 }
2332 sock.write_all(&close_complete()).await?;
2335 }
2336
2337 b'H' => {}
2341
2342 b'S' => {
2343 failed = false;
2344 sock.write_all(&ready()).await?;
2345 }
2346
2347 other => {
2348 sock.write_all(&err_msg(
2349 "08P01",
2350 &format!("unexpected frontend message {:?}", other as char),
2351 )).await?;
2352 failed = true;
2353 }
2354 }
2355 }
2356}
2357
2358const READ_ONLY_MSG: &str =
2359 "this endpoint is running read-only (NEDBD_PG_READ_ONLY=1). Writes are \
2360 implemented but disabled on this server — unset the flag to allow them.";
2361
2362fn no_db(db_name: &str) -> Vec<u8> {
2363 err_msg("3D000", &format!(
2364 "database {:?} is not open on this server — create it first \
2365 (POST /v1/databases), or connect with -d <name>", db_name))
2366}
2367
2368fn wants_returning(sql: &str) -> bool {
2372 find_kw(&sql.to_uppercase(), "RETURNING").is_some()
2373}
2374
2375fn next_row_id() -> String {
2377 use std::sync::atomic::{AtomicU64, Ordering};
2378 static N: AtomicU64 = AtomicU64::new(0);
2379 let n = N.fetch_add(1, Ordering::Relaxed);
2380 let ts = std::time::SystemTime::now()
2381 .duration_since(std::time::UNIX_EPOCH)
2382 .map(|d| d.as_micros())
2383 .unwrap_or(0);
2384 format!("r{}{}", ts, n)
2385}
2386
2387pub struct Executed {
2395 pub rows: Vec<Value>,
2397 pub project: Vec<Col>,
2399 pub has_rows: bool,
2403 pub tag: String,
2406 pub tag_counts_rows: bool,
2408}
2409
2410impl Executed {
2411 fn nothing(tag: &str) -> Self {
2412 Executed { rows: vec![], project: vec![], has_rows: false, tag: tag.to_string(), tag_counts_rows: false }
2413 }
2414 fn tag_for(&self, sent: usize) -> String {
2416 if self.tag_counts_rows { format!("{} {}", self.tag, sent) } else { self.tag.clone() }
2417 }
2418}
2419
2420fn execute_stmt(
2426 stmt_sql: &str,
2427 db_name: &str,
2428 db: Option<&Arc<Db>>,
2429 read_only: bool,
2430) -> Result<Executed, Vec<u8>> {
2431 let stmt = translate(stmt_sql).map_err(|why| err_msg("0A000", &why))?;
2432
2433 macro_rules! need_db {
2436 () => {
2437 match db {
2438 Some(db) => db,
2439 None => return Err(no_db(db_name)),
2440 }
2441 };
2442 }
2443 macro_rules! need_write {
2444 () => {
2445 if read_only {
2446 return Err(err_msg("25006", READ_ONLY_MSG));
2447 }
2448 };
2449 }
2450
2451 match stmt {
2452 Stmt::Ok(tag) => Ok(Executed::nothing(if tag.is_empty() { "SELECT 0" } else { tag })),
2453
2454 Stmt::Canned { cols, row } => {
2455 let mut obj = serde_json::Map::new();
2458 for (c, v) in cols.iter().zip(row.iter()) {
2459 obj.insert(c.clone(), Value::String(v.clone()));
2460 }
2461 Ok(Executed {
2462 rows: vec![Value::Object(obj)],
2463 project: cols.iter().map(|c| Col::same(c)).collect(),
2464 has_rows: true,
2465 tag: "SELECT".into(),
2466 tag_counts_rows: true,
2467 })
2468 }
2469
2470 Stmt::Query { nql, project } => {
2471 let db = need_db!();
2472 let (rows, _) = crate::nql::query(db, &nql).map_err(|e| {
2473 err_msg("42601", &format!("{} (translated to NQL: {})", e, nql))
2474 })?;
2475 Ok(Executed { rows, project, has_rows: true, tag: "SELECT".into(), tag_counts_rows: true })
2476 }
2477
2478 Stmt::Insert { coll, rows, returning } => {
2479 let db = need_db!();
2480 need_write!();
2481 let mut written: Vec<Value> = vec![];
2482 for (i, r) in rows.iter().enumerate() {
2483 let id = match &r.id {
2487 Some(id) => id.clone(),
2488 None => format!("{}-{}", next_row_id(), i),
2489 };
2490 let node = db
2491 .put(&coll, &id, Value::Object(r.doc.clone()),
2492 r.caused_by.clone(), r.valid_from.clone(), r.valid_to.clone())
2493 .map_err(|e| err_msg("XX000", &format!("INSERT failed: {}", e)))?;
2494 written.push(crate::nql::node_to_json(&node));
2495 }
2496 let n = written.len();
2497 let has_rows = wants_returning(stmt_sql);
2498 Ok(Executed {
2499 rows: if has_rows { written } else { vec![] },
2500 project: returning,
2501 has_rows,
2502 tag: format!("INSERT 0 {}", n),
2504 tag_counts_rows: false,
2505 })
2506 }
2507
2508 Stmt::Update { coll, set, nql, returning } => {
2509 let db = need_db!();
2510 need_write!();
2511 let (matched, _) = crate::nql::query(db, &nql).map_err(|e| {
2514 err_msg("42601", &format!("{} (translated to NQL: {})", e, nql))
2515 })?;
2516 let mut written: Vec<Value> = vec![];
2517 for row in &matched {
2518 let id = match row.get("_id").and_then(|v| v.as_str()) {
2519 Some(id) => id.to_string(),
2520 None => continue,
2521 };
2522 let mut doc = match db.get(&coll, &id) {
2526 Some(n) => match n.data {
2527 Value::Object(m) => m,
2528 _ => serde_json::Map::new(),
2529 },
2530 None => continue,
2531 };
2532 for (k, v) in &set {
2533 doc.insert(k.clone(), v.clone());
2534 }
2535 let node = db
2538 .put(&coll, &id, Value::Object(doc), vec![], None, None)
2539 .map_err(|e| err_msg("XX000", &format!("UPDATE failed: {}", e)))?;
2540 written.push(crate::nql::node_to_json(&node));
2541 }
2542 let n = written.len();
2543 let has_rows = wants_returning(stmt_sql);
2544 Ok(Executed {
2545 rows: if has_rows { written } else { vec![] },
2546 project: returning,
2547 has_rows,
2548 tag: format!("UPDATE {}", n),
2549 tag_counts_rows: false,
2550 })
2551 }
2552
2553 Stmt::Delete { coll, nql, returning } => {
2554 let db = need_db!();
2555 need_write!();
2556 let (matched, _) = crate::nql::query(db, &nql).map_err(|e| {
2557 err_msg("42601", &format!("{} (translated to NQL: {})", e, nql))
2558 })?;
2559 let returned = matched.clone();
2562 let mut n = 0usize;
2563 for row in &matched {
2564 if let Some(id) = row.get("_id").and_then(|v| v.as_str()) {
2565 match db.delete(&coll, id) {
2566 Ok(true) => n += 1,
2567 Ok(false) => {}
2568 Err(e) => return Err(err_msg("XX000", &format!("DELETE failed: {}", e))),
2569 }
2570 }
2571 }
2572 let has_rows = wants_returning(stmt_sql);
2573 Ok(Executed {
2574 rows: if has_rows { returned } else { vec![] },
2575 project: returning,
2576 has_rows,
2577 tag: format!("DELETE {}", n),
2578 tag_counts_rows: false,
2579 })
2580 }
2581 }
2582}
2583
2584fn run_simple_query(sql: &str, db_name: &str, db: Option<&Arc<Db>>, read_only: bool) -> Vec<u8> {
2586 let mut out = vec![];
2587 let statements = split_statements(sql);
2588 if statements.is_empty() {
2589 return Out::msg(b'I').finish();
2591 }
2592 for stmt_sql in statements {
2593 match execute_stmt(&stmt_sql, db_name, db, read_only) {
2594 Err(encoded) => {
2596 out.extend_from_slice(&encoded);
2597 return out;
2598 }
2599 Ok(ex) => {
2600 if ex.has_rows {
2601 out.extend_from_slice(&encode_rows(&ex.rows, &ex.project));
2602 }
2603 out.extend_from_slice(&command_complete(&ex.tag_for(ex.rows.len())));
2604 }
2605 }
2606 }
2607 out
2608}
2609
2610fn split_statements(sql: &str) -> Vec<String> {
2612 let mut out = vec![];
2613 let mut cur = String::new();
2614 let mut in_s = false;
2615 for c in sql.chars() {
2616 match c {
2617 '\'' => { in_s = !in_s; cur.push(c); }
2618 ';' if !in_s => {
2619 if !cur.trim().is_empty() { out.push(cur.clone()); }
2620 cur.clear();
2621 }
2622 _ => cur.push(c),
2623 }
2624 }
2625 if !cur.trim().is_empty() {
2626 out.push(cur);
2627 }
2628 out
2629}
2630
2631pub async fn run(host: &str, port: u16, resolver: Arc<dyn DbResolver>) -> anyhow::Result<()> {
2633 let read_only = std::env::var("NEDBD_PG_READ_ONLY")
2637 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
2638 .unwrap_or(false);
2639 let listener = TcpListener::bind((host, port)).await?;
2640 println!(" pgwire postgres endpoint on {}:{} — psql / DBeaver / psycopg ({})",
2641 host, port,
2642 if read_only { "SELECT only — read-only mode" } else { "SELECT + INSERT/UPDATE/DELETE" });
2643 loop {
2644 let (sock, _peer) = match listener.accept().await {
2645 Ok(v) => v,
2646 Err(e) => {
2647 eprintln!(" [pgwire] accept failed: {}", e);
2648 continue;
2649 }
2650 };
2651 let r = Arc::clone(&resolver);
2652 tokio::spawn(async move {
2653 let _ = sock.set_nodelay(true);
2654 if let Err(e) = handle(sock, r, read_only).await {
2655 if e.kind() != std::io::ErrorKind::UnexpectedEof
2657 && e.kind() != std::io::ErrorKind::ConnectionReset
2658 {
2659 eprintln!(" [pgwire] connection error: {}", e);
2660 }
2661 }
2662 });
2663 }
2664}
2665
2666#[cfg(test)]
2669mod tests {
2670 use super::*;
2671 use serde_json::json;
2672
2673 fn q(sql: &str) -> String {
2674 match translate(sql) {
2675 Ok(Stmt::Query { nql, .. }) => nql,
2676 other => panic!("expected a query for {:?}, got {:?}", sql, other),
2677 }
2678 }
2679 fn proj(sql: &str) -> Vec<String> {
2681 match translate(sql) {
2682 Ok(Stmt::Query { project, .. }) => project.iter().map(|c| c.out.clone()).collect(),
2683 other => panic!("expected a query for {:?}, got {:?}", sql, other),
2684 }
2685 }
2686 fn proj_pairs(sql: &str) -> Vec<(String, String)> {
2688 match translate(sql) {
2689 Ok(Stmt::Query { project, .. }) =>
2690 project.iter().map(|c| (c.src.clone(), c.out.clone())).collect(),
2691 other => panic!("expected a query for {:?}, got {:?}", sql, other),
2692 }
2693 }
2694 fn names(cols: &[Col]) -> Vec<String> { cols.iter().map(|c| c.out.clone()).collect() }
2695
2696 #[test]
2697 fn select_star_becomes_bare_from() {
2698 assert_eq!(q("SELECT * FROM orders"), "FROM orders");
2699 assert_eq!(q("select * from orders;"), "FROM orders");
2700 assert_eq!(proj("SELECT * FROM orders"), Vec::<String>::new());
2701 }
2702
2703 #[test]
2704 fn a_column_list_becomes_a_projection_not_a_clause() {
2705 assert_eq!(q("SELECT status, total FROM orders"), "FROM orders");
2708 assert_eq!(proj("SELECT status, total FROM orders"), vec!["status", "total"]);
2709 }
2710
2711 #[test]
2712 fn aliases_and_qualified_names_reduce_to_the_field() {
2713 assert_eq!(proj("SELECT o.status AS s, o.total total FROM orders o"),
2714 vec!["status", "total"]);
2715 assert_eq!(q("SELECT * FROM public.orders"), "FROM orders");
2716 assert_eq!(q("SELECT * FROM \"orders\""), "FROM orders");
2717 }
2718
2719 #[test]
2720 fn where_clauses_pass_through_with_sql_literals_rewritten() {
2721 assert_eq!(q("SELECT * FROM orders WHERE status = 'paid'"),
2722 r#"FROM orders WHERE status = "paid""#);
2723 assert_eq!(q("SELECT * FROM orders WHERE status <> 'paid'"),
2724 r#"FROM orders WHERE status != "paid""#);
2725 assert_eq!(q("SELECT * FROM orders WHERE status IN ('paid','open')"),
2726 r#"FROM orders WHERE status IN ("paid","open")"#);
2727 }
2728
2729 #[test]
2732 fn a_doubled_sql_quote_is_one_literal_character() {
2733 assert_eq!(q("SELECT * FROM t WHERE name = 'it''s'"),
2734 r#"FROM t WHERE name = "it's""#);
2735 }
2736
2737 #[test]
2740 fn a_double_quote_inside_a_sql_literal_is_escaped_for_nql() {
2741 assert_eq!(q(r#"SELECT * FROM t WHERE name = 'say "hi"'"#),
2742 r#"FROM t WHERE name = "say \"hi\"""#);
2743 }
2744
2745 #[test]
2746 fn the_shared_clauses_are_handed_to_nql_unchanged() {
2747 assert_eq!(q("SELECT * FROM orders ORDER BY total DESC LIMIT 10 OFFSET 5"),
2748 "FROM orders ORDER BY total DESC LIMIT 10 OFFSET 5");
2749 assert_eq!(q("SELECT * FROM orders GROUP BY region"), "FROM orders GROUP BY region");
2750 assert_eq!(q("SELECT * FROM o WHERE total BETWEEN 1 AND 9 ORDER BY a, b DESC"),
2751 "FROM o WHERE total BETWEEN 1 AND 9 ORDER BY a, b DESC");
2752 }
2753
2754 #[test]
2761 fn an_aggregate_is_one_column_named_as_sql_names_it() {
2762 assert_eq!(proj_pairs("SELECT COUNT(*) FROM orders"),
2763 vec![("count".to_string(), "count".to_string())]);
2764 assert_eq!(proj_pairs("SELECT SUM(total) FROM orders"),
2765 vec![("sum_total".to_string(), "sum".to_string())]);
2766 assert_eq!(proj_pairs("SELECT avg(total) FROM orders"),
2767 vec![("avg_total".to_string(), "avg".to_string())]);
2768 assert_eq!(proj_pairs("SELECT MIN(total) FROM orders"),
2769 vec![("min_total".to_string(), "min".to_string())]);
2770 let rows = vec![json!({"count": 4, "sum_total": 420, "value": 420})];
2772 let p = vec![Col::renamed("sum_total", "sum")];
2773 let cols = columns_for(&rows, &p);
2774 assert_eq!(names(&cols), vec!["sum"], "one column, SQL's name");
2775 assert_eq!(cell(rows[0].get(&cols[0].src)), Some("420".to_string()));
2776 }
2777
2778 #[test]
2783 fn a_bare_column_with_group_by_is_refused_not_nulled() {
2784 let e = translate("SELECT region, total FROM orders GROUP BY region").unwrap_err();
2785 assert!(e.contains("must appear in the GROUP BY clause"), "{}", e);
2786 assert!(e.contains("total"), "the message names the offending column: {}", e);
2787
2788 assert!(translate("SELECT region FROM orders GROUP BY region").is_ok());
2790 assert!(translate("SELECT region, count FROM orders GROUP BY region").is_ok());
2791 assert!(translate("SELECT SUM(total) FROM orders GROUP BY region").is_ok());
2793 assert!(translate("SELECT * FROM orders GROUP BY region").is_ok());
2795 }
2796
2797 #[test]
2798 fn count_star_becomes_nql_count() {
2799 assert_eq!(q("SELECT COUNT(*) FROM orders"), "FROM orders COUNT");
2800 assert_eq!(q("SELECT count(*) FROM orders WHERE total > 5"),
2801 "FROM orders COUNT WHERE total > 5");
2802 }
2803
2804 #[test]
2805 fn aggregates_carry_their_target_column() {
2806 assert_eq!(q("SELECT SUM(total) FROM orders"), "FROM orders SUM total");
2807 assert_eq!(q("SELECT avg(total) FROM orders WHERE region = 'eu'"),
2808 r#"FROM orders AVG total WHERE region = "eu""#);
2809 assert!(translate("SELECT SUM(*) FROM orders").is_err());
2810 }
2811
2812 #[test]
2815 fn as_of_system_time_bridges_to_nql_as_of() {
2816 assert_eq!(q("SELECT * FROM orders AS OF SYSTEM TIME 42"),
2817 "FROM orders AS OF 42");
2818 assert_eq!(q("SELECT * FROM orders AS OF SYSTEM TIME 42 WHERE total > 1"),
2819 "FROM orders AS OF 42 WHERE total > 1");
2820 let e = translate("SELECT * FROM orders AS OF SYSTEM TIME '2026-01-01'").unwrap_err();
2822 assert!(e.contains("sequence number"), "{}", e);
2823 }
2824
2825 #[test]
2826 fn handshake_queries_are_answered_so_clients_can_connect() {
2827 assert!(matches!(translate("SELECT version()"), Ok(Stmt::Canned { .. })));
2828 assert!(matches!(translate("SHOW transaction_isolation"), Ok(Stmt::Canned { .. })));
2829 assert!(matches!(translate("SELECT current_schema()"), Ok(Stmt::Canned { .. })));
2830 assert!(matches!(translate("SET extra_float_digits = 3"), Ok(Stmt::Ok(_))));
2831 assert!(matches!(translate("BEGIN"), Ok(Stmt::Ok(_))));
2832 assert!(matches!(translate(""), Ok(Stmt::Ok(_))));
2833 }
2834
2835 #[test]
2838 fn unsupported_sql_is_refused_with_a_reason() {
2839 for (sql, expect) in [
2840 ("INSERT INTO t VALUES (1)", "explicit column list"),
2841 ("CREATE TABLE t (a int)", "DDL"),
2842 ("TRUNCATE t", "append-only"),
2843 ("GRANT ALL ON t TO x", "privilege system"),
2844 ("SELECT * FROM a JOIN b ON a.x = b.x", "JOIN is not supported"),
2845 ("SELECT * FROM a UNION SELECT * FROM b", "UNION"),
2846 ("SELECT DISTINCT region FROM orders", "GROUP BY"),
2847 ("SELECT * FROM (SELECT 1) x", "subqueries in FROM"),
2848 ("SELECT * FROM a, b", "more than one collection"),
2849 ("SELECT lower(status) FROM orders", "expressions in the select list"),
2850 ("VACUUM", "only SELECT"),
2851 ] {
2852 let e = translate(sql).unwrap_err();
2853 assert!(e.contains(expect), "for {:?} expected {:?} in {:?}", sql, expect, e);
2854 }
2855 }
2856
2857 fn ins(sql: &str) -> (String, Vec<InsertRow>, Vec<Col>) {
2865 match translate(sql) {
2866 Ok(Stmt::Insert { coll, rows, returning }) => (coll, rows, returning),
2867 other => panic!("expected INSERT for {:?}, got {:?}", sql, other),
2868 }
2869 }
2870
2871 #[test]
2872 fn insert_becomes_a_put_per_row() {
2873 let (coll, rows, ret) = ins("INSERT INTO orders (_id, status, total) VALUES ('o1', 'paid', 120)");
2874 assert_eq!(coll, "orders");
2875 assert_eq!(rows.len(), 1);
2876 assert_eq!(rows[0].id.as_deref(), Some("o1"));
2877 assert_eq!(rows[0].doc.get("status"), Some(&json!("paid")));
2878 assert_eq!(rows[0].doc.get("total"), Some(&json!(120)));
2879 assert!(!rows[0].doc.contains_key("_id"));
2881 assert!(ret.is_empty());
2882 }
2883
2884 #[test]
2885 fn a_multi_row_insert_yields_one_row_each() {
2886 let (_, rows, _) = ins(
2887 "INSERT INTO t (id, n) VALUES ('a', 1), ('b', 2), ('c', 3)");
2888 assert_eq!(rows.len(), 3);
2889 assert_eq!(rows[1].id.as_deref(), Some("b"));
2890 assert_eq!(rows[2].doc.get("n"), Some(&json!(3)));
2891 }
2892
2893 #[test]
2894 fn an_insert_without_an_id_column_lets_the_server_assign_one() {
2895 let (_, rows, _) = ins("INSERT INTO t (n) VALUES (1)");
2896 assert_eq!(rows[0].id, None, "the executor mints a unique key");
2897 assert_eq!(rows[0].doc.get("n"), Some(&json!(1)));
2898 }
2899
2900 #[test]
2903 fn insert_lifts_provenance_out_of_reserved_columns() {
2904 let (_, rows, _) = ins(
2905 "INSERT INTO audit (_id, _caused_by, _valid_from, kind) \
2906 VALUES ('e1', 'abc123', '2026-01-01', 'reprice')");
2907 assert_eq!(rows[0].caused_by, vec!["abc123".to_string()]);
2908 assert_eq!(rows[0].valid_from.as_deref(), Some("2026-01-01"));
2909 assert_eq!(rows[0].doc.get("kind"), Some(&json!("reprice")));
2910 for k in ["_id", "_caused_by", "_valid_from"] {
2912 assert!(!rows[0].doc.contains_key(k), "{} leaked into the doc", k);
2913 }
2914 }
2915
2916 #[test]
2917 fn insert_values_cover_the_scalar_types() {
2918 let (_, rows, _) = ins(
2919 "INSERT INTO t (s, i, f, b, n) VALUES ('x', 42, 1.5, TRUE, NULL)");
2920 assert_eq!(rows[0].doc.get("s"), Some(&json!("x")));
2921 assert_eq!(rows[0].doc.get("i"), Some(&json!(42)));
2922 assert_eq!(rows[0].doc.get("f"), Some(&json!(1.5)));
2923 assert_eq!(rows[0].doc.get("b"), Some(&json!(true)));
2924 assert_eq!(rows[0].doc.get("n"), Some(&Value::Null));
2925 }
2926
2927 #[test]
2930 fn insert_literals_survive_quotes_and_commas() {
2931 let (_, rows, _) = ins("INSERT INTO t (a, b) VALUES ('it''s', 'x,y')");
2932 assert_eq!(rows[0].doc.get("a"), Some(&json!("it's")));
2933 assert_eq!(rows[0].doc.get("b"), Some(&json!("x,y")));
2934 }
2935
2936 #[test]
2937 fn insert_refuses_what_it_cannot_store_faithfully() {
2938 assert!(translate("INSERT INTO t (a) VALUES (1 + 1)").is_err());
2940 assert!(translate("INSERT INTO t (a) VALUES (now())").is_err());
2941 let e = translate("INSERT INTO t (a, b) VALUES (1)").unwrap_err();
2943 assert!(e.contains("values for"), "{}", e);
2944 let e2 = translate("INSERT INTO t VALUES (1)").unwrap_err();
2946 assert!(e2.contains("explicit column list"), "{}", e2);
2947 }
2948
2949 #[test]
2950 fn update_finds_rows_with_the_full_predicate_surface() {
2951 match translate("UPDATE orders SET status = 'void' WHERE total < 50 AND region IN ('eu')") {
2952 Ok(Stmt::Update { coll, set, nql, .. }) => {
2953 assert_eq!(coll, "orders");
2954 assert_eq!(set, vec![("status".to_string(), json!("void"))]);
2955 assert_eq!(nql, r#"FROM orders WHERE total < 50 AND region IN ("eu")"#);
2957 }
2958 other => panic!("expected UPDATE, got {:?}", other),
2959 }
2960 }
2961
2962 #[test]
2963 fn update_without_where_targets_the_whole_collection() {
2964 match translate("UPDATE t SET a = 1") {
2966 Ok(Stmt::Update { nql, .. }) => assert_eq!(nql, "FROM t"),
2967 other => panic!("expected UPDATE, got {:?}", other),
2968 }
2969 }
2970
2971 #[test]
2972 fn update_handles_several_assignments() {
2973 match translate("UPDATE t SET a = 1, b = 'x,y', c = NULL WHERE id = 'k'") {
2974 Ok(Stmt::Update { set, .. }) => {
2975 assert_eq!(set.len(), 3);
2976 assert_eq!(set[1], ("b".to_string(), json!("x,y")));
2977 assert_eq!(set[2], ("c".to_string(), Value::Null));
2978 }
2979 other => panic!("expected UPDATE, got {:?}", other),
2980 }
2981 assert!(translate("UPDATE t SET").is_err());
2982 assert!(translate("UPDATE t SET a").is_err());
2983 }
2984
2985 #[test]
2986 fn delete_becomes_a_predicate_over_the_collection() {
2987 match translate("DELETE FROM orders WHERE status = 'void'") {
2988 Ok(Stmt::Delete { coll, nql, .. }) => {
2989 assert_eq!(coll, "orders");
2990 assert_eq!(nql, r#"FROM orders WHERE status = "void""#);
2991 }
2992 other => panic!("expected DELETE, got {:?}", other),
2993 }
2994 match translate("DELETE FROM t") {
2995 Ok(Stmt::Delete { nql, .. }) => assert_eq!(nql, "FROM t"),
2996 other => panic!("expected DELETE, got {:?}", other),
2997 }
2998 }
2999
3000 #[test]
3001 fn returning_is_parsed_off_every_write() {
3002 let (_, _, ret) = ins("INSERT INTO t (a) VALUES (1) RETURNING a, _id");
3003 assert_eq!(ret.iter().map(|c| c.out.clone()).collect::<Vec<_>>(), vec!["a", "_id"]);
3004 let (_, _, star) = ins("INSERT INTO t (a) VALUES (1) RETURNING *");
3007 assert!(star.is_empty());
3008 assert!(wants_returning("INSERT INTO t (a) VALUES (1) RETURNING *"));
3009 assert!(!wants_returning("INSERT INTO t (a) VALUES (1)"));
3010
3011 match translate("UPDATE t SET a = 1 WHERE id = 'k' RETURNING a") {
3012 Ok(Stmt::Update { nql, returning, .. }) => {
3013 assert_eq!(returning.len(), 1);
3014 assert!(!nql.to_uppercase().contains("RETURNING"), "{}", nql);
3016 }
3017 other => panic!("expected UPDATE, got {:?}", other),
3018 }
3019 match translate("DELETE FROM t WHERE id = 'k' RETURNING *") {
3020 Ok(Stmt::Delete { nql, .. }) =>
3021 assert!(!nql.to_uppercase().contains("RETURNING"), "{}", nql),
3022 other => panic!("expected DELETE, got {:?}", other),
3023 }
3024 }
3025
3026 #[test]
3027 fn a_keyword_inside_a_value_is_not_a_clause() {
3028 match translate("UPDATE t SET note = 'where returning from' WHERE id = 'k'") {
3029 Ok(Stmt::Update { set, nql, .. }) => {
3030 assert_eq!(set[0].1, json!("where returning from"));
3031 assert_eq!(nql, r#"FROM t WHERE id = "k""#);
3032 }
3033 other => panic!("expected UPDATE, got {:?}", other),
3034 }
3035 }
3036
3037 #[test]
3038 fn split_top_respects_quotes_and_nesting() {
3039 assert_eq!(split_top("a, b, c", ',').len(), 3);
3040 assert_eq!(split_top("(1, 2), (3, 4)", ',').len(), 2);
3041 assert_eq!(split_top("'a,b', c", ',').len(), 2);
3042 assert_eq!(split_top("'it''s, fine', c", ',').len(), 2);
3043 }
3044
3045 #[test]
3046 fn comments_and_whitespace_do_not_confuse_the_translator() {
3047 assert_eq!(q("SELECT *\n FROM orders -- trailing note\n"), "FROM orders");
3048 assert_eq!(q("SELECT /* inline */ * FROM orders"), "FROM orders");
3049 assert_eq!(q("SELECT * FROM t WHERE note = 'from here to JOIN'"),
3051 r#"FROM t WHERE note = "from here to JOIN""#);
3052 }
3053
3054 #[test]
3055 fn find_kw_ignores_quotes_parens_and_substrings() {
3056 assert_eq!(find_kw("SELECT A FROM B", "FROM"), Some(9));
3057 assert_eq!(find_kw("SELECT 'FROM' FROM B", "FROM"), Some(14));
3058 assert_eq!(find_kw("SELECT F(x FROM y) FROM B", "FROM"), Some(19));
3059 assert_eq!(find_kw("SELECT FROMAGE", "FROM"), None);
3060 assert_eq!(find_kw("SELECT X_FROM", "FROM"), None);
3061 }
3062
3063 #[test]
3066 fn provenance_columns_sort_after_the_users_own_fields() {
3067 let rows = vec![json!({"_id":"1","_hash":"ab","status":"paid","total":9})];
3068 assert_eq!(names(&columns_for(&rows, &[])),
3069 vec!["status", "total", "_hash", "_id"]);
3070 }
3071
3072 #[test]
3073 fn an_explicit_projection_sets_the_column_order() {
3074 let rows = vec![json!({"a":1,"b":2})];
3075 let p = vec![Col::same("b"), Col::same("a")];
3076 assert_eq!(names(&columns_for(&rows, &p)), vec!["b", "a"]);
3077 }
3078
3079 #[test]
3080 fn columns_are_the_union_across_sparse_rows() {
3081 let rows = vec![json!({"a":1}), json!({"b":2})];
3083 assert_eq!(names(&columns_for(&rows, &[])), vec!["a", "b"]);
3084 }
3085
3086 #[test]
3087 fn type_oids_follow_the_first_non_null_value() {
3088 let rows = vec![json!({"i":1,"f":1.5,"b":true,"s":"x","n":null})];
3089 assert_eq!(oid_for(&rows, "i"), OID_INT8);
3090 assert_eq!(oid_for(&rows, "f"), OID_FLOAT8);
3091 assert_eq!(oid_for(&rows, "b"), OID_BOOL);
3092 assert_eq!(oid_for(&rows, "s"), OID_TEXT);
3093 assert_eq!(oid_for(&rows, "n"), OID_TEXT);
3095 assert_eq!(oid_for(&rows, "absent"), OID_TEXT);
3096 }
3097
3098 #[test]
3099 fn a_column_that_is_null_in_the_first_row_still_gets_its_type() {
3100 let rows = vec![json!({"v": null}), json!({"v": 7})];
3101 assert_eq!(oid_for(&rows, "v"), OID_INT8);
3102 }
3103
3104 #[test]
3105 fn cells_render_in_postgres_text_format() {
3106 assert_eq!(cell(Some(&json!("x"))), Some("x".to_string()));
3107 assert_eq!(cell(Some(&json!(true))), Some("t".to_string()));
3108 assert_eq!(cell(Some(&json!(false))), Some("f".to_string()));
3109 assert_eq!(cell(Some(&json!(42))), Some("42".to_string()));
3110 assert_eq!(cell(Some(&json!(null))), None);
3111 assert_eq!(cell(None), None);
3112 assert_eq!(cell(Some(&json!({"a":1}))), Some("{\"a\":1}".to_string()));
3114 }
3115
3116 #[test]
3119 fn message_framing_length_excludes_the_tag() {
3120 let mut m = Out::msg(b'Z');
3121 m.bytes(b"I");
3122 let bytes = m.finish();
3123 assert_eq!(bytes[0], b'Z');
3124 assert_eq!(i32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]), 5);
3125 assert_eq!(bytes.len(), 6);
3126 }
3127
3128 #[test]
3129 fn a_result_set_encodes_as_description_then_rows_then_complete() {
3130 let rows = vec![json!({"a": 1}), json!({"a": 2})];
3131 let out = encode_result(&rows, &[]);
3132 assert_eq!(out[0], b'T');
3133 let tags: Vec<u8> = {
3134 let mut t = vec![];
3136 let mut i = 0usize;
3137 while i < out.len() {
3138 t.push(out[i]);
3139 let len = i32::from_be_bytes([out[i+1], out[i+2], out[i+3], out[i+4]]) as usize;
3140 i += 1 + len;
3141 }
3142 t
3143 };
3144 assert_eq!(tags, vec![b'T', b'D', b'D', b'C'],
3145 "one description, one row each, one completion");
3146 }
3147
3148 #[test]
3153 fn a_write_with_returning_emits_exactly_one_command_complete() {
3154 let rows = vec![json!({"_id": "o1", "total": 9})];
3155 let mut out = encode_rows(&rows, &[Col::same("_id")]);
3156 out.extend_from_slice(&command_complete("INSERT 0 1"));
3157 let mut tags = vec![];
3158 let mut i = 0usize;
3159 while i < out.len() {
3160 tags.push(out[i]);
3161 let len = i32::from_be_bytes([out[i+1], out[i+2], out[i+3], out[i+4]]) as usize;
3162 i += 1 + len;
3163 }
3164 assert_eq!(tags, vec![b'T', b'D', b'C'], "one description, one row, ONE tag");
3165 assert_eq!(tags.iter().filter(|t| **t == b'C').count(), 1);
3166 assert!(!encode_rows(&rows, &[]).contains(&b'C')
3168 || encode_rows(&rows, &[]).iter().filter(|b| **b == b'C').count() > 0);
3169 let bare = encode_rows(&rows, &[Col::same("_id")]);
3170 let mut bare_tags = vec![];
3171 let mut j = 0usize;
3172 while j < bare.len() {
3173 bare_tags.push(bare[j]);
3174 let len = i32::from_be_bytes([bare[j+1], bare[j+2], bare[j+3], bare[j+4]]) as usize;
3175 j += 1 + len;
3176 }
3177 assert_eq!(bare_tags, vec![b'T', b'D'], "encode_rows never appends a tag");
3178 }
3179
3180 #[test]
3181 fn an_empty_result_still_sends_a_description() {
3182 let out = encode_result(&[], &[Col::same("a")]);
3183 assert_eq!(out[0], b'T', "clients need the shape even with no rows");
3184 }
3185
3186 #[test]
3187 fn statements_split_on_top_level_semicolons_only() {
3188 assert_eq!(split_statements("SELECT 1; SELECT 2").len(), 2);
3189 assert_eq!(split_statements("SELECT ';'").len(), 1);
3190 assert_eq!(split_statements("SELECT 1;").len(), 1);
3191 assert_eq!(split_statements(" ").len(), 0);
3192 }
3193
3194 #[test]
3195 fn an_error_names_its_sqlstate() {
3196 let e = String::from_utf8_lossy(&err_msg("0A000", "x")).to_string();
3197 assert!(e.contains("ERROR"));
3198 assert!(e.contains("0A000"));
3199 }
3200
3201 #[test]
3204 fn placeholders_are_counted_outside_string_literals() {
3205 assert_eq!(param_count("SELECT a FROM t WHERE b = $1 AND c = $2"), 2);
3206 assert_eq!(param_count("SELECT a FROM t"), 0);
3207 assert_eq!(param_count("WHERE a = $2 OR b = $2 OR c = $1"), 2);
3209 assert_eq!(param_count("SELECT a FROM t WHERE b = '$1'"), 0,
3210 "a placeholder inside a literal is data, not a parameter");
3211 assert_eq!(param_count("WHERE a = $10 AND b = $1"), 10,
3212 "two-digit indexes must not be read as $1 followed by 0");
3213 }
3214
3215 #[test]
3216 fn parameters_are_spliced_as_literals() {
3217 let out = substitute_params("WHERE a = $1 AND b = $2 AND c = $3",
3218 &[Some("'x'".into()), Some("42".into()), None]).unwrap();
3219 assert_eq!(out, "WHERE a = 'x' AND b = 42 AND c = NULL");
3220 }
3221
3222 #[test]
3223 fn substitution_leaves_string_literals_alone() {
3224 let out = substitute_params("WHERE a = '$1' AND b = $1", &[Some("9".into())]).unwrap();
3225 assert_eq!(out, "WHERE a = '$1' AND b = 9");
3226 }
3227
3228 #[test]
3229 fn too_few_parameters_is_an_error_not_a_silent_null() {
3230 let e = substitute_params("WHERE a = $2", &[Some("1".into())]).unwrap_err();
3233 assert!(e.contains("$2"), "{}", e);
3234 }
3235
3236 #[test]
3237 fn a_quote_in_a_parameter_cannot_escape_its_literal() {
3238 let lit = decode_param(Some(b"it's"), OID_TEXT, 0).unwrap().unwrap();
3239 assert_eq!(lit, "'it''s'");
3240 let out = substitute_params("WHERE a = $1", &[Some(lit)]).unwrap();
3242 assert_eq!(out, "WHERE a = 'it''s'");
3243 }
3244
3245 #[test]
3246 fn binary_parameters_decode_in_every_width_psycopg_sends() {
3247 assert_eq!(decode_param(Some(&[0x00, 0x2a]), OID_INT2, 1).unwrap().unwrap(), "42");
3250 assert_eq!(decode_param(Some(&[0, 0, 0, 7]), OID_INT4, 1).unwrap().unwrap(), "7");
3251 assert_eq!(
3252 decode_param(Some(&[0, 0, 0, 0, 0, 0, 0, 9]), OID_INT8, 1).unwrap().unwrap(), "9");
3253 assert_eq!(
3254 decode_param(Some(&0x400c_0000_0000_0000u64.to_be_bytes()), OID_FLOAT8, 1)
3255 .unwrap().unwrap(), "3.5");
3256 assert_eq!(decode_param(Some(&[1]), OID_BOOL, 1).unwrap().unwrap(), "TRUE");
3257 assert_eq!(decode_param(Some(&[0]), OID_BOOL, 1).unwrap().unwrap(), "FALSE");
3258 }
3259
3260 #[test]
3261 fn a_negative_binary_integer_keeps_its_sign() {
3262 assert_eq!(decode_param(Some(&(-5i32).to_be_bytes()), OID_INT4, 1).unwrap().unwrap(), "-5");
3263 assert_eq!(decode_param(Some(&(-5i16).to_be_bytes()), OID_INT2, 1).unwrap().unwrap(), "-5");
3264 }
3265
3266 #[test]
3267 fn a_binary_parameter_of_the_wrong_width_is_refused() {
3268 let e = decode_param(Some(&[0x2a]), OID_INT4, 1).unwrap_err();
3271 assert!(e.contains("4 bytes"), "{}", e);
3272 }
3273
3274 #[test]
3275 fn an_unspecified_text_parameter_is_treated_as_a_string() {
3276 assert_eq!(decode_param(Some(b"hello"), 0, 0).unwrap().unwrap(), "'hello'");
3279 }
3280
3281 #[test]
3282 fn a_null_parameter_decodes_to_none_in_every_format() {
3283 assert_eq!(decode_param(None, OID_TEXT, 0).unwrap(), None);
3284 assert_eq!(decode_param(None, OID_INT8, 1).unwrap(), None);
3285 }
3286
3287 #[test]
3288 fn an_unsupported_binary_type_says_so_by_name() {
3289 let e = decode_param(Some(&[0u8; 8]), 1114, 1).unwrap_err();
3290 assert!(e.contains("1114"), "{}", e);
3291 assert!(e.contains("text"), "the error should point at the way out: {}", e);
3292 }
3293
3294 #[test]
3295 fn a_text_number_that_is_not_a_number_gets_quoted() {
3296 assert_eq!(decode_param(Some(b"oops"), OID_INT8, 0).unwrap().unwrap(), "'oops'");
3299 }
3300
3301 #[test]
3302 fn a_client_declared_type_is_believed_over_inference() {
3303 let oids = infer_param_oids("SELECT a FROM t WHERE b = $1 AND c = $2", &[OID_INT4, 0], None);
3306 assert_eq!(oids, vec![OID_INT4, OID_TEXT]);
3307 }
3308
3309 #[test]
3310 fn parameter_arity_is_taken_from_the_sql_when_the_client_declares_none() {
3311 let oids = infer_param_oids("SELECT a FROM t WHERE b = $1 AND c = $2", &[], None);
3314 assert_eq!(oids.len(), 2);
3315 }
3316
3317 #[test]
3318 fn the_field_behind_each_placeholder_is_identified() {
3319 assert_eq!(
3320 param_fields("SELECT a FROM t WHERE qty > $1 AND status = $2", 2),
3321 vec![Some("qty".to_string()), Some("status".to_string())]);
3322 }
3323
3324 #[test]
3325 fn word_operators_do_not_hide_the_field() {
3326 assert_eq!(param_fields("SELECT a FROM t WHERE name LIKE $1", 1),
3327 vec![Some("name".to_string())]);
3328 assert_eq!(param_fields("SELECT a FROM t WHERE qty BETWEEN $1 AND $2", 2),
3329 vec![Some("qty".to_string()), Some("qty".to_string())]);
3330 assert_eq!(param_fields("SELECT a FROM t WHERE region IN ($1, $2)", 2),
3331 vec![Some("region".to_string()), Some("region".to_string())]);
3332 }
3333
3334 #[test]
3335 fn a_clause_position_types_from_the_grammar_not_from_a_column() {
3336 assert_eq!(
3340 infer_param_oids("SELECT a FROM t AS OF SYSTEM TIME $1 WHERE b = $2", &[], None),
3341 vec![OID_INT8, OID_TEXT]);
3342 assert_eq!(infer_param_oids("SELECT a FROM t AS OF $1", &[], None), vec![OID_INT8]);
3343 assert_eq!(
3346 infer_param_oids("SELECT a FROM t VALID AS OF $1", &[], None), vec![OID_TEXT]);
3347 assert_eq!(
3348 infer_param_oids("SELECT a FROM t LIMIT $1 OFFSET $2", &[], None),
3349 vec![OID_INT8, OID_INT8]);
3350 }
3351
3352 #[test]
3353 fn an_aggregate_column_types_from_what_the_aggregate_means() {
3354 assert_eq!(aggregate_oid("count", None, "t"), Some(OID_INT8));
3358 assert_eq!(aggregate_oid("avg_fee", None, "t"), Some(OID_FLOAT8),
3359 "an average is fractional even over integers");
3360 assert_eq!(aggregate_oid("max__seq", None, "t"), Some(OID_INT8));
3363 assert_eq!(aggregate_oid("total", None, "t"), None, "not an aggregate");
3364 }
3365
3366 #[test]
3367 fn the_parse_probe_uses_a_literal_that_every_clause_accepts() {
3368 let probe = probe_sql("SELECT a FROM t AS OF SYSTEM TIME $1 WHERE b = $2", 2);
3372 assert!(!probe.contains("NULL"), "{}", probe);
3373 assert!(translate(&probe).is_ok(), "the probe must parse: {}", probe);
3374 }
3375
3376 #[test]
3377 fn a_column_with_mixed_types_across_documents_is_advertised_as_text() {
3378 let rows = vec![json!({"x": 3}), json!({"x": "n/a"})];
3382 assert_eq!(oid_for(&rows, "x"), OID_TEXT);
3383 let rows = vec![json!({"x": 3}), json!({"x": 1.5})];
3385 assert_eq!(oid_for(&rows, "x"), OID_FLOAT8);
3386 let rows = vec![json!({"x": Value::Null}), json!({"x": 7})];
3388 assert_eq!(oid_for(&rows, "x"), OID_INT8);
3389 }
3390
3391 #[test]
3392 fn binary_output_encodes_each_advertised_type() {
3393 assert_eq!(cell_binary(Some(&json!(true)), OID_BOOL).unwrap().unwrap(), vec![1]);
3394 assert_eq!(cell_binary(Some(&json!(42)), OID_INT8).unwrap().unwrap(),
3395 42i64.to_be_bytes().to_vec());
3396 assert_eq!(cell_binary(Some(&json!(3.5)), OID_FLOAT8).unwrap().unwrap(),
3397 3.5f64.to_be_bytes().to_vec());
3398 assert_eq!(cell_binary(Some(&json!("hi")), OID_TEXT).unwrap().unwrap(), b"hi".to_vec());
3400 assert_eq!(cell_binary(Some(&Value::Null), OID_INT8).unwrap(), None);
3401 assert_eq!(cell(Some(&json!(true))).unwrap(), "t");
3403 }
3404
3405 #[test]
3406 fn a_value_that_does_not_fit_its_advertised_binary_type_is_refused() {
3407 let e = cell_binary(Some(&json!("nope")), OID_INT8).unwrap_err();
3412 assert!(e.contains("a string"), "{}", e);
3413 assert!(e.contains("more than one type"), "the error should explain WHY: {}", e);
3414 }
3415
3416 #[test]
3417 fn a_row_description_carries_the_requested_format_per_column() {
3418 let cols = [Col::same("a"), Col::same("b")];
3419 let m = row_description_fmt(&cols, &[OID_INT8, OID_TEXT], &[1, 0]);
3420 assert_eq!(m[0], b'T');
3421 assert_eq!(m[m.len() - 1], 0, "the last column was requested as text");
3423 }
3424
3425 #[test]
3426 fn a_qualified_column_resolves_to_its_bare_name() {
3427 assert_eq!(param_fields("SELECT a FROM t WHERE t.qty = $1", 1),
3428 vec![Some("qty".to_string())]);
3429 }
3430
3431 #[test]
3432 fn insert_placeholders_map_positionally_to_the_column_list() {
3433 assert_eq!(
3434 param_fields("INSERT INTO t (_id, qty, status) VALUES ($1, $2, $3)", 3),
3435 vec![Some("_id".to_string()), Some("qty".to_string()), Some("status".to_string())]);
3436 }
3437
3438 #[test]
3439 fn a_set_clause_placeholder_finds_its_column() {
3440 assert_eq!(param_fields("UPDATE t SET status = $1 WHERE _id = $2", 2),
3441 vec![Some("status".to_string()), Some("_id".to_string())]);
3442 }
3443
3444 #[test]
3445 fn the_target_collection_is_found_for_every_statement_kind() {
3446 assert_eq!(stmt_collection("SELECT a FROM inv WHERE b = $1"), "inv");
3447 assert_eq!(stmt_collection("UPDATE inv SET a = $1"), "inv");
3448 assert_eq!(stmt_collection("DELETE FROM inv WHERE a = $1"), "inv");
3449 assert_eq!(stmt_collection("INSERT INTO inv (a) VALUES ($1)"), "inv");
3450 assert_eq!(stmt_collection("SELECT a FROM public.inv"), "inv");
3452 assert_eq!(stmt_collection("INSERT INTO inv(a) VALUES ($1)"), "inv");
3453 }
3454
3455 #[test]
3456 fn engine_metadata_fields_type_without_touching_storage() {
3457 assert_eq!(infer_field_oid(None, "t", "_seq"), OID_INT8);
3458 assert_eq!(infer_field_oid(None, "t", "_id"), OID_TEXT);
3459 }
3460
3461 #[test]
3462 fn the_protocol_acknowledgements_are_single_empty_messages() {
3463 for (m, tag) in [
3465 (parse_complete(), b'1'), (bind_complete(), b'2'),
3466 (close_complete(), b'3'), (no_data(), b'n'), (portal_suspended(), b's'),
3467 ] {
3468 assert_eq!(m.len(), 5, "{:?}", tag as char);
3469 assert_eq!(m[0], tag);
3470 assert_eq!(i32::from_be_bytes([m[1], m[2], m[3], m[4]]), 4);
3471 }
3472 }
3473
3474 #[test]
3475 fn parameter_description_reports_its_arity_and_types() {
3476 let m = parameter_description(&[OID_TEXT, OID_INT8]);
3477 assert_eq!(m[0], b't');
3478 assert_eq!(i16::from_be_bytes([m[5], m[6]]), 2);
3479 assert_eq!(i32::from_be_bytes([m[7], m[8], m[9], m[10]]), OID_TEXT);
3480 assert_eq!(i32::from_be_bytes([m[11], m[12], m[13], m[14]]), OID_INT8);
3481 }
3482
3483 #[test]
3484 fn a_cstring_is_taken_without_its_terminator() {
3485 let body = b"one\0two\0".to_vec();
3486 let mut at = 0usize;
3487 assert_eq!(take_cstr(&body, &mut at), "one");
3488 assert_eq!(take_cstr(&body, &mut at), "two");
3489 assert_eq!(at, body.len());
3490 }
3491
3492 #[test]
3493 fn truncated_integers_are_reported_rather_than_read_past_the_end() {
3494 let body = vec![0u8, 1];
3495 let mut at = 0usize;
3496 assert!(take_i32(&body, &mut at).is_err());
3497 let mut at = 0usize;
3498 assert!(take_i16(&body, &mut at).is_ok());
3499 }
3500
3501 #[test]
3502 fn a_binary_result_format_request_is_refused_rather_than_faked() {
3503 let out = encode_rows(&[], &[Col::same("a")]);
3506 let desc_format = &out[out.len() - 2..];
3507 assert_eq!(i16::from_be_bytes([desc_format[0], desc_format[1]]), 0,
3508 "every column is advertised as text format");
3509 }
3510
3511 #[test]
3512 fn a_float_parameter_does_not_render_as_rust_infinity() {
3513 assert_eq!(fmt_float(f64::INFINITY), "'Infinity'");
3514 assert_eq!(fmt_float(f64::NEG_INFINITY), "'-Infinity'");
3515 assert_eq!(fmt_float(f64::NAN), "'NaN'");
3516 assert_eq!(fmt_float(3.0), "3", "a whole float should not gain a .0 tail");
3517 assert_eq!(fmt_float(3.5), "3.5");
3518 }
3519}