Skip to content

Commit 7b0ceef

Browse files
authored
Constants, comments, CI fixes, dead code clean-up (#21)
* constants * server.rs docs * client.rs comments * dead code; comments * comment * query cancellation comments * remove unnecessary cast * move db setup up one step * query cancellation test * new line; good night
1 parent bb84dce commit 7b0ceef

File tree

8 files changed

+208
-115
lines changed

8 files changed

+208
-115
lines changed

.circleci/run_tests.sh

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
set -e
44
set -o xtrace
55

6+
psql -e -h 127.0.0.1 -p 5432 -U postgres -f tests/sharding/query_routing_setup.sql
7+
68
./target/debug/pgcat &
79

810
sleep 1
911

10-
psql -e -h 127.0.0.1 -p 5432 -U postgres -f tests/sharding/query_routing_setup.sql
11-
1212
# Setup PgBench
1313
pgbench -i -h 127.0.0.1 -p 6432
1414

@@ -18,6 +18,13 @@ pgbench -h 127.0.0.1 -p 6432 -t 500 -c 2 --protocol simple
1818
# Extended protocol
1919
pgbench -h 127.0.0.1 -p 6432 -t 500 -c 2 --protocol extended
2020

21+
# COPY TO STDOUT test
22+
psql -h 127.0.0.1 -p 6432 -c 'COPY (SELECT * FROM pgbench_accounts LIMIT 15) TO STDOUT;' > /dev/null
23+
24+
# Query cancellation test
25+
(psql -h 127.0.0.1 -p 6432 -c 'SELECT pg_sleep(5)' || true) &
26+
killall psql -s SIGINT
27+
2128
# Sharding insert
2229
psql -e -h 127.0.0.1 -p 6432 -f tests/sharding/query_routing_test_insert.sql
2330

@@ -29,3 +36,6 @@ psql -e -h 127.0.0.1 -p 6432 -f tests/sharding/query_routing_test_primary_replic
2936

3037
# Attempt clean shut down
3138
killall pgcat -s SIGINT
39+
40+
# Allow for graceful shutdown
41+
sleep 1

src/client.rs

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ use bytes::{Buf, BufMut, BytesMut};
55
use once_cell::sync::OnceCell;
66
use regex::Regex;
77
use tokio::io::{AsyncReadExt, BufReader};
8-
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
9-
use tokio::net::TcpStream;
8+
use tokio::net::{
9+
tcp::{OwnedReadHalf, OwnedWriteHalf},
10+
TcpStream,
11+
};
1012

1113
use std::collections::HashMap;
1214

1315
use crate::config::Role;
16+
use crate::constants::*;
1417
use crate::errors::Error;
1518
use crate::messages::*;
1619
use crate::pool::{ClientServerMap, ConnectionPool};
@@ -97,15 +100,15 @@ impl Client {
97100

98101
match code {
99102
// Client wants SSL. We don't support it at the moment.
100-
80877103 => {
103+
SSL_REQUEST_CODE => {
101104
let mut no = BytesMut::with_capacity(1);
102105
no.put_u8(b'N');
103106

104107
write_all(&mut stream, no).await?;
105108
}
106109

107110
// Regular startup message.
108-
196608 => {
111+
PROTOCOL_VERSION_NUMBER => {
109112
// TODO: perform actual auth.
110113
let parameters = parse_startup(bytes.clone())?;
111114

@@ -138,7 +141,7 @@ impl Client {
138141
}
139142

140143
// Query cancel request.
141-
80877102 => {
144+
CANCEL_REQUEST_CODE => {
142145
let (read, write) = stream.into_split();
143146

144147
let process_id = bytes.get_i32();
@@ -168,23 +171,31 @@ impl Client {
168171

169172
/// Client loop. We handle all messages between the client and the database here.
170173
pub async fn handle(&mut self, mut pool: ConnectionPool) -> Result<(), Error> {
171-
// Special: cancelling existing running query
174+
// The client wants to cancel a query it has issued previously.
172175
if self.cancel_mode {
173176
let (process_id, secret_key, address, port) = {
174177
let guard = self.client_server_map.lock().unwrap();
178+
175179
match guard.get(&(self.process_id, self.secret_key)) {
176180
// Drop the mutex as soon as possible.
181+
// We found the server the client is using for its query
182+
// that it wants to cancel.
177183
Some((process_id, secret_key, address, port)) => (
178184
process_id.clone(),
179185
secret_key.clone(),
180186
address.clone(),
181187
port.clone(),
182188
),
189+
190+
// The client doesn't know / got the wrong server,
191+
// we're closing the connection for security reasons.
183192
None => return Ok(()),
184193
}
185194
};
186195

187-
// TODO: pass actual server host and port somewhere.
196+
// Opens a new separate connection to the server, sends the backend_id
197+
// and secret_key and then closes it for security reasons. No other interactions
198+
// take place.
188199
return Ok(Server::cancel(&address, &port, process_id, secret_key).await?);
189200
}
190201

@@ -217,7 +228,7 @@ impl Client {
217228
};
218229

219230
// Parse for special server role selection command.
220-
//
231+
// SET SERVER ROLE TO '(primary|replica)';
221232
match self.select_role(message.clone()) {
222233
Some(r) => {
223234
custom_protocol_response_ok(&mut self.write, "SET SERVER ROLE").await?;
@@ -236,15 +247,17 @@ impl Client {
236247
}
237248
};
238249

239-
let mut proxy = connection.0;
250+
let mut reference = connection.0;
240251
let _address = connection.1;
241-
let server = &mut *proxy;
252+
let server = &mut *reference;
242253

243254
// Claim this server as mine for query cancellation.
244255
server.claim(self.process_id, self.secret_key);
245256

257+
// Transaction loop. Multiple queries can be issued by the client here.
258+
// The connection belongs to the client until the transaction is over,
259+
// or until the client disconnects if we are in session mode.
246260
loop {
247-
// No messages in the buffer, read one.
248261
let mut message = if message.len() == 0 {
249262
match read_message(&mut self.read).await {
250263
Ok(message) => message,
@@ -268,19 +281,26 @@ impl Client {
268281
msg
269282
};
270283

271-
let original = message.clone(); // To be forwarded to the server
284+
// The message will be forwarded to the server intact. We still would like to
285+
// parse it below to figure out what to do with it.
286+
let original = message.clone();
287+
272288
let code = message.get_u8() as char;
273289
let _len = message.get_i32() as usize;
274290

275291
match code {
292+
// ReadyForQuery
276293
'Q' => {
277294
// TODO: implement retries here for read-only transactions.
278295
server.send(original).await?;
279296

297+
// Read all data the server has to offer, which can be multiple messages
298+
// buffered in 8196 bytes chunks.
280299
loop {
281300
// TODO: implement retries here for read-only transactions.
282301
let response = server.recv().await?;
283302

303+
// Send server reply to the client.
284304
match write_all_half(&mut self.write, response).await {
285305
Ok(_) => (),
286306
Err(err) => {
@@ -294,15 +314,18 @@ impl Client {
294314
}
295315
}
296316

297-
// Send statistic
317+
// Report query executed statistics.
298318
self.stats.query();
299319

300-
// Transaction over
320+
// The transaction is over, we can release the connection back to the pool.
301321
if !server.in_transaction() {
322+
// Report transaction executed statistics.
302323
self.stats.transaction();
303324

304-
// Release server
325+
// Release server back to the pool if we are in transaction mode.
326+
// If we are in session mode, we keep the server until the client disconnects.
305327
if self.transaction_mode {
328+
// Report this client as idle.
306329
self.stats.client_idle();
307330

308331
shard = None;
@@ -313,6 +336,7 @@ impl Client {
313336
}
314337
}
315338

339+
// Terminate
316340
'X' => {
317341
// Client closing. Rollback and clean up
318342
// connection before releasing into the pool.
@@ -326,35 +350,46 @@ impl Client {
326350
return Ok(());
327351
}
328352

353+
// Parse
354+
// The query with placeholders is here, e.g. `SELECT * FROM users WHERE email = $1 AND active = $2`.
329355
'P' => {
330-
// Extended protocol, let's buffer most of it
331356
self.buffer.put(&original[..]);
332357
}
333358

359+
// Bind
360+
// The placeholder's replacements are here, e.g. 'user@email.com' and 'true'
334361
'B' => {
335362
self.buffer.put(&original[..]);
336363
}
337364

338365
// Describe
366+
// Command a client can issue to describe a previously prepared named statement.
339367
'D' => {
340368
self.buffer.put(&original[..]);
341369
}
342370

371+
// Execute
372+
// Execute a prepared statement prepared in `P` and bound in `B`.
343373
'E' => {
344374
self.buffer.put(&original[..]);
345375
}
346376

377+
// Sync
378+
// Frontend (client) is asking for the query result now.
347379
'S' => {
348-
// Extended protocol, client requests sync
349380
self.buffer.put(&original[..]);
350381

351-
// TODO: retries for read-only transactions
382+
// TODO: retries for read-only transactions.
352383
server.send(self.buffer.clone()).await?;
384+
353385
self.buffer.clear();
354386

387+
// Read all data the server has to offer, which can be multiple messages
388+
// buffered in 8196 bytes chunks.
355389
loop {
356390
// TODO: retries for read-only transactions
357391
let response = server.recv().await?;
392+
358393
match write_all_half(&mut self.write, response).await {
359394
Ok(_) => (),
360395
Err(err) => {
@@ -368,9 +403,11 @@ impl Client {
368403
}
369404
}
370405

406+
// Report query executed statistics.
371407
self.stats.query();
372408

373-
// Release server
409+
// Release server back to the pool if we are in transaction mode.
410+
// If we are in session mode, we keep the server until the client disconnects.
374411
if !server.in_transaction() {
375412
self.stats.transaction();
376413

@@ -392,10 +429,13 @@ impl Client {
392429
server.send(original).await?;
393430
}
394431

432+
// CopyDone or CopyFail
433+
// Copy is done, successfully or not.
395434
'c' | 'f' => {
396-
// Copy is done.
397435
server.send(original).await?;
436+
398437
let response = server.recv().await?;
438+
399439
match write_all_half(&mut self.write, response).await {
400440
Ok(_) => (),
401441
Err(err) => {
@@ -404,24 +444,29 @@ impl Client {
404444
}
405445
};
406446

407-
// Release the server
447+
// Release server back to the pool if we are in transaction mode.
448+
// If we are in session mode, we keep the server until the client disconnects.
408449
if !server.in_transaction() {
409450
self.stats.transaction();
410451

411452
if self.transaction_mode {
412453
shard = None;
413454
role = self.default_server_role;
455+
414456
break;
415457
}
416458
}
417459
}
418460

461+
// Some unexpected message. We either did not implement the protocol correctly
462+
// or this is not a Postgres client we're talking to.
419463
_ => {
420464
println!(">>> Unexpected code: {}", code);
421465
}
422466
}
423467
}
424468

469+
// The server is no longer bound to us, we can't cancel it's queries anymore.
425470
self.release();
426471
}
427472
}
@@ -450,18 +495,21 @@ impl Client {
450495

451496
let len = buf.get_i32();
452497
let query = String::from_utf8_lossy(&buf[..len as usize - 4 - 1]).to_ascii_uppercase(); // Don't read the ternminating null
498+
453499
let rgx = match SHARDING_REGEX_RE.get() {
454500
Some(r) => r,
455501
None => return None,
456502
};
457503

458504
if rgx.is_match(&query) {
459505
let shard = query.split("'").collect::<Vec<&str>>()[1];
506+
460507
match shard.parse::<i64>() {
461508
Ok(shard) => {
462509
let sharder = Sharder::new(shards);
463510
Some(sharder.pg_bigint_hash(shard))
464511
}
512+
465513
Err(_) => None,
466514
}
467515
} else {
@@ -481,6 +529,7 @@ impl Client {
481529

482530
let len = buf.get_i32();
483531
let query = String::from_utf8_lossy(&buf[..len as usize - 4 - 1]).to_ascii_uppercase();
532+
484533
let rgx = match ROLE_REGEX_RE.get() {
485534
Some(r) => r,
486535
None => return None,
@@ -490,6 +539,7 @@ impl Client {
490539
// it'll be time to abstract :).
491540
if rgx.is_match(&query) {
492541
let role = query.split("'").collect::<Vec<&str>>()[1];
542+
493543
match role {
494544
"PRIMARY" => Some(Role::Primary),
495545
"REPLICA" => Some(Role::Replica),

src/constants.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// Various protocol constants, as defined in
2+
/// https://www.postgresql.org/docs/12/protocol-message-formats.html
3+
/// and elsewhere in the source code.
4+
/// Also other constants we use elsewhere.
5+
6+
// Used in the StartupMessage to indicate regular handshake.
7+
pub const PROTOCOL_VERSION_NUMBER: i32 = 196608;
8+
9+
// SSLRequest: used to indicate we want an SSL connection.
10+
pub const SSL_REQUEST_CODE: i32 = 80877103;
11+
12+
// CancelRequest: the cancel request code.
13+
pub const CANCEL_REQUEST_CODE: i32 = 80877102;
14+
15+
// AuthenticationMD5Password
16+
pub const MD5_ENCRYPTED_PASSWORD: i32 = 5;
17+
18+
// AuthenticationOk
19+
pub const AUTHENTICATION_SUCCESSFUL: i32 = 0;
20+
21+
// ErrorResponse: A code identifying the field type; if zero, this is the message terminator and no string follows.
22+
pub const MESSAGE_TERMINATOR: u8 = 0;

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use tokio::sync::mpsc;
3535

3636
mod client;
3737
mod config;
38+
mod constants;
3839
mod errors;
3940
mod messages;
4041
mod pool;

0 commit comments

Comments
 (0)