Skip to content

Commit e14b283

Browse files
zainkabanilevkk
andauthored
Make infer role configurable and fix double parse bug (#533)
* Make infer role configurable and fix double parse bug * Fix tests * Enable infer_role_from query in toml for tests * Fix test * Add max length config, add logging for which application is failing to parse, and change config name * fmt * Update src/config.rs --------- Co-authored-by: Lev Kokotov <[email protected]>
1 parent 7c3c90c commit e14b283

File tree

8 files changed

+197
-60
lines changed

8 files changed

+197
-60
lines changed

Diff for: .circleci/pgcat.toml

+5
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ default_role = "any"
7474
# we'll direct it to the primary.
7575
query_parser_enabled = true
7676

77+
# If the query parser is enabled and this setting is enabled, we'll attempt to
78+
# infer the role from the query itself.
79+
query_parser_read_write_splitting = true
80+
7781
# If the query parser is enabled and this setting is enabled, the primary will be part of the pool of databases used for
7882
# load balancing of read queries. Otherwise, the primary will only be used for write
7983
# queries. The primary can always be explicitely selected with our custom protocol.
@@ -134,6 +138,7 @@ database = "shard2"
134138
pool_mode = "session"
135139
default_role = "primary"
136140
query_parser_enabled = true
141+
query_parser_read_write_splitting = true
137142
primary_reads_enabled = true
138143
sharding_function = "pg_bigint_hash"
139144

Diff for: examples/docker/pgcat.toml

+4
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ default_role = "any"
7171
# we'll direct it to the primary.
7272
query_parser_enabled = true
7373

74+
# If the query parser is enabled and this setting is enabled, we'll attempt to
75+
# infer the role from the query itself.
76+
query_parser_read_write_splitting = true
77+
7478
# If the query parser is enabled and this setting is enabled, the primary will be part of the pool of databases used for
7579
# load balancing of read queries. Otherwise, the primary will only be used for write
7680
# queries. The primary can always be explicitly selected with our custom protocol.

Diff for: pgcat.toml

+4
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ default_role = "any"
162162
# we'll direct it to the primary.
163163
query_parser_enabled = true
164164

165+
# If the query parser is enabled and this setting is enabled, we'll attempt to
166+
# infer the role from the query itself.
167+
query_parser_read_write_splitting = true
168+
165169
# If the query parser is enabled and this setting is enabled, the primary will be part of the pool of databases used for
166170
# load balancing of read queries. Otherwise, the primary will only be used for write
167171
# queries. The primary can always be explicitly selected with our custom protocol.

Diff for: src/client.rs

+76-36
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,9 @@ where
774774
let mut prepared_statement = None;
775775
let mut will_prepare = false;
776776

777+
let client_identifier =
778+
ClientIdentifier::new(&self.application_name, &self.username, &self.pool_name);
779+
777780
// Our custom protocol loop.
778781
// We expect the client to either start a transaction with regular queries
779782
// or issue commands for our sharding and server selection protocol.
@@ -812,6 +815,21 @@ where
812815
message_result = read_message(&mut self.read) => message_result?
813816
};
814817

