1use std::net::SocketAddr;
47use std::sync::Arc;
48
49use anyhow::{bail, Result};
50use beck_core::read::{self, Answer, Cardinality, Column, Datum, Schema, SqlError, Table};
51use beck_core::Value;
52use tokio::io::{AsyncReadExt, AsyncWriteExt};
53use tokio::net::{TcpListener, TcpStream};
54
55use crate::app::App;
56
57const PROTOCOL_3: i32 = 196_608;
59const SSL_REQUEST: i32 = 80_877_103;
60const GSSENC_REQUEST: i32 = 80_877_104;
61const CANCEL_REQUEST: i32 = 80_877_102;
62
63const MAX_MESSAGE: usize = 1 << 20;
70
71pub async fn serve(app: Arc<App>, addr: SocketAddr) -> Result<()> {
73 serve_on(bind(addr).await?, app).await
74}
75
76pub async fn bind(addr: SocketAddr) -> Result<TcpListener> {
85 if !addr.ip().is_loopback() {
86 bail!(
87 "{addr} is not a loopback address. The read-model port has no authentication and no \
88 transport security, so it is bound to localhost only; forward it (kubectl \
89 port-forward, an SSH tunnel, a sidecar) rather than exposing it — \
90 docs/adr/0020 is the record that would have to change first"
91 );
92 }
93 Ok(TcpListener::bind(addr).await?)
94}
95
96pub async fn serve_on(listener: TcpListener, app: Arc<App>) -> Result<()> {
98 let schema = Arc::new(Schema::of(app.runtime().placed(), app.runtime().plan()));
99 tracing::info!(
100 addr = %listener.local_addr()?,
101 tables = schema.tables.len(),
102 "read models on pgwire (no authentication; loopback only)"
103 );
104 loop {
105 let (socket, peer) = listener.accept().await?;
106 let app = app.clone();
107 let schema = schema.clone();
108 tokio::spawn(async move {
109 if let Err(e) = session(socket, app, schema).await {
110 tracing::debug!(peer = %peer, error = %e, "pgwire session ended");
111 }
112 });
113 }
114}
115
116#[derive(Clone, Default)]
122struct Statement {
123 sql: String,
124 columns: Vec<Column>,
125}
126
127#[derive(Clone, Default)]
129struct Portal {
130 statement: Statement,
131 formats: Vec<i16>,
133}
134
135async fn session(mut socket: TcpStream, app: Arc<App>, schema: Arc<Schema>) -> Result<()> {
136 socket.set_nodelay(true)?;
137 if !startup(&mut socket).await? {
138 return Ok(());
139 }
140
141 let reader = app.shared_dataflow().reader();
144
145 let mut out = Vec::new();
146 authentication_ok(&mut out);
147 parameter_status(&mut out, "server_version", "15.0 (beck)");
148 parameter_status(&mut out, "server_encoding", "UTF8");
149 parameter_status(&mut out, "client_encoding", "UTF8");
150 parameter_status(&mut out, "DateStyle", "ISO, MDY");
151 parameter_status(&mut out, "TimeZone", "UTC");
152 parameter_status(&mut out, "integer_datetimes", "on");
153 parameter_status(&mut out, "standard_conforming_strings", "on");
154 message(&mut out, b'K', |b| {
157 b.extend_from_slice(&0i32.to_be_bytes());
158 b.extend_from_slice(&0i32.to_be_bytes());
159 });
160 ready(&mut out);
161 socket.write_all(&out).await?;
162
163 let mut statements: std::collections::HashMap<String, Statement> = Default::default();
164 let mut portals: std::collections::HashMap<String, Portal> = Default::default();
165 let mut failed = false;
167
168 loop {
169 let mut tag = [0u8; 1];
170 if socket.read_exact(&mut tag).await.is_err() {
171 return Ok(());
172 }
173 let body = read_body(&mut socket).await?;
174 let mut out = Vec::new();
175 match tag[0] {
176 b'X' => return Ok(()),
177 b'S' => {
178 failed = false;
179 ready(&mut out);
180 }
181 _ if failed => {}
182 b'Q' => {
183 let sql = cstr(&body, &mut 0)?;
184 simple_query(&app, &schema, &reader, &sql, &mut out).await;
185 ready(&mut out);
186 }
187 b'P' => {
188 let mut i = 0;
189 let name = cstr(&body, &mut i)?;
190 let sql = cstr(&body, &mut i)?;
191 match schema.describe(&sql) {
192 Ok(columns) => {
193 statements.insert(name, Statement { sql, columns });
194 message(&mut out, b'1', |_| {});
195 }
196 Err(e) => {
197 error_response(&mut out, &e);
198 failed = true;
199 }
200 }
201 }
202 b'B' => {
203 let mut i = 0;
204 let portal = cstr(&body, &mut i)?;
205 let statement = cstr(&body, &mut i)?;
206 let formats = i16s(&body, &mut i)?;
209 let _ = formats;
210 let params = i16_count(&body, &mut i)?;
211 for _ in 0..params {
212 let len = i32_at(&body, &mut i)?;
213 if len > 0 {
214 i += len as usize;
215 }
216 }
217 let results = i16s(&body, &mut i)?;
218 match statements.get(&statement) {
219 Some(s) => {
220 portals.insert(
221 portal,
222 Portal {
223 statement: s.clone(),
224 formats: results,
225 },
226 );
227 message(&mut out, b'2', |_| {});
228 }
229 None => {
230 error_response(
231 &mut out,
232 &SqlError {
233 message: format!("there is no prepared statement \"{statement}\""),
234 code: "26000",
235 },
236 );
237 failed = true;
238 }
239 }
240 }
241 b'D' => {
242 let mut i = 0;
243 let what = *body.first().unwrap_or(&b'S');
244 i += 1;
245 let name = cstr(&body, &mut i)?;
246 let (columns, formats) = match what {
247 b'P' => match portals.get(&name) {
248 Some(p) => (p.statement.columns.clone(), p.formats.clone()),
249 None => (Vec::new(), Vec::new()),
250 },
251 _ => {
252 message(&mut out, b't', |b| {
256 b.extend_from_slice(&0i16.to_be_bytes());
257 });
258 match statements.get(&name) {
259 Some(s) => (s.columns.clone(), Vec::new()),
260 None => (Vec::new(), Vec::new()),
261 }
262 }
263 };
264 if columns.is_empty() {
265 message(&mut out, b'n', |_| {});
266 } else {
267 row_description(&mut out, &columns, &formats);
268 }
269 }
270 b'E' => {
271 let mut i = 0;
272 let name = cstr(&body, &mut i)?;
273 let max = i32_at(&body, &mut i)?.max(0) as usize;
274 match portals.get(&name).cloned() {
275 Some(p) => match run(&app, &schema, &reader, &p.statement.sql).await {
276 Ok(answer) => {
277 let total = answer.rows.len();
278 let take = if max == 0 { total } else { max.min(total) };
279 for row in answer.rows.iter().take(take) {
280 data_row(&mut out, row, &p.formats);
281 }
282 if take < total {
283 message(&mut out, b's', |_| {});
284 } else {
285 command_complete(&mut out, &answer.tag);
286 }
287 }
288 Err(e) => {
289 error_response(&mut out, &e);
290 failed = true;
291 }
292 },
293 None => {
294 error_response(
295 &mut out,
296 &SqlError {
297 message: format!("there is no portal \"{name}\""),
298 code: "34000",
299 },
300 );
301 failed = true;
302 }
303 }
304 }
305 b'C' => {
306 let mut i = 0;
307 let what = *body.first().unwrap_or(&b'S');
308 i += 1;
309 let name = cstr(&body, &mut i)?;
310 if what == b'P' {
311 portals.remove(&name);
312 } else {
313 statements.remove(&name);
314 }
315 message(&mut out, b'3', |_| {});
316 }
317 b'H' => {}
318 other => {
319 error_response(
320 &mut out,
321 &SqlError {
322 message: format!("message type '{}' is not supported", other as char),
323 code: "08P01",
324 },
325 );
326 failed = true;
327 }
328 }
329 if !out.is_empty() {
330 socket.write_all(&out).await?;
331 }
332 }
333}
334
335async fn startup(socket: &mut TcpStream) -> Result<bool> {
337 loop {
338 let mut len = [0u8; 4];
339 if socket.read_exact(&mut len).await.is_err() {
340 return Ok(false);
341 }
342 let len = i32::from_be_bytes(len);
343 if !(8..=MAX_MESSAGE as i32).contains(&len) {
344 bail!("a startup packet of {len} bytes");
345 }
346 let mut body = vec![0u8; len as usize - 4];
347 socket.read_exact(&mut body).await?;
348 let version = i32::from_be_bytes([body[0], body[1], body[2], body[3]]);
349 match version {
350 SSL_REQUEST | GSSENC_REQUEST => {
354 socket.write_all(b"N").await?;
355 }
356 CANCEL_REQUEST => return Ok(false),
357 PROTOCOL_3 => return Ok(true),
358 other => {
359 let mut out = Vec::new();
360 error_response(
361 &mut out,
362 &SqlError {
363 message: format!(
364 "this read model speaks protocol 3.0; the client asked for {}.{}",
365 other >> 16,
366 other & 0xffff
367 ),
368 code: "0A000",
369 },
370 );
371 socket.write_all(&out).await?;
372 return Ok(false);
373 }
374 }
375 }
376}
377
378async fn simple_query(
379 app: &Arc<App>,
380 schema: &Schema,
381 reader: &beck_core::engine::Reader,
382 sql: &str,
383 out: &mut Vec<u8>,
384) {
385 if sql.trim().is_empty() {
386 message(out, b'I', |_| {});
387 return;
388 }
389 match run(app, schema, reader, sql).await {
390 Ok(answer) => {
391 if !answer.columns.is_empty() {
392 row_description(out, &answer.columns, &[]);
393 for row in &answer.rows {
394 data_row(out, row, &[]);
395 }
396 }
397 command_complete(out, &answer.tag);
398 }
399 Err(e) => error_response(out, &e),
400 }
401}
402
403async fn run(
415 app: &Arc<App>,
416 schema: &Schema,
417 reader: &beck_core::engine::Reader,
418 sql: &str,
419) -> Result<Answer, SqlError> {
420 app.read_snapshot(|state, version| {
421 let rows = Snapshot {
422 app,
423 reader,
424 state,
425 version,
426 };
427 schema.run(sql, &rows)
428 })
429 .await
430}
431
432struct Snapshot<'a> {
434 app: &'a Arc<App>,
435 reader: &'a beck_core::engine::Reader,
436 state: &'a Value,
437 version: u64,
438}
439
440impl read::Rows for Snapshot<'_> {
441 fn scan(&self, table: &Table) -> Result<Vec<Value>, SqlError> {
442 let values = match &table.source {
443 read::Source::Catalogue | read::Source::Pg(_) => return Ok(Vec::new()),
447 read::Source::State(path) => {
448 let at = read::at_path(self.state, path).ok_or_else(|| SqlError {
449 message: format!(
450 "\"{}\" is not in this state — the accumulator has no such field",
451 table.name
452 ),
453 code: "42P01",
454 })?;
455 match table.cardinality {
456 Cardinality::Many => read::elements(&at),
457 Cardinality::One => vec![at],
458 }
459 }
460 read::Source::View(op) => {
461 let vals = self
462 .reader
463 .read(self.state, self.version, *op)
464 .map_err(|e| SqlError {
465 message: format!("the view this table reads could not be maintained: {e}"),
466 code: "58000",
467 })?;
468 match table.cardinality {
469 Cardinality::One => vals.into_iter().take(1).collect(),
470 Cardinality::Many => match vals.as_slice() {
473 [one @ (Value::List(_) | Value::Map(_))] => read::elements(one),
474 _ => vals,
475 },
476 }
477 }
478 };
479 Ok(values)
480 }
481
482 fn count(&self, table: &Table) -> Result<Option<u64>, SqlError> {
493 Ok(match &table.source {
494 read::Source::Catalogue | read::Source::Pg(_) => None,
495 read::Source::State(path) => match read::at_path(self.state, path) {
496 Some(Value::Map(m)) => Some(m.len() as u64),
497 Some(Value::List(xs)) => Some(xs.len() as u64),
498 _ => None,
500 },
501 read::Source::View(op) => {
502 self.reader
503 .len(self.state, self.version, *op)
504 .map_err(|e| SqlError {
505 message: format!("the view this table reads could not be maintained: {e}"),
506 code: "58000",
507 })?
508 }
509 })
510 }
511
512 fn backend(&self) -> Option<&dyn beck_core::backend::Backend> {
516 Some(self.app.runtime().executor())
517 }
518}
519
520fn message(out: &mut Vec<u8>, tag: u8, body: impl FnOnce(&mut Vec<u8>)) {
526 out.push(tag);
527 let at = out.len();
528 out.extend_from_slice(&0i32.to_be_bytes());
529 body(out);
530 let len = (out.len() - at) as i32;
531 out[at..at + 4].copy_from_slice(&len.to_be_bytes());
532}
533
534fn authentication_ok(out: &mut Vec<u8>) {
535 message(out, b'R', |b| {
536 b.extend_from_slice(&0i32.to_be_bytes());
537 });
538}
539
540fn parameter_status(out: &mut Vec<u8>, key: &str, value: &str) {
541 message(out, b'S', |b| {
542 put_cstr(b, key);
543 put_cstr(b, value);
544 });
545}
546
547fn ready(out: &mut Vec<u8>) {
548 message(out, b'Z', |b| b.push(b'I'));
551}
552
553fn command_complete(out: &mut Vec<u8>, tag: &str) {
554 message(out, b'C', |b| put_cstr(b, tag));
555}
556
557fn row_description(out: &mut Vec<u8>, columns: &[Column], formats: &[i16]) {
558 message(out, b'T', |b| {
559 b.extend_from_slice(&(columns.len() as i16).to_be_bytes());
560 for (i, c) in columns.iter().enumerate() {
561 put_cstr(b, &c.name);
562 b.extend_from_slice(&0i32.to_be_bytes());
565 b.extend_from_slice(&0i16.to_be_bytes());
566 b.extend_from_slice(&c.ty.oid().to_be_bytes());
567 b.extend_from_slice(&c.ty.width().to_be_bytes());
568 b.extend_from_slice(&(-1i32).to_be_bytes());
569 b.extend_from_slice(&format_of(formats, i).to_be_bytes());
570 }
571 });
572}
573
574fn format_of(formats: &[i16], i: usize) -> i16 {
575 match formats.len() {
576 0 => 0,
577 1 => formats[0],
578 _ => formats.get(i).copied().unwrap_or(0),
579 }
580}
581
582fn data_row(out: &mut Vec<u8>, row: &[Option<Datum>], formats: &[i16]) {
583 message(out, b'D', |b| {
584 b.extend_from_slice(&(row.len() as i16).to_be_bytes());
585 for (i, cell) in row.iter().enumerate() {
586 match cell {
587 None => b.extend_from_slice(&(-1i32).to_be_bytes()),
588 Some(d) => {
589 let bytes = if format_of(formats, i) == 1 {
590 binary(d)
591 } else {
592 d.text().into_bytes()
593 };
594 b.extend_from_slice(&(bytes.len() as i32).to_be_bytes());
595 b.extend_from_slice(&bytes);
596 }
597 }
598 }
599 });
600}
601
602fn binary(d: &Datum) -> Vec<u8> {
604 match d {
605 Datum::Boolean(v) => vec![u8::from(*v)],
606 Datum::Bigint(v) => v.to_be_bytes().to_vec(),
607 Datum::Double(v) => v.to_bits().to_be_bytes().to_vec(),
608 Datum::Text(s) => s.as_bytes().to_vec(),
609 }
610}
611
612fn error_response(out: &mut Vec<u8>, e: &SqlError) {
613 message(out, b'E', |b| {
614 b.push(b'S');
615 put_cstr(b, "ERROR");
616 b.push(b'V');
617 put_cstr(b, "ERROR");
618 b.push(b'C');
619 put_cstr(b, e.code);
620 b.push(b'M');
621 put_cstr(b, &e.message);
622 b.push(0);
623 });
624}
625
626fn put_cstr(out: &mut Vec<u8>, s: &str) {
627 for byte in s.bytes() {
630 out.push(if byte == 0 { b' ' } else { byte });
631 }
632 out.push(0);
633}
634
635async fn read_body(socket: &mut TcpStream) -> Result<Vec<u8>> {
640 let mut len = [0u8; 4];
641 socket.read_exact(&mut len).await?;
642 let len = i32::from_be_bytes(len);
643 if !(4..=MAX_MESSAGE as i32).contains(&len) {
644 bail!("a message of {len} bytes");
645 }
646 let mut body = vec![0u8; len as usize - 4];
647 socket.read_exact(&mut body).await?;
648 Ok(body)
649}
650
651fn cstr(body: &[u8], i: &mut usize) -> Result<String> {
652 let start = *i;
653 while *i < body.len() && body[*i] != 0 {
654 *i += 1;
655 }
656 if *i >= body.len() {
657 bail!("a string in a message is not terminated");
658 }
659 let s = String::from_utf8_lossy(&body[start..*i]).into_owned();
660 *i += 1;
661 Ok(s)
662}
663
664fn i32_at(body: &[u8], i: &mut usize) -> Result<i32> {
665 if *i + 4 > body.len() {
666 bail!("a message ends inside a number");
667 }
668 let v = i32::from_be_bytes([body[*i], body[*i + 1], body[*i + 2], body[*i + 3]]);
669 *i += 4;
670 Ok(v)
671}
672
673fn i16_count(body: &[u8], i: &mut usize) -> Result<usize> {
674 if *i + 2 > body.len() {
675 bail!("a message ends inside a count");
676 }
677 let v = i16::from_be_bytes([body[*i], body[*i + 1]]);
678 *i += 2;
679 Ok(v.max(0) as usize)
680}
681
682fn i16s(body: &[u8], i: &mut usize) -> Result<Vec<i16>> {
683 let n = i16_count(body, i)?;
684 let mut out = Vec::with_capacity(n);
685 for _ in 0..n {
686 if *i + 2 > body.len() {
687 bail!("a message ends inside a format code");
688 }
689 out.push(i16::from_be_bytes([body[*i], body[*i + 1]]));
690 *i += 2;
691 }
692 Ok(out)
693}
694
695#[cfg(test)]
696mod tests {
697 use super::*;
698
699 #[test]
700 fn a_message_carries_its_own_length() {
701 let mut out = Vec::new();
702 command_complete(&mut out, "SELECT 2");
703 assert_eq!(out[0], b'C');
704 let len = i32::from_be_bytes([out[1], out[2], out[3], out[4]]) as usize;
705 assert_eq!(len, out.len() - 1);
706 assert_eq!(&out[5..out.len() - 1], b"SELECT 2");
707 assert_eq!(out[out.len() - 1], 0);
708 }
709
710 #[test]
711 fn a_nul_inside_a_string_cannot_end_it() {
712 let mut out = Vec::new();
713 put_cstr(&mut out, "a\0b");
714 assert_eq!(out, b"a b\0");
715 }
716
717 #[test]
718 fn binary_is_big_endian_and_text_is_not() {
719 assert_eq!(binary(&Datum::Bigint(1)), vec![0, 0, 0, 0, 0, 0, 0, 1]);
720 assert_eq!(Datum::Bigint(1).text(), "1");
721 assert_eq!(binary(&Datum::Boolean(true)), vec![1]);
722 assert_eq!(Datum::Boolean(true).text(), "t");
723 }
724
725 #[test]
726 fn one_format_code_covers_every_column() {
727 assert_eq!(format_of(&[], 3), 0);
728 assert_eq!(format_of(&[1], 3), 1);
729 assert_eq!(format_of(&[0, 1], 1), 1);
730 }
731}