2
2
/// We are pretending to the server in this scenario,
3
3
/// and this module implements that.
4
4
use bytes:: { Buf , BufMut , BytesMut } ;
5
- use once_cell:: sync:: OnceCell ;
6
- use regex:: Regex ;
7
5
use tokio:: io:: { AsyncReadExt , BufReader } ;
8
6
use tokio:: net:: {
9
7
tcp:: { OwnedReadHalf , OwnedWriteHalf } ,
@@ -17,16 +15,10 @@ use crate::constants::*;
17
15
use crate :: errors:: Error ;
18
16
use crate :: messages:: * ;
19
17
use crate :: pool:: { ClientServerMap , ConnectionPool } ;
18
+ use crate :: query_router:: QueryRouter ;
20
19
use crate :: server:: Server ;
21
- use crate :: sharding:: Sharder ;
22
20
use crate :: stats:: Reporter ;
23
21
24
- pub const SHARDING_REGEX : & str = r"SET SHARDING KEY TO '[0-9]+';" ;
25
- pub const ROLE_REGEX : & str = r"SET SERVER ROLE TO '(PRIMARY|REPLICA)';" ;
26
-
27
- pub static SHARDING_REGEX_RE : OnceCell < Regex > = OnceCell :: new ( ) ;
28
- pub static ROLE_REGEX_RE : OnceCell < Regex > = OnceCell :: new ( ) ;
29
-
30
22
/// The client state. One of these is created per client.
31
23
pub struct Client {
32
24
// The reads are buffered (8K by default).
@@ -199,15 +191,11 @@ impl Client {
199
191
return Ok ( Server :: cancel ( & address, & port, process_id, secret_key) . await ?) ;
200
192
}
201
193
202
- // Active shard we're talking to.
203
- // The lifetime of this depends on the pool mode:
204
- // - if in session mode, this lives until the client disconnects,
205
- // - if in transaction mode, this lives for the duration of one transaction.
206
- let mut shard: Option < usize > = None ;
207
-
208
- // Active database role we want to talk to, e.g. primary or replica.
209
- let mut role: Option < Role > = self . default_server_role ;
194
+ let mut query_router = QueryRouter :: new ( self . default_server_role , pool. shards ( ) ) ;
210
195
196
+ // Our custom protocol loop.
197
+ // We expect the client to either start a transaction with regular queries
198
+ // or issue commands for our sharding and server selection protocols.
211
199
loop {
212
200
// Read a complete message from the client, which normally would be
213
201
// either a `Q` (query) or `P` (prepare, extended protocol).
@@ -218,32 +206,31 @@ impl Client {
218
206
219
207
// Parse for special select shard command.
220
208
// SET SHARDING KEY TO 'bigint';
221
- match self . select_shard ( message. clone ( ) , pool . shards ( ) ) {
222
- Some ( s ) => {
223
- custom_protocol_response_ok ( & mut self . write , "SET SHARDING KEY" ) . await ? ;
224
- shard = Some ( s ) ;
225
- continue ;
226
- }
227
- None => ( ) ,
228
- } ;
209
+ if query_router . select_shard ( message. clone ( ) ) {
210
+ custom_protocol_response_ok (
211
+ & mut self . write ,
212
+ & format ! ( "SET SHARD TO {}" , query_router . shard ( ) ) ,
213
+ )
214
+ . await ? ;
215
+ continue ;
216
+ }
229
217
230
218
// Parse for special server role selection command.
231
219
// SET SERVER ROLE TO '(primary|replica)';
232
- match self . select_role ( message. clone ( ) ) {
233
- Some ( r) => {
234
- custom_protocol_response_ok ( & mut self . write , "SET SERVER ROLE" ) . await ?;
235
- role = Some ( r) ;
236
- continue ;
237
- }
238
- None => ( ) ,
239
- } ;
220
+ if query_router. select_role ( message. clone ( ) ) {
221
+ custom_protocol_response_ok ( & mut self . write , "SET SERVER ROLE" ) . await ?;
222
+ continue ;
223
+ }
240
224
241
- // Grab a server from the pool.
242
- let connection = match pool. get ( shard, role) . await {
225
+ // Grab a server from the pool: the client issued a regular query .
226
+ let connection = match pool. get ( query_router . shard ( ) , query_router . role ( ) ) . await {
243
227
Ok ( conn) => conn,
244
228
Err ( err) => {
245
229
println ! ( ">> Could not get connection from pool: {:?}" , err) ;
246
- return Err ( err) ;
230
+ error_response ( & mut self . write , "could not get connection from the pool" )
231
+ . await ?;
232
+ query_router. reset ( ) ;
233
+ continue ;
247
234
}
248
235
} ;
249
236
@@ -264,11 +251,8 @@ impl Client {
264
251
Err ( err) => {
265
252
// Client disconnected without warning.
266
253
if server. in_transaction ( ) {
267
- // TODO: this is what PgBouncer does
268
- // which leads to connection thrashing.
269
- //
270
- // I think we could issue a ROLLBACK here instead.
271
- // server.mark_bad();
254
+ // Client left dirty server. Clean up and proceed
255
+ // without thrashing this connection.
272
256
server. query ( "ROLLBACK; DISCARD ALL;" ) . await ?;
273
257
}
274
258
@@ -328,8 +312,7 @@ impl Client {
328
312
// Report this client as idle.
329
313
self . stats . client_idle ( ) ;
330
314
331
- shard = None ;
332
- role = self . default_server_role ;
315
+ query_router. reset ( ) ;
333
316
334
317
break ;
335
318
}
@@ -414,8 +397,7 @@ impl Client {
414
397
if self . transaction_mode {
415
398
self . stats . client_idle ( ) ;
416
399
417
- shard = None ;
418
- role = self . default_server_role ;
400
+ query_router. reset ( ) ;
419
401
420
402
break ;
421
403
}
@@ -450,8 +432,7 @@ impl Client {
450
432
self . stats . transaction ( ) ;
451
433
452
434
if self . transaction_mode {
453
- shard = None ;
454
- role = self . default_server_role ;
435
+ query_router. reset ( ) ;
455
436
456
437
break ;
457
438
}
@@ -476,77 +457,4 @@ impl Client {
476
457
let mut guard = self . client_server_map . lock ( ) . unwrap ( ) ;
477
458
guard. remove ( & ( self . process_id , self . secret_key ) ) ;
478
459
}
479
-
480
- /// Determine if the query is part of our special syntax, extract
481
- /// the shard key, and return the shard to query based on Postgres'
482
- /// PARTITION BY HASH function.
483
- fn select_shard ( & self , mut buf : BytesMut , shards : usize ) -> Option < usize > {
484
- let code = buf. get_u8 ( ) as char ;
485
-
486
- // Only supporting simpe protocol here, so
487
- // one would have to execute something like this:
488
- // psql -c "SET SHARDING KEY TO '1234'"
489
- // after sanitizing the value manually, which can be just done with an
490
- // int parser, e.g. `let key = "1234".parse::<i64>().unwrap()`.
491
- match code {
492
- 'Q' => ( ) ,
493
- _ => return None ,
494
- } ;
495
-
496
- let len = buf. get_i32 ( ) ;
497
- let query = String :: from_utf8_lossy ( & buf[ ..len as usize - 4 - 1 ] ) . to_ascii_uppercase ( ) ; // Don't read the ternminating null
498
-
499
- let rgx = match SHARDING_REGEX_RE . get ( ) {
500
- Some ( r) => r,
501
- None => return None ,
502
- } ;
503
-
504
- if rgx. is_match ( & query) {
505
- let shard = query. split ( "'" ) . collect :: < Vec < & str > > ( ) [ 1 ] ;
506
-
507
- match shard. parse :: < i64 > ( ) {
508
- Ok ( shard) => {
509
- let sharder = Sharder :: new ( shards) ;
510
- Some ( sharder. pg_bigint_hash ( shard) )
511
- }
512
-
513
- Err ( _) => None ,
514
- }
515
- } else {
516
- None
517
- }
518
- }
519
-
520
- // Pick a primary or a replica from the pool.
521
- fn select_role ( & self , mut buf : BytesMut ) -> Option < Role > {
522
- let code = buf. get_u8 ( ) as char ;
523
-
524
- // Same story as select_shard() above.
525
- match code {
526
- 'Q' => ( ) ,
527
- _ => return None ,
528
- } ;
529
-
530
- let len = buf. get_i32 ( ) ;
531
- let query = String :: from_utf8_lossy ( & buf[ ..len as usize - 4 - 1 ] ) . to_ascii_uppercase ( ) ;
532
-
533
- let rgx = match ROLE_REGEX_RE . get ( ) {
534
- Some ( r) => r,
535
- None => return None ,
536
- } ;
537
-
538
- // Copy / paste from above. If we get one more of these use cases,
539
- // it'll be time to abstract :).
540
- if rgx. is_match ( & query) {
541
- let role = query. split ( "'" ) . collect :: < Vec < & str > > ( ) [ 1 ] ;
542
-
543
- match role {
544
- "PRIMARY" => Some ( Role :: Primary ) ,
545
- "REPLICA" => Some ( Role :: Replica ) ,
546
- _ => return None ,
547
- }
548
- } else {
549
- None
550
- }
551
- }
552
460
}
0 commit comments