1mod protocol;
21mod types;
22
23use std::collections::{BTreeMap, HashMap};
24use std::future::Future;
25use std::sync::Arc;
26
27use corium_core::EntityId;
28use corium_db::Db;
29use corium_query::edn::Edn;
30use corium_sql::{MutationKind, 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 #[error("database {0:?} is read-only through this catalog")]
48 ReadOnly(String),
49 #[error("{0}")]
51 Conflict(String),
52 #[error("{0}")]
54 Rejected(String),
55 #[error("{0}")]
57 Denied(String),
58 #[error("{0}")]
61 Unsupported(String),
62}
63
64pub struct CatalogTxResult {
66 pub db_after: Db,
68 pub tempids: BTreeMap<String, EntityId>,
70}
71
72#[async_trait::async_trait]
79pub trait DbCatalog: Send + Sync + 'static {
80 async fn list(&self) -> Result<Vec<String>, CatalogError>;
85
86 async fn db(&self, name: &str) -> Result<Db, CatalogError>;
92
93 async fn transact(
97 &self,
98 name: &str,
99 _expected_basis_t: u64,
100 _forms: Vec<Edn>,
101 ) -> Result<CatalogTxResult, CatalogError> {
102 Err(CatalogError::ReadOnly(name.to_owned()))
103 }
104}
105
106#[derive(Clone, Debug)]
108pub struct PgWireConfig {
109 pub password: Option<String>,
112 pub server_version: String,
114}
115
116impl Default for PgWireConfig {
117 fn default() -> Self {
118 Self {
119 password: None,
120 server_version: concat!("16.0 (corium ", env!("CARGO_PKG_VERSION"), ")").to_owned(),
121 }
122 }
123}
124
125pub async fn serve<C, F>(
133 listener: TcpListener,
134 catalog: Arc<C>,
135 config: PgWireConfig,
136 shutdown: F,
137) -> std::io::Result<()>
138where
139 C: DbCatalog,
140 F: Future<Output = ()>,
141{
142 let config = Arc::new(config);
143 tokio::pin!(shutdown);
144 loop {
145 tokio::select! {
146 () = &mut shutdown => return Ok(()),
147 accepted = listener.accept() => {
148 let (stream, peer) = accepted?;
149 let catalog = Arc::clone(&catalog);
150 let config = Arc::clone(&config);
151 tokio::spawn(async move {
152 let (read, write) = stream.into_split();
153 let mut session = ConnectionSession::new(
154 FrontendReader::new(read),
155 BackendWriter::new(write),
156 catalog,
157 config,
158 );
159 if let Err(error) = session.run().await {
160 tracing::debug!(%peer, %error, "pgwire connection closed");
161 }
162 });
163 }
164 }
165 }
166}
167
168struct Portal {
170 sql: String,
171 database: Option<String>,
172 params: Vec<corium_sql::SqlValue>,
173 result_formats: Vec<i16>,
174}
175
176struct PreparedStatement {
177 sql: String,
178 parameter_types: Vec<i32>,
179}
180
181enum Statement {
183 Control(&'static str),
185 Begin,
188 Commit,
190 Rollback,
192 Use(String),
194 ShowDatabases,
196 Query,
198 Mutation,
200}
201
202enum Dispatch {
204 NoDatabase,
206 Catalog(CatalogError),
208 Sql(SqlError),
210}
211
212struct ConnectionSession<R, W, C> {
214 reader: FrontendReader<R>,
215 writer: BackendWriter<W>,
216 catalog: Arc<C>,
217 config: Arc<PgWireConfig>,
218 current_db: Option<String>,
220 statements: HashMap<String, PreparedStatement>,
221 portals: HashMap<String, Portal>,
222 failed: bool,
225 in_transaction: bool,
231 transaction_failed: bool,
233 transaction_db: Option<(String, Db)>,
236}
237
238impl<R, W, C> ConnectionSession<R, W, C>
239where
240 R: AsyncRead + Unpin,
241 W: AsyncWrite + Unpin,
242 C: DbCatalog,
243{
244 fn new(
245 reader: FrontendReader<R>,
246 writer: BackendWriter<W>,
247 catalog: Arc<C>,
248 config: Arc<PgWireConfig>,
249 ) -> Self {
250 Self {
251 reader,
252 writer,
253 catalog,
254 config,
255 current_db: None,
256 statements: HashMap::new(),
257 portals: HashMap::new(),
258 failed: false,
259 in_transaction: false,
260 transaction_failed: false,
261 transaction_db: None,
262 }
263 }
264
265 async fn run(&mut self) -> std::io::Result<()> {
266 let startup = self.reader.read_startup(&mut self.writer).await?;
267 if !self.authenticate().await? {
268 return Ok(());
269 }
270 self.current_db = startup
273 .get("database")
274 .filter(|value| !value.is_empty())
275 .map(str::to_owned);
276 self.send_ready_banner(startup.get("application_name").unwrap_or(""))
277 .await?;
278
279 while let Some(message) = self.reader.read_message().await? {
280 match message {
281 Frontend::Query(sql) => {
282 if !self.failed {
285 self.simple_query(&sql).await?;
286 self.writer.flush().await?;
287 }
288 }
289 Frontend::Parse {
290 name,
291 query,
292 parameter_types,
293 } => self.handle_parse(name, query, parameter_types),
294 Frontend::Bind {
295 portal,
296 statement,
297 parameter_formats,
298 parameters,
299 result_formats,
300 } => self.handle_bind(
301 &portal,
302 &statement,
303 ¶meter_formats,
304 ¶meters,
305 &result_formats,
306 ),
307 Frontend::Describe { kind, name } => self.handle_describe(kind, &name).await?,
308 Frontend::Execute { portal } => self.handle_execute(&portal).await?,
309 Frontend::Close { kind, name } => {
310 if !self.failed {
311 if kind == b'S' {
312 self.statements.remove(&name);
313 } else {
314 self.portals.remove(&name);
315 }
316 self.writer.close_complete();
317 }
318 }
319 Frontend::Sync => {
320 self.failed = false;
321 self.writer.ready_for_query(self.ready_status());
322 self.writer.flush().await?;
323 }
324 Frontend::Flush => self.writer.flush().await?,
325 Frontend::Password(_) => {}
326 Frontend::Terminate => break,
327 }
328 }
329 Ok(())
330 }
331
332 async fn authenticate(&mut self) -> std::io::Result<bool> {
335 let Some(expected) = self.config.password.clone() else {
336 self.writer.authentication_ok();
337 return Ok(true);
338 };
339 self.writer.authentication_cleartext_password();
340 self.writer.flush().await?;
341 match self.reader.read_message().await? {
342 Some(Frontend::Password(supplied)) if supplied == expected => {
343 self.writer.authentication_ok();
344 Ok(true)
345 }
346 _ => {
347 self.writer.error_response(&ErrorFields {
348 code: "28P01",
349 message: "password authentication failed",
350 });
351 self.writer.flush().await?;
352 Ok(false)
353 }
354 }
355 }
356
357 async fn send_ready_banner(&mut self, application_name: &str) -> std::io::Result<()> {
360 self.writer
361 .parameter_status("server_version", &self.config.server_version);
362 self.writer.parameter_status("server_encoding", "UTF8");
363 self.writer.parameter_status("client_encoding", "UTF8");
364 self.writer.parameter_status("DateStyle", "ISO, MDY");
365 self.writer.parameter_status("TimeZone", "UTC");
366 self.writer.parameter_status("integer_datetimes", "on");
367 self.writer
368 .parameter_status("standard_conforming_strings", "on");
369 self.writer
370 .parameter_status("application_name", application_name);
371 self.writer.backend_key_data(0, 0);
372 self.writer.ready_for_query(b'I');
373 self.writer.flush().await
374 }
375
376 async fn simple_query(&mut self, sql: &str) -> std::io::Result<()> {
379 let statements = split_statements(sql);
380 if statements.is_empty() {
381 self.writer.empty_query_response();
382 self.writer.ready_for_query(self.ready_status());
383 return Ok(());
384 }
385 for statement in statements {
386 if !self.run_simple_statement(&statement).await? {
387 break;
388 }
389 }
390 self.writer.ready_for_query(self.ready_status());
391 Ok(())
392 }
393
394 async fn run_simple_statement(&mut self, sql: &str) -> std::io::Result<bool> {
397 let statement = classify(sql);
398 if self.transaction_failed && !matches!(&statement, Statement::Rollback) {
399 self.report_transaction_aborted();
400 return Ok(false);
401 }
402 match statement {
403 Statement::Control(tag) => {
404 self.writer.command_complete(tag);
405 Ok(true)
406 }
407 Statement::Begin => {
408 self.begin_transaction();
409 self.writer.command_complete("BEGIN");
410 Ok(true)
411 }
412 Statement::Commit => {
413 self.end_transaction();
414 self.writer.command_complete("COMMIT");
415 Ok(true)
416 }
417 Statement::Rollback => {
418 self.end_transaction();
419 self.writer.command_complete("ROLLBACK");
420 Ok(true)
421 }
422 Statement::Use(name) => match self.use_database(&name).await {
423 Ok(()) => {
424 self.writer.command_complete("USE");
425 Ok(true)
426 }
427 Err(error) => {
428 self.report_dispatch(&error);
429 Ok(false)
430 }
431 },
432 Statement::ShowDatabases => match self.show_databases(true, &[]).await {
433 Ok(()) => Ok(true),
434 Err(error) => {
435 self.report_dispatch(&error);
436 Ok(false)
437 }
438 },
439 Statement::Query => {
440 let db = match self.snapshot(None).await {
441 Ok(db) => db,
442 Err(error) => {
443 self.report_dispatch(&error);
444 return Ok(false);
445 }
446 };
447 match self.run_statement(&db, sql, &[], true, &[]).await {
448 Ok(rows) => {
449 self.writer.command_complete(&command_tag(sql, rows));
450 Ok(true)
451 }
452 Err(error) => {
453 self.report_dispatch(&Dispatch::Sql(error));
454 Ok(false)
455 }
456 }
457 }
458 Statement::Mutation => {
459 if self.in_transaction {
460 self.report_dispatch(&explicit_transaction_write_error());
461 return Ok(false);
462 }
463 let Some(database) = self.current_db.clone() else {
464 self.report_dispatch(&Dispatch::NoDatabase);
465 return Ok(false);
466 };
467 let db = match self.snapshot(Some(&database)).await {
468 Ok(db) => db,
469 Err(error) => {
470 self.report_dispatch(&error);
471 return Ok(false);
472 }
473 };
474 match self.run_mutation(&database, &db, sql, &[], true, &[]).await {
475 Ok((kind, rows)) => {
476 self.writer
477 .command_complete(&mutation_command_tag(kind, rows));
478 Ok(true)
479 }
480 Err(error) => {
481 self.report_dispatch(&error);
482 Ok(false)
483 }
484 }
485 }
486 }
487 }
488
489 fn handle_parse(&mut self, name: String, query: String, mut parameter_types: Vec<i32>) {
490 if self.failed {
491 return;
492 }
493 let inferred_count = placeholder_count(&query);
494 if parameter_types.len() > inferred_count {
495 self.fail_extended("08P01", "too many parameter types in Parse");
496 return;
497 }
498 parameter_types.resize(inferred_count, 0);
499 self.statements.insert(
500 name,
501 PreparedStatement {
502 sql: query,
503 parameter_types,
504 },
505 );
506 self.writer.parse_complete();
507 }
508
509 fn handle_bind(
510 &mut self,
511 portal: &str,
512 statement: &str,
513 parameter_formats: &[i16],
514 parameters: &[Option<Vec<u8>>],
515 result_formats: &[i16],
516 ) {
517 if self.failed {
518 return;
519 }
520 if result_formats.iter().any(|format| !matches!(format, 0 | 1)) {
521 self.fail_extended("08P01", "result format code must be zero or one");
522 return;
523 }
524 let Some(prepared) = self.statements.get(statement) else {
525 self.fail_extended("26000", "prepared statement does not exist");
526 return;
527 };
528 if parameters.len() != prepared.parameter_types.len() {
529 self.fail_extended("08P01", "bound parameter count does not match Parse");
530 return;
531 }
532 let formats = match expand_formats(parameter_formats, parameters.len()) {
533 Ok(formats) => formats,
534 Err(message) => {
535 self.fail_extended("08P01", message);
536 return;
537 }
538 };
539 let params = prepared
540 .parameter_types
541 .iter()
542 .zip(formats)
543 .zip(parameters)
544 .map(|((oid, format), value)| types::decode_parameter(*oid, format, value.as_deref()))
545 .collect::<Result<Vec<_>, _>>();
546 let params = match params {
547 Ok(params) => params,
548 Err(message) => {
549 self.fail_extended("22P02", &message);
550 return;
551 }
552 };
553 self.portals.insert(
554 portal.to_owned(),
555 Portal {
556 sql: prepared.sql.clone(),
557 database: self.current_db.clone(),
558 params,
559 result_formats: result_formats.to_vec(),
560 },
561 );
562 self.writer.bind_complete();
563 }
564
565 async fn handle_describe(&mut self, kind: u8, name: &str) -> std::io::Result<()> {
566 if self.failed {
567 return Ok(());
568 }
569 let (sql, database, params, result_formats) = if kind == b'S' {
571 let Some((sql, parameter_types)) = self
572 .statements
573 .get(name)
574 .map(|statement| (statement.sql.clone(), statement.parameter_types.clone()))
575 else {
576 self.fail_extended("26000", "prepared statement does not exist");
577 return Ok(());
578 };
579 self.writer.parameter_description(¶meter_types);
580 let params = parameter_types
581 .into_iter()
582 .map(types::describe_parameter)
583 .collect::<Result<Vec<_>, _>>();
584 let params = match params {
585 Ok(params) => params,
586 Err(message) => {
587 self.fail_extended("0A000", &message);
588 return Ok(());
589 }
590 };
591 (sql, self.current_db.clone(), params, Vec::new())
592 } else {
593 let Some(portal) = self.portals.get(name) else {
594 self.fail_extended("34000", "portal does not exist");
595 return Ok(());
596 };
597 (
598 portal.sql.clone(),
599 portal.database.clone(),
600 portal.params.clone(),
601 portal.result_formats.clone(),
602 )
603 };
604 match classify(&sql) {
605 Statement::ShowDatabases => {
606 self.write_row_description(&[database_field()], &result_formats);
607 }
608 Statement::Query if !sql.trim().is_empty() => {
609 let db = match self.snapshot(database.as_deref()).await {
610 Ok(db) => db,
611 Err(error) => {
612 self.fail_dispatch(&error);
613 return Ok(());
614 }
615 };
616 match self.describe_columns(&db, &sql, ¶ms).await {
617 Ok(fields) => {
618 self.write_row_description(&fields, &result_formats);
619 }
620 Err(error) => self.fail_dispatch(&Dispatch::Sql(error)),
621 }
622 }
623 Statement::Mutation => {
624 let db = match self.snapshot(database.as_deref()).await {
625 Ok(db) => db,
626 Err(error) => {
627 self.fail_dispatch(&error);
628 return Ok(());
629 }
630 };
631 match SqlSession::new(&db) {
632 Ok(session) => match session.mutation_columns(&sql, ¶ms).await {
633 Ok(Some(columns)) if columns.is_empty() => self.writer.no_data(),
634 Ok(Some(columns)) => {
635 self.write_row_description(
636 &columns.iter().map(field_of).collect::<Vec<_>>(),
637 &result_formats,
638 );
639 }
640 Ok(None) => self.writer.no_data(),
641 Err(error) => self.fail_dispatch(&Dispatch::Sql(error)),
642 },
643 Err(error) => self.fail_dispatch(&Dispatch::Sql(error)),
644 }
645 }
646 _ => self.writer.no_data(),
647 }
648 Ok(())
649 }
650
651 async fn handle_execute(&mut self, portal: &str) -> std::io::Result<()> {
652 if self.failed {
653 return Ok(());
654 }
655 let Some((sql, database, params, result_formats)) =
656 self.portals.get(portal).map(|portal| {
657 (
658 portal.sql.clone(),
659 portal.database.clone(),
660 portal.params.clone(),
661 portal.result_formats.clone(),
662 )
663 })
664 else {
665 self.fail_extended("34000", "portal does not exist");
666 return Ok(());
667 };
668 if sql.trim().is_empty() {
669 self.writer.empty_query_response();
670 return Ok(());
671 }
672 let statement = classify(&sql);
673 if self.transaction_failed && !matches!(&statement, Statement::Rollback) {
674 self.fail_extended(
675 "25P02",
676 "current transaction is aborted; commands ignored until end of transaction block",
677 );
678 return Ok(());
679 }
680 match statement {
681 Statement::Control(tag) => self.writer.command_complete(tag),
682 Statement::Begin => {
683 self.begin_transaction();
684 self.writer.command_complete("BEGIN");
685 }
686 Statement::Commit => {
687 self.end_transaction();
688 self.writer.command_complete("COMMIT");
689 }
690 Statement::Rollback => {
691 self.end_transaction();
692 self.writer.command_complete("ROLLBACK");
693 }
694 Statement::Use(name) => match self.use_database(&name).await {
695 Ok(()) => self.writer.command_complete("USE"),
696 Err(error) => self.fail_dispatch(&error),
697 },
698 Statement::ShowDatabases => {
699 if let Err(error) = self.show_databases(false, &result_formats).await {
700 self.fail_dispatch(&error);
701 }
702 }
703 Statement::Query => {
704 let db = match self.snapshot(database.as_deref()).await {
705 Ok(db) => db,
706 Err(error) => {
707 self.fail_dispatch(&error);
708 return Ok(());
709 }
710 };
711 match self
712 .run_statement(&db, &sql, ¶ms, false, &result_formats)
713 .await
714 {
715 Ok(rows) => self.writer.command_complete(&command_tag(&sql, rows)),
716 Err(error) => self.fail_dispatch(&Dispatch::Sql(error)),
717 }
718 }
719 Statement::Mutation => {
720 if self.in_transaction {
721 self.fail_dispatch(&explicit_transaction_write_error());
722 return Ok(());
723 }
724 let Some(database) = database.or_else(|| self.current_db.clone()) else {
725 self.fail_dispatch(&Dispatch::NoDatabase);
726 return Ok(());
727 };
728 let db = match self.snapshot(Some(&database)).await {
729 Ok(db) => db,
730 Err(error) => {
731 self.fail_dispatch(&error);
732 return Ok(());
733 }
734 };
735 match self
736 .run_mutation(&database, &db, &sql, ¶ms, false, &result_formats)
737 .await
738 {
739 Ok((kind, rows)) => self
740 .writer
741 .command_complete(&mutation_command_tag(kind, rows)),
742 Err(error) => self.fail_dispatch(&error),
743 }
744 }
745 }
746 Ok(())
747 }
748
749 async fn use_database(&mut self, name: &str) -> Result<(), Dispatch> {
752 self.snapshot(Some(name)).await?;
753 self.current_db = Some(name.to_owned());
754 Ok(())
755 }
756
757 async fn show_databases(
759 &mut self,
760 with_row_description: bool,
761 result_formats: &[i16],
762 ) -> Result<(), Dispatch> {
763 let names = self.catalog.list().await.map_err(Dispatch::Catalog)?;
764 if with_row_description {
765 self.write_row_description(&[database_field()], result_formats);
766 }
767 for name in &names {
768 self.writer.data_row(&[Some(name.clone().into_bytes())]);
769 }
770 self.writer.command_complete("SHOW");
771 Ok(())
772 }
773
774 async fn snapshot(&mut self, database: Option<&str>) -> Result<Db, Dispatch> {
777 let name = database
778 .map(str::to_owned)
779 .or_else(|| self.current_db.clone())
780 .ok_or(Dispatch::NoDatabase)?;
781 if self.in_transaction {
782 if let Some((pinned_name, pinned)) = &self.transaction_db {
783 if pinned_name != &name {
784 return Err(Dispatch::Sql(SqlError::Mutation(
785 "cannot switch databases inside an explicit transaction".into(),
786 )));
787 }
788 return Ok(pinned.clone());
789 }
790 let db = self.catalog.db(&name).await.map_err(Dispatch::Catalog)?;
791 self.transaction_db = Some((name, db.clone()));
792 Ok(db)
793 } else {
794 self.catalog.db(&name).await.map_err(Dispatch::Catalog)
795 }
796 }
797
798 fn ready_status(&self) -> u8 {
799 if self.transaction_failed {
800 b'E'
801 } else if self.in_transaction {
802 b'T'
803 } else {
804 b'I'
805 }
806 }
807
808 async fn describe_columns(
810 &self,
811 db: &Db,
812 sql: &str,
813 params: &[corium_sql::SqlValue],
814 ) -> Result<Vec<FieldDescription>, SqlError> {
815 let session = SqlSession::new(db)?;
816 let query = session.query_params(sql, params).await?;
817 Ok(query.columns().iter().map(field_of).collect())
818 }
819
820 async fn run_statement(
823 &mut self,
824 db: &Db,
825 sql: &str,
826 params: &[corium_sql::SqlValue],
827 with_row_description: bool,
828 result_formats: &[i16],
829 ) -> Result<usize, SqlError> {
830 let session = SqlSession::new(db)?;
831 let mut query = session.query_params(sql, params).await?;
832 let columns = query.columns().to_vec();
833 let formats = expand_result_formats(result_formats, columns.len())
834 .map_err(|message| SqlError::Mutation(message.into()))?;
835 if with_row_description {
836 let fields = columns.iter().map(field_of).collect::<Vec<_>>();
837 self.writer.row_description_with_formats(&fields, &formats);
838 }
839 let mut count = 0usize;
840 while let Some(row) = query.next_row().await? {
841 let values = row
842 .iter()
843 .zip(&columns)
844 .zip(&formats)
845 .map(|((value, column), format)| {
846 types::encode_result(value, &column.data_type, *format)
847 .map_err(SqlError::Mutation)
848 })
849 .collect::<Result<Vec<_>, _>>()?;
850 self.writer.data_row(&values);
851 count += 1;
852 if count.is_multiple_of(1024) {
854 self.writer
855 .flush()
856 .await
857 .map_err(|error| SqlError::Schema(error.to_string()))?;
858 }
859 }
860 Ok(count)
861 }
862
863 async fn run_mutation(
864 &mut self,
865 database: &str,
866 db: &Db,
867 sql: &str,
868 params: &[corium_sql::SqlValue],
869 with_row_description: bool,
870 result_formats: &[i16],
871 ) -> Result<(MutationKind, usize), Dispatch> {
872 let session = SqlSession::new(db).map_err(Dispatch::Sql)?;
873 let mutation = session
874 .mutation_params(sql, params)
875 .await
876 .map_err(Dispatch::Sql)?
877 .ok_or_else(|| Dispatch::Sql(SqlError::Mutation("expected a mutation".into())))?;
878 let (db_after, tempids) = if mutation.is_empty() {
879 (db.clone(), BTreeMap::new())
880 } else {
881 let result = self
882 .catalog
883 .transact(
884 database,
885 mutation.expected_basis_t(),
886 mutation.forms().to_vec(),
887 )
888 .await
889 .map_err(Dispatch::Catalog)?;
890 (result.db_after, result.tempids)
891 };
892 let returned = mutation
893 .finish(&db_after, &tempids)
894 .await
895 .map_err(Dispatch::Sql)?;
896 let formats = expand_result_formats(result_formats, returned.columns.len())
897 .map_err(|message| Dispatch::Sql(SqlError::Mutation(message.into())))?;
898 if with_row_description && !returned.columns.is_empty() {
899 self.writer.row_description_with_formats(
900 &returned.columns.iter().map(field_of).collect::<Vec<_>>(),
901 &formats,
902 );
903 }
904 for row in returned.rows {
905 let values = row
906 .iter()
907 .zip(&returned.columns)
908 .zip(&formats)
909 .map(|((value, column), format)| {
910 types::encode_result(value, &column.data_type, *format)
911 .map_err(|message| Dispatch::Sql(SqlError::Mutation(message)))
912 })
913 .collect::<Result<Vec<_>, _>>()?;
914 self.writer.data_row(&values);
915 }
916 Ok((mutation.kind(), mutation.affected()))
917 }
918
919 fn report_dispatch(&mut self, error: &Dispatch) {
921 let (code, message) = dispatch_error_fields(error);
922 self.writer.error_response(&ErrorFields {
923 code,
924 message: &message,
925 });
926 if self.in_transaction {
927 self.transaction_failed = true;
928 }
929 }
930
931 fn fail_dispatch(&mut self, error: &Dispatch) {
933 self.report_dispatch(error);
934 self.failed = true;
935 }
936
937 fn fail_extended(&mut self, code: &str, message: &str) {
940 self.writer.error_response(&ErrorFields { code, message });
941 self.failed = true;
942 if self.in_transaction {
943 self.transaction_failed = true;
944 }
945 }
946
947 fn report_transaction_aborted(&mut self) {
948 self.writer.error_response(&ErrorFields {
949 code: "25P02",
950 message: "current transaction is aborted; commands ignored until end of transaction block",
951 });
952 }
953
954 fn begin_transaction(&mut self) {
955 self.in_transaction = true;
956 self.transaction_failed = false;
957 self.transaction_db = None;
958 }
959
960 fn end_transaction(&mut self) {
961 self.in_transaction = false;
962 self.transaction_failed = false;
963 self.transaction_db = None;
964 }
965
966 fn write_row_description(&mut self, fields: &[FieldDescription], requested: &[i16]) {
967 match expand_result_formats(requested, fields.len()) {
968 Ok(formats) => self.writer.row_description_with_formats(fields, &formats),
969 Err(message) => self.fail_extended("08P01", message),
970 }
971 }
972}
973
974fn database_field() -> FieldDescription {
976 let type_oid = types::type_oid(&SqlType::Text);
977 FieldDescription {
978 name: "database".to_owned(),
979 type_oid,
980 type_len: types::type_len(type_oid),
981 }
982}
983
984fn field_of(column: &SqlColumn) -> FieldDescription {
986 let type_oid = types::type_oid(&column.data_type);
987 FieldDescription {
988 name: column.name.clone(),
989 type_oid,
990 type_len: types::type_len(type_oid),
991 }
992}
993
994fn dispatch_error_fields(error: &Dispatch) -> (&'static str, String) {
996 match error {
997 Dispatch::NoDatabase => (
998 "3D000",
999 "no database selected; run \"USE <database>\" first".to_owned(),
1000 ),
1001 Dispatch::Catalog(error @ CatalogError::NotFound(_)) => ("3D000", error.to_string()),
1002 Dispatch::Catalog(error @ CatalogError::Unavailable(_)) => ("08006", error.to_string()),
1003 Dispatch::Catalog(error @ CatalogError::ReadOnly(_)) => ("25006", error.to_string()),
1004 Dispatch::Catalog(error @ CatalogError::Conflict(_)) => ("40001", error.to_string()),
1005 Dispatch::Catalog(error @ CatalogError::Rejected(_)) => ("23000", error.to_string()),
1006 Dispatch::Catalog(error @ CatalogError::Denied(_)) => ("42501", error.to_string()),
1007 Dispatch::Catalog(error @ CatalogError::Unsupported(_)) => ("0A000", error.to_string()),
1008 Dispatch::Sql(error) => (sqlstate_for(error), error.to_string()),
1009 }
1010}
1011
1012fn explicit_transaction_write_error() -> Dispatch {
1013 Dispatch::Sql(SqlError::Mutation(
1014 "writes inside explicit transaction blocks are not supported; use autocommit".to_owned(),
1015 ))
1016}
1017
1018fn sqlstate_for(error: &SqlError) -> &'static str {
1020 match error {
1021 SqlError::Schema(_) => "42P01",
1023 SqlError::Mutation(_) => "0A000",
1024 SqlError::Parser(_) | SqlError::DataFusion(_) | SqlError::Arrow(_) => "42601",
1026 }
1027}
1028
1029fn command_tag(sql: &str, rows: usize) -> String {
1031 if first_keyword(sql).eq_ignore_ascii_case("explain") {
1032 "EXPLAIN".to_owned()
1033 } else {
1034 format!("SELECT {rows}")
1035 }
1036}
1037
1038fn mutation_command_tag(kind: MutationKind, rows: usize) -> String {
1039 match kind {
1040 MutationKind::Insert => format!("INSERT 0 {rows}"),
1041 MutationKind::Update => format!("UPDATE {rows}"),
1042 MutationKind::Delete => format!("DELETE {rows}"),
1043 }
1044}
1045
1046fn expand_formats(formats: &[i16], count: usize) -> Result<Vec<i16>, &'static str> {
1047 match formats {
1048 [] => Ok(vec![0; count]),
1049 [format] => Ok(vec![*format; count]),
1050 formats if formats.len() == count => Ok(formats.to_vec()),
1051 _ => Err("parameter format count must be zero, one, or the parameter count"),
1052 }
1053}
1054
1055fn expand_result_formats(formats: &[i16], count: usize) -> Result<Vec<i16>, &'static str> {
1056 match formats {
1057 [] => Ok(vec![0; count]),
1058 [format] => Ok(vec![*format; count]),
1059 formats if formats.len() == count => Ok(formats.to_vec()),
1060 _ => Err("result format count must be zero, one, or the result column count"),
1061 }
1062}
1063
1064fn placeholder_count(sql: &str) -> usize {
1068 #[derive(Clone, Copy)]
1069 enum State {
1070 Normal,
1071 Single { backslash_escapes: bool },
1072 Double,
1073 LineComment,
1074 BlockComment(usize),
1075 }
1076 let bytes = sql.as_bytes();
1077 let mut state = State::Normal;
1078 let mut count = 0usize;
1079 let mut index = 0usize;
1080 while index < bytes.len() {
1081 match state {
1082 State::Normal if bytes[index..].starts_with(b"--") => {
1083 state = State::LineComment;
1084 index += 1;
1085 }
1086 State::Normal if bytes[index..].starts_with(b"/*") => {
1087 state = State::BlockComment(1);
1088 index += 1;
1089 }
1090 State::Normal if bytes[index] == b'\'' => {
1091 let backslash_escapes = index > 0
1092 && matches!(bytes[index - 1], b'e' | b'E')
1093 && (index < 2
1094 || !matches!(
1095 bytes[index - 2],
1096 b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_'
1097 ));
1098 state = State::Single { backslash_escapes };
1099 }
1100 State::Normal if bytes[index] == b'"' => state = State::Double,
1101 State::Normal if bytes[index] == b'$' => {
1102 let start = index + 1;
1103 let mut end = start;
1104 while bytes.get(end).is_some_and(u8::is_ascii_digit) {
1105 end += 1;
1106 }
1107 if end > start
1108 && let Ok(number) = sql[start..end].parse::<usize>()
1109 {
1110 count = count.max(number);
1111 index = end - 1;
1112 } else if let Some(delimiter_end) = dollar_quote_end(bytes, index) {
1113 let delimiter = &bytes[index..delimiter_end];
1114 let body_start = delimiter_end;
1115 if let Some(offset) = find_bytes(&bytes[body_start..], delimiter) {
1116 index = body_start + offset + delimiter.len() - 1;
1117 } else {
1118 index = bytes.len();
1119 }
1120 }
1121 }
1122 State::Single {
1123 backslash_escapes: true,
1124 } if bytes[index] == b'\\' => {
1125 index += usize::from(index + 1 < bytes.len());
1126 }
1127 State::Single { .. } if bytes[index] == b'\'' => {
1128 if bytes.get(index + 1) == Some(&b'\'') {
1129 index += 1;
1130 } else {
1131 state = State::Normal;
1132 }
1133 }
1134 State::Double if bytes[index] == b'"' => {
1135 if bytes.get(index + 1) == Some(&b'"') {
1136 index += 1;
1137 } else {
1138 state = State::Normal;
1139 }
1140 }
1141 State::LineComment if matches!(bytes[index], b'\r' | b'\n') => {
1142 state = State::Normal;
1143 }
1144 State::BlockComment(depth) if bytes[index..].starts_with(b"/*") => {
1145 state = State::BlockComment(depth + 1);
1146 index += 1;
1147 }
1148 State::BlockComment(depth) if bytes[index..].starts_with(b"*/") => {
1149 state = if depth == 1 {
1150 State::Normal
1151 } else {
1152 State::BlockComment(depth - 1)
1153 };
1154 index += 1;
1155 }
1156 State::Normal
1157 | State::Single { .. }
1158 | State::Double
1159 | State::LineComment
1160 | State::BlockComment(_) => {}
1161 }
1162 index += 1;
1163 }
1164 count
1165}
1166
1167fn dollar_quote_end(bytes: &[u8], start: usize) -> Option<usize> {
1168 let mut end = start + 1;
1169 if bytes.get(end) == Some(&b'$') {
1170 return Some(end + 1);
1171 }
1172 if !bytes
1173 .get(end)
1174 .is_some_and(|byte| byte.is_ascii_alphabetic() || *byte == b'_')
1175 {
1176 return None;
1177 }
1178 end += 1;
1179 while bytes
1180 .get(end)
1181 .is_some_and(|byte| byte.is_ascii_alphanumeric() || *byte == b'_')
1182 {
1183 end += 1;
1184 }
1185 (bytes.get(end) == Some(&b'$')).then_some(end + 1)
1186}
1187
1188fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
1189 haystack
1190 .windows(needle.len())
1191 .position(|window| window == needle)
1192}
1193
1194fn classify(sql: &str) -> Statement {
1197 let mut words = sql.split_whitespace();
1198 let first = words.next().unwrap_or("").to_ascii_uppercase();
1199 match first.as_str() {
1200 "USE" => parse_use_target(sql).map_or(Statement::Query, Statement::Use),
1201 "SHOW"
1202 if words
1203 .next()
1204 .is_some_and(|word| word.eq_ignore_ascii_case("databases")) =>
1205 {
1206 Statement::ShowDatabases
1207 }
1208 "BEGIN" | "START" => Statement::Begin,
1209 "COMMIT" | "END" => Statement::Commit,
1210 "ROLLBACK" | "ABORT" => Statement::Rollback,
1211 "SET" => Statement::Control("SET"),
1212 "RESET" => Statement::Control("RESET"),
1213 "DISCARD" => Statement::Control("DISCARD ALL"),
1214 "INSERT" | "UPDATE" | "DELETE" => Statement::Mutation,
1215 _ => Statement::Query,
1216 }
1217}
1218
1219fn parse_use_target(sql: &str) -> Option<String> {
1221 let trimmed = sql.trim();
1222 let rest = trimmed.get(3..)?.trim().trim_end_matches(';').trim();
1225 if rest.is_empty() {
1226 return None;
1227 }
1228 Some(unquote(rest))
1229}
1230
1231fn unquote(value: &str) -> String {
1234 if let Some(inner) = value
1235 .strip_prefix('"')
1236 .and_then(|rest| rest.strip_suffix('"'))
1237 {
1238 inner.replace("\"\"", "\"")
1239 } else if let Some(inner) = value
1240 .strip_prefix('\'')
1241 .and_then(|rest| rest.strip_suffix('\''))
1242 {
1243 inner.replace("''", "'")
1244 } else {
1245 value.split_whitespace().next().unwrap_or("").to_owned()
1246 }
1247}
1248
1249fn first_keyword(sql: &str) -> &str {
1251 sql.split_whitespace().next().unwrap_or("")
1252}
1253
1254fn split_statements(input: &str) -> Vec<String> {
1258 #[derive(Clone, Copy, Eq, PartialEq)]
1259 enum State {
1260 Normal,
1261 SingleQuote,
1262 DoubleQuote,
1263 LineComment,
1264 BlockComment,
1265 }
1266 let bytes = input.as_bytes();
1267 let mut state = State::Normal;
1268 let mut statements = Vec::new();
1269 let mut start = 0;
1270 let mut index = 0;
1271 while index < bytes.len() {
1272 let byte = bytes[index];
1273 let next = bytes.get(index + 1).copied();
1274 match state {
1275 State::Normal => match (byte, next) {
1276 (b'\'', _) => state = State::SingleQuote,
1277 (b'"', _) => state = State::DoubleQuote,
1278 (b'-', Some(b'-')) => {
1279 state = State::LineComment;
1280 index += 1;
1281 }
1282 (b'/', Some(b'*')) => {
1283 state = State::BlockComment;
1284 index += 1;
1285 }
1286 (b';', _) => {
1287 let statement = input[start..index].trim();
1288 if has_sql_content(statement) {
1289 statements.push(statement.to_owned());
1290 }
1291 start = index + 1;
1292 }
1293 _ => {}
1294 },
1295 State::SingleQuote => {
1296 if byte == b'\'' {
1297 if next == Some(b'\'') {
1298 index += 1;
1299 } else {
1300 state = State::Normal;
1301 }
1302 }
1303 }
1304 State::DoubleQuote => {
1305 if byte == b'"' {
1306 if next == Some(b'"') {
1307 index += 1;
1308 } else {
1309 state = State::Normal;
1310 }
1311 }
1312 }
1313 State::LineComment if byte == b'\n' => state = State::Normal,
1314 State::BlockComment if byte == b'*' && next == Some(b'/') => {
1315 state = State::Normal;
1316 index += 1;
1317 }
1318 State::LineComment | State::BlockComment => {}
1319 }
1320 index += 1;
1321 }
1322 let remainder = input[start..].trim();
1323 if has_sql_content(remainder) {
1324 statements.push(remainder.to_owned());
1325 }
1326 statements
1327}
1328
1329fn has_sql_content(segment: &str) -> bool {
1333 let bytes = segment.as_bytes();
1334 let mut index = 0;
1335 while index < bytes.len() {
1336 let byte = bytes[index];
1337 match (byte, bytes.get(index + 1).copied()) {
1338 (b'-', Some(b'-')) => {
1339 index += 2;
1340 while index < bytes.len() && bytes[index] != b'\n' {
1341 index += 1;
1342 }
1343 }
1344 (b'/', Some(b'*')) => {
1345 index += 2;
1346 while index < bytes.len()
1347 && !(bytes[index] == b'*' && bytes.get(index + 1) == Some(&b'/'))
1348 {
1349 index += 1;
1350 }
1351 index += 2;
1352 }
1353 _ if byte.is_ascii_whitespace() => index += 1,
1354 _ => return true,
1355 }
1356 }
1357 false
1358}
1359
1360#[cfg(test)]
1361mod tests {
1362 use super::*;
1363
1364 #[test]
1365 fn statement_splitter_handles_quotes_and_trailing_statement() {
1366 let statements = split_statements("SELECT ';'; SELECT \"a;b\"; SELECT 3");
1367 assert_eq!(statements, vec!["SELECT ';'", "SELECT \"a;b\"", "SELECT 3"]);
1368 }
1369
1370 #[test]
1371 fn empty_query_splits_to_nothing() {
1372 assert!(split_statements(" ; -- comment\n").is_empty());
1373 }
1374
1375 #[test]
1376 fn control_statements_are_recognized_case_insensitively() {
1377 assert!(matches!(classify("begin"), Statement::Begin));
1378 assert!(matches!(
1379 classify(" SET client_encoding TO 'UTF8'"),
1380 Statement::Control("SET")
1381 ));
1382 assert!(matches!(classify("COMMIT"), Statement::Commit));
1383 assert!(matches!(classify("SELECT 1"), Statement::Query));
1384 }
1385
1386 #[test]
1387 fn use_and_show_are_recognized() {
1388 assert!(matches!(
1389 classify("show databases"),
1390 Statement::ShowDatabases
1391 ));
1392 match classify("USE \"my-db\"") {
1393 Statement::Use(name) => assert_eq!(name, "my-db"),
1394 _ => panic!("expected USE"),
1395 }
1396 match classify("use people;") {
1397 Statement::Use(name) => assert_eq!(name, "people"),
1398 _ => panic!("expected USE"),
1399 }
1400 assert!(matches!(classify("USE"), Statement::Query));
1402 }
1403
1404 #[test]
1405 fn command_tag_counts_selects_and_names_explains() {
1406 assert_eq!(command_tag("SELECT * FROM t", 7), "SELECT 7");
1407 assert_eq!(command_tag("EXPLAIN SELECT 1", 3), "EXPLAIN");
1408 }
1409
1410 #[test]
1411 fn placeholder_count_skips_every_postgres_quoting_form() {
1412 let sql = r#"
1413 SELECT $2, '$99', E'escaped \' $98', "$97",
1414 $$ body $96 $$, $tag$ body $95 $tag$
1415 -- $94
1416 /* $93 /* nested $92 */ still comment */
1417 WHERE value = $7
1418 "#;
1419 assert_eq!(placeholder_count(sql), 7);
1420 }
1421}