Fixed query building and dice track
This commit is contained in:
@@ -11,5 +11,5 @@ post {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auth:bearer {
|
auth:bearer {
|
||||||
token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjI1MDg0MjI2MTIyMTI3NzY5NywibmFtZSI6ImJzaGVycmlmZiIsImlhdCI6MTczNDg0MzA0NywiZXhwIjoxNzM0OTI5NDQ3LCJqdGkiOiIycEowbmN5YmF1TVo4TG1aQ0VwU1B2OWgzMXFzU1FwaCJ9.gYf6oAm2POBXOHUnG4dTy5maKxTjUk8WxawOrIafjEE
|
token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjI1MDg0MjI2MTIyMTI3NzY5NywibmFtZSI6ImJzaGVycmlmZiIsImlhdCI6MTczNDkwMjgzOSwiZXhwIjoxNzM0OTg5MjM5LCJqdGkiOiJWTlFjeXpBN25sZEt1SWtzcDFzc1pRNHNacUZ2dWZPZCJ9.JnO-Rklv9YZKWjRvehR4-tfP1dlO5vIEWpSh_W4xZWY
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::api::auth::{csprng, AuthCredential};
|
use crate::api::auth::{csprng, AuthCredential};
|
||||||
use crate::api::auth::AuthorizationMiddleware;
|
use crate::api::auth::AuthorizationMiddleware;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
use crate::data::ExecutableQuery;
|
||||||
use crate::data::condition::Condition;
|
use crate::data::condition::Condition;
|
||||||
use crate::data::insert::InsertBuilder;
|
use crate::data::insert::InsertBuilder;
|
||||||
use crate::data::query::QueryBuilder;
|
use crate::data::query::QueryBuilder;
|
||||||
@@ -52,7 +53,8 @@ impl ApiKey {
|
|||||||
.column("access_mask", Value::Int(self.access_mask))
|
.column("access_mask", Value::Int(self.access_mask))
|
||||||
.column("created_at", Value::DateTime(self.created_at))
|
.column("created_at", Value::DateTime(self.created_at))
|
||||||
.column("last_used_at", Value::OptionalDateTime(self.last_used_at))
|
.column("last_used_at", Value::OptionalDateTime(self.last_used_at))
|
||||||
.execute().await?;
|
.execute()
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +66,9 @@ impl ApiKey {
|
|||||||
.column("created_at", Value::DateTime(self.created_at))
|
.column("created_at", Value::DateTime(self.created_at))
|
||||||
.column("last_used_at", Value::OptionalDateTime(self.last_used_at))
|
.column("last_used_at", Value::OptionalDateTime(self.last_used_at))
|
||||||
.where_condition(Condition::is_equal("key", Value::Text(self.key.clone())))
|
.where_condition(Condition::is_equal("key", Value::Text(self.key.clone())))
|
||||||
.execute().await {
|
.execute()
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!("error: {}", err);
|
log::error!("error: {}", err);
|
||||||
@@ -76,7 +80,8 @@ impl ApiKey {
|
|||||||
pub async fn find_by_key(key: &str) -> Option<Self> {
|
pub async fn find_by_key(key: &str) -> Option<Self> {
|
||||||
QueryBuilder::new(TABLE_NAME)
|
QueryBuilder::new(TABLE_NAME)
|
||||||
.where_condition(Condition::is_equal("key", Value::Text(key.to_string())))
|
.where_condition(Condition::is_equal("key", Value::Text(key.to_string())))
|
||||||
.fetch_optional().await
|
.fetch_optional()
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_by_id(key: &str) -> SirenResult<()> {
|
pub async fn delete_by_id(key: &str) -> SirenResult<()> {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ use uuid::Uuid;
|
|||||||
use crate::api::auth::{AuthCredential, AuthorizationMiddleware};
|
use crate::api::auth::{AuthCredential, AuthorizationMiddleware};
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::bot::commands::fun::roll::{format_roll, parse_dice};
|
use crate::bot::commands::fun::roll::{format_roll, parse_dice};
|
||||||
|
use crate::data::condition::Condition;
|
||||||
|
use crate::data::{ExecutableQuery, Value};
|
||||||
use crate::data::query::QueryBuilder;
|
use crate::data::query::QueryBuilder;
|
||||||
use crate::error::{Error, SirenResult};
|
use crate::error::{Error, SirenResult};
|
||||||
|
|
||||||
@@ -95,22 +97,26 @@ struct QueryDiceTrack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl QueryDiceTrack {
|
impl QueryDiceTrack {
|
||||||
pub async fn find() -> SirenResult<Vec<Self>> {
|
pub async fn find(dice: &InsertDiceTrack) -> Option<Self> {
|
||||||
let pool = crate::data::pool();
|
QueryBuilder::new(TABLE_NAME)
|
||||||
let query = QueryBuilder::new(TABLE_NAME)
|
.where_condition(Condition::and(
|
||||||
// .where_condition(
|
Condition::is_equal("guild_id", Value::BigInt(dice.guild_id)),
|
||||||
// Condition::and(
|
Condition::and(
|
||||||
// Condition::is_equal("guild_id", "$1"),
|
Condition::is_equal("owner_id", Value::BigInt(dice.owner_id)),
|
||||||
// Condition::and(
|
Condition::and(
|
||||||
// Condition::is_equal("owner_id", "$2"),
|
Condition::is_equal("dice", Value::Text(dice.dice.clone())),
|
||||||
//
|
Condition::and(
|
||||||
// )
|
Condition::is_equal("user_id", Value::OptionalBigInt(dice.user_id)),
|
||||||
// )
|
Condition::and(
|
||||||
// )
|
Condition::is_equal("value", Value::OptionalInt(dice.value)),
|
||||||
.build();
|
Condition::is_equal("operator", Value::OptionalText(dice.operator.clone())),
|
||||||
let items: Vec<QueryDiceTrack> = sqlx::query_as(&query.0).fetch_all(pool).await?;
|
),
|
||||||
|
),
|
||||||
Ok(items)
|
),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.fetch_optional()
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +174,7 @@ pub async fn add_track_dice(
|
|||||||
|
|
||||||
let dice = parse_dice(&payload.dice)?;
|
let dice = parse_dice(&payload.dice)?;
|
||||||
|
|
||||||
let dice = InsertDiceTrack {
|
let insert_dice = InsertDiceTrack {
|
||||||
guild_id: guild_id.get() as i64,
|
guild_id: guild_id.get() as i64,
|
||||||
owner_id: owner_id.get() as i64,
|
owner_id: owner_id.get() as i64,
|
||||||
dice: format_roll(dice.0, dice.1, dice.2),
|
dice: format_roll(dice.0, dice.1, dice.2),
|
||||||
@@ -180,6 +186,14 @@ pub async fn add_track_dice(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let dice_track = dice.insert().await?;
|
// Check for existing dice tracks
|
||||||
|
let results = QueryDiceTrack::find(&insert_dice).await;
|
||||||
|
|
||||||
|
match results {
|
||||||
|
Some(dice_track) => Ok(Json(dice_track)),
|
||||||
|
None => {
|
||||||
|
let dice_track = insert_dice.insert().await?;
|
||||||
Ok(Json(dice_track))
|
Ok(Json(dice_track))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ pub use app::App;
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use serde::Deserialize;
|
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
|
|||||||
@@ -24,12 +24,20 @@ impl Condition {
|
|||||||
Condition::Group(Box::new(self))
|
Condition::Group(Box::new(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_equal(left: &str, right: impl Into<Value>) -> Self {
|
pub fn is_equal(left: &str, right: impl Into<Value> + Clone) -> Self {
|
||||||
Condition::Simple(format!("{} = ?", left), vec![right.into()])
|
let value = right.clone().into();
|
||||||
|
match Self::from_optional_value(left, value) {
|
||||||
|
Some(condition) => condition,
|
||||||
|
None => Condition::Simple(format!("{} = ?", left), vec![right.into()]),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn not_equal(left: &str, right: impl Into<Value>) -> Self {
|
pub fn not_equal(left: &str, right: impl Into<Value> + Clone) -> Self {
|
||||||
Condition::Simple(format!("{} != ?", left), vec![right.into()])
|
let value = right.clone().into();
|
||||||
|
match Self::from_optional_value(left, value) {
|
||||||
|
Some(condition) => condition,
|
||||||
|
None => Condition::Simple(format!("{} != ?", left), vec![right.into()]),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_null(value: &str) -> Self {
|
pub fn is_null(value: &str) -> Self {
|
||||||
@@ -41,6 +49,11 @@ impl Condition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_in(left: &str, right: Vec<Value>) -> Self {
|
pub fn is_in(left: &str, right: Vec<Value>) -> Self {
|
||||||
|
// Use helper function to handle special cases
|
||||||
|
if let Some(condition) = Self::handle_empty_or_all_none(left, &right, true) {
|
||||||
|
return condition;
|
||||||
|
}
|
||||||
|
|
||||||
let right_list = right
|
let right_list = right
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| "'?'".to_string())
|
.map(|v| "'?'".to_string())
|
||||||
@@ -50,6 +63,11 @@ impl Condition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn not_in(left: &str, right: Vec<Value>) -> Self {
|
pub fn not_in(left: &str, right: Vec<Value>) -> Self {
|
||||||
|
// Use helper function to handle special cases
|
||||||
|
if let Some(condition) = Self::handle_empty_or_all_none(left, &right, true) {
|
||||||
|
return condition;
|
||||||
|
}
|
||||||
|
|
||||||
let right_list = right
|
let right_list = right
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| "'?'".to_string())
|
.map(|v| "'?'".to_string())
|
||||||
@@ -58,36 +76,122 @@ impl Condition {
|
|||||||
Condition::Simple(format!("{} NOT IN ({})", left, right_list), right)
|
Condition::Simple(format!("{} NOT IN ({})", left, right_list), right)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn like(left: &str, right: impl Into<Value>) -> Self {
|
pub fn like(left: &str, right: impl Into<Value> + Clone) -> Self {
|
||||||
Condition::Simple(format!("{} LIKE '?'", left), vec![right.into()])
|
let value = right.clone().into();
|
||||||
|
match Self::from_optional_value(left, value) {
|
||||||
|
Some(condition) => condition,
|
||||||
|
None => Condition::Simple(format!("{} LIKE '?'", left), vec![right.into()]),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn not_like(left: &str, right: impl Into<Value>) -> Self {
|
pub fn not_like(left: &str, right: impl Into<Value> + Clone) -> Self {
|
||||||
Condition::Simple(format!("{} NOT LIKE '?'", left), vec![right.into()])
|
let value = right.clone().into();
|
||||||
|
match Self::from_optional_value(left, value) {
|
||||||
|
Some(condition) => condition,
|
||||||
|
None => Condition::Simple(format!("{} NOT LIKE '?'", left), vec![right.into()]),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn i_like(left: &str, right: impl Into<Value>) -> Self {
|
pub fn i_like(left: &str, right: impl Into<Value> + Clone) -> Self {
|
||||||
Condition::Simple(format!("{} ILIKE '?'", left), vec![right.into()])
|
let value = right.clone().into();
|
||||||
|
match Self::from_optional_value(left, value) {
|
||||||
|
Some(condition) => condition,
|
||||||
|
None => Condition::Simple(format!("{} ILIKE '?'", left), vec![right.into()]),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn not_i_like(left: &str, right: impl Into<Value>) -> Self {
|
pub fn not_i_like(left: &str, right: impl Into<Value> + Clone) -> Self {
|
||||||
Condition::Simple(format!("{} NOT ILIKE '?'", left), vec![right.into()])
|
let value = right.clone().into();
|
||||||
|
match Self::from_optional_value(left, value) {
|
||||||
|
Some(condition) => condition,
|
||||||
|
None => Condition::Simple(format!("{} NOT ILIKE '?'", left), vec![right.into()]),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gt(left: &str, right: impl Into<Value>) -> Self {
|
pub fn gt(left: &str, right: impl Into<Value> + Clone) -> Self {
|
||||||
Condition::Simple(format!("{} > ?", left), vec![right.into()])
|
let value = right.clone().into();
|
||||||
|
match Self::from_optional_value(left, value) {
|
||||||
|
Some(condition) => condition,
|
||||||
|
None => Condition::Simple(format!("{} > ?", left), vec![right.into()]),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gte(left: &str, right: impl Into<Value>) -> Self {
|
pub fn gte(left: &str, right: impl Into<Value> + Clone) -> Self {
|
||||||
Condition::Simple(format!("{} >= ?", left), vec![right.into()])
|
let value = right.clone().into();
|
||||||
|
match Self::from_optional_value(left, value) {
|
||||||
|
Some(condition) => condition,
|
||||||
|
None => Condition::Simple(format!("{} >= ?", left), vec![right.into()]),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lt(left: &str, right: impl Into<Value>) -> Self {
|
pub fn lt(left: &str, right: impl Into<Value> + Clone) -> Self {
|
||||||
Condition::Simple(format!("{} < ?", left), vec![right.into()])
|
let value = right.clone().into();
|
||||||
|
match Self::from_optional_value(left, value) {
|
||||||
|
Some(condition) => condition,
|
||||||
|
None => Condition::Simple(format!("{} < ?", left), vec![right.into()]),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lte(left: &str, right: impl Into<Value>) -> Self {
|
pub fn lte(left: &str, right: impl Into<Value> + Clone) -> Self {
|
||||||
Condition::Simple(format!("{} <= ?", left), vec![right.into()])
|
let value = right.clone().into();
|
||||||
|
match Self::from_optional_value(left, value) {
|
||||||
|
Some(condition) => condition,
|
||||||
|
None => Condition::Simple(format!("{} <= ?", left), vec![right.into()]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private helper function to handle optional values
|
||||||
|
fn from_optional_value(left: &str, value: Value) -> Option<Self> {
|
||||||
|
match value {
|
||||||
|
Value::OptionalInt(None) => Some(Condition::is_null(left)),
|
||||||
|
Value::OptionalBigInt(None) => Some(Condition::is_null(left)),
|
||||||
|
Value::OptionalFloat(None) => Some(Condition::is_null(left)),
|
||||||
|
Value::OptionalDouble(None) => Some(Condition::is_null(left)),
|
||||||
|
Value::OptionalBool(None) => Some(Condition::is_null(left)),
|
||||||
|
Value::OptionalText(None) => Some(Condition::is_null(left)),
|
||||||
|
Value::OptionalDateTime(None) => Some(Condition::is_null(left)),
|
||||||
|
_ => None, // For non-optional or Some(value), let the primary method handle it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private helper to handle `empty` or `all-None` lists
|
||||||
|
fn handle_empty_or_all_none(left: &str, right: &[Value], negate: bool) -> Option<Self> {
|
||||||
|
if right.is_empty() {
|
||||||
|
// For an empty list, return an always-false condition
|
||||||
|
// NOT IN with empty list is always TRUE, but we're defaulting to SQL-SAFE result (FALSE)
|
||||||
|
return Some(Condition::Simple(
|
||||||
|
if negate {
|
||||||
|
"TRUE".to_string()
|
||||||
|
} else {
|
||||||
|
"FALSE".to_string()
|
||||||
|
},
|
||||||
|
vec![],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all elements in the `right` vector are `None` (Optional*)
|
||||||
|
if right.iter().all(|v| {
|
||||||
|
matches!(
|
||||||
|
v,
|
||||||
|
Value::OptionalInt(None)
|
||||||
|
| Value::OptionalBigInt(None)
|
||||||
|
| Value::OptionalFloat(None)
|
||||||
|
| Value::OptionalDouble(None)
|
||||||
|
| Value::OptionalBool(None)
|
||||||
|
| Value::OptionalText(None)
|
||||||
|
| Value::OptionalDateTime(None)
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
// If all values are None, handle as NULL or NOT NULL
|
||||||
|
return Some(if negate {
|
||||||
|
Condition::not_null(left)
|
||||||
|
} else {
|
||||||
|
Condition::is_null(left)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, this is not an empty or all-none case
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_sql(&self, mut counter: &mut usize) -> (String, Vec<Value>) {
|
pub fn to_sql(&self, mut counter: &mut usize) -> (String, Vec<Value>) {
|
||||||
@@ -96,12 +200,20 @@ impl Condition {
|
|||||||
|
|
||||||
match self {
|
match self {
|
||||||
Condition::Simple(condition, values) => {
|
Condition::Simple(condition, values) => {
|
||||||
// Replace all instances of '?' with a numbered bind
|
// Replace each instance of '?' with increasing numbered binds
|
||||||
let mut bind_index = *counter;
|
let mut numbered_condition = String::new();
|
||||||
let numbered_condition = condition.replace("?", {
|
let mut chars = condition.chars().peekable();
|
||||||
bind_index += 1;
|
|
||||||
&format!("${}", bind_index)
|
while let Some(c) = chars.next() {
|
||||||
});
|
if c == '?' {
|
||||||
|
// Increment the counter and replace `?` with a numbered bind
|
||||||
|
*counter += 1;
|
||||||
|
numbered_condition.push_str(&format!("${}", *counter));
|
||||||
|
} else {
|
||||||
|
numbered_condition.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sql.push_str(&numbered_condition);
|
sql.push_str(&numbered_condition);
|
||||||
binds.extend(values.clone());
|
binds.extend(values.clone());
|
||||||
}
|
}
|
||||||
|
|||||||
105
src/data/executable_query.rs
Normal file
105
src/data/executable_query.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
use sqlx::{FromRow, Postgres};
|
||||||
|
use crate::data::Value;
|
||||||
|
|
||||||
|
pub trait ExecutableQuery {
|
||||||
|
fn build(&self) -> (String, Vec<Value>);
|
||||||
|
|
||||||
|
async fn execute(&self) -> Result<sqlx::postgres::PgQueryResult, sqlx::Error> {
|
||||||
|
// Build the SQL query and its values
|
||||||
|
let (query_string, values) = self.build();
|
||||||
|
|
||||||
|
// Start constructing the query
|
||||||
|
let mut query = sqlx::query(&query_string);
|
||||||
|
|
||||||
|
// Bind each value to its respective placeholder
|
||||||
|
for value in values {
|
||||||
|
match value {
|
||||||
|
Value::Int(n) => query = query.bind(n),
|
||||||
|
Value::OptionalInt(n) => query = query.bind(n),
|
||||||
|
Value::BigInt(n) => query = query.bind(n),
|
||||||
|
Value::OptionalBigInt(n) => query = query.bind(n),
|
||||||
|
Value::Float(n) => query = query.bind(n),
|
||||||
|
Value::OptionalFloat(n) => query = query.bind(n),
|
||||||
|
Value::Double(n) => query = query.bind(n),
|
||||||
|
Value::OptionalDouble(n) => query = query.bind(n),
|
||||||
|
Value::Bool(n) => query = query.bind(n),
|
||||||
|
Value::OptionalBool(n) => query = query.bind(n),
|
||||||
|
Value::Text(n) => query = query.bind(n),
|
||||||
|
Value::OptionalText(n) => query = query.bind(n),
|
||||||
|
Value::DateTime(n) => query = query.bind(n),
|
||||||
|
Value::OptionalDateTime(n) => query = query.bind(n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pool = crate::data::pool();
|
||||||
|
query.execute(pool).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_optional<
|
||||||
|
T: Send + Unpin + for<'r> FromRow<'r, <Postgres as sqlx::Database>::Row>,
|
||||||
|
>(
|
||||||
|
&self,
|
||||||
|
) -> Option<T> {
|
||||||
|
let (query_string, values) = self.build();
|
||||||
|
let mut query_as = sqlx::query_as(&query_string);
|
||||||
|
for value in values {
|
||||||
|
match value {
|
||||||
|
Value::Int(n) => query_as = query_as.bind(n),
|
||||||
|
Value::OptionalInt(n) => query_as = query_as.bind(n),
|
||||||
|
Value::BigInt(n) => query_as = query_as.bind(n),
|
||||||
|
Value::OptionalBigInt(n) => query_as = query_as.bind(n),
|
||||||
|
Value::Float(n) => query_as = query_as.bind(n),
|
||||||
|
Value::OptionalFloat(n) => query_as = query_as.bind(n),
|
||||||
|
Value::Double(n) => query_as = query_as.bind(n),
|
||||||
|
Value::OptionalDouble(n) => query_as = query_as.bind(n),
|
||||||
|
Value::Bool(n) => query_as = query_as.bind(n),
|
||||||
|
Value::OptionalBool(n) => query_as = query_as.bind(n),
|
||||||
|
Value::Text(n) => query_as = query_as.bind(n),
|
||||||
|
Value::OptionalText(n) => query_as = query_as.bind(n),
|
||||||
|
Value::DateTime(n) => query_as = query_as.bind(n),
|
||||||
|
Value::OptionalDateTime(n) => query_as = query_as.bind(n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pool = crate::data::pool();
|
||||||
|
query_as.fetch_optional(pool).await.unwrap_or_else(|err| {
|
||||||
|
log::error!(
|
||||||
|
"Unable to fetch optional on query '{}': {}",
|
||||||
|
query_string,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_all<T: Send + Unpin + for<'r> FromRow<'r, <Postgres as sqlx::Database>::Row>>(
|
||||||
|
&self,
|
||||||
|
) -> Vec<T> {
|
||||||
|
let (query_string, values) = self.build();
|
||||||
|
let mut query_as = sqlx::query_as(&query_string);
|
||||||
|
for value in values {
|
||||||
|
match value {
|
||||||
|
Value::Int(n) => query_as = query_as.bind(n),
|
||||||
|
Value::OptionalInt(n) => query_as = query_as.bind(n),
|
||||||
|
Value::BigInt(n) => query_as = query_as.bind(n),
|
||||||
|
Value::OptionalBigInt(n) => query_as = query_as.bind(n),
|
||||||
|
Value::Float(n) => query_as = query_as.bind(n),
|
||||||
|
Value::OptionalFloat(n) => query_as = query_as.bind(n),
|
||||||
|
Value::Double(n) => query_as = query_as.bind(n),
|
||||||
|
Value::OptionalDouble(n) => query_as = query_as.bind(n),
|
||||||
|
Value::Bool(n) => query_as = query_as.bind(n),
|
||||||
|
Value::OptionalBool(n) => query_as = query_as.bind(n),
|
||||||
|
Value::Text(n) => query_as = query_as.bind(n),
|
||||||
|
Value::OptionalText(n) => query_as = query_as.bind(n),
|
||||||
|
Value::DateTime(n) => query_as = query_as.bind(n),
|
||||||
|
Value::OptionalDateTime(n) => query_as = query_as.bind(n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pool = crate::data::pool();
|
||||||
|
query_as.fetch_all(pool).await.unwrap_or_else(|err| {
|
||||||
|
log::error!("Unable to fetch all on query '{}': {}", query_string, err);
|
||||||
|
vec![]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use sqlx::Database;
|
use sqlx::Database;
|
||||||
use crate::data::condition::Condition;
|
use crate::data::condition::Condition;
|
||||||
|
use crate::data::executable_query::ExecutableQuery;
|
||||||
use crate::data::insert::InsertBuilder;
|
use crate::data::insert::InsertBuilder;
|
||||||
use crate::data::query::QueryBuilder;
|
use crate::data::query::QueryBuilder;
|
||||||
use crate::data::update::UpdateBuilder;
|
use crate::data::update::UpdateBuilder;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::data::executable_query::ExecutableQuery;
|
||||||
use crate::data::Value;
|
use crate::data::Value;
|
||||||
|
|
||||||
pub struct InsertBuilder {
|
pub struct InsertBuilder {
|
||||||
@@ -27,39 +28,10 @@ impl InsertBuilder {
|
|||||||
self.returning = columns.iter().map(|s| s.to_string()).collect();
|
self.returning = columns.iter().map(|s| s.to_string()).collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute(self) -> Result<sqlx::postgres::PgQueryResult, sqlx::Error> {
|
|
||||||
// Build the SQL query and its values
|
|
||||||
let (query_string, values) = self.build();
|
|
||||||
|
|
||||||
// Start constructing the query
|
|
||||||
let mut query = sqlx::query(&query_string);
|
|
||||||
|
|
||||||
// Bind each value to its respective placeholder
|
|
||||||
for value in values {
|
|
||||||
match value {
|
|
||||||
Value::Int(n) => query = query.bind(n),
|
|
||||||
Value::OptionalInt(n) => query = query.bind(n),
|
|
||||||
Value::BigInt(n) => query = query.bind(n),
|
|
||||||
Value::OptionalBigInt(n) => query = query.bind(n),
|
|
||||||
Value::Float(n) => query = query.bind(n),
|
|
||||||
Value::OptionalFloat(n) => query = query.bind(n),
|
|
||||||
Value::Double(n) => query = query.bind(n),
|
|
||||||
Value::OptionalDouble(n) => query = query.bind(n),
|
|
||||||
Value::Bool(n) => query = query.bind(n),
|
|
||||||
Value::OptionalBool(n) => query = query.bind(n),
|
|
||||||
Value::Text(n) => query = query.bind(n),
|
|
||||||
Value::OptionalText(n) => query = query.bind(n),
|
|
||||||
Value::DateTime(n) => query = query.bind(n),
|
|
||||||
Value::OptionalDateTime(n) => query = query.bind(n),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let pool = crate::data::pool();
|
impl ExecutableQuery for InsertBuilder {
|
||||||
query.execute(pool).await
|
fn build(&self) -> (String, Vec<Value>) {
|
||||||
}
|
|
||||||
|
|
||||||
fn build(self) -> (String, Vec<Value>) {
|
|
||||||
if self.columns.is_empty() || self.values.is_empty() {
|
if self.columns.is_empty() || self.values.is_empty() {
|
||||||
panic!("Cannot build insert query without columns and values");
|
panic!("Cannot build insert query without columns and values");
|
||||||
}
|
}
|
||||||
@@ -84,6 +56,6 @@ impl InsertBuilder {
|
|||||||
query.push_str(&format!(" RETURNING {}", self.returning.join(", ")));
|
query.push_str(&format!(" RETURNING {}", self.returning.join(", ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
(query, self.values)
|
(query, self.values.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ use crate::error::SirenResult;
|
|||||||
|
|
||||||
pub mod condition;
|
pub mod condition;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
|
mod executable_query;
|
||||||
pub mod guilds;
|
pub mod guilds;
|
||||||
pub mod insert;
|
pub mod insert;
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
pub mod query;
|
pub mod query;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
mod executable_query;
|
pub use executable_query::ExecutableQuery;
|
||||||
|
|
||||||
static POOL: OnceLock<Pool<Postgres>> = OnceLock::new();
|
static POOL: OnceLock<Pool<Postgres>> = OnceLock::new();
|
||||||
static REDIS: OnceLock<RedisClient> = OnceLock::new();
|
static REDIS: OnceLock<RedisClient> = OnceLock::new();
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
use std::fmt;
|
|
||||||
use std::fmt::Display;
|
|
||||||
use sqlx::{FromRow, Postgres};
|
|
||||||
use crate::data::condition::Condition;
|
use crate::data::condition::Condition;
|
||||||
|
use crate::data::executable_query::ExecutableQuery;
|
||||||
use crate::data::Value;
|
use crate::data::Value;
|
||||||
|
|
||||||
pub struct QueryBuilder {
|
pub struct QueryBuilder {
|
||||||
@@ -44,45 +42,10 @@ impl QueryBuilder {
|
|||||||
self.limit = Some(limit);
|
self.limit = Some(limit);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_optional<
|
|
||||||
T: Send + Unpin + for<'r> FromRow<'r, <Postgres as sqlx::Database>::Row>,
|
|
||||||
>(
|
|
||||||
self,
|
|
||||||
) -> Option<T> {
|
|
||||||
let (query_string, values) = self.build();
|
|
||||||
let mut query_as = sqlx::query_as(&query_string);
|
|
||||||
for value in values {
|
|
||||||
match value {
|
|
||||||
Value::Int(n) => query_as = query_as.bind(n),
|
|
||||||
Value::OptionalInt(n) => query_as = query_as.bind(n),
|
|
||||||
Value::BigInt(n) => query_as = query_as.bind(n),
|
|
||||||
Value::OptionalBigInt(n) => query_as = query_as.bind(n),
|
|
||||||
Value::Float(n) => query_as = query_as.bind(n),
|
|
||||||
Value::OptionalFloat(n) => query_as = query_as.bind(n),
|
|
||||||
Value::Double(n) => query_as = query_as.bind(n),
|
|
||||||
Value::OptionalDouble(n) => query_as = query_as.bind(n),
|
|
||||||
Value::Bool(n) => query_as = query_as.bind(n),
|
|
||||||
Value::OptionalBool(n) => query_as = query_as.bind(n),
|
|
||||||
Value::Text(n) => query_as = query_as.bind(n),
|
|
||||||
Value::OptionalText(n) => query_as = query_as.bind(n),
|
|
||||||
Value::DateTime(n) => query_as = query_as.bind(n),
|
|
||||||
Value::OptionalDateTime(n) => query_as = query_as.bind(n),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let pool = crate::data::pool();
|
impl ExecutableQuery for QueryBuilder {
|
||||||
query_as.fetch_optional(pool).await.unwrap_or_else(|err| {
|
fn build(&self) -> (String, Vec<Value>) {
|
||||||
log::error!(
|
|
||||||
"Unable to fetch optional on query '{}': {}",
|
|
||||||
query_string,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
None
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> (String, Vec<Value>) {
|
|
||||||
let columns = if self.columns.is_empty() {
|
let columns = if self.columns.is_empty() {
|
||||||
"*".to_string()
|
"*".to_string()
|
||||||
} else {
|
} else {
|
||||||
@@ -92,7 +55,7 @@ impl QueryBuilder {
|
|||||||
let mut query = format!("SELECT {} FROM {}", columns, self.table);
|
let mut query = format!("SELECT {} FROM {}", columns, self.table);
|
||||||
|
|
||||||
let mut values: Vec<Value> = Vec::new();
|
let mut values: Vec<Value> = Vec::new();
|
||||||
if let Some(condition) = self.condition {
|
if let Some(condition) = &self.condition {
|
||||||
let where_condition = condition.to_sql(&mut 0);
|
let where_condition = condition.to_sql(&mut 0);
|
||||||
query.push_str(&format!(" WHERE {}", where_condition.0));
|
query.push_str(&format!(" WHERE {}", where_condition.0));
|
||||||
values = where_condition.1;
|
values = where_condition.1;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::data::condition::Condition;
|
use crate::data::condition::Condition;
|
||||||
|
use crate::data::executable_query::ExecutableQuery;
|
||||||
use crate::data::Value;
|
use crate::data::Value;
|
||||||
|
|
||||||
pub struct UpdateBuilder {
|
pub struct UpdateBuilder {
|
||||||
@@ -28,39 +29,10 @@ impl UpdateBuilder {
|
|||||||
self.condition = Some(condition);
|
self.condition = Some(condition);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute(self) -> Result<sqlx::postgres::PgQueryResult, sqlx::Error> {
|
|
||||||
// Build the SQL query and its values
|
|
||||||
let (query_string, values) = self.build();
|
|
||||||
|
|
||||||
// Start constructing the query
|
|
||||||
let mut query = sqlx::query(&query_string);
|
|
||||||
|
|
||||||
// Bind each value to its respective placeholder
|
|
||||||
for value in values {
|
|
||||||
match value {
|
|
||||||
Value::Int(n) => query = query.bind(n),
|
|
||||||
Value::OptionalInt(n) => query = query.bind(n),
|
|
||||||
Value::BigInt(n) => query = query.bind(n),
|
|
||||||
Value::OptionalBigInt(n) => query = query.bind(n),
|
|
||||||
Value::Float(n) => query = query.bind(n),
|
|
||||||
Value::OptionalFloat(n) => query = query.bind(n),
|
|
||||||
Value::Double(n) => query = query.bind(n),
|
|
||||||
Value::OptionalDouble(n) => query = query.bind(n),
|
|
||||||
Value::Bool(n) => query = query.bind(n),
|
|
||||||
Value::OptionalBool(n) => query = query.bind(n),
|
|
||||||
Value::Text(n) => query = query.bind(n),
|
|
||||||
Value::OptionalText(n) => query = query.bind(n),
|
|
||||||
Value::DateTime(n) => query = query.bind(n),
|
|
||||||
Value::OptionalDateTime(n) => query = query.bind(n),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let pool = crate::data::pool();
|
impl ExecutableQuery for UpdateBuilder {
|
||||||
query.execute(pool).await
|
fn build(&self) -> (String, Vec<Value>) {
|
||||||
}
|
|
||||||
|
|
||||||
fn build(self) -> (String, Vec<Value>) {
|
|
||||||
if self.columns.is_empty() {
|
if self.columns.is_empty() {
|
||||||
panic!("Cannot build update query without columns to set");
|
panic!("Cannot build update query without columns to set");
|
||||||
}
|
}
|
||||||
@@ -76,10 +48,10 @@ impl UpdateBuilder {
|
|||||||
|
|
||||||
let mut query = format!("UPDATE {} SET {}", self.table, set_clause);
|
let mut query = format!("UPDATE {} SET {}", self.table, set_clause);
|
||||||
let mut counter = self.values.len();
|
let mut counter = self.values.len();
|
||||||
let mut values: Vec<Value> = self.values;
|
let mut values: Vec<Value> = self.values.clone();
|
||||||
|
|
||||||
// Build where clause
|
// Build where clause
|
||||||
if let Some(condition) = self.condition {
|
if let Some(condition) = &self.condition {
|
||||||
let where_condition = condition.to_sql(&mut counter);
|
let where_condition = condition.to_sql(&mut counter);
|
||||||
query.push_str(&format!(" WHERE {}", where_condition.0));
|
query.push_str(&format!(" WHERE {}", where_condition.0));
|
||||||
values.extend(where_condition.1);
|
values.extend(where_condition.1);
|
||||||
|
|||||||
Reference in New Issue
Block a user