From c7d6273037b8eb33c8b102c0d124c65a80bc5321 Mon Sep 17 00:00:00 2001 From: Lev Kokotov Date: Fri, 16 Jun 2023 12:57:44 -0700 Subject: [PATCH] Support for prepared statements (#474) * Start prepared statements * parse * Ok * optional * dont rewrite anonymous prepared stmts * Dont rewrite anonymous prep statements * hm? * prep statements * I see! * comment * Print config value * Rewrite bind and add sqlx test * fmt * ok * Fix * Fix stats * its late * clean up PREPARE --- dev/Dockerfile | 2 +- pgcat.toml | 3 + src/admin.rs | 10 + src/client.rs | 215 ++++++- src/config.rs | 9 + src/errors.rs | 7 + src/messages.rs | 293 +++++++++ src/query_router.rs | 5 +- src/server.rs | 43 +- src/stats/server.rs | 15 + tests/rust/.gitignore | 1 + tests/rust/Cargo.lock | 1322 ++++++++++++++++++++++++++++++++++++++++ tests/rust/Cargo.toml | 10 + tests/rust/src/main.rs | 29 + 14 files changed, 1954 insertions(+), 10 deletions(-) create mode 100644 tests/rust/.gitignore create mode 100644 tests/rust/Cargo.lock create mode 100644 tests/rust/Cargo.toml create mode 100644 tests/rust/src/main.rs diff --git a/dev/Dockerfile b/dev/Dockerfile index f2054ef..bd24174 100644 --- a/dev/Dockerfile +++ b/dev/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:bullseye +FROM rust:1.70-bullseye # Dependencies RUN apt-get update -y \ diff --git a/pgcat.toml b/pgcat.toml index e6b54b2..41d0210 100644 --- a/pgcat.toml +++ b/pgcat.toml @@ -60,6 +60,9 @@ tcp_keepalives_count = 5 # Number of seconds between keepalive packets. tcp_keepalives_interval = 5 +# Handle prepared statements. +prepared_statements = true + # Path to TLS Certificate file to use for TLS connections # tls_certificate = ".circleci/server.cert" # Path to TLS private key file to use for TLS connections diff --git a/src/admin.rs b/src/admin.rs index 9ae5e9d..bbca956 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -699,6 +699,8 @@ where ("bytes_sent", DataType::Numeric), ("bytes_received", DataType::Numeric), ("age_seconds", DataType::Numeric), + ("prepare_cache_hit", DataType::Numeric), + ("prepare_cache_miss", DataType::Numeric), ]; let new_map = get_server_stats(); @@ -722,6 +724,14 @@ where .duration_since(server.connect_time()) .as_secs() .to_string(), + server + .prepared_hit_count + .load(Ordering::Relaxed) + .to_string(), + server + .prepared_miss_count + .load(Ordering::Relaxed) + .to_string(), ]; res.put(data_row(&row)); diff --git a/src/client.rs b/src/client.rs index 1ff558b..608d838 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,8 +3,9 @@ use crate::pool::BanReason; /// Handle clients by pretending to be a PostgreSQL server. use bytes::{Buf, BufMut, BytesMut}; use log::{debug, error, info, trace, warn}; +use once_cell::sync::Lazy; use std::collections::HashMap; -use std::sync::Arc; +use std::sync::{atomic::AtomicUsize, Arc}; use std::time::Instant; use tokio::io::{split, AsyncReadExt, BufReader, ReadHalf, WriteHalf}; use tokio::net::TcpStream; @@ -13,7 +14,9 @@ use tokio::sync::mpsc::Sender; use crate::admin::{generate_server_info_for_admin, handle_admin}; use crate::auth_passthrough::refetch_auth_hash; -use crate::config::{get_config, get_idle_client_in_transaction_timeout, Address, PoolMode}; +use crate::config::{ + get_config, get_idle_client_in_transaction_timeout, get_prepared_statements, Address, PoolMode, +}; use crate::constants::*; use crate::messages::*; use crate::plugins::PluginOutput; @@ -25,6 +28,11 @@ use crate::tls::Tls; use tokio_rustls::server::TlsStream; +/// Incrementally count prepared statements +/// to avoid random conflicts in places where the random number generator is weak. +pub static PREPARED_STATEMENT_COUNTER: Lazy> = + Lazy::new(|| Arc::new(AtomicUsize::new(0))); + /// Type of connection received from client. enum ClientConnectionType { Startup, @@ -93,6 +101,9 @@ pub struct Client { /// Used to notify clients about an impending shutdown shutdown: Receiver<()>, + + /// Prepared statements + prepared_statements: HashMap, } /// Client entrypoint. @@ -682,6 +693,7 @@ where application_name: application_name.to_string(), shutdown, connected_to_server: false, + prepared_statements: HashMap::new(), }) } @@ -716,6 +728,7 @@ where application_name: String::from("undefined"), shutdown, connected_to_server: false, + prepared_statements: HashMap::new(), }) } @@ -757,6 +770,10 @@ where // Result returned by one of the plugins. let mut plugin_output = None; + // Prepared statement being executed + let mut prepared_statement = None; + let mut will_prepare = false; + // Our custom protocol loop. // We expect the client to either start a transaction with regular queries // or issue commands for our sharding and server selection protocol. @@ -766,13 +783,16 @@ where self.transaction_mode ); + // Should we rewrite prepared statements and bind messages? + let mut prepared_statements_enabled = get_prepared_statements(); + // Read a complete message from the client, which normally would be // either a `Q` (query) or `P` (prepare, extended protocol). // We can parse it here before grabbing a server from the pool, // in case the client is sending some custom protocol messages, e.g. // SET SHARDING KEY TO 'bigint'; - let message = tokio::select! { + let mut message = tokio::select! { _ = self.shutdown.recv() => { if !self.admin { error_response_terminal( @@ -800,7 +820,21 @@ where // allocate a connection, we wouldn't be able to send back an error message // to the client so we buffer them and defer the decision to error out or not // to when we get the S message - 'D' | 'E' => { + 'D' => { + if prepared_statements_enabled { + let name; + (name, message) = self.rewrite_describe(message).await?; + + if let Some(name) = name { + prepared_statement = Some(name); + } + } + + self.buffer.put(&message[..]); + continue; + } + + 'E' => { self.buffer.put(&message[..]); continue; } @@ -830,6 +864,11 @@ where } 'P' => { + if prepared_statements_enabled { + (prepared_statement, message) = self.rewrite_parse(message)?; + will_prepare = true; + } + self.buffer.put(&message[..]); if query_router.query_parser_enabled() { @@ -846,6 +885,10 @@ where } 'B' => { + if prepared_statements_enabled { + (prepared_statement, message) = self.rewrite_bind(message).await?; + } + self.buffer.put(&message[..]); if query_router.query_parser_enabled() { @@ -1054,7 +1097,48 @@ where // If the client is in session mode, no more custom protocol // commands will be accepted. loop { - let message = match initial_message { + // Only check if we should rewrite prepared statements + // in session mode. In transaction mode, we check at the beginning of + // each transaction. + if !self.transaction_mode { + prepared_statements_enabled = get_prepared_statements(); + } + + debug!("Prepared statement active: {:?}", prepared_statement); + + // We are processing a prepared statement. + if let Some(ref name) = prepared_statement { + debug!("Checking prepared statement is on server"); + // Get the prepared statement the server expects to see. + let statement = match self.prepared_statements.get(name) { + Some(statement) => { + debug!("Prepared statement `{}` found in cache", name); + statement + } + None => { + return Err(Error::ClientError(format!( + "prepared statement `{}` not found", + name + ))) + } + }; + + // Since it's already in the buffer, we don't need to prepare it on this server. + if will_prepare { + server.will_prepare(&statement.name); + will_prepare = false; + } else { + // The statement is not prepared on the server, so we need to prepare it. + if server.should_prepare(&statement.name) { + server.prepare(statement).await?; + } + } + + // Done processing the prepared statement. + prepared_statement = None; + } + + let mut message = match initial_message { None => { trace!("Waiting for message inside transaction or in session mode"); @@ -1173,6 +1257,11 @@ where // Parse // The query with placeholders is here, e.g. `SELECT * FROM users WHERE email = $1 AND active = $2`. 'P' => { + if prepared_statements_enabled { + (prepared_statement, message) = self.rewrite_parse(message)?; + will_prepare = true; + } + if query_router.query_parser_enabled() { if let Ok(ast) = QueryRouter::parse(&message) { if let Ok(output) = query_router.execute_plugins(&ast).await { @@ -1187,12 +1276,25 @@ where // Bind // The placeholder's replacements are here, e.g. 'user@email.com' and 'true' 'B' => { + if prepared_statements_enabled { + (prepared_statement, message) = self.rewrite_bind(message).await?; + } + self.buffer.put(&message[..]); } // Describe // Command a client can issue to describe a previously prepared named statement. 'D' => { + if prepared_statements_enabled { + let name; + (name, message) = self.rewrite_describe(message).await?; + + if let Some(name) = name { + prepared_statement = Some(name); + } + } + self.buffer.put(&message[..]); } @@ -1235,7 +1337,7 @@ where let first_message_code = (*self.buffer.get(0).unwrap_or(&0)) as char; // Almost certainly true - if first_message_code == 'P' { + if first_message_code == 'P' && !prepared_statements_enabled { // Message layout // P followed by 32 int followed by null-terminated statement name // So message code should be in offset 0 of the buffer, first character @@ -1363,6 +1465,107 @@ where } } + /// Rewrite Parse (F) message to set the prepared statement name to one we control. + /// Save it into the client cache. + fn rewrite_parse(&mut self, message: BytesMut) -> Result<(Option, BytesMut), Error> { + let parse: Parse = (&message).try_into()?; + + let name = parse.name.clone(); + + // Don't rewrite anonymous prepared statements + if parse.anonymous() { + debug!("Anonymous prepared statement"); + return Ok((None, message)); + } + + let parse = parse.rename(); + + debug!( + "Renamed prepared statement `{}` to `{}` and saved to cache", + name, parse.name + ); + + self.prepared_statements.insert(name.clone(), parse.clone()); + + Ok((Some(name), parse.try_into()?)) + } + + /// Rewrite the Bind (F) message to use the prepared statement name + /// saved in the client cache. + async fn rewrite_bind( + &mut self, + message: BytesMut, + ) -> Result<(Option, BytesMut), Error> { + let bind: Bind = (&message).try_into()?; + let name = bind.prepared_statement.clone(); + + if bind.anonymous() { + debug!("Anonymous bind message"); + return Ok((None, message)); + } + + match self.prepared_statements.get(&name) { + Some(prepared_stmt) => { + let bind = bind.reassign(prepared_stmt); + + debug!("Rewrote bind `{}` to `{}`", name, bind.prepared_statement); + + Ok((Some(name), bind.try_into()?)) + } + None => { + debug!("Got bind for unknown prepared statement {:?}", bind); + + error_response( + &mut self.write, + &format!( + "prepared statement \"{}\" does not exist", + bind.prepared_statement + ), + ) + .await?; + + Err(Error::ClientError(format!( + "Prepared statement `{}` doesn't exist", + name + ))) + } + } + } + + /// Rewrite the Describe (F) message to use the prepared statement name + /// saved in the client cache. + async fn rewrite_describe( + &mut self, + message: BytesMut, + ) -> Result<(Option, BytesMut), Error> { + let describe: Describe = (&message).try_into()?; + let name = describe.statement_name.clone(); + + if describe.anonymous() { + debug!("Anonymous describe"); + return Ok((None, message)); + } + + match self.prepared_statements.get(&name) { + Some(prepared_stmt) => { + let describe = describe.rename(&prepared_stmt.name); + + debug!( + "Rewrote describe `{}` to `{}`", + name, describe.statement_name + ); + + Ok((Some(name), describe.try_into()?)) + } + + None => { + debug!("Got describe for unknown prepared statement {:?}", describe); + + Ok((None, message)) + } + } + } + /// Release the server from the client: it can't cancel its queries anymore. pub fn release(&self) { let mut guard = self.client_server_map.lock(); diff --git a/src/config.rs b/src/config.rs index 7573d39..66c2075 100644 --- a/src/config.rs +++ b/src/config.rs @@ -320,6 +320,9 @@ pub struct General { pub auth_query: Option, pub auth_query_user: Option, pub auth_query_password: Option, + + #[serde(default)] + pub prepared_statements: bool, } impl General { @@ -434,6 +437,7 @@ impl Default for General { server_lifetime: Self::default_server_lifetime(), server_round_robin: false, validate_config: true, + prepared_statements: false, } } } @@ -1015,6 +1019,7 @@ impl Config { "Server TLS certificate verification: {}", self.general.verify_server_certificate ); + info!("Prepared statements: {}", self.general.prepared_statements); info!( "Plugins: {}", match self.plugins { @@ -1239,6 +1244,10 @@ pub fn get_idle_client_in_transaction_timeout() -> u64 { .idle_client_in_transaction_timeout } +pub fn get_prepared_statements() -> bool { + (*(*CONFIG.load())).general.prepared_statements +} + /// Parse the configuration file located at the path. pub async fn parse(path: &str) -> Result<(), Error> { let mut contents = String::new(); diff --git a/src/errors.rs b/src/errors.rs index b1796ee..c076a3f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -26,6 +26,7 @@ pub enum Error { AuthPassthroughError(String), UnsupportedStatement, QueryRouterParserError(String), + QueryRouterError(String), } #[derive(Clone, PartialEq, Debug)] @@ -121,3 +122,9 @@ impl std::fmt::Display for Error { } } } + +impl From for Error { + fn from(err: std::ffi::NulError) -> Self { + Error::QueryRouterError(err.to_string()) + } +} diff --git a/src/messages.rs b/src/messages.rs index ee4886d..552497f 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -7,11 +7,15 @@ use socket2::{SockRef, TcpKeepalive}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; +use crate::client::PREPARED_STATEMENT_COUNTER; use crate::config::get_config; use crate::errors::Error; + use std::collections::HashMap; +use std::ffi::CString; use std::io::{BufRead, Cursor}; use std::mem; +use std::sync::atomic::Ordering; use std::time::Duration; /// Postgres data type mappings @@ -526,6 +530,13 @@ pub fn command_complete(command: &str) -> BytesMut { res } +pub fn flush() -> BytesMut { + let mut bytes = BytesMut::new(); + bytes.put_u8(b'H'); + bytes.put_i32(4); + bytes +} + /// Write all data in the buffer to the TcpStream. pub async fn write_all(stream: &mut S, buf: BytesMut) -> Result<(), Error> where @@ -689,3 +700,285 @@ impl BytesMutReader for Cursor<&BytesMut> { } } } + +/// Parse (F) message. +/// See: +#[derive(Clone, Debug)] +pub struct Parse { + code: char, + #[allow(dead_code)] + len: i32, + pub name: String, + pub generated_name: String, + query: String, + num_params: i16, + param_types: Vec, +} + +impl TryFrom<&BytesMut> for Parse { + type Error = Error; + + fn try_from(buf: &BytesMut) -> Result { + let mut cursor = Cursor::new(buf); + let code = cursor.get_u8() as char; + let len = cursor.get_i32(); + let name = cursor.read_string()?; + let query = cursor.read_string()?; + let num_params = cursor.get_i16(); + let mut param_types = Vec::new(); + + for _ in 0..num_params { + param_types.push(cursor.get_i32()); + } + + Ok(Parse { + code, + len, + name, + generated_name: prepared_statement_name(), + query, + num_params, + param_types, + }) + } +} + +impl TryFrom for BytesMut { + type Error = Error; + + fn try_from(parse: Parse) -> Result { + let mut bytes = BytesMut::new(); + + let name_binding = CString::new(parse.name)?; + let name = name_binding.as_bytes_with_nul(); + + let query_binding = CString::new(parse.query)?; + let query = query_binding.as_bytes_with_nul(); + + // Recompute length of the message. + let len = 4 // self + + name.len() + + query.len() + + 2 + + 4 * parse.num_params as usize; + + bytes.put_u8(parse.code as u8); + bytes.put_i32(len as i32); + bytes.put_slice(name); + bytes.put_slice(query); + bytes.put_i16(parse.num_params); + for param in parse.param_types { + bytes.put_i32(param); + } + + Ok(bytes) + } +} + +impl TryFrom<&Parse> for BytesMut { + type Error = Error; + + fn try_from(parse: &Parse) -> Result { + parse.clone().try_into() + } +} + +impl Parse { + pub fn rename(mut self) -> Self { + self.name = self.generated_name.to_string(); + self + } + + pub fn anonymous(&self) -> bool { + self.name.is_empty() + } +} + +/// Bind (B) message. +/// See: +#[derive(Clone, Debug)] +pub struct Bind { + code: char, + #[allow(dead_code)] + len: i64, + portal: String, + pub prepared_statement: String, + num_param_format_codes: i16, + param_format_codes: Vec, + num_param_values: i16, + param_values: Vec<(i32, BytesMut)>, + num_result_column_format_codes: i16, + result_columns_format_codes: Vec, +} + +impl TryFrom<&BytesMut> for Bind { + type Error = Error; + + fn try_from(buf: &BytesMut) -> Result { + let mut cursor = Cursor::new(buf); + let code = cursor.get_u8() as char; + let len = cursor.get_i32(); + let portal = cursor.read_string()?; + let prepared_statement = cursor.read_string()?; + let num_param_format_codes = cursor.get_i16(); + let mut param_format_codes = Vec::new(); + + for _ in 0..num_param_format_codes { + param_format_codes.push(cursor.get_i16()); + } + + let num_param_values = cursor.get_i16(); + let mut param_values = Vec::new(); + + for _ in 0..num_param_values { + let param_len = cursor.get_i32(); + let mut param = BytesMut::with_capacity(param_len as usize); + param.resize(param_len as usize, b'0'); + cursor.copy_to_slice(&mut param); + param_values.push((param_len, param)); + } + + let num_result_column_format_codes = cursor.get_i16(); + let mut result_columns_format_codes = Vec::new(); + + for _ in 0..num_result_column_format_codes { + result_columns_format_codes.push(cursor.get_i16()); + } + + Ok(Bind { + code, + len: len as i64, + portal, + prepared_statement, + num_param_format_codes, + param_format_codes, + num_param_values, + param_values, + num_result_column_format_codes, + result_columns_format_codes, + }) + } +} + +impl TryFrom for BytesMut { + type Error = Error; + + fn try_from(bind: Bind) -> Result { + let mut bytes = BytesMut::new(); + + let portal_binding = CString::new(bind.portal)?; + let portal = portal_binding.as_bytes_with_nul(); + + let prepared_statement_binding = CString::new(bind.prepared_statement)?; + let prepared_statement = prepared_statement_binding.as_bytes_with_nul(); + + let mut len = 4 // self + + portal.len() + + prepared_statement.len() + + 2 // num_param_format_codes + + 2 * bind.num_param_format_codes as usize // num_param_format_codes + + 2; // num_param_values + + for (param_len, _) in &bind.param_values { + len += 4 + *param_len as usize; + } + len += 2; // num_result_column_format_codes + len += 2 * bind.num_result_column_format_codes as usize; + + bytes.put_u8(bind.code as u8); + bytes.put_i32(len as i32); + bytes.put_slice(portal); + bytes.put_slice(prepared_statement); + bytes.put_i16(bind.num_param_format_codes); + for param_format_code in bind.param_format_codes { + bytes.put_i16(param_format_code); + } + bytes.put_i16(bind.num_param_values); + for (param_len, param) in bind.param_values { + bytes.put_i32(param_len); + bytes.put_slice(¶m); + } + bytes.put_i16(bind.num_result_column_format_codes); + for result_column_format_code in bind.result_columns_format_codes { + bytes.put_i16(result_column_format_code); + } + + Ok(bytes) + } +} + +impl Bind { + pub fn reassign(mut self, parse: &Parse) -> Self { + self.prepared_statement = parse.name.clone(); + self + } + + pub fn anonymous(&self) -> bool { + self.prepared_statement.is_empty() + } +} + +#[derive(Debug, Clone)] +pub struct Describe { + code: char, + + #[allow(dead_code)] + len: i32, + target: char, + pub statement_name: String, +} + +impl TryFrom<&BytesMut> for Describe { + type Error = Error; + + fn try_from(bytes: &BytesMut) -> Result { + let mut cursor = Cursor::new(bytes); + let code = cursor.get_u8() as char; + let len = cursor.get_i32(); + let target = cursor.get_u8() as char; + let statement_name = cursor.read_string()?; + + Ok(Describe { + code, + len, + target, + statement_name, + }) + } +} + +impl TryFrom for BytesMut { + type Error = Error; + + fn try_from(describe: Describe) -> Result { + let mut bytes = BytesMut::new(); + let statement_name_binding = CString::new(describe.statement_name)?; + let statement_name = statement_name_binding.as_bytes_with_nul(); + let len = 4 + 1 + statement_name.len(); + + bytes.put_u8(describe.code as u8); + bytes.put_i32(len as i32); + bytes.put_u8(describe.target as u8); + bytes.put_slice(statement_name); + + Ok(bytes) + } +} + +impl Describe { + pub fn rename(mut self, name: &str) -> Self { + self.statement_name = name.to_string(); + self + } + + pub fn anonymous(&self) -> bool { + self.statement_name.is_empty() + } +} + +pub fn prepared_statement_name() -> String { + format!( + "P_{}", + PREPARED_STATEMENT_COUNTER.fetch_add(1, Ordering::SeqCst) + ) +} diff --git a/src/query_router.rs b/src/query_router.rs index 3e3a23a..126b813 100644 --- a/src/query_router.rs +++ b/src/query_router.rs @@ -331,7 +331,7 @@ impl QueryRouter { Some((command, value)) } - pub fn parse(message: &BytesMut) -> Result, Error> { + pub fn parse(message: &BytesMut) -> Result, Error> { let mut message_cursor = Cursor::new(message); let code = message_cursor.get_u8() as char; @@ -348,12 +348,13 @@ impl QueryRouter { // Parse (prepared statement) 'P' => { // Reads statement name - message_cursor.read_string().unwrap(); + let _name = message_cursor.read_string().unwrap(); // Reads query string let query = message_cursor.read_string().unwrap(); debug!("Prepared statement: '{}'", query); + query } diff --git a/src/server.rs b/src/server.rs index 32dd91f..ab29db0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -5,7 +5,7 @@ use fallible_iterator::FallibleIterator; use log::{debug, error, info, trace, warn}; use parking_lot::{Mutex, RwLock}; use postgres_protocol::message; -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use std::io::Read; use std::net::IpAddr; use std::sync::Arc; @@ -198,6 +198,9 @@ pub struct Server { /// Should clean up dirty connections? cleanup_connections: bool, + + /// Prepared statements + prepared_statements: BTreeSet, } impl Server { @@ -692,6 +695,7 @@ impl Server { )), }, cleanup_connections, + prepared_statements: BTreeSet::new(), }; server.set_name("pgcat").await?; @@ -910,6 +914,43 @@ impl Server { Ok(bytes) } + pub fn will_prepare(&mut self, name: &str) { + debug!("Will prepare `{}`", name); + + self.prepared_statements.insert(name.to_string()); + } + + pub fn should_prepare(&self, name: &str) -> bool { + let should_prepare = !self.prepared_statements.contains(name); + + debug!("Should prepare `{}`: {}", name, should_prepare); + + if should_prepare { + self.stats.prepared_cache_miss(); + } else { + self.stats.prepared_cache_hit(); + } + + should_prepare + } + + pub async fn prepare(&mut self, parse: &Parse) -> Result<(), Error> { + debug!("Preparing `{}`", parse.name); + + let bytes: BytesMut = parse.try_into()?; + self.send(&bytes).await?; + self.send(&flush()).await?; + + // Read and discard ParseComplete (B) + let _ = read_message(&mut self.stream).await?; + + self.prepared_statements.insert(parse.name.to_string()); + + debug!("Prepared `{}`", parse.name); + + Ok(()) + } + /// If the server is still inside a transaction. /// If the client disconnects while the server is in a transaction, we will clean it up. pub fn in_transaction(&self) -> bool { diff --git a/src/stats/server.rs b/src/stats/server.rs index e156ee0..6fb2dc9 100644 --- a/src/stats/server.rs +++ b/src/stats/server.rs @@ -47,6 +47,8 @@ pub struct ServerStats { pub transaction_count: Arc, pub query_count: Arc, pub error_count: Arc, + pub prepared_hit_count: Arc, + pub prepared_miss_count: Arc, } impl Default for ServerStats { @@ -63,6 +65,8 @@ impl Default for ServerStats { query_count: Arc::new(AtomicU64::new(0)), error_count: Arc::new(AtomicU64::new(0)), reporter: get_reporter(), + prepared_hit_count: Arc::new(AtomicU64::new(0)), + prepared_miss_count: Arc::new(AtomicU64::new(0)), } } } @@ -172,6 +176,7 @@ impl ServerStats { self.set_application(application_name.to_string()); self.address.stats.query_count_add(); self.address.stats.query_time_add(milliseconds); + self.query_count.fetch_add(1, Ordering::Relaxed); } /// Report a transaction executed by a client a server @@ -198,4 +203,14 @@ impl ServerStats { .fetch_add(amount_bytes as u64, Ordering::Relaxed); self.address.stats.bytes_received_add(amount_bytes as u64); } + + /// Report a prepared statement that already exists on the server. + pub fn prepared_cache_hit(&self) { + self.prepared_hit_count.fetch_add(1, Ordering::Relaxed); + } + + /// Report a prepared statement that does not exist on the server yet. + pub fn prepared_cache_miss(&self) { + self.prepared_miss_count.fetch_add(1, Ordering::Relaxed); + } } diff --git a/tests/rust/.gitignore b/tests/rust/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/tests/rust/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/tests/rust/Cargo.lock b/tests/rust/Cargo.lock new file mode 100644 index 0000000..f24df81 --- /dev/null +++ b/tests/rust/Cargo.lock @@ -0,0 +1,1322 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "allocator-api2" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" + +[[package]] +name = "atoi" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-intrusive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.11.2", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" +dependencies = [ + "hashbrown 0.14.0", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnetwork" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f84f1612606f3753f205a4e9a2efd6fe5b4c573a6269b2cc6c3003d44a0d127" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.146" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "md-5" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +dependencies = [ + "digest", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.8", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall 0.2.16", + "thiserror", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rust" +version = "0.1.0" +dependencies = [ + "sqlx", + "tokio", +] + +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.2", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "sqlformat" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188" +dependencies = [ + "sqlx-core", + "sqlx-macros", +] + +[[package]] +name = "sqlx-core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" +dependencies = [ + "ahash 0.7.6", + "atoi", + "base64 0.13.1", + "bitflags", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "dirs", + "dotenvy", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-util", + "hashlink", + "hex", + "hkdf", + "hmac", + "indexmap", + "ipnetwork", + "itoa", + "libc", + "log", + "md-5", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rand", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", + "time", + "tokio-stream", + "url", + "uuid", + "webpki-roots", + "whoami", +] + +[[package]] +name = "sqlx-macros" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" +dependencies = [ + "dotenvy", + "either", + "heck", + "once_cell", + "proc-macro2", + "quote", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-rt", + "syn 1.0.109", + "url", +] + +[[package]] +name = "sqlx-rt" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" +dependencies = [ + "once_cell", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "time" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot 0.12.1", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "uuid" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.18", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "whoami" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/tests/rust/Cargo.toml b/tests/rust/Cargo.toml new file mode 100644 index 0000000..1600dac --- /dev/null +++ b/tests/rust/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +sqlx = { version = "0.6.2", features = [ "runtime-tokio-rustls", "postgres", "json", "tls", "migrate", "time", "uuid", "ipnetwork"] } +tokio = { version = "1", features = ["full"] } diff --git a/tests/rust/src/main.rs b/tests/rust/src/main.rs new file mode 100644 index 0000000..79667bc --- /dev/null +++ b/tests/rust/src/main.rs @@ -0,0 +1,29 @@ +#[tokio::main] +async fn main() { + test_prepared_statements().await; +} + +async fn test_prepared_statements() { + let pool = sqlx::postgres::PgPoolOptions::new() + .max_connections(5) + .connect("postgres://sharding_user:sharding_user@127.0.0.1:6432/sharded_db") + .await + .unwrap(); + + let mut handles = Vec::new(); + + for _ in 0..5 { + let pool = pool.clone(); + let handle = tokio::task::spawn(async move { + for _ in 0..1000 { + sqlx::query("SELECT 1").fetch_all(&pool).await.unwrap(); + } + }); + + handles.push(handle); + } + + for handle in handles { + handle.await.unwrap(); + } +}