use hyperdb_api_core::types::{oids, Date, OffsetTimestamp, Oid, Time, Timestamp};
pub trait ToSqlParam: Send + Sync {
fn encode_param(&self) -> Option<Vec<u8>>;
fn sql_oid(&self) -> Oid {
Oid::new(0)
}
fn to_sql_literal(&self) -> String;
}
impl ToSqlParam for i16 {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(self.to_be_bytes().to_vec())
}
fn sql_oid(&self) -> Oid {
oids::SMALL_INT
}
fn to_sql_literal(&self) -> String {
self.to_string()
}
}
impl ToSqlParam for i32 {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(self.to_be_bytes().to_vec())
}
fn sql_oid(&self) -> Oid {
oids::INT
}
fn to_sql_literal(&self) -> String {
self.to_string()
}
}
impl ToSqlParam for i64 {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(self.to_be_bytes().to_vec())
}
fn sql_oid(&self) -> Oid {
oids::BIG_INT
}
fn to_sql_literal(&self) -> String {
self.to_string()
}
}
impl ToSqlParam for f32 {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(self.to_be_bytes().to_vec())
}
fn sql_oid(&self) -> Oid {
oids::FLOAT
}
fn to_sql_literal(&self) -> String {
if self.is_nan() {
"'NaN'".to_string()
} else if self.is_infinite() {
if *self > 0.0 {
"'Infinity'".to_string()
} else {
"'-Infinity'".to_string()
}
} else {
self.to_string()
}
}
}
impl ToSqlParam for f64 {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(self.to_be_bytes().to_vec())
}
fn sql_oid(&self) -> Oid {
oids::DOUBLE
}
fn to_sql_literal(&self) -> String {
if self.is_nan() {
"'NaN'".to_string()
} else if self.is_infinite() {
if *self > 0.0 {
"'Infinity'".to_string()
} else {
"'-Infinity'".to_string()
}
} else {
self.to_string()
}
}
}
impl ToSqlParam for bool {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(vec![u8::from(*self)])
}
fn sql_oid(&self) -> Oid {
oids::BOOL
}
fn to_sql_literal(&self) -> String {
if *self { "TRUE" } else { "FALSE" }.to_string()
}
}
impl ToSqlParam for str {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(self.as_bytes().to_vec())
}
fn sql_oid(&self) -> Oid {
oids::TEXT
}
fn to_sql_literal(&self) -> String {
format!("'{}'", self.replace('\'', "''"))
}
}
impl ToSqlParam for String {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(self.as_bytes().to_vec())
}
fn sql_oid(&self) -> Oid {
oids::TEXT
}
fn to_sql_literal(&self) -> String {
format!("'{}'", self.replace('\'', "''"))
}
}
impl ToSqlParam for &str {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(self.as_bytes().to_vec())
}
fn sql_oid(&self) -> Oid {
oids::TEXT
}
fn to_sql_literal(&self) -> String {
format!("'{}'", self.replace('\'', "''"))
}
}
impl<T: ToSqlParam> ToSqlParam for &T {
fn encode_param(&self) -> Option<Vec<u8>> {
(*self).encode_param()
}
fn sql_oid(&self) -> Oid {
(*self).sql_oid()
}
fn to_sql_literal(&self) -> String {
(*self).to_sql_literal()
}
}
impl<T: ToSqlParam> ToSqlParam for Option<T> {
fn encode_param(&self) -> Option<Vec<u8>> {
match self {
Some(value) => value.encode_param(),
None => None, }
}
fn sql_oid(&self) -> Oid {
match self {
Some(value) => value.sql_oid(),
None => Oid::new(0),
}
}
fn to_sql_literal(&self) -> String {
match self {
Some(value) => value.to_sql_literal(),
None => "NULL".to_string(),
}
}
}
impl ToSqlParam for Date {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(self.to_julian_day().to_be_bytes().to_vec())
}
fn sql_oid(&self) -> Oid {
oids::DATE
}
fn to_sql_literal(&self) -> String {
format!("DATE '{self}'")
}
}
impl ToSqlParam for Time {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(self.to_microseconds().to_be_bytes().to_vec())
}
fn sql_oid(&self) -> Oid {
oids::TIME
}
fn to_sql_literal(&self) -> String {
format!("TIME '{self}'")
}
}
impl ToSqlParam for Timestamp {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(self.to_microseconds().to_be_bytes().to_vec())
}
fn sql_oid(&self) -> Oid {
oids::TIMESTAMP
}
fn to_sql_literal(&self) -> String {
format!("TIMESTAMP '{self}'")
}
}
impl ToSqlParam for OffsetTimestamp {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(self.to_microseconds_utc().to_be_bytes().to_vec())
}
fn sql_oid(&self) -> Oid {
oids::TIMESTAMP_TZ
}
fn to_sql_literal(&self) -> String {
format!("TIMESTAMPTZ '{self}'")
}
}
impl ToSqlParam for [u8] {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(self.to_vec())
}
fn sql_oid(&self) -> Oid {
oids::BYTE_A
}
#[expect(
clippy::format_collect,
reason = "readable hex/string formatting loop; refactoring to fold! obscures intent"
)]
fn to_sql_literal(&self) -> String {
let hex_str: String = self.iter().map(|b| format!("{b:02x}")).collect();
format!("E'\\\\x{hex_str}'")
}
}
impl ToSqlParam for Vec<u8> {
fn encode_param(&self) -> Option<Vec<u8>> {
Some(self.clone())
}
fn sql_oid(&self) -> Oid {
oids::BYTE_A
}
#[expect(
clippy::format_collect,
reason = "readable hex/string formatting loop; refactoring to fold! obscures intent"
)]
fn to_sql_literal(&self) -> String {
let hex_str: String = self.iter().map(|b| format!("{b:02x}")).collect();
format!("E'\\\\x{hex_str}'")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_i32_encoding() {
assert_eq!(42i32.encode_param(), Some(vec![0, 0, 0, 42]));
assert_eq!((-1i32).encode_param(), Some(vec![255, 255, 255, 255]));
}
#[test]
fn test_i64_encoding() {
assert_eq!(42i64.encode_param(), Some(vec![0, 0, 0, 0, 0, 0, 0, 42]));
}
#[test]
fn test_string_encoding() {
assert_eq!("hello".encode_param(), Some(b"hello".to_vec()));
assert_eq!(
String::from("world").encode_param(),
Some(b"world".to_vec())
);
}
#[test]
fn test_bool_encoding() {
assert_eq!(true.encode_param(), Some(vec![1]));
assert_eq!(false.encode_param(), Some(vec![0]));
}
#[test]
fn test_option_encoding() {
assert_eq!(Some(42i32).encode_param(), Some(vec![0, 0, 0, 42]));
assert_eq!(None::<i32>.encode_param(), None);
}
#[test]
fn test_reference_encoding() {
let value = 42i32;
assert_eq!(value.encode_param(), Some(vec![0, 0, 0, 42]));
assert_eq!((&&value).encode_param(), Some(vec![0, 0, 0, 42]));
}
}