feat(mysql): allow to connect with mysql driver without default behavor (#3037)

* feat(mysql): provide options to disable default connection settings after connecting

* style(mysql): remove unecessary newlines and run rustfmt

* feat(mysql): allow to pass a custom timezone to the database after connecting
docs(mysql): improve docs for options set_names and no_engine_substitution
This commit is contained in:
darkecho731 2024-02-25 04:30:50 +01:00 committed by GitHub
parent 8c43b8c7bc
commit 27a49914ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 92 additions and 13 deletions

View file

@ -46,22 +46,36 @@ impl ConnectOptions for MySqlConnectOptions {
// https://mathiasbynens.be/notes/mysql-utf8mb4
let mut options = String::new();
let mut sql_mode = Vec::new();
if self.pipes_as_concat {
options.push_str(r#"SET sql_mode=(SELECT CONCAT(@@sql_mode, ',PIPES_AS_CONCAT,NO_ENGINE_SUBSTITUTION')),"#);
} else {
options.push_str(
r#"SET sql_mode=(SELECT CONCAT(@@sql_mode, ',NO_ENGINE_SUBSTITUTION')),"#,
);
sql_mode.push(r#"PIPES_AS_CONCAT"#);
}
if self.no_engine_subsitution {
sql_mode.push(r#"NO_ENGINE_SUBSTITUTION"#);
}
options.push_str(r#"time_zone='+00:00',"#);
options.push_str(&format!(
r#"NAMES {} COLLATE {};"#,
conn.stream.charset.as_str(),
conn.stream.collation.as_str()
));
conn.execute(&*options).await?;
let mut options = Vec::new();
if !sql_mode.is_empty() {
options.push(format!(
r#"sql_mode=(SELECT CONCAT(@@sql_mode, ',{}'))"#,
sql_mode.join(",")
));
}
if let Some(timezone) = &self.timezone {
options.push(format!(r#"time_zone='{}'"#, timezone));
}
if self.set_names {
options.push(format!(
r#"NAMES {} COLLATE {}"#,
conn.stream.charset.as_str(),
conn.stream.collation.as_str()
))
}
if !options.is_empty() {
conn.execute(&*format!(r#"SET {};"#, options.join(",")))
.await?;
}
Ok(conn)
})

View file

@ -77,6 +77,9 @@ pub struct MySqlConnectOptions {
pub(crate) log_settings: LogSettings,
pub(crate) pipes_as_concat: bool,
pub(crate) enable_cleartext_plugin: bool,
pub(crate) no_engine_subsitution: bool,
pub(crate) timezone: Option<String>,
pub(crate) set_names: bool,
}
impl Default for MySqlConnectOptions {
@ -105,6 +108,9 @@ impl MySqlConnectOptions {
log_settings: Default::default(),
pipes_as_concat: true,
enable_cleartext_plugin: false,
no_engine_subsitution: true,
timezone: Some(String::from("+00:00")),
set_names: true,
}
}
@ -333,6 +339,65 @@ impl MySqlConnectOptions {
self.enable_cleartext_plugin = flag_val;
self
}
/// Flag that enables or disables the `NO_ENGINE_SUBSTITUTION` sql_mode setting after
/// connection.
///
/// If not set, if the available storage engine specified by a `CREATE TABLE` is not available,
/// a warning is given and the default storage engine is used instead.
///
/// By default, this is `true` (`NO_ENGINE_SUBSTITUTION` is passed, forbidding engine
/// substitution).
///
/// https://mariadb.com/kb/en/sql-mode/
pub fn no_engine_subsitution(mut self, flag_val: bool) -> Self {
self.no_engine_subsitution = flag_val;
self
}
/// If `Some`, sets the `time_zone` option to the given string after connecting to the database.
///
/// If `None`, no `time_zone` parameter is sent; the server timezone will be used instead.
///
/// Defaults to `Some(String::from("+00:00"))` to ensure all timestamps are in UTC.
///
/// ### Warning
/// Changing this setting from its default will apply an unexpected skew to any
/// `time::OffsetDateTime` or `chrono::DateTime<Utc>` value, whether passed as a parameter or
/// decoded as a result. `TIMESTAMP` values are not encoded with their UTC offset in the MySQL
/// protocol, so encoding and decoding of these types assumes the server timezone is *always*
/// UTC.
///
/// If you are changing this option, ensure your application only uses
/// `time::PrimitiveDateTime` or `chrono::NaiveDateTime` and that it does not assume these
/// timestamps can be placed on a real timeline without applying the proper offset.
pub fn timezone(mut self, value: impl Into<Option<String>>) -> Self {
self.timezone = value.into();
self
}
/// If enabled, `SET NAMES '{charset}' COLLATE '{collation}'` is passed with the values of
/// [`.charset()`] and [`.collation()`] after connecting to the database.
///
/// This ensures the connection uses the specified character set and collation.
///
/// Enabled by default.
///
/// ### Warning
/// If this is disabled and the default charset is not binary-compatible with UTF-8, query
/// strings, column names and string values will likely not decode (or encode) correctly, which
/// may result in unexpected errors or garbage outputs at runtime.
///
/// For proper functioning, you *must* ensure the server is using a binary-compatible charset,
/// such as ASCII or Latin-1 (ISO 8859-1), and that you do not pass any strings containing
/// codepoints not supported by said charset.
///
/// Instead of disabling this, you may also consider setting [`.charset()`] to a charset that
/// is supported by your MySQL or MariaDB server version and compatible with UTF-8.
pub fn set_names(mut self, flag_val: bool) -> Self {
self.set_names = flag_val;
self
}
}
impl MySqlConnectOptions {