-
Notifications
You must be signed in to change notification settings - Fork 256
feat: partial sharding support for PREPARE and EXECUTE #1368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4f1b98c
5fa99b6
f0df3ca
53e0237
edf0627
bf6675f
fc209d7
5cdec64
c16ec72
44d9020
01a9579
312a6cb
99526be
e0d26c6
6a1db8a
f01d710
de027de
6b8cbe2
ebcc0c7
158f761
0038a5e
42634de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,15 +5,16 @@ use std::{ | |
| time::{Duration, Instant}, | ||
| }; | ||
|
|
||
| use crate::util::time::deadline; | ||
| use crate::{ | ||
| frontend::{self, prepared_statements::GlobalCache}, | ||
| net::{ | ||
| Close, CloseComplete, FromBytes, Message, ParseComplete, Protocol, ProtocolMessage, | ||
| ToBytes, | ||
| messages::{ParameterDescription, RowDescription, parse::Parse}, | ||
| }, | ||
| state::State, | ||
| }; | ||
| use crate::{net::ErrorResponse, util::time::deadline}; | ||
| use parking_lot::RwLock; | ||
| use pgdog_stats::PreparedStatementsConfig; | ||
|
|
||
|
|
@@ -105,6 +106,7 @@ pub struct PreparedStatements { | |
| config: PreparedStatementsConfig, | ||
| memory_used: usize, | ||
| oids: Arc<Oids>, | ||
| server_state: State, | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
|
|
@@ -126,6 +128,7 @@ impl PreparedStatements { | |
| config: PreparedStatementsConfig::default(), | ||
| memory_used: 0, | ||
| oids, | ||
| server_state: State::Idle, | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -135,6 +138,10 @@ impl PreparedStatements { | |
| self.config = config; | ||
| } | ||
|
|
||
| pub(super) fn set_server_state(&mut self, state: State) { | ||
| self.server_state = state; | ||
| } | ||
|
|
||
| /// Current prepared statement settings. | ||
| pub fn config(&self) -> PreparedStatementsConfig { | ||
| self.config | ||
|
|
@@ -272,6 +279,8 @@ impl PreparedStatements { | |
|
|
||
| if !parse.anonymous() { | ||
| if self.contains(parse.name()) { | ||
| // TODO(lev): perform the same in errored transaction check | ||
| // as we do for PREPARE below. | ||
| self.state.add_simulated(ParseComplete.message()?); | ||
| return Ok(HandleResult::Drop); | ||
| } else { | ||
|
|
@@ -302,15 +311,43 @@ impl PreparedStatements { | |
| self.state.add('3'); | ||
| } | ||
| } | ||
| ProtocolMessage::Prepare { name, .. } => { | ||
| if self.contains(name) { | ||
| ProtocolMessage::PrepareFromClient(prepare) => { | ||
| use crate::net::{CommandComplete, ReadyForQuery}; | ||
| if self.contains(prepare.name()) { | ||
| if self.server_state == State::TransactionError { | ||
| self.state | ||
| .add_simulated(ErrorResponse::in_failed_transaction().message()?); | ||
| } else { | ||
| self.state | ||
| .add_simulated(CommandComplete::from_str("PREPARE").message()?); | ||
| } | ||
|
|
||
| self.state.add_simulated( | ||
| if self.server_state == State::TransactionError { | ||
| ReadyForQuery::error() | ||
| } else { | ||
| ReadyForQuery::in_transaction( | ||
| self.server_state == State::IdleInTransaction, | ||
| ) | ||
| } | ||
| .message()?, | ||
| ); | ||
| return Ok(HandleResult::Drop); | ||
| } else { | ||
| self.parses.push_back(prepare.name().to_owned()); | ||
| self.state.add(ExecutionCode::ReadyForQuery); | ||
| } | ||
| } | ||
| ProtocolMessage::EnsurePrepared(prepare) => { | ||
| if self.contains(prepare.name()) { | ||
| return Ok(HandleResult::Drop); | ||
| } else { | ||
| self.parses.push_back(name.clone()); | ||
| self.parses.push_back(prepare.name().to_string()); | ||
| self.state.add_ignore('C'); | ||
|
|
||
| // Prepare turns into a Simple Query ('Q') so it expects a regular RFQ back. | ||
| self.state.add_ignore(ExecutionCode::ReadyForQuery); | ||
| self.parses.push_back(prepare.name().to_owned()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we pushing name twice in this block |
||
| return Ok(HandleResult::Forward); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -569,7 +569,7 @@ impl Server { | |
| if let Some(message) = self.prepared_statements.state_mut().get_simulated() { | ||
| // INVARIANT: omni dedup in multi_shard relies on this being process-unique; | ||
| // never substitute a non-unique value here. | ||
| return Ok(message.backend(self.id)); | ||
| break message.backend(self.id); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why this change? that seems like simulated prepared will now be processed and setting
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because we need We just push whatever message we "simulate" through the state manager though and it seems to work. |
||
| } | ||
| match self.stream_buffer.read(self.stream.as_mut().unwrap()).await { | ||
| Ok(message) => { | ||
|
|
@@ -680,6 +680,9 @@ impl Server { | |
| _ => (), | ||
| } | ||
|
|
||
| self.prepared_statements | ||
| .set_server_state(self.stats.get_state()); | ||
|
|
||
| trace!("{:#?} <<< [{}]", message, self.addr()); | ||
|
|
||
| Ok(message) | ||
|
|
@@ -1326,16 +1329,18 @@ impl Drop for Server { | |
| pub mod test { | ||
| use std::time::SystemTime; | ||
|
|
||
| use bytes::{BufMut, BytesMut}; | ||
| use bytes::{BufMut, Bytes, BytesMut}; | ||
| use pgdog_stats::PreparedStatementsConfig; | ||
| use tokio::{ | ||
| io::{AsyncReadExt, AsyncWriteExt}, | ||
| net::TcpListener, | ||
| }; | ||
|
|
||
| use crate::{ | ||
| backend::pool::token_cache::TokenCache, config::Memory, frontend::PreparedStatements, | ||
| net::*, | ||
| backend::pool::token_cache::TokenCache, | ||
| config::Memory, | ||
| frontend::{PreparedStatements, RewritePlan}, | ||
| net::{Prepare, *}, | ||
| }; | ||
|
|
||
| use super::{Error, *}; | ||
|
|
@@ -2314,20 +2319,20 @@ pub mod test { | |
|
|
||
| #[tokio::test] | ||
| async fn test_manual_prepared() { | ||
| crate::logger(); | ||
| let mut server = test_server().await; | ||
|
|
||
| let mut prep = PreparedStatements::new(); | ||
| let mut parse = Parse::named("test", "SELECT 1::bigint"); | ||
| prep.insert_prepare(&mut parse); | ||
| assert_eq!(parse.name(), "__pgdog_1"); | ||
| let name = "test"; | ||
| let query = Bytes::from("SELECT 1::bigint".to_owned()); | ||
| let prepare = prep.insert_prepare(name, query.clone(), &RewritePlan::default()); | ||
| assert_eq!(prepare.name(), "__pgdog_1"); | ||
|
|
||
| server | ||
| .send( | ||
| &vec![ProtocolMessage::from(Query::new(format!( | ||
| "PREPARE {} AS {}", | ||
| parse.name(), | ||
| parse.query() | ||
| )))] | ||
| &vec![ProtocolMessage::Query(Query::new( | ||
| "PREPARE __pgdog_1 AS SELECT 1::bigint", | ||
| ))] | ||
| .into(), | ||
| ) | ||
| .await | ||
|
|
@@ -4362,6 +4367,126 @@ pub mod test { | |
| ); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_prepare_from_client() { | ||
| let mut server = test_server().await; | ||
|
|
||
| // The last 2 will be simulated | ||
| // and we won't receive a "prepared statement already exists" error. | ||
| for _ in 0..3 { | ||
| server | ||
| .send( | ||
| &vec![ProtocolMessage::PrepareFromClient(Prepare::new( | ||
| "__stmt_1", | ||
| "PREPARE __pgdog_template_name AS SELECT $1", | ||
| ))] | ||
| .into(), | ||
| ) | ||
| .await | ||
| .unwrap(); | ||
|
|
||
| for c in ['C', 'Z'] { | ||
| let msg = server.read().await.unwrap(); | ||
| assert_eq!(msg.code(), c); | ||
| } | ||
| } | ||
|
|
||
| assert!(server.prepared_statements_mut().contains("__stmt_1")); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_prepared_execute() { | ||
| let mut server = test_server().await; | ||
|
|
||
| for _ in 0..3 { | ||
| let req = vec![ | ||
| ProtocolMessage::EnsurePrepared(Prepare::new( | ||
| "__stmt_1", | ||
| "PREPARE __pgdog_template_name (int) AS SELECT $1", | ||
| )), | ||
| ProtocolMessage::Query(Query::new("EXECUTE __stmt_1 (1)")), | ||
| ]; | ||
|
|
||
| server.send(&req.into()).await.unwrap(); | ||
|
|
||
| for c in ['T', 'D', 'C', 'Z'] { | ||
| let msg = server.read().await.unwrap(); | ||
| assert_eq!(msg.code(), c); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_prepare_in_transaction() { | ||
| let mut server = test_server().await; | ||
|
|
||
| server.execute("BEGIN").await.unwrap(); | ||
|
|
||
| for _ in 0..3 { | ||
| server | ||
| .send( | ||
| &vec![ProtocolMessage::PrepareFromClient(Prepare::new( | ||
| "__stmt_1", | ||
| "PREPARE __pgdog_template_name AS SELECT $1", | ||
| ))] | ||
| .into(), | ||
| ) | ||
| .await | ||
| .unwrap(); | ||
|
|
||
| let cmd = server.read().await.unwrap(); | ||
| assert_eq!(cmd.code(), 'C'); | ||
| let rfq = server.read().await.unwrap(); | ||
| assert!(rfq.in_transaction()); | ||
| } | ||
|
|
||
| server.execute("ROLLBACK").await.unwrap(); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_prepare_in_transaction_error() { | ||
| let mut server = test_server().await; | ||
|
|
||
| server | ||
| .send( | ||
| &vec![ProtocolMessage::PrepareFromClient(Prepare::new( | ||
| "__stmt_1", | ||
| "PREPARE __pgdog_template_name AS SELECT $1", | ||
| ))] | ||
| .into(), | ||
| ) | ||
| .await | ||
| .unwrap(); | ||
|
|
||
| let cmd = server.read().await.unwrap(); | ||
| assert_eq!(cmd.code(), 'C'); | ||
| let rfq = server.read().await.unwrap(); | ||
| assert!(!rfq.in_transaction()); | ||
|
|
||
| server.execute("BEGIN").await.unwrap(); | ||
|
|
||
| let _ = server.execute("SELECT asd").await; | ||
|
|
||
| for _ in 0..3 { | ||
| server | ||
| .send( | ||
| &vec![ProtocolMessage::PrepareFromClient(Prepare::new( | ||
| "__stmt_1", | ||
| "PREPARE __pgdog_template_name AS SELECT $1", | ||
| ))] | ||
| .into(), | ||
| ) | ||
| .await | ||
| .unwrap(); | ||
|
|
||
| let err = ErrorResponse::try_from(server.read().await.unwrap()).unwrap(); | ||
| assert_eq!(err.code, "25P02"); | ||
|
|
||
| let rfq = ReadyForQuery::try_from(server.read().await.unwrap()).unwrap(); | ||
| assert!(rfq.is_transaction_aborted()); | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_effective_max_age_default_is_base() { | ||
| let server = Server::default(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to use check_prepared at some extend - the ttl functionality and parses deduplication is not working for prepared rn
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#1382