use crate::client::{AuraClient, RequestBody};
use crate::error::AuraError;
use crate::types::{AuraResponse, CountMode, SchemaTable};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::future::Future;
use std::future::IntoFuture;
use std::pin::Pin;
pub struct DatabaseService {
client: AuraClient,
}
impl DatabaseService {
pub fn new(client: AuraClient) -> Self {
Self { client }
}
fn prefix(&self) -> String {
"/v1/db".to_string()
}
pub fn from<Row>(&self, table: &str) -> QueryBuilder<Vec<Row>> {
QueryBuilder::new(self.client.clone(), format!("{}/{}", self.prefix(), table))
}
pub async fn sql<T>(
&self,
query: &str,
params: Option<Vec<serde_json::Value>>,
) -> Result<AuraResponse<T>, AuraError>
where
T: DeserializeOwned,
{
let body = serde_json::json!({
"query": query,
"params": params.unwrap_or_default(),
});
self.client
.request(
reqwest::Method::POST,
&format!("{}/raw", self.prefix()),
RequestBody::Json(body),
)
.await
}
pub async fn schema(&self) -> Result<AuraResponse<Vec<SchemaTable>>, AuraError> {
self.client
.request(
reqwest::Method::GET,
&format!("{}/schema", self.prefix()),
RequestBody::None,
)
.await
}
}
#[derive(Debug, Clone)]
pub struct StoredFilter {
pub column: String,
pub op: String,
pub value: serde_json::Value,
}
#[derive(Debug, Clone)]
pub struct OrGroup {
pub filters: Vec<StoredFilter>,
}
#[derive(Debug, Clone)]
pub enum MutationMode {
Insert {
body: serde_json::Value,
},
Update {
id: String,
body: serde_json::Value,
},
Delete {
id: String,
},
Upsert {
body: serde_json::Value,
on_conflict: String,
},
}
pub struct QueryBuilder<T> {
client: AuraClient,
base_path: String,
filters: Vec<StoredFilter>,
or_groups: Vec<OrGroup>,
select_columns: Option<Vec<String>>,
sort_column: Option<String>,
sort_direction: String,
limit_val: Option<u64>,
offset_val: Option<u64>,
cursor_val: Option<String>,
count_mode: Option<CountMode>,
raw_select: bool,
single_mode: bool,
throw_on_error: bool,
mutation: Option<MutationMode>,
_marker: std::marker::PhantomData<T>,
}
impl<T> QueryBuilder<T> {
pub fn new(client: AuraClient, base_path: String) -> Self {
Self {
client,
base_path,
filters: Vec::new(),
or_groups: Vec::new(),
select_columns: None,
sort_column: None,
sort_direction: "asc".to_string(),
limit_val: None,
offset_val: None,
cursor_val: None,
count_mode: None,
raw_select: false,
single_mode: false,
throw_on_error: false,
mutation: None,
_marker: std::marker::PhantomData,
}
}
pub fn select(mut self, columns: &str) -> Self {
if columns == "*" {
self.select_columns = None;
self.raw_select = false;
} else if columns.contains('(') {
self.select_columns = Some(vec![columns.to_string()]);
self.raw_select = true;
} else {
let cols = columns.split(',').map(|c| c.trim().to_string()).collect();
self.select_columns = Some(cols);
self.raw_select = false;
}
self
}
pub fn eq<V: Serialize>(mut self, column: &str, value: V) -> Self {
if let Ok(val) = serde_json::to_value(value) {
self.filters.push(StoredFilter {
column: column.to_string(),
op: "eq".to_string(),
value: val,
});
}
self
}
pub fn neq<V: Serialize>(mut self, column: &str, value: V) -> Self {
if let Ok(val) = serde_json::to_value(value) {
self.filters.push(StoredFilter {
column: column.to_string(),
op: "neq".to_string(),
value: val,
});
}
self
}
pub fn gt<V: Serialize>(mut self, column: &str, value: V) -> Self {
if let Ok(val) = serde_json::to_value(value) {
self.filters.push(StoredFilter {
column: column.to_string(),
op: "gt".to_string(),
value: val,
});
}
self
}
pub fn gte<V: Serialize>(mut self, column: &str, value: V) -> Self {
if let Ok(val) = serde_json::to_value(value) {
self.filters.push(StoredFilter {
column: column.to_string(),
op: "gte".to_string(),
value: val,
});
}
self
}
pub fn lt<V: Serialize>(mut self, column: &str, value: V) -> Self {
if let Ok(val) = serde_json::to_value(value) {
self.filters.push(StoredFilter {
column: column.to_string(),
op: "lt".to_string(),
value: val,
});
}
self
}
pub fn lte<V: Serialize>(mut self, column: &str, value: V) -> Self {
if let Ok(val) = serde_json::to_value(value) {
self.filters.push(StoredFilter {
column: column.to_string(),
op: "lte".to_string(),
value: val,
});
}
self
}
pub fn like(mut self, column: &str, pattern: &str) -> Self {
self.filters.push(StoredFilter {
column: column.to_string(),
op: "like".to_string(),
value: serde_json::Value::String(pattern.to_string()),
});
self
}
pub fn ilike(mut self, column: &str, pattern: &str) -> Self {
self.filters.push(StoredFilter {
column: column.to_string(),
op: "ilike".to_string(),
value: serde_json::Value::String(pattern.to_string()),
});
self
}
pub fn is(mut self, column: &str, value: &str) -> Self {
if value == "not.null" || value == "not_null" {
self.filters.push(StoredFilter {
column: column.to_string(),
op: "neq".to_string(),
value: serde_json::Value::Null,
});
} else {
self.filters.push(StoredFilter {
column: column.to_string(),
op: "is".to_string(),
value: serde_json::Value::String(value.to_string()),
});
}
self
}
pub fn r#in<V: Serialize>(mut self, column: &str, values: Vec<V>) -> Self {
let val_arr: Vec<serde_json::Value> = values
.into_iter()
.filter_map(|v| serde_json::to_value(v).ok())
.collect();
self.filters.push(StoredFilter {
column: column.to_string(),
op: "in".to_string(),
value: serde_json::Value::Array(val_arr),
});
self
}
pub fn or(mut self, filters: Vec<StoredFilter>) -> Self {
self.or_groups.push(OrGroup { filters });
self
}
pub fn order(mut self, column: &str, direction: &str) -> Self {
self.sort_column = Some(column.to_string());
self.sort_direction = direction.to_string();
self
}
pub fn limit(mut self, n: u64) -> Self {
self.limit_val = Some(n);
self
}
pub fn offset(mut self, n: u64) -> Self {
self.offset_val = Some(n);
self
}
pub fn cursor(mut self, cursor: impl Into<String>) -> Self {
self.cursor_val = Some(cursor.into());
self
}
pub fn count(mut self, mode: CountMode) -> Self {
self.count_mode = Some(mode);
self
}
pub fn throw_on_error(mut self) -> Self {
self.throw_on_error = true;
self
}
pub fn insert<V: Serialize>(mut self, body: V) -> Self {
if let Ok(val) = serde_json::to_value(body) {
self.mutation = Some(MutationMode::Insert { body: val });
}
self
}
pub fn update<V: Serialize>(mut self, id: &str, body: V) -> Self {
if let Ok(val) = serde_json::to_value(body) {
self.mutation = Some(MutationMode::Update {
id: id.to_string(),
body: val,
});
}
self
}
pub fn delete(mut self, id: &str) -> Self {
self.mutation = Some(MutationMode::Delete { id: id.to_string() });
self
}
pub fn upsert<V: Serialize>(mut self, body: V, on_conflict: Option<&str>) -> Self {
if let Ok(val) = serde_json::to_value(body) {
self.mutation = Some(MutationMode::Upsert {
body: val,
on_conflict: on_conflict.unwrap_or("id").to_string(),
});
}
self
}
pub fn build_query_string(&self) -> String {
let mut params = Vec::new();
if let Some(ref cols) = self.select_columns {
if !cols.is_empty() {
if self.raw_select {
params.push(format!("select={}", urlencoding::encode(&cols[0])));
} else {
params.push(format!("select={}", urlencoding::encode(&cols.join(","))));
}
}
}
for f in &self.filters {
if f.op == "in" {
if let Some(arr) = f.value.as_array() {
let items: Vec<String> = arr
.iter()
.map(|v| match v {
serde_json::Value::String(s) => s.clone(),
_ => v.to_string(),
})
.collect();
params.push(format!(
"{}={}",
f.column,
urlencoding::encode(&format!("in.({})", items.join(",")))
));
}
} else if f.op == "is" {
let val_str = match &f.value {
serde_json::Value::String(s) => s.clone(),
_ => f.value.to_string(),
};
params.push(format!(
"{}={}",
f.column,
urlencoding::encode(&format!("is.{}", val_str))
));
} else {
let val_str = match &f.value {
serde_json::Value::String(s) => s.clone(),
_ => f.value.to_string(),
};
params.push(format!(
"{}={}",
f.column,
urlencoding::encode(&format!("{}.{}", f.op, val_str))
));
}
}
for group in &self.or_groups {
let parts: Vec<String> = group
.filters
.iter()
.map(|f| {
if f.op == "in" {
let items: Vec<String> = if let Some(arr) = f.value.as_array() {
arr.iter()
.map(|v| match v {
serde_json::Value::String(s) => s.clone(),
_ => v.to_string(),
})
.collect()
} else {
vec![f.value.to_string()]
};
format!("{}.in.({})", f.column, items.join(","))
} else if f.op == "is" {
let val = match &f.value {
serde_json::Value::String(s) => s.clone(),
_ => f.value.to_string(),
};
format!("{}.is.{}", f.column, val)
} else {
let val = match &f.value {
serde_json::Value::String(s) => s.clone(),
_ => f.value.to_string(),
};
format!("{}.{}.{}", f.column, f.op, val)
}
})
.collect();
params.push(format!(
"or={}",
urlencoding::encode(&format!("({})", parts.join(",")))
));
}
if let Some(ref col) = self.sort_column {
params.push(format!("sort_by={}", urlencoding::encode(col)));
params.push(format!(
"sort_order={}",
urlencoding::encode(&self.sort_direction)
));
}
if let Some(l) = self.limit_val {
params.push(format!("limit={}", l));
}
if let Some(o) = self.offset_val {
params.push(format!("offset={}", o));
}
if let Some(ref c) = self.cursor_val {
params.push(format!("cursor={}", urlencoding::encode(c)));
}
if let Some(mode) = self.count_mode {
let mode_str = match mode {
CountMode::Exact => "exact",
CountMode::Estimated => "estimated",
};
params.push(format!("count={}", mode_str));
}
params.join("&")
}
}
impl<Row> QueryBuilder<Vec<Row>> {
pub fn single(self) -> QueryBuilder<Row> {
QueryBuilder {
client: self.client,
base_path: self.base_path,
filters: self.filters,
or_groups: self.or_groups,
select_columns: self.select_columns,
sort_column: self.sort_column,
sort_direction: self.sort_direction,
limit_val: Some(1),
offset_val: self.offset_val,
cursor_val: self.cursor_val,
count_mode: self.count_mode,
raw_select: self.raw_select,
single_mode: true,
throw_on_error: self.throw_on_error,
mutation: self.mutation,
_marker: std::marker::PhantomData,
}
}
}
impl<T> QueryBuilder<T>
where
T: DeserializeOwned + Send + Sync + 'static,
{
pub async fn execute(self) -> Result<AuraResponse<T>, AuraError> {
let mut path = self.base_path.clone();
let method = if let Some(ref muta) = self.mutation {
match muta {
MutationMode::Insert { .. } => reqwest::Method::POST,
MutationMode::Update { id, .. } => {
path = format!("{}/{}", path, id);
reqwest::Method::PATCH
}
MutationMode::Delete { id } => {
path = format!("{}/{}", path, id);
reqwest::Method::DELETE
}
MutationMode::Upsert { on_conflict, .. } => {
path = format!("{}?on_conflict={}", path, on_conflict);
reqwest::Method::POST
}
}
} else {
let qs = self.build_query_string();
if !qs.is_empty() {
path = format!("{}?{}", path, qs);
}
reqwest::Method::GET
};
let body = if let Some(muta) = &self.mutation {
match muta {
MutationMode::Insert { body } => RequestBody::Json(body.clone()),
MutationMode::Update { body, .. } => RequestBody::Json(body.clone()),
MutationMode::Delete { .. } => RequestBody::None,
MutationMode::Upsert { body, .. } => RequestBody::Json(body.clone()),
}
} else {
RequestBody::None
};
if self.single_mode {
let res = self.client.request::<Vec<T>>(method, &path, body).await?;
let single_data = if let Some(mut arr) = res.data {
if !arr.is_empty() {
Some(arr.remove(0))
} else {
None
}
} else {
None
};
if self.throw_on_error {
if let Some(ref err) = res.error {
return Err(err.clone());
}
}
Ok(AuraResponse {
data: single_data,
error: res.error,
meta: res.meta,
})
} else {
let res = self.client.request::<T>(method, &path, body).await?;
if self.throw_on_error {
if let Some(ref err) = res.error {
return Err(err.clone());
}
}
Ok(res)
}
}
}
impl<T> IntoFuture for QueryBuilder<T>
where
T: DeserializeOwned + Send + Sync + 'static,
{
type Output = Result<AuraResponse<T>, AuraError>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { self.execute().await })
}
}