Revert "proxy: update tokio-postgres to allow arbitrary config params (#8076)" (#8654)

This reverts #8076 - which was already reverted from the release branch
since forever (it would have been a breaking change to release for all
users who currently set TimeZone options). It's causing conflicts now so
we should revert it here as well.
This commit is contained in:
Conrad Ludgate
2024-08-09 09:09:29 +01:00
committed by GitHub
parent 2ca5ff26d7
commit 7e08fbd1b9
6 changed files with 92 additions and 119 deletions

8
Cargo.lock generated
View File

@@ -3960,7 +3960,7 @@ dependencies = [
[[package]] [[package]]
name = "postgres" name = "postgres"
version = "0.19.4" version = "0.19.4"
source = "git+https://github.com/neondatabase/rust-postgres.git?branch=neon#cff6927e4f58b1af6ecc2ee7279df1f2ff537295" source = "git+https://github.com/neondatabase/rust-postgres.git?branch=neon#20031d7a9ee1addeae6e0968e3899ae6bf01cee2"
dependencies = [ dependencies = [
"bytes", "bytes",
"fallible-iterator", "fallible-iterator",
@@ -3973,7 +3973,7 @@ dependencies = [
[[package]] [[package]]
name = "postgres-protocol" name = "postgres-protocol"
version = "0.6.4" version = "0.6.4"
source = "git+https://github.com/neondatabase/rust-postgres.git?branch=neon#cff6927e4f58b1af6ecc2ee7279df1f2ff537295" source = "git+https://github.com/neondatabase/rust-postgres.git?branch=neon#20031d7a9ee1addeae6e0968e3899ae6bf01cee2"
dependencies = [ dependencies = [
"base64 0.20.0", "base64 0.20.0",
"byteorder", "byteorder",
@@ -3992,7 +3992,7 @@ dependencies = [
[[package]] [[package]]
name = "postgres-types" name = "postgres-types"
version = "0.2.4" version = "0.2.4"
source = "git+https://github.com/neondatabase/rust-postgres.git?branch=neon#cff6927e4f58b1af6ecc2ee7279df1f2ff537295" source = "git+https://github.com/neondatabase/rust-postgres.git?branch=neon#20031d7a9ee1addeae6e0968e3899ae6bf01cee2"
dependencies = [ dependencies = [
"bytes", "bytes",
"fallible-iterator", "fallible-iterator",
@@ -6187,7 +6187,7 @@ dependencies = [
[[package]] [[package]]
name = "tokio-postgres" name = "tokio-postgres"
version = "0.7.7" version = "0.7.7"
source = "git+https://github.com/neondatabase/rust-postgres.git?branch=neon#cff6927e4f58b1af6ecc2ee7279df1f2ff537295" source = "git+https://github.com/neondatabase/rust-postgres.git?branch=neon#20031d7a9ee1addeae6e0968e3899ae6bf01cee2"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"byteorder", "byteorder",

View File

@@ -144,7 +144,20 @@ impl PgConnectionConfig {
// implement and this function is hardly a bottleneck. The function is only called around // implement and this function is hardly a bottleneck. The function is only called around
// establishing a new connection. // establishing a new connection.
#[allow(unstable_name_collisions)] #[allow(unstable_name_collisions)]
config.options(&encode_options(&self.options)); config.options(
&self
.options
.iter()
.map(|s| {
if s.contains(['\\', ' ']) {
Cow::Owned(s.replace('\\', "\\\\").replace(' ', "\\ "))
} else {
Cow::Borrowed(s.as_str())
}
})
.intersperse(Cow::Borrowed(" ")) // TODO: use impl from std once it's stabilized
.collect::<String>(),
);
} }
config config
} }
@@ -165,21 +178,6 @@ impl PgConnectionConfig {
} }
} }
#[allow(unstable_name_collisions)]
fn encode_options(options: &[String]) -> String {
options
.iter()
.map(|s| {
if s.contains(['\\', ' ']) {
Cow::Owned(s.replace('\\', "\\\\").replace(' ', "\\ "))
} else {
Cow::Borrowed(s.as_str())
}
})
.intersperse(Cow::Borrowed(" ")) // TODO: use impl from std once it's stabilized
.collect::<String>()
}
impl fmt::Display for PgConnectionConfig { impl fmt::Display for PgConnectionConfig {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// The password is intentionally hidden and not part of this display string. // The password is intentionally hidden and not part of this display string.
@@ -208,7 +206,7 @@ impl fmt::Debug for PgConnectionConfig {
#[cfg(test)] #[cfg(test)]
mod tests_pg_connection_config { mod tests_pg_connection_config {
use crate::{encode_options, PgConnectionConfig}; use crate::PgConnectionConfig;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use url::Host; use url::Host;
@@ -257,12 +255,18 @@ mod tests_pg_connection_config {
#[test] #[test]
fn test_with_options() { fn test_with_options() {
let options = encode_options(&[ let cfg = PgConnectionConfig::new_host_port(STUB_HOST.clone(), 123).extend_options([
"hello".to_owned(), "hello",
"world".to_owned(), "world",
"with space".to_owned(), "with space",
"and \\ backslashes".to_owned(), "and \\ backslashes",
]); ]);
assert_eq!(options, "hello world with\\ space and\\ \\\\\\ backslashes"); assert_eq!(cfg.host(), &*STUB_HOST);
assert_eq!(cfg.port(), 123);
assert_eq!(cfg.raw_address(), "stub.host.example:123");
assert_eq!(
cfg.to_tokio_postgres_config().get_options(),
Some("hello world with\\ space and\\ \\\\\\ backslashes")
);
} }
} }

View File

@@ -103,8 +103,12 @@ impl ConnCfg {
/// Reuse password or auth keys from the other config. /// Reuse password or auth keys from the other config.
pub fn reuse_password(&mut self, other: Self) { pub fn reuse_password(&mut self, other: Self) {
if let Some(password) = other.get_auth() { if let Some(password) = other.get_password() {
self.auth(password); self.password(password);
}
if let Some(keys) = other.get_auth_keys() {
self.auth_keys(keys);
} }
} }
@@ -120,64 +124,48 @@ impl ConnCfg {
/// Apply startup message params to the connection config. /// Apply startup message params to the connection config.
pub fn set_startup_params(&mut self, params: &StartupMessageParams) { pub fn set_startup_params(&mut self, params: &StartupMessageParams) {
let mut client_encoding = false; // Only set `user` if it's not present in the config.
for (k, v) in params.iter() { // Link auth flow takes username from the console's response.
match k { if let (None, Some(user)) = (self.get_user(), params.get("user")) {
"user" => { self.user(user);
// Only set `user` if it's not present in the config. }
// Link auth flow takes username from the console's response.
if self.get_user().is_none() { // Only set `dbname` if it's not present in the config.
self.user(v); // Link auth flow takes dbname from the console's response.
} if let (None, Some(dbname)) = (self.get_dbname(), params.get("database")) {
self.dbname(dbname);
}
// Don't add `options` if they were only used for specifying a project.
// Connection pools don't support `options`, because they affect backend startup.
if let Some(options) = filtered_options(params) {
self.options(&options);
}
if let Some(app_name) = params.get("application_name") {
self.application_name(app_name);
}
// TODO: This is especially ugly...
if let Some(replication) = params.get("replication") {
use tokio_postgres::config::ReplicationMode;
match replication {
"true" | "on" | "yes" | "1" => {
self.replication_mode(ReplicationMode::Physical);
} }
"database" => { "database" => {
// Only set `dbname` if it's not present in the config. self.replication_mode(ReplicationMode::Logical);
// Link auth flow takes dbname from the console's response.
if self.get_dbname().is_none() {
self.dbname(v);
}
}
"options" => {
// Don't add `options` if they were only used for specifying a project.
// Connection pools don't support `options`, because they affect backend startup.
if let Some(options) = filtered_options(v) {
self.options(&options);
}
}
// the special ones in tokio-postgres that we don't want being set by the user
"dbname" => {}
"password" => {}
"sslmode" => {}
"host" => {}
"port" => {}
"connect_timeout" => {}
"keepalives" => {}
"keepalives_idle" => {}
"keepalives_interval" => {}
"keepalives_retries" => {}
"target_session_attrs" => {}
"channel_binding" => {}
"max_backend_message_size" => {}
"client_encoding" => {
client_encoding = true;
// only error should be from bad null bytes,
// but we've already checked for those.
_ = self.param("client_encoding", v);
}
_ => {
// only error should be from bad null bytes,
// but we've already checked for those.
_ = self.param(k, v);
} }
_other => {}
} }
} }
if !client_encoding {
// for compatibility since we removed it from tokio-postgres // TODO: extend the list of the forwarded startup parameters.
self.param("client_encoding", "UTF8").unwrap(); // Currently, tokio-postgres doesn't allow us to pass
} // arbitrary parameters, but the ones above are a good start.
//
// This and the reverse params problem can be better addressed
// in a bespoke connection machinery (a new library for that sake).
} }
} }
@@ -350,9 +338,10 @@ impl ConnCfg {
} }
/// Retrieve `options` from a startup message, dropping all proxy-secific flags. /// Retrieve `options` from a startup message, dropping all proxy-secific flags.
fn filtered_options(options: &str) -> Option<String> { fn filtered_options(params: &StartupMessageParams) -> Option<String> {
#[allow(unstable_name_collisions)] #[allow(unstable_name_collisions)]
let options: String = StartupMessageParams::parse_options_raw(options) let options: String = params
.options_raw()?
.filter(|opt| parse_endpoint_param(opt).is_none() && neon_option(opt).is_none()) .filter(|opt| parse_endpoint_param(opt).is_none() && neon_option(opt).is_none())
.intersperse(" ") // TODO: use impl from std once it's stabilized .intersperse(" ") // TODO: use impl from std once it's stabilized
.collect(); .collect();
@@ -424,23 +413,27 @@ mod tests {
#[test] #[test]
fn test_filtered_options() { fn test_filtered_options() {
// Empty options is unlikely to be useful anyway. // Empty options is unlikely to be useful anyway.
assert_eq!(filtered_options(""), None); let params = StartupMessageParams::new([("options", "")]);
assert_eq!(filtered_options(&params), None);
// It's likely that clients will only use options to specify endpoint/project. // It's likely that clients will only use options to specify endpoint/project.
let params = "project=foo"; let params = StartupMessageParams::new([("options", "project=foo")]);
assert_eq!(filtered_options(params), None); assert_eq!(filtered_options(&params), None);
// Same, because unescaped whitespaces are no-op. // Same, because unescaped whitespaces are no-op.
let params = " project=foo "; let params = StartupMessageParams::new([("options", " project=foo ")]);
assert_eq!(filtered_options(params), None); assert_eq!(filtered_options(&params).as_deref(), None);
let params = r"\ project=foo \ "; let params = StartupMessageParams::new([("options", r"\ project=foo \ ")]);
assert_eq!(filtered_options(params).as_deref(), Some(r"\ \ ")); assert_eq!(filtered_options(&params).as_deref(), Some(r"\ \ "));
let params = "project = foo"; let params = StartupMessageParams::new([("options", "project = foo")]);
assert_eq!(filtered_options(params).as_deref(), Some("project = foo")); assert_eq!(filtered_options(&params).as_deref(), Some("project = foo"));
let params = "project = foo neon_endpoint_type:read_write neon_lsn:0/2"; let params = StartupMessageParams::new([(
assert_eq!(filtered_options(params).as_deref(), Some("project = foo")); "options",
"project = foo neon_endpoint_type:read_write neon_lsn:0/2",
)]);
assert_eq!(filtered_options(&params).as_deref(), Some("project = foo"));
} }
} }

View File

@@ -236,10 +236,6 @@ impl ConnectMechanism for TokioMechanism {
.dbname(&self.conn_info.dbname) .dbname(&self.conn_info.dbname)
.connect_timeout(timeout); .connect_timeout(timeout);
config
.param("client_encoding", "UTF8")
.expect("client encoding UTF8 is always valid");
let pause = ctx.latency_timer_pause(crate::metrics::Waiting::Compute); let pause = ctx.latency_timer_pause(crate::metrics::Waiting::Compute);
let res = config.connect(tokio_postgres::NoTls).await; let res = config.connect(tokio_postgres::NoTls).await;
drop(pause); drop(pause);

View File

@@ -203,7 +203,6 @@ fn get_conn_info(
options = Some(NeonOptions::parse_options_raw(&value)); options = Some(NeonOptions::parse_options_raw(&value));
} }
} }
ctx.set_db_options(params.freeze());
let user_info = ComputeUserInfo { let user_info = ComputeUserInfo {
endpoint, endpoint,

View File

@@ -53,25 +53,6 @@ def test_proxy_select_1(static_proxy: NeonProxy):
assert out[0][0] == 42 assert out[0][0] == 42
def test_proxy_server_params(static_proxy: NeonProxy):
"""
Test that server params are passing through to postgres
"""
out = static_proxy.safe_psql(
"select to_json('0 seconds'::interval)", options="-c intervalstyle=iso_8601"
)
assert out[0][0] == "PT0S"
out = static_proxy.safe_psql(
"select to_json('0 seconds'::interval)", options="-c intervalstyle=sql_standard"
)
assert out[0][0] == "0"
out = static_proxy.safe_psql(
"select to_json('0 seconds'::interval)", options="-c intervalstyle=postgres"
)
assert out[0][0] == "00:00:00"
def test_password_hack(static_proxy: NeonProxy): def test_password_hack(static_proxy: NeonProxy):
""" """
Check the PasswordHack auth flow: an alternative to SCRAM auth for Check the PasswordHack auth flow: an alternative to SCRAM auth for