1mod protocol;
23mod types;
24
25use std::collections::HashMap;
26use std::future::Future;
27use std::sync::Arc;
28
29use corium_db::Db;
30use corium_sql::{SqlColumn, SqlError, SqlSession, SqlType};
31use thiserror::Error;
32use tokio::io::{AsyncRead, AsyncWrite};
33use tokio::net::TcpListener;
34
35use protocol::{BackendWriter, ErrorFields, FieldDescription, Frontend, FrontendReader};
36
37#[derive(Debug, Error)]
39pub enum CatalogError {
40 #[error("database {0:?} is not available")]
42 NotFound(String),
43 #[error("{0}")]
45 Unavailable(String),
46}
47
48#[async_trait::async_trait]
55pub trait DbCatalog: Send + Sync + 'static {
56 async fn list(&self) -> Result<Vec<String>, CatalogError>;
61
62 async fn db(&self, name: &str) -> Result<Db, CatalogError>;
68}
69
70#[derive(Clone, Debug)]
72pub struct PgWireConfig {
73 pub password: Option<String>,
76 pub server_version: String,
78}
79
80impl Default for PgWireConfig {
81 fn default() -> Self {
82 Self {
83 password: None,
84 server_version: concat!("16.0 (corium ", env!("CARGO_PKG_VERSION"), ")").to_owned(),
85 }
86 }
87}
88
89pub async fn serve<C, F>(
97 listener: TcpListener,
98 catalog: Arc<C>,
99 config: PgWireConfig,
100 shutdown: F,
101) -> std::io::Result<()>
102where
103 C: DbCatalog,
104 F: Future<Output = ()>,
105{
106 let config = Arc::new(config);
107 tokio::pin!(shutdown);
108 loop {
109 tokio::select! {
110 () = &mut shutdown => return Ok(()),
111 accepted = listener.accept() => {
112 let (stream, peer) = accepted?;
113 let catalog = Arc::clone(&catalog);
114 let config = Arc::clone(&config);
115 tokio::spawn(async move {
116 let (read, write) = stream.into_split();
117 let mut session = ConnectionSession::new(
118 FrontendReader::new(read),
119 BackendWriter::new(write),
120 catalog,
121 config,
122 );
123 if let Err(error) = session.run().await {
124 tracing::debug!(%peer, %error, "pgwire connection closed");
125 }
126 });
127 }
128 }
129 }
130}
131
132struct Portal {
134 sql: String,
135 database: Option<String>,
136}
137
138enum Statement {
140 Control(&'static str),
142 Use(String),
144 ShowDatabases,
146 Query,
148}
149
150enum Dispatch {
152 NoDatabase,
154 Catalog(CatalogError),
156 Sql(SqlError),
158}
159
160struct ConnectionSession<R, W, C> {
162 reader: FrontendReader<R>,
163 writer: BackendWriter<W>,
164 catalog: Arc<C>,
165 config: Arc<PgWireConfig>,
166 current_db: Option<String>,
168 statements: HashMap<String, String>,
169 portals: HashMap<String, Portal>,
170 failed: bool,
173}
174
175impl<R, W, C> ConnectionSession<R, W, C>
176where
177 R: AsyncRead + Unpin,
178 W: AsyncWrite + Unpin,
179 C: DbCatalog,
180{
181 fn new(
182 reader: FrontendReader<R>,
183 writer: BackendWriter<W>,
184 catalog: Arc<C>,
185 config: Arc<PgWireConfig>,
186 ) -> Self {
187 Self {
188 reader,
189 writer,
190 catalog,
191 config,
192 current_db: None,
193 statements: HashMap::new(),
194 portals: HashMap::new(),
195 failed: false,
196 }
197 }
198
199 async fn run(&mut self) -> std::io::Result<()> {
200 let startup = self.reader.read_startup(&mut self.writer).await?;
201 if !self.authenticate().await? {
202 return Ok(());
203 }
204 self.current_db = startup
207 .get("database")
208 .filter(|value| !value.is_empty())
209 .map(str::to_owned);
210 self.send_ready_banner(startup.get("application_name").unwrap_or(""))
211 .await?;
212
213 while let Some(message) = self.reader.read_message().await? {
214 match message {
215 Frontend::Query(sql) => {
216 if !self.failed {
219 self.simple_query(&sql).await?;
220 self.writer.flush().await?;
221 }
222 }
223 Frontend::Parse {
224 name,
225 query,
226 parameter_count,
227 } => self.handle_parse(name, query, parameter_count),
228 Frontend::Bind {
229 portal,
230 statement,
231 parameter_count,
232 result_formats,
233 } => self.handle_bind(&portal, &statement, parameter_count, &result_formats),
234 Frontend::Describe { kind, name } => self.handle_describe(kind, &name).await?,
235 Frontend::Execute { portal } => self.handle_execute(&portal).await?,
236 Frontend::Close { kind, name } => {
237 if !self.failed {
238 if kind == b'S' {
239 self.statements.remove(&name);
240 } else {
241 self.portals.remove(&name);
242 }
243 self.writer.close_complete();
244 }
245 }
246 Frontend::Sync => {
247 self.failed = false;
248 self.writer.ready_for_query(b'I');
249 self.writer.flush().await?;
250 }
251 Frontend::Flush => self.writer.flush().await?,
252 Frontend::Password(_) => {}
253 Frontend::Terminate => break,
254 }
255 }
256 Ok(())
257 }
258
259 async fn authenticate(&mut self) -> std::io::Result<bool> {
262 let Some(expected) = self.config.password.clone() else {
263 self.writer.authentication_ok();
264 return Ok(true);
265 };
266 self.writer.authentication_cleartext_password();
267 self.writer.flush().await?;
268 match self.reader.read_message().await? {
269 Some(Frontend::Password(supplied)) if supplied == expected => {
270 self.writer.authentication_ok();
271 Ok(true)
272 }
273 _ => {
274 self.writer.error_response(&ErrorFields {
275 code: "28P01",
276 message: "password authentication failed",
277 });
278 self.writer.flush().await?;
279 Ok(false)
280 }
281 }
282 }
283
284 async fn send_ready_banner(&mut self, application_name: &str) -> std::io::Result<()> {
287 self.writer
288 .parameter_status("server_version", &self.config.server_version);
289 self.writer.parameter_status("server_encoding", "UTF8");
290 self.writer.parameter_status("client_encoding", "UTF8");
291 self.writer.parameter_status("DateStyle", "ISO, MDY");
292 self.writer.parameter_status("TimeZone", "UTC");
293 self.writer.parameter_status("integer_datetimes", "on");
294 self.writer
295 .parameter_status("standard_conforming_strings", "on");
296 self.writer
297 .parameter_status("application_name", application_name);
298 self.writer.backend_key_data(0, 0);
299 self.writer.ready_for_query(b'I');
300 self.writer.flush().await
301 }
302
303 async fn simple_query(&mut self, sql: &str) -> std::io::Result<()> {
306 let statements = split_statements(sql);
307 if statements.is_empty() {
308 self.writer.empty_query_response();
309 self.writer.ready_for_query(b'I');
310 return Ok(());
311 }
312 for statement in statements {
313 if !self.run_simple_statement(&statement).await? {
314 break;
315 }
316 }
317 self.writer.ready_for_query(b'I');
318 Ok(())
319 }
320
321 async fn run_simple_statement(&mut self, sql: &str) -> std::io::Result<bool> {
324 match classify(sql) {
325 Statement::Control(tag) => {
326 self.writer.command_complete(tag);
327 Ok(true)
328 }
329 Statement::Use(name) => match self.use_database(&name).await {
330 Ok(()) => {
331 self.writer.command_complete("USE");
332 Ok(true)
333 }
334 Err(error) => {
335 self.report_dispatch(&error);
336 Ok(false)
337 }
338 },
339 Statement::ShowDatabases => match self.show_databases().await {
340 Ok(()) => Ok(true),
341 Err(error) => {
342 self.report_dispatch(&error);
343 Ok(false)
344 }
345 },
346 Statement::Query => {
347 let db = match self.snapshot(None).await {
348 Ok(db) => db,
349 Err(error) => {
350 self.report_dispatch(&error);
351 return Ok(false);
352 }
353 };
354 match self.run_statement(&db, sql, true).await {
355 Ok(rows) => {
356 self.writer.command_complete(&command_tag(sql, rows));
357 Ok(true)
358 }
359 Err(error) => {
360 self.report_dispatch(&Dispatch::Sql(error));
361 Ok(false)
362 }
363 }
364 }
365 }
366 }
367
368 fn handle_parse(&mut self, name: String, query: String, parameter_count: usize) {
369 if self.failed {
370 return;
371 }
372 if parameter_count > 0 {
373 self.fail_extended("0A000", "bound parameters are not supported");
374 return;
375 }
376 self.statements.insert(name, query);
377 self.writer.parse_complete();
378 }
379
380 fn handle_bind(
381 &mut self,
382 portal: &str,
383 statement: &str,
384 parameter_count: usize,
385 result_formats: &[i16],
386 ) {
387 if self.failed {
388 return;
389 }
390 if parameter_count > 0 {
391 self.fail_extended("0A000", "bound parameters are not supported");
392 return;
393 }
394 if result_formats.contains(&1) {
395 self.fail_extended("0A000", "binary result format is not supported");
396 return;
397 }
398 let Some(sql) = self.statements.get(statement) else {
399 self.fail_extended("26000", "prepared statement does not exist");
400 return;
401 };
402 self.portals.insert(
403 portal.to_owned(),
404 Portal {
405 sql: sql.clone(),
406 database: self.current_db.clone(),
407 },
408 );
409 self.writer.bind_complete();
410 }
411
412 async fn handle_describe(&mut self, kind: u8, name: &str) -> std::io::Result<()> {
413 if self.failed {
414 return Ok(());
415 }
416 let (sql, database) = if kind == b'S' {
418 self.writer.parameter_description_empty();
419 let Some(sql) = self.statements.get(name) else {
420 self.fail_extended("26000", "prepared statement does not exist");
421 return Ok(());
422 };
423 (sql.clone(), self.current_db.clone())
424 } else {
425 let Some(portal) = self.portals.get(name) else {
426 self.fail_extended("34000", "portal does not exist");
427 return Ok(());
428 };
429 (portal.sql.clone(), portal.database.clone())
430 };
431 match classify(&sql) {
432 Statement::ShowDatabases => self.writer.row_description(&[database_field()]),
433 Statement::Query if !sql.trim().is_empty() => {
434 let db = match self.snapshot(database.as_deref()).await {
435 Ok(db) => db,
436 Err(error) => {
437 self.fail_dispatch(&error);
438 return Ok(());
439 }
440 };
441 match self.describe_columns(&db, &sql).await {
442 Ok(fields) => self.writer.row_description(&fields),
443 Err(error) => self.fail_dispatch(&Dispatch::Sql(error)),
444 }
445 }
446 _ => self.writer.no_data(),
447 }
448 Ok(())
449 }
450
451 async fn handle_execute(&mut self, portal: &str) -> std::io::Result<()> {
452 if self.failed {
453 return Ok(());
454 }
455 let Some((sql, database)) = self
456 .portals
457 .get(portal)
458 .map(|portal| (portal.sql.clone(), portal.database.clone()))
459 else {
460 self.fail_extended("34000", "portal does not exist");
461 return Ok(());
462 };
463 if sql.trim().is_empty() {
464 self.writer.empty_query_response();
465 return Ok(());
466 }
467 match classify(&sql) {
468 Statement::Control(tag) => self.writer.command_complete(tag),
469 Statement::Use(name) => match self.use_database(&name).await {
470 Ok(()) => self.writer.command_complete("USE"),
471 Err(error) => self.fail_dispatch(&error),
472 },
473 Statement::ShowDatabases => {
474 if let Err(error) = self.show_databases().await {
475 self.fail_dispatch(&error);
476 }
477 }
478 Statement::Query => {
479 let db = match self.snapshot(database.as_deref()).await {
480 Ok(db) => db,
481 Err(error) => {
482 self.fail_dispatch(&error);
483 return Ok(());
484 }
485 };
486 match self.run_statement(&db, &sql, false).await {
487 Ok(rows) => self.writer.command_complete(&command_tag(&sql, rows)),
488 Err(error) => self.fail_dispatch(&Dispatch::Sql(error)),
489 }
490 }
491 }
492 Ok(())
493 }
494
495 async fn use_database(&mut self, name: &str) -> Result<(), Dispatch> {
498 self.snapshot(Some(name)).await?;
499 self.current_db = Some(name.to_owned());
500 Ok(())
501 }
502
503 async fn show_databases(&mut self) -> Result<(), Dispatch> {
505 let names = self.catalog.list().await.map_err(Dispatch::Catalog)?;
506 self.writer.row_description(&[database_field()]);
507 for name in &names {
508 self.writer.data_row(&[Some(name.clone().into_bytes())]);
509 }
510 self.writer.command_complete("SHOW");
511 Ok(())
512 }
513
514 async fn snapshot(&self, database: Option<&str>) -> Result<Db, Dispatch> {
517 let name = database
518 .map(str::to_owned)
519 .or_else(|| self.current_db.clone())
520 .ok_or(Dispatch::NoDatabase)?;
521 self.catalog.db(&name).await.map_err(Dispatch::Catalog)
522 }
523
524 async fn describe_columns(
526 &self,
527 db: &Db,
528 sql: &str,
529 ) -> Result<Vec<FieldDescription>, SqlError> {
530 let session = SqlSession::new(db)?;
531 let query = session.query(sql).await?;
532 Ok(query.columns().iter().map(field_of).collect())
533 }
534
535 async fn run_statement(
538 &mut self,
539 db: &Db,
540 sql: &str,
541 with_row_description: bool,
542 ) -> Result<usize, SqlError> {
543 let session = SqlSession::new(db)?;
544 let mut query = session.query(sql).await?;
545 if with_row_description {
546 let fields = query.columns().iter().map(field_of).collect::<Vec<_>>();
547 self.writer.row_description(&fields);
548 }
549 let mut count = 0usize;
550 while let Some(row) = query.next_row().await? {
551 let values = row.iter().map(types::encode_value).collect::<Vec<_>>();
552 self.writer.data_row(&values);
553 count += 1;
554 if count.is_multiple_of(1024) {
556 self.writer
557 .flush()
558 .await
559 .map_err(|error| SqlError::Schema(error.to_string()))?;
560 }
561 }
562 Ok(count)
563 }
564
565 fn report_dispatch(&mut self, error: &Dispatch) {
567 let (code, message) = dispatch_error_fields(error);
568 self.writer.error_response(&ErrorFields {
569 code,
570 message: &message,
571 });
572 }
573
574 fn fail_dispatch(&mut self, error: &Dispatch) {
576 self.report_dispatch(error);
577 self.failed = true;
578 }
579
580 fn fail_extended(&mut self, code: &str, message: &str) {
583 self.writer.error_response(&ErrorFields { code, message });
584 self.failed = true;
585 }
586}
587
588fn database_field() -> FieldDescription {
590 let type_oid = types::type_oid(&SqlType::Text);
591 FieldDescription {
592 name: "database".to_owned(),
593 type_oid,
594 type_len: types::type_len(type_oid),
595 }
596}
597
598fn field_of(column: &SqlColumn) -> FieldDescription {
600 let type_oid = types::type_oid(&column.data_type);
601 FieldDescription {
602 name: column.name.clone(),
603 type_oid,
604 type_len: types::type_len(type_oid),
605 }
606}
607
608fn dispatch_error_fields(error: &Dispatch) -> (&'static str, String) {
610 match error {
611 Dispatch::NoDatabase => (
612 "3D000",
613 "no database selected; run \"USE <database>\" first".to_owned(),
614 ),
615 Dispatch::Catalog(error @ CatalogError::NotFound(_)) => ("3D000", error.to_string()),
616 Dispatch::Catalog(error @ CatalogError::Unavailable(_)) => ("08006", error.to_string()),
617 Dispatch::Sql(error) => (sqlstate_for(error), error.to_string()),
618 }
619}
620
621fn sqlstate_for(error: &SqlError) -> &'static str {
623 match error {
624 SqlError::Schema(_) => "42P01",
626 SqlError::DataFusion(_) | SqlError::Arrow(_) => "42601",
628 }
629}
630
631fn command_tag(sql: &str, rows: usize) -> String {
633 if first_keyword(sql).eq_ignore_ascii_case("explain") {
634 "EXPLAIN".to_owned()
635 } else {
636 format!("SELECT {rows}")
637 }
638}
639
640fn classify(sql: &str) -> Statement {
643 let mut words = sql.split_whitespace();
644 let first = words.next().unwrap_or("").to_ascii_uppercase();
645 match first.as_str() {
646 "USE" => parse_use_target(sql).map_or(Statement::Query, Statement::Use),
647 "SHOW"
648 if words
649 .next()
650 .is_some_and(|word| word.eq_ignore_ascii_case("databases")) =>
651 {
652 Statement::ShowDatabases
653 }
654 "BEGIN" | "START" => Statement::Control("BEGIN"),
655 "COMMIT" | "END" => Statement::Control("COMMIT"),
656 "ROLLBACK" | "ABORT" => Statement::Control("ROLLBACK"),
657 "SET" => Statement::Control("SET"),
658 "RESET" => Statement::Control("RESET"),
659 "DISCARD" => Statement::Control("DISCARD ALL"),
660 _ => Statement::Query,
661 }
662}
663
664fn parse_use_target(sql: &str) -> Option<String> {
666 let trimmed = sql.trim();
667 let rest = trimmed.get(3..)?.trim().trim_end_matches(';').trim();
670 if rest.is_empty() {
671 return None;
672 }
673 Some(unquote(rest))
674}
675
676fn unquote(value: &str) -> String {
679 if let Some(inner) = value
680 .strip_prefix('"')
681 .and_then(|rest| rest.strip_suffix('"'))
682 {
683 inner.replace("\"\"", "\"")
684 } else if let Some(inner) = value
685 .strip_prefix('\'')
686 .and_then(|rest| rest.strip_suffix('\''))
687 {
688 inner.replace("''", "'")
689 } else {
690 value.split_whitespace().next().unwrap_or("").to_owned()
691 }
692}
693
694fn first_keyword(sql: &str) -> &str {
696 sql.split_whitespace().next().unwrap_or("")
697}
698
699fn split_statements(input: &str) -> Vec<String> {
703 #[derive(Clone, Copy, Eq, PartialEq)]
704 enum State {
705 Normal,
706 SingleQuote,
707 DoubleQuote,
708 LineComment,
709 BlockComment,
710 }
711 let bytes = input.as_bytes();
712 let mut state = State::Normal;
713 let mut statements = Vec::new();
714 let mut start = 0;
715 let mut index = 0;
716 while index < bytes.len() {
717 let byte = bytes[index];
718 let next = bytes.get(index + 1).copied();
719 match state {
720 State::Normal => match (byte, next) {
721 (b'\'', _) => state = State::SingleQuote,
722 (b'"', _) => state = State::DoubleQuote,
723 (b'-', Some(b'-')) => {
724 state = State::LineComment;
725 index += 1;
726 }
727 (b'/', Some(b'*')) => {
728 state = State::BlockComment;
729 index += 1;
730 }
731 (b';', _) => {
732 let statement = input[start..index].trim();
733 if has_sql_content(statement) {
734 statements.push(statement.to_owned());
735 }
736 start = index + 1;
737 }
738 _ => {}
739 },
740 State::SingleQuote => {
741 if byte == b'\'' {
742 if next == Some(b'\'') {
743 index += 1;
744 } else {
745 state = State::Normal;
746 }
747 }
748 }
749 State::DoubleQuote => {
750 if byte == b'"' {
751 if next == Some(b'"') {
752 index += 1;
753 } else {
754 state = State::Normal;
755 }
756 }
757 }
758 State::LineComment if byte == b'\n' => state = State::Normal,
759 State::BlockComment if byte == b'*' && next == Some(b'/') => {
760 state = State::Normal;
761 index += 1;
762 }
763 State::LineComment | State::BlockComment => {}
764 }
765 index += 1;
766 }
767 let remainder = input[start..].trim();
768 if has_sql_content(remainder) {
769 statements.push(remainder.to_owned());
770 }
771 statements
772}
773
774fn has_sql_content(segment: &str) -> bool {
778 let bytes = segment.as_bytes();
779 let mut index = 0;
780 while index < bytes.len() {
781 let byte = bytes[index];
782 match (byte, bytes.get(index + 1).copied()) {
783 (b'-', Some(b'-')) => {
784 index += 2;
785 while index < bytes.len() && bytes[index] != b'\n' {
786 index += 1;
787 }
788 }
789 (b'/', Some(b'*')) => {
790 index += 2;
791 while index < bytes.len()
792 && !(bytes[index] == b'*' && bytes.get(index + 1) == Some(&b'/'))
793 {
794 index += 1;
795 }
796 index += 2;
797 }
798 _ if byte.is_ascii_whitespace() => index += 1,
799 _ => return true,
800 }
801 }
802 false
803}
804
805#[cfg(test)]
806mod tests {
807 use super::*;
808
809 #[test]
810 fn statement_splitter_handles_quotes_and_trailing_statement() {
811 let statements = split_statements("SELECT ';'; SELECT \"a;b\"; SELECT 3");
812 assert_eq!(statements, vec!["SELECT ';'", "SELECT \"a;b\"", "SELECT 3"]);
813 }
814
815 #[test]
816 fn empty_query_splits_to_nothing() {
817 assert!(split_statements(" ; -- comment\n").is_empty());
818 }
819
820 #[test]
821 fn control_statements_are_recognized_case_insensitively() {
822 assert!(matches!(classify("begin"), Statement::Control("BEGIN")));
823 assert!(matches!(
824 classify(" SET client_encoding TO 'UTF8'"),
825 Statement::Control("SET")
826 ));
827 assert!(matches!(classify("COMMIT"), Statement::Control("COMMIT")));
828 assert!(matches!(classify("SELECT 1"), Statement::Query));
829 }
830
831 #[test]
832 fn use_and_show_are_recognized() {
833 assert!(matches!(
834 classify("show databases"),
835 Statement::ShowDatabases
836 ));
837 match classify("USE \"my-db\"") {
838 Statement::Use(name) => assert_eq!(name, "my-db"),
839 _ => panic!("expected USE"),
840 }
841 match classify("use people;") {
842 Statement::Use(name) => assert_eq!(name, "people"),
843 _ => panic!("expected USE"),
844 }
845 assert!(matches!(classify("USE"), Statement::Query));
847 }
848
849 #[test]
850 fn command_tag_counts_selects_and_names_explains() {
851 assert_eq!(command_tag("SELECT * FROM t", 7), "SELECT 7");
852 assert_eq!(command_tag("EXPLAIN SELECT 1", 3), "EXPLAIN");
853 }
854}