impl SqlValue {
pub fn is_null(&self) -> bool {
matches!(
self,
SqlValue::NullBool
| SqlValue::NullSmallInt
| SqlValue::NullInt
| SqlValue::NullBigInt
| SqlValue::NullFloat
| SqlValue::NullDouble
| SqlValue::NullDecimal
| SqlValue::NullText
| SqlValue::NullBytes
| SqlValue::NullUuid
| SqlValue::NullDateTime
| SqlValue::NullDate
| SqlValue::NullTime
| SqlValue::NullJson
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SqlValue {
NullBool,
NullSmallInt,
NullInt,
NullBigInt,
NullFloat,
NullDouble,
NullDecimal,
NullText,
NullBytes,
NullUuid,
NullDateTime,
NullDate,
NullTime,
NullJson,
Bool(bool),
SmallInt(i16),
Int(i32),
BigInt(i64),
Float(f32),
Double(f64),
Decimal(rust_decimal::Decimal),
Text(String),
Bytes(Vec<u8>),
Uuid(uuid::Uuid),
DateTime(chrono::DateTime<chrono::Utc>),
Date(chrono::NaiveDate),
Time(chrono::NaiveTime),
Json(serde_json::Value),
}
pub trait ToSqlValue {
fn to_sql_value(&self) -> SqlValue;
}
impl From<&str> for SqlValue {
fn from(s: &str) -> Self {
SqlValue::Text(s.to_string())
}
}
impl From<String> for SqlValue {
fn from(s: String) -> Self {
SqlValue::Text(s)
}
}
impl From<bool> for SqlValue {
fn from(v: bool) -> Self {
SqlValue::Bool(v)
}
}
impl From<i16> for SqlValue {
fn from(v: i16) -> Self {
SqlValue::SmallInt(v)
}
}
impl From<i32> for SqlValue {
fn from(v: i32) -> Self {
SqlValue::Int(v)
}
}
impl From<i64> for SqlValue {
fn from(v: i64) -> Self {
SqlValue::BigInt(v)
}
}
impl From<f32> for SqlValue {
fn from(v: f32) -> Self {
SqlValue::Float(v)
}
}
impl From<f64> for SqlValue {
fn from(v: f64) -> Self {
SqlValue::Double(v)
}
}
impl From<rust_decimal::Decimal> for SqlValue {
fn from(v: rust_decimal::Decimal) -> Self {
SqlValue::Decimal(v)
}
}
impl From<uuid::Uuid> for SqlValue {
fn from(v: uuid::Uuid) -> Self {
SqlValue::Uuid(v)
}
}
impl From<chrono::DateTime<chrono::Utc>> for SqlValue {
fn from(v: chrono::DateTime<chrono::Utc>) -> Self {
SqlValue::DateTime(v)
}
}
impl From<chrono::NaiveDate> for SqlValue {
fn from(v: chrono::NaiveDate) -> Self {
SqlValue::Date(v)
}
}
impl From<chrono::NaiveTime> for SqlValue {
fn from(v: chrono::NaiveTime) -> Self {
SqlValue::Time(v)
}
}
impl From<Vec<u8>> for SqlValue {
fn from(v: Vec<u8>) -> Self {
SqlValue::Bytes(v)
}
}
impl From<serde_json::Value> for SqlValue {
fn from(v: serde_json::Value) -> Self {
SqlValue::Json(v)
}
}
pub trait Filterable: Sized {}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be written to the database",
note = "add `#[derive(Model)]` (or `#[rango::model]`) to `{Self}`"
)]
pub trait ModelValues {
fn field_values(&self) -> Vec<(&'static str, SqlValue)>;
fn pk_value(&self) -> SqlValue;
fn pk_column() -> &'static str;
fn set_generated_pk(&mut self, _id: i64) {}
}
pub fn insert_values<M: ModelValues>(model: &M) -> Vec<(&'static str, SqlValue)> {
let pk = model.pk_value();
let fields = model.field_values();
let mut values = Vec::with_capacity(fields.len() + 1);
if !pk.is_null() {
values.push((M::pk_column(), pk));
}
values.extend(fields);
values
}
pub fn quote_ident(ident: &str) -> String {
format!("\"{}\"", ident.replace('"', "\"\""))
}
pub fn quote_ident_mysql(ident: &str) -> String {
format!("`{}`", ident.replace('`', "``"))
}
#[cfg(test)]
mod quote_ident_tests {
use super::{quote_ident, quote_ident_mysql};
#[test]
fn test_quote_ident_mysql_escapes_backticks() {
assert_eq!(quote_ident_mysql("user"), "`user`");
assert_eq!(
quote_ident_mysql("a`; DROP TABLE x; --"),
"`a``; DROP TABLE x; --`"
);
}
#[test]
fn test_quote_ident_escapes_double_quotes() {
assert_eq!(quote_ident("name"), "\"name\"");
assert_eq!(
quote_ident("a\"; DROP TABLE x; --"),
"\"a\"\"; DROP TABLE x; --\""
);
}
}