#![warn(missing_docs)]
use std::{collections::HashMap, fmt::Debug, marker::PhantomData};
#[cfg(feature = "mysql")]
use sqlx::mysql::MySqlRow;
#[cfg(feature = "postgres")]
use sqlx::postgres::PgRow;
#[cfg(feature = "sqlite")]
use sqlx::sqlite::SqliteRow;
use crate::{
operations::query::JoinInfo,
schema::{Column, ColumnInfo, Schema, Value},
};
pub struct Row<S: Schema + Debug> {
data: std::collections::HashMap<String, Value>,
_phanton: PhantomData<S>,
}
impl<S: Schema + Debug> Debug for Row<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Row").field("data", &self.data).finish()
}
}
impl<S: Schema + Debug> Row<S> {
pub(crate) fn _new() -> Self {
Self {
data: std::collections::HashMap::new(),
_phanton: PhantomData,
}
}
pub(crate) fn _insert<T>(&mut self, column: ColumnInfo, value: T)
where
T: Into<Value>,
{
self.data.insert(column.name.to_string(), value.into());
}
pub fn get<T>(&self, column: &'static Column<T>) -> Option<T>
where
T: TryFrom<Value>,
{
self.data
.get(column.name)
.and_then(|v| T::try_from(v.clone()).ok())
}
#[cfg(feature = "mysql")]
pub(crate) fn from_mysql_row(rows: Vec<MySqlRow>, joins: Option<&Vec<JoinInfo>>) -> Vec<Self> {
let mut rows_: Vec<Self> = Vec::new();
for row in rows {
let mut map = HashMap::new();
let main_columns = S::get_all_columns();
for column in main_columns {
let value = Self::extract_column_value(&row, &column.name, &column.data_type);
if let Some(value) = value {
map.insert(column.name.to_string(), value);
}
}
if joins.is_some() {
for join in joins.unwrap() {
let joined_column = &join.columns;
for column in joined_column {
let value =
Self::extract_column_value(&row, &column.name, &column.data_type);
if let Some(value) = value {
if map.contains_key(column.name) {
let fq_key = format!("{}.{}", join.table_name, column.name);
map.entry(fq_key).or_insert(value);
} else {
map.insert(column.name.to_string(), value);
}
}
}
}
}
rows_.push(Self {
data: map,
_phanton: PhantomData,
});
}
rows_
}
#[cfg(feature = "postgres")]
pub(crate) fn from_postgres_row(rows: Vec<PgRow>, joins: Option<&Vec<JoinInfo>>) -> Vec<Self> {
let mut rows_: Vec<Self> = Vec::new();
for row in rows {
let mut map = HashMap::new();
let main_columns = S::get_all_columns();
for column in main_columns {
let value = Self::extract_column_value(&row, &column.name, &column.data_type);
if let Some(value) = value {
map.insert(column.name.to_string(), value);
}
}
if joins.is_some() {
for join in joins.unwrap() {
let joined_column = &join.columns;
for column in joined_column {
let value =
Self::extract_column_value(&row, &column.name, &column.data_type);
if let Some(value) = value {
if map.contains_key(column.name) {
let fq_key = format!("{}.{}", join.table_name, column.name);
map.entry(fq_key).or_insert(value);
} else {
map.insert(column.name.to_string(), value);
}
}
}
}
}
rows_.push(Self {
data: map,
_phanton: PhantomData,
});
}
rows_
}
#[cfg(feature = "sqlite")]
pub(crate) fn from_sqlite_row(
rows: Vec<SqliteRow>,
joins: Option<&Vec<JoinInfo>>,
) -> Vec<Self> {
let mut rows_: Vec<Self> = Vec::new();
for row in rows {
let mut map = HashMap::new();
let main_columns = S::get_all_columns();
for column in main_columns {
let value = Self::extract_column_value(&row, &column.name, &column.data_type);
if let Some(value) = value {
map.insert(column.name.to_string(), value);
}
}
if joins.is_some() {
for join in joins.unwrap() {
let joined_column = &join.columns;
for column in joined_column {
let value =
Self::extract_column_value(&row, &column.name, &column.data_type);
if let Some(value) = value {
if map.contains_key(column.name) {
let fq_key = format!("{}.{}", join.table_name, column.name);
map.entry(fq_key).or_insert(value);
} else {
map.insert(column.name.to_string(), value);
}
}
}
}
}
rows_.push(Self {
data: map,
_phanton: PhantomData,
});
}
rows_
}
#[cfg(feature = "mysql")]
fn extract_column_value(row: &MySqlRow, column_name: &str, data_type: &str) -> Option<Value> {
use sqlx::Row as _;
match data_type {
"TEXT" => {
if let Ok(val) = row.try_get::<String, _>(column_name) {
Some(Value::String(val))
} else if let Ok(val) = row.try_get::<Option<String>, _>(column_name) {
val.map(Value::String)
} else {
None
}
}
"TINYINT" => {
if let Ok(val) = row.try_get::<i8, _>(column_name) {
Some(Value::Int8(val))
} else if let Ok(val) = row.try_get::<Option<i8>, _>(column_name) {
val.map(Value::Int8)
} else {
None
}
}
"SMALLINT" => {
if let Ok(val) = row.try_get::<i16, _>(column_name) {
Some(Value::Int16(val))
} else if let Ok(val) = row.try_get::<Option<i16>, _>(column_name) {
val.map(Value::Int16)
} else {
None
}
}
"INTEGER" => {
if let Ok(val) = row.try_get::<i32, _>(column_name) {
Some(Value::Int32(val))
} else if let Ok(val) = row.try_get::<Option<i32>, _>(column_name) {
val.map(Value::Int32)
} else {
None
}
}
"BIGINT" => {
if let Ok(val) = row.try_get::<i64, _>(column_name) {
Some(Value::Int64(val))
} else if let Ok(val) = row.try_get::<Option<i64>, _>(column_name) {
val.map(Value::Int64)
} else {
None
}
}
"TINYINT UNSIGNED" => {
if let Ok(val) = row.try_get::<u8, _>(column_name) {
Some(Value::UInt8(val))
} else if let Ok(val) = row.try_get::<Option<u8>, _>(column_name) {
val.map(Value::UInt8)
} else {
None
}
}
"SMALLINT UNSIGNED" => {
if let Ok(val) = row.try_get::<u16, _>(column_name) {
Some(Value::UInt16(val))
} else if let Ok(val) = row.try_get::<Option<u16>, _>(column_name) {
val.map(Value::UInt16)
} else {
None
}
}
"INTEGER UNSIGNED" => {
if let Ok(val) = row.try_get::<u32, _>(column_name) {
Some(Value::UInt32(val))
} else if let Ok(val) = row.try_get::<Option<u32>, _>(column_name) {
val.map(Value::UInt32)
} else {
None
}
}
"BIGINT UNSIGNED" => {
if let Ok(val) = row.try_get::<u64, _>(column_name) {
Some(Value::UInt64(val))
} else if let Ok(val) = row.try_get::<Option<u64>, _>(column_name) {
val.map(Value::UInt64)
} else {
None
}
}
"FLOAT" => {
if let Ok(val) = row.try_get::<f32, _>(column_name) {
Some(Value::Float32(val))
} else if let Ok(val) = row.try_get::<Option<f32>, _>(column_name) {
val.map(Value::Float32)
} else {
None
}
}
"REAL" | "DOUBLE PRECISION" | "DOUBLE" => {
if let Ok(val) = row.try_get::<f64, _>(column_name) {
Some(Value::Float64(val))
} else if let Ok(val) = row.try_get::<Option<f64>, _>(column_name) {
val.map(Value::Float64)
} else {
None
}
}
"BOOLEAN" => {
if let Ok(val) = row.try_get::<bool, _>(column_name) {
Some(Value::Bool(val))
} else if let Ok(val) = row.try_get::<Option<bool>, _>(column_name) {
val.map(Value::Bool)
} else {
None
}
}
_ => {
if let Ok(val) = row.try_get::<String, _>(column_name) {
Some(Value::String(val))
} else if let Ok(val) = row.try_get::<Option<String>, _>(column_name) {
val.map(Value::String)
} else {
None
}
}
}
}
#[cfg(feature = "postgres")]
fn extract_column_value(row: &PgRow, column_name: &str, data_type: &str) -> Option<Value> {
use sqlx::Row as _;
match data_type {
"TEXT" => {
if let Ok(val) = row.try_get::<String, _>(column_name) {
Some(Value::String(val))
} else if let Ok(val) = row.try_get::<Option<String>, _>(column_name) {
val.map(Value::String)
} else {
None
}
}
"SMALLINT" => {
if let Ok(val) = row.try_get::<i16, _>(column_name) {
Some(Value::Int16(val))
} else if let Ok(val) = row.try_get::<Option<i16>, _>(column_name) {
val.map(Value::Int16)
} else {
None
}
}
"INTEGER" => {
if let Ok(val) = row.try_get::<i32, _>(column_name) {
Some(Value::Int32(val))
} else if let Ok(val) = row.try_get::<Option<i32>, _>(column_name) {
val.map(Value::Int32)
} else {
None
}
}
"BIGINT" => {
if let Ok(val) = row.try_get::<i64, _>(column_name) {
Some(Value::Int64(val))
} else if let Ok(val) = row.try_get::<Option<i64>, _>(column_name) {
val.map(Value::Int64)
} else {
None
}
}
"FLOAT" => {
if let Ok(val) = row.try_get::<f32, _>(column_name) {
Some(Value::Float32(val))
} else if let Ok(val) = row.try_get::<Option<f32>, _>(column_name) {
val.map(Value::Float32)
} else {
None
}
}
"REAL" | "DOUBLE PRECISION" | "DOUBLE" => {
if let Ok(val) = row.try_get::<f64, _>(column_name) {
Some(Value::Float64(val))
} else if let Ok(val) = row.try_get::<Option<f64>, _>(column_name) {
val.map(Value::Float64)
} else {
None
}
}
"BOOLEAN" => {
if let Ok(val) = row.try_get::<bool, _>(column_name) {
Some(Value::Bool(val))
} else if let Ok(val) = row.try_get::<Option<bool>, _>(column_name) {
val.map(Value::Bool)
} else {
None
}
}
_ => {
if let Ok(val) = row.try_get::<String, _>(column_name) {
Some(Value::String(val))
} else if let Ok(val) = row.try_get::<Option<String>, _>(column_name) {
val.map(Value::String)
} else {
None
}
}
}
}
#[cfg(feature = "sqlite")]
fn extract_column_value(row: &SqliteRow, column_name: &str, data_type: &str) -> Option<Value> {
use sqlx::Row as _;
match data_type {
"TEXT" => {
if let Ok(val) = row.try_get::<String, _>(column_name) {
Some(Value::String(val))
} else if let Ok(val) = row.try_get::<Option<String>, _>(column_name) {
val.map(Value::String)
} else {
None
}
}
"SMALLINT" => {
if let Ok(val) = row.try_get::<i16, _>(column_name) {
Some(Value::Int16(val))
} else if let Ok(val) = row.try_get::<Option<i16>, _>(column_name) {
val.map(Value::Int16)
} else {
None
}
}
"INTEGER" => {
if let Ok(val) = row.try_get::<i32, _>(column_name) {
Some(Value::Int32(val))
} else if let Ok(val) = row.try_get::<Option<i32>, _>(column_name) {
val.map(Value::Int32)
} else {
None
}
}
"BIGINT" => {
if let Ok(val) = row.try_get::<i64, _>(column_name) {
Some(Value::Int64(val))
} else if let Ok(val) = row.try_get::<Option<i64>, _>(column_name) {
val.map(Value::Int64)
} else {
None
}
}
"FLOAT" => {
if let Ok(val) = row.try_get::<f32, _>(column_name) {
Some(Value::Float32(val))
} else if let Ok(val) = row.try_get::<Option<f32>, _>(column_name) {
val.map(Value::Float32)
} else {
None
}
}
"REAL" | "DOUBLE PRECISION" | "DOUBLE" => {
if let Ok(val) = row.try_get::<f64, _>(column_name) {
Some(Value::Float64(val))
} else if let Ok(val) = row.try_get::<Option<f64>, _>(column_name) {
val.map(Value::Float64)
} else {
None
}
}
"BOOLEAN" => {
if let Ok(val) = row.try_get::<bool, _>(column_name) {
Some(Value::Bool(val))
} else if let Ok(val) = row.try_get::<Option<bool>, _>(column_name) {
val.map(Value::Bool)
} else {
None
}
}
_ => {
if let Ok(val) = row.try_get::<String, _>(column_name) {
Some(Value::String(val))
} else if let Ok(val) = row.try_get::<Option<String>, _>(column_name) {
val.map(Value::String)
} else {
None
}
}
}
}
}