use std::{borrow::Cow, marker::PhantomData};
use crate::{
TypeCast, TypeMeta,
alias::Aliased,
lower::LowerCtx,
param::{Param, ParamValue},
query::{
LowerFilter, LowerFromItem, LowerGroupBy, LowerHaving, LowerOrderBy, LowerProject,
TypedCompiled,
},
};
#[derive(Debug, Clone)]
pub struct Raw {
sql: Cow<'static, str>,
params: Vec<Param>,
data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct RawQuery<T> {
raw: Raw,
marker: PhantomData<T>,
}
#[derive(Debug, Clone)]
pub struct RawScalar<T: TypeMeta> {
raw: Raw,
marker: PhantomData<T>,
}
pub fn raw(sql: impl Into<Cow<'static, str>>) -> Raw {
Raw::new(sql)
}
impl Raw {
pub fn new(sql: impl Into<Cow<'static, str>>) -> Self {
Self {
sql: sql.into(),
params: Vec::new(),
data: Vec::new(),
}
}
pub fn bind<'a>(mut self, value: impl Into<ParamValue<'a>>) -> Self {
self.push_bind(value);
self
}
pub fn push_bind<'a>(&mut self, value: impl Into<ParamValue<'a>>) -> &mut Self {
self.params.push(value.into().into_param(&mut self.data));
self
}
pub fn typed<T>(self) -> RawQuery<T> {
RawQuery {
raw: self,
marker: PhantomData,
}
}
pub fn scalar<T: TypeMeta>(self) -> RawScalar<T> {
RawScalar {
raw: self,
marker: PhantomData,
}
}
pub fn to_sql(&self) -> String {
self.sql.to_string()
}
pub fn into_compiled(self) -> TypedCompiled<()> {
TypedCompiled {
sql: self.sql.into_owned(),
params: self.params,
data: self.data,
marker: PhantomData,
}
}
fn lower_fragment(self, ctx: &mut LowerCtx) -> usize {
ctx.lower_raw(self.sql.as_ref(), self.data, self.params)
}
}
impl<T> RawQuery<T> {
pub fn to_sql(&self) -> String {
self.raw.to_sql()
}
pub fn into_compiled(self) -> TypedCompiled<T> {
TypedCompiled {
sql: self.raw.sql.into_owned(),
params: self.raw.params,
data: self.raw.data,
marker: PhantomData,
}
}
}
impl<T: TypeMeta + TypeCast> RawScalar<T> {
pub fn to_sql(&self) -> String {
self.raw.to_sql()
}
pub fn into_compiled(self) -> TypedCompiled<<T as TypeCast>::From> {
TypedCompiled {
sql: self.raw.sql.into_owned(),
params: self.raw.params,
data: self.raw.data,
marker: PhantomData,
}
}
}
impl LowerProject for Raw {
fn lower_project(self, ctx: &mut LowerCtx) {
let _ = self.lower_fragment(ctx);
}
}
impl LowerProject for Aliased<Raw> {
fn lower_project(self, ctx: &mut LowerCtx) {
let inner = self.inner.lower_fragment(ctx);
let _ = ctx.lower_alias(self.alias, inner);
}
}
impl LowerFromItem for Raw {
fn lower_from_item(self, ctx: &mut LowerCtx) {
let _ = self.lower_fragment(ctx);
}
}
impl LowerFilter for Raw {
fn lower_filter(self, ctx: &mut LowerCtx) {
let _ = self.lower_fragment(ctx);
}
}
impl LowerHaving for Raw {
fn lower_having(self, ctx: &mut LowerCtx) {
let _ = self.lower_fragment(ctx);
}
}
impl LowerGroupBy for Raw {
fn lower_group_by(self, ctx: &mut LowerCtx) {
let _ = self.lower_fragment(ctx);
}
}
impl LowerOrderBy for Raw {
fn lower_order_by(self, ctx: &mut LowerCtx) {
let _ = self.lower_fragment(ctx);
}
}
#[cfg(test)]
mod tests {
use super::raw;
use crate::{Postgres, Sqlite, alias::Alias, query::select};
#[test]
fn raw_keeps_question_mark_for_postgres() {
let sql = raw("select * from users where id = ? and name = ?")
.bind(1_i64)
.bind("lea")
.to_sql();
assert_eq!(sql, "select * from users where id = ? and name = ?");
}
#[test]
fn raw_keeps_question_mark_for_sqlite() {
let sql = raw("select * from users where id = ? and name = ?")
.bind(1_i64)
.bind("lea")
.to_sql();
assert_eq!(sql, "select * from users where id = ? and name = ?");
}
#[test]
fn raw_keeps_sql_text_unchanged() {
let sql = raw(
"select '?' as literal, ?? as escaped, body from notes where id = ? -- ?\n/* ? */ and tag = $tag$?$tag$ and slug = ?",
)
.bind(1_i64)
.bind("rust")
.to_sql();
assert_eq!(
sql,
"select '?' as literal, ?? as escaped, body from notes where id = ? -- ?\n/* ? */ and tag = $tag$?$tag$ and slug = ?"
);
}
#[test]
fn raw_fragment_works_in_select_from_and_filter() {
let mut query = select(raw("count(*)").alias("count"))
.from(raw("users u"))
.filter(raw("lower(u.name) = ?").bind("lea"));
let sql = query.to_sql::<Postgres>();
assert_eq!(
sql,
r#"select count(*) as "count" from users u where lower(u.name) = ?"#
);
let debug = query.to_debug_sql::<Sqlite>();
assert_eq!(
debug,
r#"select count(*) as "count" from users u where lower(u.name) = ?; params=["lea"]"#
);
}
}