use std::fmt::Display;
use chrono::{DateTime, Utc};
use sqlx::{postgres::PgArguments, Database, Encode, Executor, Postgres, Type};
use uuid::Uuid;
pub struct Driver {
prql: String,
arguments: PgArguments,
}
impl Driver {
pub fn new() -> Self {
Driver {
prql: String::new(),
arguments: PgArguments::default(),
}
}
pub fn prql(&self) -> &str {
&self.prql
}
pub fn sql(&self) -> String {
use prqlc::{sql::Dialect, Options, Target};
let opts = &Options {
format: false,
signature_comment: false,
target: Target::Sql(Some(Dialect::Postgres)),
..Default::default()
};
match prqlc::compile(&self.prql, opts) {
Ok(sql) => {
tracing::debug!("compiling prql:\n{}\ninto sql:\n{}", &self.prql, &sql);
sql
}
Err(e) => {
tracing::error!("bad prql:\n{}", &self.prql);
panic!("must compile prql: {}", e)
}
}
}
pub fn is_empty(&self) -> bool {
self.prql.is_empty()
}
pub fn push(&mut self, prql: impl Display) {
use std::fmt::Write as _;
write!(&mut self.prql, "{}", prql).expect("must write pqrl");
}
pub fn push_bind<T>(&mut self, value: T)
where
for<'q> T: Encode<'q, Postgres> + Send + Type<Postgres>,
{
use sqlx::Arguments as _;
self.arguments.add(value);
self.arguments
.format_placeholder(&mut self.prql)
.expect("must format placeholder");
}
pub async fn execute_without_compilation<'c>(
self,
executor: impl Executor<'c, Database = Postgres>,
) -> sqlx::Result<<Postgres as Database>::QueryResult> {
use sqlx::QueryBuilder;
QueryBuilder::with_arguments(self.prql, self.arguments)
.build()
.execute(executor)
.await
}
pub async fn fetch_all(
self,
executor: impl Executor<'_, Database = Postgres>,
) -> sqlx::Result<Vec<<Postgres as Database>::Row>> {
use sqlx::QueryBuilder;
QueryBuilder::with_arguments(self.sql(), self.arguments)
.build()
.fetch_all(executor)
.await
}
pub async fn fetch_one(
self,
executor: impl Executor<'_, Database = Postgres>,
) -> sqlx::Result<<Postgres as Database>::Row> {
use sqlx::QueryBuilder;
QueryBuilder::with_arguments(self.sql(), self.arguments)
.build()
.fetch_one(executor)
.await
}
pub async fn fetch_optional(
self,
executor: impl Executor<'_, Database = Postgres>,
) -> sqlx::Result<Option<<Postgres as Database>::Row>> {
use sqlx::QueryBuilder;
QueryBuilder::with_arguments(self.sql(), self.arguments)
.build()
.fetch_optional(executor)
.await
}
}
impl Default for Driver {
fn default() -> Self {
Self::new()
}
}
pub trait PushPrql {
fn push_to_driver(&self, driver: &mut Driver);
}
impl PushPrql for String {
fn push_to_driver(&self, driver: &mut Driver) {
driver.push_bind(self);
}
}
impl PushPrql for &str {
fn push_to_driver(&self, driver: &mut Driver) {
driver.push_bind(self);
}
}
impl PushPrql for i32 {
fn push_to_driver(&self, driver: &mut Driver) {
driver.push_bind(self);
}
}
impl PushPrql for i64 {
fn push_to_driver(&self, driver: &mut Driver) {
driver.push_bind(self);
}
}
impl PushPrql for u32 {
fn push_to_driver(&self, driver: &mut Driver) {
driver.push_bind(*self as i32);
}
}
impl PushPrql for u64 {
fn push_to_driver(&self, driver: &mut Driver) {
driver.push_bind(*self as i64);
}
}
impl PushPrql for bool {
fn push_to_driver(&self, driver: &mut Driver) {
driver.push_bind(self);
}
}
impl PushPrql for Uuid {
fn push_to_driver(&self, driver: &mut Driver) {
driver.push_bind(self);
}
}
impl PushPrql for DateTime<Utc> {
fn push_to_driver(&self, driver: &mut Driver) {
driver.push_bind(self);
}
}
impl<T> PushPrql for Option<T>
where
for<'q> T: 'q + Encode<'q, Postgres> + Sync + Type<Postgres>,
{
fn push_to_driver(&self, driver: &mut Driver) {
driver.push_bind(self);
}
}
impl<T> PushPrql for Vec<T>
where
T: PushPrql,
{
fn push_to_driver(&self, driver: &mut Driver) {
for (i, value) in self.iter().enumerate() {
if i > 0 {
driver.push(", ");
}
value.push_to_driver(driver);
}
}
}
impl<T> PushPrql for &T
where
T: PushPrql,
{
fn push_to_driver(&self, driver: &mut Driver) {
(*self).push_to_driver(driver);
}
}
impl PushPrql for &dyn PushPrql {
fn push_to_driver(&self, driver: &mut Driver) {
(*self).push_to_driver(driver)
}
}
pub fn sql(sql: &'static str) -> SQL {
SQL { sql }
}
pub struct SQL {
pub sql: &'static str,
}
impl PushPrql for SQL {
fn push_to_driver(&self, driver: &mut crate::driver::Driver) {
driver.push("s\"");
driver.push(self.sql);
driver.push('\"');
}
}