use crate::dialect::Dialect;
use crate::expr::{BinOp, CastTarget, ExprKind, SortDir, Value};
pub(crate) trait Sink {
fn text(&mut self, s: &str);
fn ch(&mut self, c: char);
fn bind(&mut self, value: &Value);
}
#[doc(hidden)]
pub struct QuerySink<D> {
sql: String,
params: Vec<Value>,
_dialect: std::marker::PhantomData<fn() -> D>,
}
impl<D: Dialect> QuerySink<D> {
pub(crate) fn new() -> Self {
QuerySink {
sql: String::new(),
params: Vec::new(),
_dialect: std::marker::PhantomData,
}
}
pub(crate) fn finish(self) -> (String, Vec<Value>) {
(self.sql, self.params)
}
}
impl<D: Dialect> Sink for QuerySink<D> {
fn text(&mut self, s: &str) {
self.sql.push_str(s);
}
fn ch(&mut self, c: char) {
self.sql.push(c);
}
fn bind(&mut self, value: &Value) {
self.params.push(value.clone());
D::write_placeholder(self.params.len(), &mut self.sql);
}
}
pub(crate) struct FragmentSink(Fragment);
impl FragmentSink {
pub(crate) fn new() -> Self {
FragmentSink(Fragment::empty())
}
pub(crate) fn finish(self) -> Fragment {
self.0
}
}
impl Sink for FragmentSink {
fn text(&mut self, s: &str) {
self.0.tail().push_str(s);
}
fn ch(&mut self, c: char) {
self.0.tail().push(c);
}
fn bind(&mut self, value: &Value) {
self.0.rest.push((value.clone(), String::new()));
}
}
pub(crate) fn render_expr<D: Dialect>(expr: &ExprKind, sink: &mut dyn Sink) {
match expr {
ExprKind::Column { table, name } => {
render_ident::<D>(sink, table);
sink.ch('.');
render_ident::<D>(sink, name);
}
ExprKind::Value(v) => sink.bind(v),
ExprKind::BinOp { op, lhs, rhs } => {
sink.ch('(');
render_expr::<D>(lhs, sink);
sink.text(match op {
BinOp::Eq => " = ",
BinOp::Ne => " <> ",
BinOp::Lt => " < ",
BinOp::Lte => " <= ",
BinOp::Gt => " > ",
BinOp::Gte => " >= ",
BinOp::Like => " LIKE ",
});
render_expr::<D>(rhs, sink);
sink.ch(')');
}
ExprKind::And(lhs, rhs) => render_bool_pair::<D>(lhs, "AND", rhs, sink),
ExprKind::Or(lhs, rhs) => render_bool_pair::<D>(lhs, "OR", rhs, sink),
ExprKind::Not(inner) => {
sink.text("(NOT ");
render_expr::<D>(inner, sink);
sink.ch(')');
}
ExprKind::Cast { expr, target } => {
sink.text("CAST(");
render_expr::<D>(expr, sink);
sink.text(" AS ");
sink.text(match target {
CastTarget::BigInt => D::CAST_BIGINT,
CastTarget::Double => D::CAST_DOUBLE,
});
sink.ch(')');
}
ExprKind::Func { name, arg } => {
sink.text(name);
sink.ch('(');
match arg {
Some(arg) => render_expr::<D>(arg, sink),
None => sink.ch('*'),
}
sink.ch(')');
}
ExprKind::IsNull { expr, negated } => {
sink.ch('(');
render_expr::<D>(expr, sink);
sink.text(if *negated {
" IS NOT NULL)"
} else {
" IS NULL)"
});
}
ExprKind::Always(yes) => sink.text(if *yes { "TRUE" } else { "FALSE" }),
ExprKind::InList { expr, values } => {
sink.ch('(');
render_expr::<D>(expr, sink);
sink.text(" IN (");
for (i, v) in values.iter().enumerate() {
if i > 0 {
sink.text(", ");
}
render_expr::<D>(v, sink);
}
sink.text("))");
}
ExprKind::Exists {
body,
selection,
negated,
} => {
sink.text(if *negated {
"(NOT EXISTS ("
} else {
"(EXISTS ("
});
body.render_into::<D>(selection, sink);
sink.text("))");
}
ExprKind::Template { head, rest } => {
sink.ch('(');
sink.text(head);
for (arg, text) in rest {
render_expr::<D>(arg, sink);
sink.text(text);
}
sink.ch(')');
}
ExprKind::Window {
func,
partition_by,
order_by,
} => {
sink.text(func);
sink.text(" OVER (");
render_expr_list::<D>(sink, "PARTITION BY ", partition_by);
let keyword = if partition_by.is_empty() {
"ORDER BY "
} else {
" ORDER BY "
};
render_order_by::<D>(sink, keyword, order_by);
sink.ch(')');
}
}
}
#[doc(hidden)]
#[derive(Debug, Clone)]
pub struct SelectItem {
pub(crate) kind: ExprKind,
pub(crate) label: Option<&'static str>,
}
impl SelectItem {
pub(crate) fn bare(kind: ExprKind) -> Self {
SelectItem { kind, label: None }
}
pub(crate) fn labeled(kind: ExprKind, label: &'static str) -> Self {
SelectItem {
kind,
label: Some(label),
}
}
}
pub(crate) fn render_select_list<D: Dialect>(items: &[SelectItem], sink: &mut dyn Sink) {
for (i, item) in items.iter().enumerate() {
if i > 0 {
sink.text(", ");
}
render_expr::<D>(&item.kind, sink);
if let Some(label) = item.label {
sink.text(" AS ");
render_ident::<D>(sink, label);
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Fragment {
head: String,
rest: Vec<(Value, String)>,
}
impl Fragment {
fn empty() -> Self {
Fragment {
head: String::new(),
rest: Vec::new(),
}
}
fn tail(&mut self) -> &mut String {
match self.rest.last_mut() {
Some((_, text)) => text,
None => &mut self.head,
}
}
pub(crate) fn splice_into(&self, sink: &mut dyn Sink) {
sink.text(&self.head);
for (value, text) in &self.rest {
sink.bind(value);
sink.text(text);
}
}
}
pub(crate) fn render_ident<D: Dialect>(sink: &mut dyn Sink, ident: &str) {
for (i, part) in ident.split('.').enumerate() {
if i > 0 {
sink.ch('.');
}
render_ident_part::<D>(sink, part);
}
}
fn render_ident_part<D: Dialect>(sink: &mut dyn Sink, ident: &str) {
sink.ch(D::IDENTIFIER_QUOTE);
for c in ident.chars() {
if c == D::IDENTIFIER_QUOTE {
sink.ch(c);
}
sink.ch(c);
}
sink.ch(D::IDENTIFIER_QUOTE);
}
fn render_bool_pair<D: Dialect>(lhs: &ExprKind, joiner: &str, rhs: &ExprKind, sink: &mut dyn Sink) {
sink.ch('(');
render_expr::<D>(lhs, sink);
sink.ch(' ');
sink.text(joiner);
sink.ch(' ');
render_expr::<D>(rhs, sink);
sink.ch(')');
}
pub(crate) fn dir_keyword(dir: SortDir) -> &'static str {
match dir {
SortDir::Asc => " ASC",
SortDir::Desc => " DESC",
}
}
pub(crate) fn render_count_wrapped<D: Dialect>(
sink: &mut QuerySink<D>,
body: impl FnOnce(&mut QuerySink<D>),
) {
sink.text("SELECT count(*) FROM (");
body(sink);
sink.text(") AS ");
render_ident::<D>(sink, "qbrs_total");
}
pub(crate) fn render_expr_list<D: Dialect>(sink: &mut dyn Sink, keyword: &str, list: &[ExprKind]) {
if list.is_empty() {
return;
}
sink.text(keyword);
for (i, e) in list.iter().enumerate() {
if i > 0 {
sink.text(", ");
}
render_expr::<D>(e, sink);
}
}
pub(crate) fn render_order_by<D: Dialect>(
sink: &mut dyn Sink,
keyword: &str,
keys: &[(ExprKind, SortDir)],
) {
if keys.is_empty() {
return;
}
sink.text(keyword);
for (i, (e, dir)) in keys.iter().enumerate() {
if i > 0 {
sink.text(", ");
}
render_expr::<D>(e, sink);
sink.text(dir_keyword(*dir));
}
}
pub(crate) fn render_and_list<D: Dialect>(sink: &mut dyn Sink, keyword: &str, list: &[ExprKind]) {
if list.is_empty() {
return;
}
sink.text(keyword);
for (i, e) in list.iter().enumerate() {
if i > 0 {
sink.text(" AND ");
}
render_expr::<D>(e, sink);
}
}