Skip to content

Commit aa79628

Browse files
authored
Query parser 3.0 (#23)
* Starting query parsing * Query parser * working config * disable by default * fix tsets * introducing log crate; test for query router; comments * typo * fixes for banning * added test for prepared stmt
1 parent 4c8a398 commit aa79628

File tree

8 files changed

+296
-75
lines changed

8 files changed

+296
-75
lines changed

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ regex = "1"
2121
num_cpus = "1"
2222
once_cell = "1"
2323
statsd = "0.15"
24+
sqlparser = "0.14"
25+
log = "0.4"

pgcat.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,15 @@ database = "shard2"
8181
# replica: round-robin between replicas only without touching the primary,
8282
# primary: all queries go to the primary unless otherwise specified.
8383
default_role = "any"
84+
85+
86+
# Query parser. If enabled, we'll attempt to parse
87+
# every incoming query to determine if it's a read or a write.
88+
# If it's a read query, we'll direct it to a replica. Otherwise, if it's a write,
89+
# we'll direct it to the primary.
90+
query_parser_enabled = false
91+
92+
# If the query parser is enabled and this setting is enabled, the primary will be part of the pool of databases used for
93+
# load balancing of read queries. Otherwise, the primary will only be used for write
94+
# queries. The primary can always be explicitely selected with our custom protocol.
95+
primary_reads_enabled = true

src/client.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ use tokio::net::{
1010

1111
use std::collections::HashMap;
1212

13-
use crate::config::Role;
1413
use crate::constants::*;
1514
use crate::errors::Error;
1615
use crate::messages::*;
@@ -47,10 +46,6 @@ pub struct Client {
4746
// to connect and cancel a query.
4847
client_server_map: ClientServerMap,
4948

50-
// Unless client specifies, route queries to the servers that have this role,
51-
// e.g. primary or replicas or any.
52-
default_server_role: Option<Role>,
53-
5449
// Client parameters, e.g. user, client_encoding, etc.
5550
#[allow(dead_code)]
5651
parameters: HashMap<String, String>,
@@ -67,7 +62,6 @@ impl Client {
6762
mut stream: TcpStream,
6863
client_server_map: ClientServerMap,
6964
transaction_mode: bool,
70-
default_server_role: Option<Role>,
7165
server_info: BytesMut,
7266
stats: Reporter,
7367
) -> Result<Client, Error> {
@@ -126,7 +120,6 @@ impl Client {
126120
process_id: process_id,
127121
secret_key: secret_key,
128122
client_server_map: client_server_map,
129-
default_server_role: default_server_role,
130123
parameters: parameters,
131124
stats: stats,
132125
});
@@ -148,7 +141,6 @@ impl Client {
148141
process_id: process_id,
149142
secret_key: secret_key,
150143
client_server_map: client_server_map,
151-
default_server_role: default_server_role,
152144
parameters: HashMap::new(),
153145
stats: stats,
154146
});
@@ -162,7 +154,11 @@ impl Client {
162154
}
163155

164156
/// Client loop. We handle all messages between the client and the database here.
165-
pub async fn handle(&mut self, mut pool: ConnectionPool) -> Result<(), Error> {
157+
pub async fn handle(
158+
&mut self,
159+
mut pool: ConnectionPool,
160+
mut query_router: QueryRouter,
161+
) -> Result<(), Error> {
166162
// The client wants to cancel a query it has issued previously.
167163
if self.cancel_mode {
168164
let (process_id, secret_key, address, port) = {
@@ -191,8 +187,6 @@ impl Client {
191187
return Ok(Server::cancel(&address, &port, process_id, secret_key).await?);
192188
}
193189

194-
let mut query_router = QueryRouter::new(self.default_server_role, pool.shards());
195-
196190
// Our custom protocol loop.
197191
// We expect the client to either start a transaction with regular queries
198192
// or issue commands for our sharding and server selection protocols.
@@ -222,6 +216,11 @@ impl Client {
222216
continue;
223217
}
224218

219+
// Attempt to parse the query to determine where it should go
220+
if query_router.query_parser_enabled() && query_router.role() == None {
221+
query_router.infer_role(message.clone());
222+
}
223+
225224
// Grab a server from the pool: the client issued a regular query.
226225
let connection = match pool.get(query_router.shard(), query_router.role()).await {
227226
Ok(conn) => conn,

src/config.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ pub enum Role {
1313
Replica,
1414
}
1515

16+
impl PartialEq<Option<Role>> for Role {
17+
fn eq(&self, other: &Option<Role>) -> bool {
18+
match other {
19+
None => true,
20+
Some(role) => *self == *role,
21+
}
22+
}
23+
}
24+
25+
impl PartialEq<Role> for Option<Role> {
26+
fn eq(&self, other: &Role) -> bool {
27+
match *self {
28+
None => true,
29+
Some(role) => role == *other,
30+
}
31+
}
32+
}
33+
1634
#[derive(Clone, PartialEq, Hash, std::cmp::Eq, Debug)]
1735
pub struct Address {
1836
pub host: String,
@@ -47,6 +65,8 @@ pub struct Shard {
4765
#[derive(Deserialize, Debug, Clone)]
4866
pub struct QueryRouter {
4967
pub default_role: String,
68+
pub query_parser_enabled: bool,
69+
pub primary_reads_enabled: bool,
5070
}
5171

5272
#[derive(Deserialize, Debug, Clone)]

src/main.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
extern crate async_trait;
1717
extern crate bb8;
1818
extern crate bytes;
19+
extern crate log;
1920
extern crate md5;
2021
extern crate num_cpus;
2122
extern crate once_cell;
2223
extern crate serde;
2324
extern crate serde_derive;
25+
extern crate sqlparser;
2426
extern crate statsd;
2527
extern crate tokio;
2628
extern crate toml;
@@ -47,6 +49,7 @@ mod stats;
4749
// secret keys to the backend's.
4850
use config::Role;
4951
use pool::{ClientServerMap, ConnectionPool};
52+
use query_router::QueryRouter;
5053
use stats::{Collector, Reporter};
5154

5255
/// Main!
@@ -118,6 +121,8 @@ async fn main() {
118121
return;
119122
}
120123
};
124+
let primary_reads_enabled = config.query_router.primary_reads_enabled;
125+
let query_parser_enabled = config.query_router.query_parser_enabled;
121126

122127
let server_info = match pool.validate().await {
123128
Ok(info) => info,
@@ -155,7 +160,6 @@ async fn main() {
155160
socket,
156161
client_server_map,
157162
transaction_mode,
158-
default_server_role,
159163
server_info,
160164
reporter,
161165
)
@@ -164,7 +168,14 @@ async fn main() {
164168
Ok(mut client) => {
165169
println!(">> Client {:?} authenticated successfully!", addr);
166170

167-
match client.handle(pool).await {
171+
let query_router = QueryRouter::new(
172+
default_server_role,
173+
pool.shards(),
174+
primary_reads_enabled,
175+
query_parser_enabled,
176+
);
177+
178+
match client.handle(pool, query_router).await {
168179
Ok(()) => {
169180
let duration = chrono::offset::Utc::now().naive_utc() - start;
170181

0 commit comments

Comments
 (0)