818+
// Handle admin database queries.
819+
if self.admin {
820+
debug!("Handling admin command");
821+
handle_admin(&mut self.write, message, self.client_server_map.clone()).await?;
822+
continue;
823+
}
824+
825+
// Get a pool instance referenced by the most up-to-date
826+
// pointer. This ensures we always read the latest config
827+
// when starting a query.
828+
let mut pool = self.get_pool().await?;
829+
query_router.update_pool_settings(pool.settings.clone());
830+
831+
let mut initial_parsed_ast = None;
832+
815833
match message[0] as char {
816834
// Buffer extended protocol messages even if we do not have
817835
// a server connection yet. Hopefully, when we get the S message
@@ -841,24 +859,34 @@ where
841859

842860
'Q' => {
843861
if query_router.query_parser_enabled() {
844-
if let Ok(ast) = QueryRouter::parse(&message) {
845-
let plugin_result = query_router.execute_plugins(&ast).await;
862+
match query_router.parse(&message) {
863+
Ok(ast) => {
864+
let plugin_result = query_router.execute_plugins(&ast).await;
846865

847-
match plugin_result {
848-
Ok(PluginOutput::Deny(error)) => {
849-
error_response(&mut self.write, &error).await?;
850-
continue;
851-
}
866+
match plugin_result {
867+
Ok(PluginOutput::Deny(error)) => {
868+
error_response(&mut self.write, &error).await?;
869+
continue;
870+
}
852871

853-
Ok(PluginOutput::Intercept(result)) => {
854-
write_all(&mut self.write, result).await?;
855-
continue;
856-
}
872+
Ok(PluginOutput::Intercept(result)) => {
873+
write_all(&mut self.write, result).await?;
874+
continue;
875+
}
857876

858-
_ => (),
859-
};
877+
_ => (),
878+
};
879+
880+
let _ = query_router.infer(&ast);
860881

861-
let _ = query_router.infer(&ast);
882+
initial_parsed_ast = Some(ast);
883+
}
884+
Err(error) => {
885+
warn!(
886+
"Query parsing error: {} (client: {})",
887+
error, client_identifier
888+
);
889+
}
862890
}
863891
}
864892
}
@@ -872,13 +900,21 @@ where
872900
self.buffer.put(&message[..]);
873901

874902
if query_router.query_parser_enabled() {
875-
if let Ok(ast) = QueryRouter::parse(&message) {
876-
if let Ok(output) = query_router.execute_plugins(&ast).await {
877-
plugin_output = Some(output);
878-
}
903+
match query_router.parse(&message) {
904+
Ok(ast) => {
905+
if let Ok(output) = query_router.execute_plugins(&ast).await {
906+
plugin_output = Some(output);
907+
}
879908

880-
let _ = query_router.infer(&ast);
881-
}
909+
let _ = query_router.infer(&ast);
910+
}
911+
Err(error) => {
912+
warn!(
913+
"Query parsing error: {} (client: {})",
914+
error, client_identifier
915+
);
916+
}
917+
};
882918
}
883919

884920
continue;
@@ -922,13 +958,6 @@ where
922958
_ => (),
923959
}
924960

925-
// Handle admin database queries.
926-
if self.admin {
927-
debug!("Handling admin command");
928-
handle_admin(&mut self.write, message, self.client_server_map.clone()).await?;
929-
continue;
930-
}
931-
932961
// Check on plugin results.
933962
match plugin_output {
934963
Some(PluginOutput::Deny(error)) => {
@@ -941,11 +970,6 @@ where
941970
_ => (),
942971
};
943972

944-
// Get a pool instance referenced by the most up-to-date
945-
// pointer. This ensures we always read the latest config
946-
// when starting a query.
947-
let mut pool = self.get_pool().await?;
948-
949973
// Check if the pool is paused and wait until it's resumed.
950974
if pool.wait_paused().await {
951975
// Refresh pool information, something might have changed.
@@ -1165,6 +1189,9 @@ where
11651189
None => {
11661190
trace!("Waiting for message inside transaction or in session mode");
11671191

1192+
// This is not an initial message so discard the initial_parsed_ast
1193+
initial_parsed_ast.take();
1194+
11681195
match tokio::time::timeout(
11691196
idle_client_timeout_duration,
11701197
read_message(&mut self.read),
@@ -1221,7 +1248,22 @@ where
12211248
// Query
12221249
'Q' => {
12231250
if query_router.query_parser_enabled() {
1224-
if let Ok(ast) = QueryRouter::parse(&message) {
1251+
// We don't want to parse again if we already parsed it as the initial message
1252+
let ast = match initial_parsed_ast {
1253+
Some(_) => Some(initial_parsed_ast.take().unwrap()),
1254+
None => match query_router.parse(&message) {
1255+
Ok(ast) => Some(ast),
1256+
Err(error) => {
1257+
warn!(
1258+
"Query parsing error: {} (client: {})",
1259+
error, client_identifier
1260+
);
1261+
None
1262+
}
1263+
},
1264+
};
1265+
1266+
if let Some(ast) = ast {
12251267
let plugin_result = query_router.execute_plugins(&ast).await;
12261268

12271269
match plugin_result {
@@ -1237,8 +1279,6 @@ where
12371279

12381280
_ => (),
12391281
};
1240-
1241-
let _ = query_router.infer(&ast);
12421282
}
12431283
}
12441284
debug!("Sending query to server");
@@ -1290,7 +1330,7 @@ where
12901330
}
12911331

12921332
if query_router.query_parser_enabled() {
1293-
if let Ok(ast) = QueryRouter::parse(&message) {
1333+
if let Ok(ast) = query_router.parse(&message) {
12941334
if let Ok(output) = query_router.execute_plugins(&ast).await {
12951335
plugin_output = Some(output);
12961336
}

Diff for: src/config.rs

+39
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,11 @@ pub struct Pool {
511511
#[serde(default)] // False
512512
pub query_parser_enabled: bool,
513513

514+
pub query_parser_max_length: Option<usize>,
515+
516+
#[serde(default)] // False
517+
pub query_parser_read_write_splitting: bool,
518+
514519
#[serde(default)] // False
515520
pub primary_reads_enabled: bool,
516521

@@ -627,6 +632,18 @@ impl Pool {
627632
}
628633
}
629634

635+
if self.query_parser_read_write_splitting && !self.query_parser_enabled {
636+
error!(
637+
"query_parser_read_write_splitting is only valid when query_parser_enabled is true"
638+
);
639+
return Err(Error::BadConfig);
640+
}
641+
642+
if self.plugins.is_some() && !self.query_parser_enabled {
643+
error!("plugins are only valid when query_parser_enabled is true");
644+
return Err(Error::BadConfig);
645+
}
646+
630647
self.automatic_sharding_key = match &self.automatic_sharding_key {
631648
Some(key) => {
632649
// No quotes in the key so we don't have to compare quoted
@@ -663,6 +680,8 @@ impl Default for Pool {
663680
users: BTreeMap::default(),
664681
default_role: String::from("any"),
665682
query_parser_enabled: false,
683+
query_parser_max_length: None,
684+
query_parser_read_write_splitting: false,
666685
primary_reads_enabled: false,
667686
sharding_function: ShardingFunction::PgBigintHash,
668687
automatic_sharding_key: None,
@@ -914,6 +933,17 @@ impl From<&Config> for std::collections::HashMap<String, String> {
914933
format!("pools.{}.query_parser_enabled", pool_name),
915934
pool.query_parser_enabled.to_string(),
916935
),
936+
(
937+
format!("pools.{}.query_parser_max_length", pool_name),
938+
match pool.query_parser_max_length {
939+
Some(max_length) => max_length.to_string(),
940+
None => String::from("unlimited"),
941+
},
942+
),
943+
(
944+
format!("pools.{}.query_parser_read_write_splitting", pool_name),
945+
pool.query_parser_read_write_splitting.to_string(),
946+
),
917947
(
918948
format!("pools.{}.default_role", pool_name),
919949
pool.default_role.clone(),
@@ -1096,6 +1126,15 @@ impl Config {
10961126
"[pool: {}] Query router: {}",
10971127
pool_name, pool_config.query_parser_enabled
10981128
);
1129+
1130+
info!(
1131+
"[pool: {}] Query parser max length: {:?}",
1132+
pool_name, pool_config.query_parser_max_length
1133+
);
1134+
info!(
1135+
"[pool: {}] Infer role from query: {}",
1136+
pool_name, pool_config.query_parser_read_write_splitting
1137+
);
10991138
info!(
11001139
"[pool: {}] Number of shards: {}",
11011140
pool_name,

Diff for: src/pool.rs

+11
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ pub struct PoolSettings {
111111
// Enable/disable query parser.
112112
pub query_parser_enabled: bool,
113113

114+
// Max length of query the parser will parse.
115+
pub query_parser_max_length: Option<usize>,
116+
117+
// Infer role
118+
pub query_parser_read_write_splitting: bool,
119+
114120
// Read from the primary as well or not.
115121
pub primary_reads_enabled: bool,
116122

@@ -157,6 +163,8 @@ impl Default for PoolSettings {
157163
db: String::default(),
158164
default_role: None,
159165
query_parser_enabled: false,
166+
query_parser_max_length: None,
167+
query_parser_read_write_splitting: false,
160168
primary_reads_enabled: true,
161169
sharding_function: ShardingFunction::PgBigintHash,
162170
automatic_sharding_key: None,
@@ -456,6 +464,9 @@ impl ConnectionPool {
456464
_ => unreachable!(),
457465
},
458466
query_parser_enabled: pool_config.query_parser_enabled,
467+
query_parser_max_length: pool_config.query_parser_max_length,
468+
query_parser_read_write_splitting: pool_config
469+
.query_parser_read_write_splitting,
459470
primary_reads_enabled: pool_config.primary_reads_enabled,
460471
sharding_function: pool_config.sharding_function,
461472
automatic_sharding_key: pool_config.automatic_sharding_key.clone(),

0 commit comments

Comments
 (0)