LCOV - code coverage report
Current view: top level - proxy/src/serverless - sql_over_http.rs (source / functions) Coverage Total Hit
Test: 42f947419473a288706e86ecdf7c2863d760d5d7.info Lines: 0.0 % 551 0
Test Date: 2024-08-02 21:34:27 Functions: 0.0 % 121 0

            Line data    Source code
       1              : use std::pin::pin;
       2              : use std::sync::Arc;
       3              : 
       4              : use bytes::Bytes;
       5              : use futures::future::select;
       6              : use futures::future::try_join;
       7              : use futures::future::Either;
       8              : use futures::StreamExt;
       9              : use futures::TryFutureExt;
      10              : use http_body_util::BodyExt;
      11              : use http_body_util::Full;
      12              : use hyper1::body::Body;
      13              : use hyper1::body::Incoming;
      14              : use hyper1::header;
      15              : use hyper1::http::HeaderName;
      16              : use hyper1::http::HeaderValue;
      17              : use hyper1::Response;
      18              : use hyper1::StatusCode;
      19              : use hyper1::{HeaderMap, Request};
      20              : use pq_proto::StartupMessageParamsBuilder;
      21              : use serde::Serialize;
      22              : use serde_json::Value;
      23              : use tokio::time;
      24              : use tokio_postgres::error::DbError;
      25              : use tokio_postgres::error::ErrorPosition;
      26              : use tokio_postgres::error::SqlState;
      27              : use tokio_postgres::GenericClient;
      28              : use tokio_postgres::IsolationLevel;
      29              : use tokio_postgres::NoTls;
      30              : use tokio_postgres::ReadyForQueryStatus;
      31              : use tokio_postgres::Transaction;
      32              : use tokio_util::sync::CancellationToken;
      33              : use tracing::error;
      34              : use tracing::info;
      35              : use typed_json::json;
      36              : use url::Url;
      37              : use utils::http::error::ApiError;
      38              : 
      39              : use crate::auth::backend::ComputeUserInfo;
      40              : use crate::auth::endpoint_sni;
      41              : use crate::auth::ComputeUserInfoParseError;
      42              : use crate::config::ProxyConfig;
      43              : use crate::config::TlsConfig;
      44              : use crate::context::RequestMonitoring;
      45              : use crate::error::ErrorKind;
      46              : use crate::error::ReportableError;
      47              : use crate::error::UserFacingError;
      48              : use crate::metrics::HttpDirection;
      49              : use crate::metrics::Metrics;
      50              : use crate::proxy::run_until_cancelled;
      51              : use crate::proxy::NeonOptions;
      52              : use crate::serverless::backend::HttpConnError;
      53              : use crate::usage_metrics::MetricCounterRecorder;
      54              : use crate::DbName;
      55              : use crate::RoleName;
      56              : 
      57              : use super::backend::PoolingBackend;
      58              : use super::conn_pool::Client;
      59              : use super::conn_pool::ConnInfo;
      60              : use super::http_util::json_response;
      61              : use super::json::json_to_pg_text;
      62              : use super::json::pg_text_row_to_json;
      63              : use super::json::JsonConversionError;
      64              : 
      65            0 : #[derive(serde::Deserialize)]
      66              : #[serde(rename_all = "camelCase")]
      67              : struct QueryData {
      68              :     query: String,
      69              :     #[serde(deserialize_with = "bytes_to_pg_text")]
      70              :     params: Vec<Option<String>>,
      71              :     #[serde(default)]
      72              :     array_mode: Option<bool>,
      73              : }
      74              : 
      75            0 : #[derive(serde::Deserialize)]
      76              : struct BatchQueryData {
      77              :     queries: Vec<QueryData>,
      78              : }
      79              : 
      80              : #[derive(serde::Deserialize)]
      81              : #[serde(untagged)]
      82              : enum Payload {
      83              :     Single(QueryData),
      84              :     Batch(BatchQueryData),
      85              : }
      86              : 
      87              : const MAX_RESPONSE_SIZE: usize = 10 * 1024 * 1024; // 10 MiB
      88              : const MAX_REQUEST_SIZE: u64 = 10 * 1024 * 1024; // 10 MiB
      89              : 
      90              : static RAW_TEXT_OUTPUT: HeaderName = HeaderName::from_static("neon-raw-text-output");
      91              : static ARRAY_MODE: HeaderName = HeaderName::from_static("neon-array-mode");
      92              : static ALLOW_POOL: HeaderName = HeaderName::from_static("neon-pool-opt-in");
      93              : static TXN_ISOLATION_LEVEL: HeaderName = HeaderName::from_static("neon-batch-isolation-level");
      94              : static TXN_READ_ONLY: HeaderName = HeaderName::from_static("neon-batch-read-only");
      95              : static TXN_DEFERRABLE: HeaderName = HeaderName::from_static("neon-batch-deferrable");
      96              : 
      97              : static HEADER_VALUE_TRUE: HeaderValue = HeaderValue::from_static("true");
      98              : 
      99            0 : fn bytes_to_pg_text<'de, D>(deserializer: D) -> Result<Vec<Option<String>>, D::Error>
     100            0 : where
     101            0 :     D: serde::de::Deserializer<'de>,
     102            0 : {
     103              :     // TODO: consider avoiding the allocation here.
     104            0 :     let json: Vec<Value> = serde::de::Deserialize::deserialize(deserializer)?;
     105            0 :     Ok(json_to_pg_text(json))
     106            0 : }
     107              : 
     108            0 : #[derive(Debug, thiserror::Error)]
     109              : pub enum ConnInfoError {
     110              :     #[error("invalid header: {0}")]
     111              :     InvalidHeader(&'static str),
     112              :     #[error("invalid connection string: {0}")]
     113              :     UrlParseError(#[from] url::ParseError),
     114              :     #[error("incorrect scheme")]
     115              :     IncorrectScheme,
     116              :     #[error("missing database name")]
     117              :     MissingDbName,
     118              :     #[error("invalid database name")]
     119              :     InvalidDbName,
     120              :     #[error("missing username")]
     121              :     MissingUsername,
     122              :     #[error("invalid username: {0}")]
     123              :     InvalidUsername(#[from] std::string::FromUtf8Error),
     124              :     #[error("missing password")]
     125              :     MissingPassword,
     126              :     #[error("missing hostname")]
     127              :     MissingHostname,
     128              :     #[error("invalid hostname: {0}")]
     129              :     InvalidEndpoint(#[from] ComputeUserInfoParseError),
     130              :     #[error("malformed endpoint")]
     131              :     MalformedEndpoint,
     132              : }
     133              : 
     134              : impl ReportableError for ConnInfoError {
     135            0 :     fn get_error_kind(&self) -> ErrorKind {
     136            0 :         ErrorKind::User
     137            0 :     }
     138              : }
     139              : 
     140              : impl UserFacingError for ConnInfoError {
     141            0 :     fn to_string_client(&self) -> String {
     142            0 :         self.to_string()
     143            0 :     }
     144              : }
     145              : 
     146            0 : fn get_conn_info(
     147            0 :     ctx: &mut RequestMonitoring,
     148            0 :     headers: &HeaderMap,
     149            0 :     tls: &TlsConfig,
     150            0 : ) -> Result<ConnInfo, ConnInfoError> {
     151            0 :     // HTTP only uses cleartext (for now and likely always)
     152            0 :     ctx.set_auth_method(crate::context::AuthMethod::Cleartext);
     153              : 
     154            0 :     let connection_string = headers
     155            0 :         .get("Neon-Connection-String")
     156            0 :         .ok_or(ConnInfoError::InvalidHeader("Neon-Connection-String"))?
     157            0 :         .to_str()
     158            0 :         .map_err(|_| ConnInfoError::InvalidHeader("Neon-Connection-String"))?;
     159              : 
     160            0 :     let connection_url = Url::parse(connection_string)?;
     161              : 
     162            0 :     let protocol = connection_url.scheme();
     163            0 :     if protocol != "postgres" && protocol != "postgresql" {
     164            0 :         return Err(ConnInfoError::IncorrectScheme);
     165            0 :     }
     166              : 
     167            0 :     let mut url_path = connection_url
     168            0 :         .path_segments()
     169            0 :         .ok_or(ConnInfoError::MissingDbName)?;
     170              : 
     171            0 :     let dbname: DbName = url_path.next().ok_or(ConnInfoError::InvalidDbName)?.into();
     172            0 :     ctx.set_dbname(dbname.clone());
     173              : 
     174            0 :     let username = RoleName::from(urlencoding::decode(connection_url.username())?);
     175            0 :     if username.is_empty() {
     176            0 :         return Err(ConnInfoError::MissingUsername);
     177            0 :     }
     178            0 :     ctx.set_user(username.clone());
     179              : 
     180            0 :     let password = connection_url
     181            0 :         .password()
     182            0 :         .ok_or(ConnInfoError::MissingPassword)?;
     183            0 :     let password = urlencoding::decode_binary(password.as_bytes());
     184              : 
     185            0 :     let hostname = connection_url
     186            0 :         .host_str()
     187            0 :         .ok_or(ConnInfoError::MissingHostname)?;
     188              : 
     189            0 :     let endpoint =
     190            0 :         endpoint_sni(hostname, &tls.common_names)?.ok_or(ConnInfoError::MalformedEndpoint)?;
     191            0 :     ctx.set_endpoint_id(endpoint.clone());
     192            0 : 
     193            0 :     let pairs = connection_url.query_pairs();
     194            0 : 
     195            0 :     let mut options = Option::None;
     196            0 : 
     197            0 :     let mut params = StartupMessageParamsBuilder::default();
     198            0 :     params.insert("user", &username);
     199            0 :     params.insert("database", &dbname);
     200            0 :     for (key, value) in pairs {
     201            0 :         params.insert(&key, &value);
     202            0 :         if key == "options" {
     203            0 :             options = Some(NeonOptions::parse_options_raw(&value));
     204            0 :         }
     205              :     }
     206            0 :     ctx.set_db_options(params.freeze());
     207            0 : 
     208            0 :     let user_info = ComputeUserInfo {
     209            0 :         endpoint,
     210            0 :         user: username,
     211            0 :         options: options.unwrap_or_default(),
     212            0 :     };
     213            0 : 
     214            0 :     Ok(ConnInfo {
     215            0 :         user_info,
     216            0 :         dbname,
     217            0 :         password: match password {
     218            0 :             std::borrow::Cow::Borrowed(b) => b.into(),
     219            0 :             std::borrow::Cow::Owned(b) => b.into(),
     220              :         },
     221              :     })
     222            0 : }
     223              : 
     224              : // TODO: return different http error codes
     225            0 : pub async fn handle(
     226            0 :     config: &'static ProxyConfig,
     227            0 :     mut ctx: RequestMonitoring,
     228            0 :     request: Request<Incoming>,
     229            0 :     backend: Arc<PoolingBackend>,
     230            0 :     cancel: CancellationToken,
     231            0 : ) -> Result<Response<Full<Bytes>>, ApiError> {
     232            0 :     let result = handle_inner(cancel, config, &mut ctx, request, backend).await;
     233              : 
     234            0 :     let mut response = match result {
     235            0 :         Ok(r) => {
     236            0 :             ctx.set_success();
     237            0 :             r
     238              :         }
     239            0 :         Err(e @ SqlOverHttpError::Cancelled(_)) => {
     240            0 :             let error_kind = e.get_error_kind();
     241            0 :             ctx.set_error_kind(error_kind);
     242            0 : 
     243            0 :             let message = "Query cancelled, connection was terminated";
     244            0 : 
     245            0 :             tracing::info!(
     246            0 :                 kind=error_kind.to_metric_label(),
     247            0 :                 error=%e,
     248            0 :                 msg=message,
     249            0 :                 "forwarding error to user"
     250              :             );
     251              : 
     252            0 :             json_response(
     253            0 :                 StatusCode::BAD_REQUEST,
     254            0 :                 json!({ "message": message, "code": SqlState::PROTOCOL_VIOLATION.code() }),
     255            0 :             )?
     256              :         }
     257            0 :         Err(e) => {
     258            0 :             let error_kind = e.get_error_kind();
     259            0 :             ctx.set_error_kind(error_kind);
     260            0 : 
     261            0 :             let mut message = e.to_string_client();
     262            0 :             let db_error = match &e {
     263            0 :                 SqlOverHttpError::ConnectCompute(HttpConnError::ConnectionError(e))
     264            0 :                 | SqlOverHttpError::Postgres(e) => e.as_db_error(),
     265            0 :                 _ => None,
     266              :             };
     267            0 :             fn get<'a, T: Default>(db: Option<&'a DbError>, x: impl FnOnce(&'a DbError) -> T) -> T {
     268            0 :                 db.map(x).unwrap_or_default()
     269            0 :             }
     270              : 
     271            0 :             if let Some(db_error) = db_error {
     272            0 :                 db_error.message().clone_into(&mut message);
     273            0 :             }
     274              : 
     275            0 :             let position = db_error.and_then(|db| db.position());
     276            0 :             let (position, internal_position, internal_query) = match position {
     277            0 :                 Some(ErrorPosition::Original(position)) => (Some(position.to_string()), None, None),
     278            0 :                 Some(ErrorPosition::Internal { position, query }) => {
     279            0 :                     (None, Some(position.to_string()), Some(query.clone()))
     280              :                 }
     281            0 :                 None => (None, None, None),
     282              :             };
     283              : 
     284            0 :             let code = get(db_error, |db| db.code().code());
     285            0 :             let severity = get(db_error, |db| db.severity());
     286            0 :             let detail = get(db_error, |db| db.detail());
     287            0 :             let hint = get(db_error, |db| db.hint());
     288            0 :             let where_ = get(db_error, |db| db.where_());
     289            0 :             let table = get(db_error, |db| db.table());
     290            0 :             let column = get(db_error, |db| db.column());
     291            0 :             let schema = get(db_error, |db| db.schema());
     292            0 :             let datatype = get(db_error, |db| db.datatype());
     293            0 :             let constraint = get(db_error, |db| db.constraint());
     294            0 :             let file = get(db_error, |db| db.file());
     295            0 :             let line = get(db_error, |db| db.line().map(|l| l.to_string()));
     296            0 :             let routine = get(db_error, |db| db.routine());
     297            0 : 
     298            0 :             tracing::info!(
     299            0 :                 kind=error_kind.to_metric_label(),
     300            0 :                 error=%e,
     301            0 :                 msg=message,
     302            0 :                 "forwarding error to user"
     303              :             );
     304              : 
     305              :             // TODO: this shouldn't always be bad request.
     306            0 :             json_response(
     307            0 :                 StatusCode::BAD_REQUEST,
     308            0 :                 json!({
     309            0 :                     "message": message,
     310            0 :                     "code": code,
     311            0 :                     "detail": detail,
     312            0 :                     "hint": hint,
     313            0 :                     "position": position,
     314            0 :                     "internalPosition": internal_position,
     315            0 :                     "internalQuery": internal_query,
     316            0 :                     "severity": severity,
     317            0 :                     "where": where_,
     318            0 :                     "table": table,
     319            0 :                     "column": column,
     320            0 :                     "schema": schema,
     321            0 :                     "dataType": datatype,
     322            0 :                     "constraint": constraint,
     323            0 :                     "file": file,
     324            0 :                     "line": line,
     325            0 :                     "routine": routine,
     326            0 :                 }),
     327            0 :             )?
     328              :         }
     329              :     };
     330              : 
     331            0 :     response
     332            0 :         .headers_mut()
     333            0 :         .insert("Access-Control-Allow-Origin", HeaderValue::from_static("*"));
     334            0 :     Ok(response)
     335            0 : }
     336              : 
     337            0 : #[derive(Debug, thiserror::Error)]
     338              : pub enum SqlOverHttpError {
     339              :     #[error("{0}")]
     340              :     ReadPayload(#[from] ReadPayloadError),
     341              :     #[error("{0}")]
     342              :     ConnectCompute(#[from] HttpConnError),
     343              :     #[error("{0}")]
     344              :     ConnInfo(#[from] ConnInfoError),
     345              :     #[error("request is too large (max is {MAX_REQUEST_SIZE} bytes)")]
     346              :     RequestTooLarge,
     347              :     #[error("response is too large (max is {MAX_RESPONSE_SIZE} bytes)")]
     348              :     ResponseTooLarge,
     349              :     #[error("invalid isolation level")]
     350              :     InvalidIsolationLevel,
     351              :     #[error("{0}")]
     352              :     Postgres(#[from] tokio_postgres::Error),
     353              :     #[error("{0}")]
     354              :     JsonConversion(#[from] JsonConversionError),
     355              :     #[error("{0}")]
     356              :     Cancelled(SqlOverHttpCancel),
     357              : }
     358              : 
     359              : impl ReportableError for SqlOverHttpError {
     360            0 :     fn get_error_kind(&self) -> ErrorKind {
     361            0 :         match self {
     362            0 :             SqlOverHttpError::ReadPayload(e) => e.get_error_kind(),
     363            0 :             SqlOverHttpError::ConnectCompute(e) => e.get_error_kind(),
     364            0 :             SqlOverHttpError::ConnInfo(e) => e.get_error_kind(),
     365            0 :             SqlOverHttpError::RequestTooLarge => ErrorKind::User,
     366            0 :             SqlOverHttpError::ResponseTooLarge => ErrorKind::User,
     367            0 :             SqlOverHttpError::InvalidIsolationLevel => ErrorKind::User,
     368            0 :             SqlOverHttpError::Postgres(p) => p.get_error_kind(),
     369            0 :             SqlOverHttpError::JsonConversion(_) => ErrorKind::Postgres,
     370            0 :             SqlOverHttpError::Cancelled(c) => c.get_error_kind(),
     371              :         }
     372            0 :     }
     373              : }
     374              : 
     375              : impl UserFacingError for SqlOverHttpError {
     376            0 :     fn to_string_client(&self) -> String {
     377            0 :         match self {
     378            0 :             SqlOverHttpError::ReadPayload(p) => p.to_string(),
     379            0 :             SqlOverHttpError::ConnectCompute(c) => c.to_string_client(),
     380            0 :             SqlOverHttpError::ConnInfo(c) => c.to_string_client(),
     381            0 :             SqlOverHttpError::RequestTooLarge => self.to_string(),
     382            0 :             SqlOverHttpError::ResponseTooLarge => self.to_string(),
     383            0 :             SqlOverHttpError::InvalidIsolationLevel => self.to_string(),
     384            0 :             SqlOverHttpError::Postgres(p) => p.to_string(),
     385            0 :             SqlOverHttpError::JsonConversion(_) => "could not parse postgres response".to_string(),
     386            0 :             SqlOverHttpError::Cancelled(_) => self.to_string(),
     387              :         }
     388            0 :     }
     389              : }
     390              : 
     391            0 : #[derive(Debug, thiserror::Error)]
     392              : pub enum ReadPayloadError {
     393              :     #[error("could not read the HTTP request body: {0}")]
     394              :     Read(#[from] hyper1::Error),
     395              :     #[error("could not parse the HTTP request body: {0}")]
     396              :     Parse(#[from] serde_json::Error),
     397              : }
     398              : 
     399              : impl ReportableError for ReadPayloadError {
     400            0 :     fn get_error_kind(&self) -> ErrorKind {
     401            0 :         match self {
     402            0 :             ReadPayloadError::Read(_) => ErrorKind::ClientDisconnect,
     403            0 :             ReadPayloadError::Parse(_) => ErrorKind::User,
     404              :         }
     405            0 :     }
     406              : }
     407              : 
     408            0 : #[derive(Debug, thiserror::Error)]
     409              : pub enum SqlOverHttpCancel {
     410              :     #[error("query was cancelled")]
     411              :     Postgres,
     412              :     #[error("query was cancelled while stuck trying to connect to the database")]
     413              :     Connect,
     414              : }
     415              : 
     416              : impl ReportableError for SqlOverHttpCancel {
     417            0 :     fn get_error_kind(&self) -> ErrorKind {
     418            0 :         match self {
     419            0 :             SqlOverHttpCancel::Postgres => ErrorKind::ClientDisconnect,
     420            0 :             SqlOverHttpCancel::Connect => ErrorKind::ClientDisconnect,
     421              :         }
     422            0 :     }
     423              : }
     424              : 
     425              : #[derive(Clone, Copy, Debug)]
     426              : struct HttpHeaders {
     427              :     raw_output: bool,
     428              :     default_array_mode: bool,
     429              :     txn_isolation_level: Option<IsolationLevel>,
     430              :     txn_read_only: bool,
     431              :     txn_deferrable: bool,
     432              : }
     433              : 
     434              : impl HttpHeaders {
     435            0 :     fn try_parse(headers: &hyper1::http::HeaderMap) -> Result<Self, SqlOverHttpError> {
     436            0 :         // Determine the output options. Default behaviour is 'false'. Anything that is not
     437            0 :         // strictly 'true' assumed to be false.
     438            0 :         let raw_output = headers.get(&RAW_TEXT_OUTPUT) == Some(&HEADER_VALUE_TRUE);
     439            0 :         let default_array_mode = headers.get(&ARRAY_MODE) == Some(&HEADER_VALUE_TRUE);
     440              : 
     441              :         // isolation level, read only and deferrable
     442            0 :         let txn_isolation_level = match headers.get(&TXN_ISOLATION_LEVEL) {
     443            0 :             Some(x) => Some(
     444            0 :                 map_header_to_isolation_level(x).ok_or(SqlOverHttpError::InvalidIsolationLevel)?,
     445              :             ),
     446            0 :             None => None,
     447              :         };
     448              : 
     449            0 :         let txn_read_only = headers.get(&TXN_READ_ONLY) == Some(&HEADER_VALUE_TRUE);
     450            0 :         let txn_deferrable = headers.get(&TXN_DEFERRABLE) == Some(&HEADER_VALUE_TRUE);
     451            0 : 
     452            0 :         Ok(Self {
     453            0 :             raw_output,
     454            0 :             default_array_mode,
     455            0 :             txn_isolation_level,
     456            0 :             txn_read_only,
     457            0 :             txn_deferrable,
     458            0 :         })
     459            0 :     }
     460              : }
     461              : 
     462            0 : fn map_header_to_isolation_level(level: &HeaderValue) -> Option<IsolationLevel> {
     463            0 :     match level.as_bytes() {
     464            0 :         b"Serializable" => Some(IsolationLevel::Serializable),
     465            0 :         b"ReadUncommitted" => Some(IsolationLevel::ReadUncommitted),
     466            0 :         b"ReadCommitted" => Some(IsolationLevel::ReadCommitted),
     467            0 :         b"RepeatableRead" => Some(IsolationLevel::RepeatableRead),
     468            0 :         _ => None,
     469              :     }
     470            0 : }
     471              : 
     472            0 : fn map_isolation_level_to_headers(level: IsolationLevel) -> Option<HeaderValue> {
     473            0 :     match level {
     474            0 :         IsolationLevel::ReadUncommitted => Some(HeaderValue::from_static("ReadUncommitted")),
     475            0 :         IsolationLevel::ReadCommitted => Some(HeaderValue::from_static("ReadCommitted")),
     476            0 :         IsolationLevel::RepeatableRead => Some(HeaderValue::from_static("RepeatableRead")),
     477            0 :         IsolationLevel::Serializable => Some(HeaderValue::from_static("Serializable")),
     478            0 :         _ => None,
     479              :     }
     480            0 : }
     481              : 
     482            0 : async fn handle_inner(
     483            0 :     cancel: CancellationToken,
     484            0 :     config: &'static ProxyConfig,
     485            0 :     ctx: &mut RequestMonitoring,
     486            0 :     request: Request<Incoming>,
     487            0 :     backend: Arc<PoolingBackend>,
     488            0 : ) -> Result<Response<Full<Bytes>>, SqlOverHttpError> {
     489            0 :     let _requeset_gauge = Metrics::get().proxy.connection_requests.guard(ctx.protocol);
     490            0 :     info!(
     491              :         protocol = %ctx.protocol,
     492            0 :         "handling interactive connection from client"
     493              :     );
     494              : 
     495              :     //
     496              :     // Determine the destination and connection params
     497              :     //
     498            0 :     let headers = request.headers();
     499              : 
     500              :     // TLS config should be there.
     501            0 :     let conn_info = get_conn_info(ctx, headers, config.tls_config.as_ref().unwrap())?;
     502            0 :     info!(user = conn_info.user_info.user.as_str(), "credentials");
     503              : 
     504              :     // Allow connection pooling only if explicitly requested
     505              :     // or if we have decided that http pool is no longer opt-in
     506            0 :     let allow_pool = !config.http_config.pool_options.opt_in
     507            0 :         || headers.get(&ALLOW_POOL) == Some(&HEADER_VALUE_TRUE);
     508              : 
     509            0 :     let parsed_headers = HttpHeaders::try_parse(headers)?;
     510              : 
     511            0 :     let request_content_length = match request.body().size_hint().upper() {
     512            0 :         Some(v) => v,
     513            0 :         None => MAX_REQUEST_SIZE + 1,
     514              :     };
     515            0 :     info!(request_content_length, "request size in bytes");
     516            0 :     Metrics::get()
     517            0 :         .proxy
     518            0 :         .http_conn_content_length_bytes
     519            0 :         .observe(HttpDirection::Request, request_content_length as f64);
     520            0 : 
     521            0 :     // we don't have a streaming request support yet so this is to prevent OOM
     522            0 :     // from a malicious user sending an extremely large request body
     523            0 :     if request_content_length > MAX_REQUEST_SIZE {
     524            0 :         return Err(SqlOverHttpError::RequestTooLarge);
     525            0 :     }
     526            0 : 
     527            0 :     let fetch_and_process_request = Box::pin(
     528            0 :         async {
     529            0 :             let body = request.into_body().collect().await?.to_bytes();
     530            0 :             info!(length = body.len(), "request payload read");
     531            0 :             let payload: Payload = serde_json::from_slice(&body)?;
     532            0 :             Ok::<Payload, ReadPayloadError>(payload) // Adjust error type accordingly
     533            0 :         }
     534            0 :         .map_err(SqlOverHttpError::from),
     535            0 :     );
     536            0 : 
     537            0 :     let authenticate_and_connect = Box::pin(
     538            0 :         async {
     539            0 :             let keys = backend
     540            0 :                 .authenticate(ctx, &config.authentication_config, &conn_info)
     541            0 :                 .await?;
     542            0 :             let client = backend
     543            0 :                 .connect_to_compute(ctx, conn_info, keys, !allow_pool)
     544            0 :                 .await?;
     545              :             // not strictly necessary to mark success here,
     546              :             // but it's just insurance for if we forget it somewhere else
     547            0 :             ctx.latency_timer.success();
     548            0 :             Ok::<_, HttpConnError>(client)
     549            0 :         }
     550            0 :         .map_err(SqlOverHttpError::from),
     551            0 :     );
     552              : 
     553            0 :     let (payload, mut client) = match run_until_cancelled(
     554            0 :         // Run both operations in parallel
     555            0 :         try_join(
     556            0 :             pin!(fetch_and_process_request),
     557            0 :             pin!(authenticate_and_connect),
     558            0 :         ),
     559            0 :         &cancel,
     560            0 :     )
     561            0 :     .await
     562              :     {
     563            0 :         Some(result) => result?,
     564            0 :         None => return Err(SqlOverHttpError::Cancelled(SqlOverHttpCancel::Connect)),
     565              :     };
     566              : 
     567            0 :     let mut response = Response::builder()
     568            0 :         .status(StatusCode::OK)
     569            0 :         .header(header::CONTENT_TYPE, "application/json");
     570              : 
     571              :     // Now execute the query and return the result.
     572            0 :     let json_output = match payload {
     573            0 :         Payload::Single(stmt) => stmt.process(cancel, &mut client, parsed_headers).await?,
     574            0 :         Payload::Batch(statements) => {
     575            0 :             if parsed_headers.txn_read_only {
     576            0 :                 response = response.header(TXN_READ_ONLY.clone(), &HEADER_VALUE_TRUE);
     577            0 :             }
     578            0 :             if parsed_headers.txn_deferrable {
     579            0 :                 response = response.header(TXN_DEFERRABLE.clone(), &HEADER_VALUE_TRUE);
     580            0 :             }
     581            0 :             if let Some(txn_isolation_level) = parsed_headers
     582            0 :                 .txn_isolation_level
     583            0 :                 .and_then(map_isolation_level_to_headers)
     584            0 :             {
     585            0 :                 response = response.header(TXN_ISOLATION_LEVEL.clone(), txn_isolation_level);
     586            0 :             }
     587              : 
     588            0 :             statements
     589            0 :                 .process(cancel, &mut client, parsed_headers)
     590            0 :                 .await?
     591              :         }
     592              :     };
     593              : 
     594            0 :     let metrics = client.metrics();
     595            0 : 
     596            0 :     let len = json_output.len();
     597            0 :     let response = response
     598            0 :         .body(Full::new(Bytes::from(json_output)))
     599            0 :         // only fails if invalid status code or invalid header/values are given.
     600            0 :         // these are not user configurable so it cannot fail dynamically
     601            0 :         .expect("building response payload should not fail");
     602            0 : 
     603            0 :     // count the egress bytes - we miss the TLS and header overhead but oh well...
     604            0 :     // moving this later in the stack is going to be a lot of effort and ehhhh
     605            0 :     metrics.record_egress(len as u64);
     606            0 :     Metrics::get()
     607            0 :         .proxy
     608            0 :         .http_conn_content_length_bytes
     609            0 :         .observe(HttpDirection::Response, len as f64);
     610            0 : 
     611            0 :     Ok(response)
     612            0 : }
     613              : 
     614              : impl QueryData {
     615            0 :     async fn process(
     616            0 :         self,
     617            0 :         cancel: CancellationToken,
     618            0 :         client: &mut Client<tokio_postgres::Client>,
     619            0 :         parsed_headers: HttpHeaders,
     620            0 :     ) -> Result<String, SqlOverHttpError> {
     621            0 :         let (inner, mut discard) = client.inner();
     622            0 :         let cancel_token = inner.cancel_token();
     623              : 
     624            0 :         let res = match select(
     625            0 :             pin!(query_to_json(&*inner, self, &mut 0, parsed_headers)),
     626            0 :             pin!(cancel.cancelled()),
     627            0 :         )
     628            0 :         .await
     629              :         {
     630              :             // The query successfully completed.
     631            0 :             Either::Left((Ok((status, results)), __not_yet_cancelled)) => {
     632            0 :                 discard.check_idle(status);
     633            0 : 
     634            0 :                 let json_output =
     635            0 :                     serde_json::to_string(&results).expect("json serialization should not fail");
     636            0 :                 Ok(json_output)
     637              :             }
     638              :             // The query failed with an error
     639            0 :             Either::Left((Err(e), __not_yet_cancelled)) => {
     640            0 :                 discard.discard();
     641            0 :                 return Err(e);
     642              :             }
     643              :             // The query was cancelled.
     644            0 :             Either::Right((_cancelled, query)) => {
     645            0 :                 tracing::info!("cancelling query");
     646            0 :                 if let Err(err) = cancel_token.cancel_query(NoTls).await {
     647            0 :                     tracing::error!(?err, "could not cancel query");
     648            0 :                 }
     649              :                 // wait for the query cancellation
     650            0 :                 match time::timeout(time::Duration::from_millis(100), query).await {
     651              :                     // query successed before it was cancelled.
     652            0 :                     Ok(Ok((status, results))) => {
     653            0 :                         discard.check_idle(status);
     654            0 : 
     655            0 :                         let json_output = serde_json::to_string(&results)
     656            0 :                             .expect("json serialization should not fail");
     657            0 :                         Ok(json_output)
     658              :                     }
     659              :                     // query failed or was cancelled.
     660            0 :                     Ok(Err(error)) => {
     661            0 :                         let db_error = match &error {
     662            0 :                             SqlOverHttpError::ConnectCompute(HttpConnError::ConnectionError(e))
     663            0 :                             | SqlOverHttpError::Postgres(e) => e.as_db_error(),
     664            0 :                             _ => None,
     665              :                         };
     666              : 
     667              :                         // if errored for some other reason, it might not be safe to return
     668            0 :                         if !db_error.is_some_and(|e| *e.code() == SqlState::QUERY_CANCELED) {
     669            0 :                             discard.discard();
     670            0 :                         }
     671              : 
     672            0 :                         Err(SqlOverHttpError::Cancelled(SqlOverHttpCancel::Postgres))
     673              :                     }
     674            0 :                     Err(_timeout) => {
     675            0 :                         discard.discard();
     676            0 :                         Err(SqlOverHttpError::Cancelled(SqlOverHttpCancel::Postgres))
     677              :                     }
     678              :                 }
     679              :             }
     680              :         };
     681            0 :         res
     682            0 :     }
     683              : }
     684              : 
     685              : impl BatchQueryData {
     686            0 :     async fn process(
     687            0 :         self,
     688            0 :         cancel: CancellationToken,
     689            0 :         client: &mut Client<tokio_postgres::Client>,
     690            0 :         parsed_headers: HttpHeaders,
     691            0 :     ) -> Result<String, SqlOverHttpError> {
     692            0 :         info!("starting transaction");
     693            0 :         let (inner, mut discard) = client.inner();
     694            0 :         let cancel_token = inner.cancel_token();
     695            0 :         let mut builder = inner.build_transaction();
     696            0 :         if let Some(isolation_level) = parsed_headers.txn_isolation_level {
     697            0 :             builder = builder.isolation_level(isolation_level);
     698            0 :         }
     699            0 :         if parsed_headers.txn_read_only {
     700            0 :             builder = builder.read_only(true);
     701            0 :         }
     702            0 :         if parsed_headers.txn_deferrable {
     703            0 :             builder = builder.deferrable(true);
     704            0 :         }
     705              : 
     706            0 :         let transaction = builder.start().await.map_err(|e| {
     707            0 :             // if we cannot start a transaction, we should return immediately
     708            0 :             // and not return to the pool. connection is clearly broken
     709            0 :             discard.discard();
     710            0 :             e
     711            0 :         })?;
     712              : 
     713            0 :         let json_output =
     714            0 :             match query_batch(cancel.child_token(), &transaction, self, parsed_headers).await {
     715            0 :                 Ok(json_output) => {
     716            0 :                     info!("commit");
     717            0 :                     let status = transaction.commit().await.map_err(|e| {
     718            0 :                         // if we cannot commit - for now don't return connection to pool
     719            0 :                         // TODO: get a query status from the error
     720            0 :                         discard.discard();
     721            0 :                         e
     722            0 :                     })?;
     723            0 :                     discard.check_idle(status);
     724            0 :                     json_output
     725              :                 }
     726              :                 Err(SqlOverHttpError::Cancelled(_)) => {
     727            0 :                     if let Err(err) = cancel_token.cancel_query(NoTls).await {
     728            0 :                         tracing::error!(?err, "could not cancel query");
     729            0 :                     }
     730              :                     // TODO: after cancelling, wait to see if we can get a status. maybe the connection is still safe.
     731            0 :                     discard.discard();
     732            0 : 
     733            0 :                     return Err(SqlOverHttpError::Cancelled(SqlOverHttpCancel::Postgres));
     734              :                 }
     735            0 :                 Err(err) => {
     736            0 :                     info!("rollback");
     737            0 :                     let status = transaction.rollback().await.map_err(|e| {
     738            0 :                         // if we cannot rollback - for now don't return connection to pool
     739            0 :                         // TODO: get a query status from the error
     740            0 :                         discard.discard();
     741            0 :                         e
     742            0 :                     })?;
     743            0 :                     discard.check_idle(status);
     744            0 :                     return Err(err);
     745              :                 }
     746              :             };
     747              : 
     748            0 :         Ok(json_output)
     749            0 :     }
     750              : }
     751              : 
     752            0 : async fn query_batch(
     753            0 :     cancel: CancellationToken,
     754            0 :     transaction: &Transaction<'_>,
     755            0 :     queries: BatchQueryData,
     756            0 :     parsed_headers: HttpHeaders,
     757            0 : ) -> Result<String, SqlOverHttpError> {
     758            0 :     let mut results = Vec::with_capacity(queries.queries.len());
     759            0 :     let mut current_size = 0;
     760            0 :     for stmt in queries.queries {
     761            0 :         let query = pin!(query_to_json(
     762            0 :             transaction,
     763            0 :             stmt,
     764            0 :             &mut current_size,
     765            0 :             parsed_headers,
     766            0 :         ));
     767            0 :         let cancelled = pin!(cancel.cancelled());
     768            0 :         let res = select(query, cancelled).await;
     769            0 :         match res {
     770              :             // TODO: maybe we should check that the transaction bit is set here
     771            0 :             Either::Left((Ok((_, values)), _cancelled)) => {
     772            0 :                 results.push(values);
     773            0 :             }
     774            0 :             Either::Left((Err(e), _cancelled)) => {
     775            0 :                 return Err(e);
     776              :             }
     777            0 :             Either::Right((_cancelled, _)) => {
     778            0 :                 return Err(SqlOverHttpError::Cancelled(SqlOverHttpCancel::Postgres));
     779              :             }
     780              :         }
     781              :     }
     782              : 
     783            0 :     let results = json!({ "results": results });
     784            0 :     let json_output = serde_json::to_string(&results).expect("json serialization should not fail");
     785            0 : 
     786            0 :     Ok(json_output)
     787            0 : }
     788              : 
     789            0 : async fn query_to_json<T: GenericClient>(
     790            0 :     client: &T,
     791            0 :     data: QueryData,
     792            0 :     current_size: &mut usize,
     793            0 :     parsed_headers: HttpHeaders,
     794            0 : ) -> Result<(ReadyForQueryStatus, impl Serialize), SqlOverHttpError> {
     795            0 :     info!("executing query");
     796            0 :     let query_params = data.params;
     797            0 :     let mut row_stream = std::pin::pin!(client.query_raw_txt(&data.query, query_params).await?);
     798            0 :     info!("finished executing query");
     799              : 
     800              :     // Manually drain the stream into a vector to leave row_stream hanging
     801              :     // around to get a command tag. Also check that the response is not too
     802              :     // big.
     803            0 :     let mut rows: Vec<tokio_postgres::Row> = Vec::new();
     804            0 :     while let Some(row) = row_stream.next().await {
     805            0 :         let row = row?;
     806            0 :         *current_size += row.body_len();
     807            0 :         rows.push(row);
     808            0 :         // we don't have a streaming response support yet so this is to prevent OOM
     809            0 :         // from a malicious query (eg a cross join)
     810            0 :         if *current_size > MAX_RESPONSE_SIZE {
     811            0 :             return Err(SqlOverHttpError::ResponseTooLarge);
     812            0 :         }
     813              :     }
     814              : 
     815            0 :     let ready = row_stream.ready_status();
     816            0 : 
     817            0 :     // grab the command tag and number of rows affected
     818            0 :     let command_tag = row_stream.command_tag().unwrap_or_default();
     819            0 :     let mut command_tag_split = command_tag.split(' ');
     820            0 :     let command_tag_name = command_tag_split.next().unwrap_or_default();
     821            0 :     let command_tag_count = if command_tag_name == "INSERT" {
     822              :         // INSERT returns OID first and then number of rows
     823            0 :         command_tag_split.nth(1)
     824              :     } else {
     825              :         // other commands return number of rows (if any)
     826            0 :         command_tag_split.next()
     827              :     }
     828            0 :     .and_then(|s| s.parse::<i64>().ok());
     829            0 : 
     830            0 :     info!(
     831            0 :         rows = rows.len(),
     832            0 :         ?ready,
     833            0 :         command_tag,
     834            0 :         "finished reading rows"
     835              :     );
     836              : 
     837            0 :     let columns_len = row_stream.columns().len();
     838            0 :     let mut fields = Vec::with_capacity(columns_len);
     839            0 :     let mut columns = Vec::with_capacity(columns_len);
     840              : 
     841            0 :     for c in row_stream.columns() {
     842            0 :         fields.push(json!({
     843            0 :             "name": c.name().to_owned(),
     844            0 :             "dataTypeID": c.type_().oid(),
     845            0 :             "tableID": c.table_oid(),
     846            0 :             "columnID": c.column_id(),
     847            0 :             "dataTypeSize": c.type_size(),
     848            0 :             "dataTypeModifier": c.type_modifier(),
     849            0 :             "format": "text",
     850            0 :         }));
     851            0 :         columns.push(client.get_type(c.type_oid()).await?);
     852              :     }
     853              : 
     854            0 :     let array_mode = data.array_mode.unwrap_or(parsed_headers.default_array_mode);
     855              : 
     856              :     // convert rows to JSON
     857            0 :     let rows = rows
     858            0 :         .iter()
     859            0 :         .map(|row| pg_text_row_to_json(row, &columns, parsed_headers.raw_output, array_mode))
     860            0 :         .collect::<Result<Vec<_>, _>>()?;
     861              : 
     862              :     // Resulting JSON format is based on the format of node-postgres result.
     863            0 :     let results = json!({
     864            0 :         "command": command_tag_name.to_string(),
     865            0 :         "rowCount": command_tag_count,
     866            0 :         "rows": rows,
     867            0 :         "fields": fields,
     868            0 :         "rowAsArray": array_mode,
     869            0 :     });
     870            0 : 
     871            0 :     Ok((ready, results))
     872            0 : }
        

Generated by: LCOV version 2.1-beta