use std::fmt::Write;
use crate::{
Query,
builder::{Delete, Update},
cte::Cte,
dialect::Dialect,
expression::{Operator, PostfixOperator, UnaryOperator},
instr::{RpnInstr, TemporalKind},
lower::Data,
precedence::binding_power,
query::{CompoundQuery, JoinKind, LockClause, SetOperator, SortKind},
};
pub enum Directive {
RewriteGlob { id: usize },
}
pub enum Side {
Left,
Right,
}
struct JsonContainsEmit<'a> {
inner: usize,
rhs: usize,
path: &'a str,
negated: bool,
compound: bool,
}
struct SelectEmit<'a> {
distinct: bool,
project: &'a [RpnInstr],
from: &'a [RpnInstr],
filters: &'a [RpnInstr],
group_by: &'a [RpnInstr],
havings: &'a [RpnInstr],
order_by: &'a [RpnInstr],
limit: &'a [RpnInstr],
offset: &'a [RpnInstr],
lock: Option<LockClause>,
}
fn binding_rank(instr: &RpnInstr, dialect: Dialect) -> Option<u8> {
match instr {
RpnInstr::Binary { .. }
| RpnInstr::Unary { .. }
| RpnInstr::Between { .. }
| RpnInstr::Postfix { .. } => {
let (left, right) = binding_power(instr, dialect);
Some(left.max(right))
}
_ => None,
}
}
fn rpn_prec(instr: &RpnInstr, dialect: Dialect) -> u8 {
match instr {
RpnInstr::Assignement { .. } => 9,
RpnInstr::Binary { .. }
| RpnInstr::Unary { .. }
| RpnInstr::Between { .. }
| RpnInstr::Postfix { .. } => binding_rank(instr, dialect).expect("binding rank"),
RpnInstr::In { .. } => 60,
RpnInstr::Exists { .. } => 95,
RpnInstr::Fn { .. } => 97,
RpnInstr::JsonExtractText { .. } => 97,
RpnInstr::JsonContains { .. } => 60,
RpnInstr::JsonContainsKey { .. } => 60,
RpnInstr::JsonLength { .. } => 97,
RpnInstr::Temporal { .. } => 97,
RpnInstr::Column { .. }
| RpnInstr::Table { .. }
| RpnInstr::Param { .. }
| RpnInstr::Null
| RpnInstr::Raw { .. }
| RpnInstr::Alias { .. }
| RpnInstr::LitInt { .. }
| RpnInstr::Subquery(_)
| RpnInstr::Star => 100,
RpnInstr::Seperated { .. } => 10,
RpnInstr::OrderBy { .. } => 5,
RpnInstr::Join { .. } => 3,
RpnInstr::CrossJoin { .. } => 3,
RpnInstr::Nop => 0,
}
}
fn assoc_ok(parent: &Operator, side: Side, child_root: &RpnInstr) -> bool {
let child_op = match child_root {
RpnInstr::Binary { op, .. } => op,
_ => return true,
};
if matches!(side, Side::Left) {
return true;
}
match parent {
Operator::And
| Operator::Or
| Operator::Add
| Operator::Mul
| Operator::BitAnd
| Operator::BitOr => std::mem::discriminant(parent) == std::mem::discriminant(child_op),
_ => false,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum JsonPathSegment<'a> {
Key(&'a str),
Index(&'a str),
}
fn parse_json_path(path: &str) -> Vec<JsonPathSegment<'_>> {
path.split("->")
.map(|segment| {
if segment.bytes().all(|byte| byte.is_ascii_digit()) {
JsonPathSegment::Index(segment)
} else {
JsonPathSegment::Key(segment)
}
})
.collect()
}
pub struct Emitter<'a, W: Write> {
writer: &'a mut W,
data: &'a Vec<u8>,
dialect: Dialect,
value: &'a mut Vec<Directive>,
params: &'a mut Vec<usize>,
}
impl<'a, W: Write> Emitter<'a, W> {
pub fn new(
writer: &'a mut W,
data: &'a Vec<u8>,
dialect: Dialect,
value: &'a mut Vec<Directive>,
params: &'a mut Vec<usize>,
) -> Self {
Self {
writer,
dialect,
value,
params,
data,
}
}
pub fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.writer.write_str(s)
}
pub fn emit_instrs(&mut self, instrs: &[RpnInstr]) -> std::fmt::Result {
let mut cursor = instrs;
if !cursor.is_empty() {
self.emit_node(&mut cursor)?;
}
Ok(())
}
pub(crate) fn write_ident(&mut self, part: &str) -> std::fmt::Result {
if part == "*" {
return self.writer.write_char('*');
}
let quote = match self.dialect {
Dialect::Postgres | Dialect::Sqlite => '"',
Dialect::MariaDb => '`',
};
self.writer.write_char(quote)?;
let dbl = if quote == '"' { "\"\"" } else { "``" };
let mut last = 0;
for (index, char) in part.char_indices() {
if char == quote {
if index != last {
self.writer.write_str(&part[last..index])?;
}
self.writer.write_str(dbl)?;
last = index + char.len_utf8();
}
}
if last < part.len() {
self.writer.write_str(&part[last..])?;
}
self.writer.write_char(quote)?;
Ok(())
}
pub(crate) fn write_table(&mut self, ident: &str) -> std::fmt::Result {
for (i, part) in ident.split('.').enumerate() {
if i > 0 {
self.writer.write_char('.')?;
}
self.write_ident(part)?;
}
Ok(())
}
pub fn write_alias(&mut self, alias: &str) -> std::fmt::Result {
self.writer.write_str(" as ")?;
self.write_ident(alias)?;
Ok(())
}
fn write_string_literal(&mut self, value: &str) -> std::fmt::Result {
self.writer.write_char('\'')?;
for ch in value.chars() {
if ch == '\'' {
self.writer.write_str("''")?;
} else {
self.writer.write_char(ch)?;
}
}
self.writer.write_char('\'')
}
fn write_json_sql_path(&mut self, path: &str) -> std::fmt::Result {
self.writer.write_str("'$")?;
for segment in parse_json_path(path) {
match segment {
JsonPathSegment::Key(key) => {
self.writer.write_char('.')?;
self.writer.write_str(key)?;
}
JsonPathSegment::Index(index) => {
self.writer.write_char('[')?;
self.writer.write_str(index)?;
self.writer.write_char(']')?;
}
}
}
self.writer.write_char('\'')
}
fn emit_postgres_json_path_args(&mut self, path: &str) -> std::fmt::Result {
for segment in parse_json_path(path) {
self.writer.write_str(", ")?;
match segment {
JsonPathSegment::Key(segment) | JsonPathSegment::Index(segment) => {
self.write_string_literal(segment)?;
}
}
}
Ok(())
}
fn emit_postgres_json_path_expr(
&mut self,
cursor: &mut &[RpnInstr],
inner: usize,
path: &str,
text: bool,
) -> std::fmt::Result {
if text {
self.writer.write_str("jsonb_extract_path_text((")?;
} else {
self.writer.write_str("jsonb_extract_path((")?;
}
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(")::jsonb")?;
self.emit_postgres_json_path_args(path)?;
self.writer.write_char(')')
}
fn emit_json_extract_text(
&mut self,
cursor: &mut &[RpnInstr],
inner: usize,
path: &str,
) -> std::fmt::Result {
match self.dialect {
Dialect::Sqlite => {
self.writer.write_str("json_extract(")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(", ")?;
self.write_json_sql_path(path)?;
self.writer.write_char(')')
}
Dialect::MariaDb => {
self.writer.write_str("json_unquote(json_extract(")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(", ")?;
self.write_json_sql_path(path)?;
self.writer.write_str("))")
}
Dialect::Postgres => self.emit_postgres_json_path_expr(cursor, inner, path, true),
}
}
fn emit_json_contains(
&mut self,
cursor: &mut &[RpnInstr],
spec: JsonContainsEmit<'_>,
) -> std::fmt::Result {
let mut rhs = take_slice(cursor, spec.rhs);
let mut inner = take_slice(cursor, spec.inner);
match self.dialect {
Dialect::Sqlite => {
if spec.compound {
panic!("sqlite json_contains only supports scalar values in qraft v1");
}
if spec.negated {
self.writer.write_str("not ")?;
}
self.writer.write_str("exists (select 1 from json_each(")?;
self.writer.write_str("json_extract(")?;
self.emit_node(&mut inner)?;
self.writer.write_str(", ")?;
self.write_json_sql_path(spec.path)?;
self.writer
.write_str(")) where json_each.value = json_extract(")?;
self.emit_node(&mut rhs)?;
self.writer.write_str(", '$'))")
}
Dialect::MariaDb => {
if spec.negated {
self.writer.write_str("not ")?;
}
self.writer.write_str("json_contains(json_extract(")?;
self.emit_node(&mut inner)?;
self.writer.write_str(", ")?;
self.write_json_sql_path(spec.path)?;
self.writer.write_str("), ")?;
self.emit_node(&mut rhs)?;
self.writer.write_char(')')
}
Dialect::Postgres => {
if spec.compound {
if spec.negated {
self.writer.write_str("not ")?;
}
self.writer.write_str("jsonb_extract_path((")?;
self.emit_node(&mut inner)?;
self.writer.write_str(")::jsonb")?;
self.emit_postgres_json_path_args(spec.path)?;
self.writer.write_char(')')?;
self.writer.write_str(" @> (")?;
self.emit_node(&mut rhs)?;
self.writer.write_str(")::jsonb")
} else {
if spec.negated {
self.writer.write_str("not ")?;
}
self.writer
.write_str("exists (select 1 from jsonb_array_elements(")?;
self.writer.write_str("jsonb_extract_path((")?;
self.emit_node(&mut inner)?;
self.writer.write_str(")::jsonb")?;
self.emit_postgres_json_path_args(spec.path)?;
self.writer.write_char(')')?;
self.writer
.write_str(") as qraft_json_value where qraft_json_value = (")?;
self.emit_node(&mut rhs)?;
self.writer.write_str(")::jsonb)")
}
}
}
}
fn emit_json_contains_key(
&mut self,
cursor: &mut &[RpnInstr],
inner: usize,
path: &str,
negated: bool,
) -> std::fmt::Result {
match self.dialect {
Dialect::Sqlite => {
self.writer.write_str("json_type(")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(", ")?;
self.write_json_sql_path(path)?;
if negated {
self.writer.write_str(") is null")
} else {
self.writer.write_str(") is not null")
}
}
Dialect::MariaDb => {
if negated {
self.writer.write_str("not ")?;
}
self.writer.write_str("json_contains_path(")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(", 'one', ")?;
self.write_json_sql_path(path)?;
self.writer.write_char(')')
}
Dialect::Postgres => {
if negated {
self.writer.write_str("not ")?;
}
self.writer.write_str("jsonb_path_exists((")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(")::jsonb, ")?;
let mut sql_path = String::from("$");
for segment in parse_json_path(path) {
match segment {
JsonPathSegment::Key(key) => {
sql_path.push('.');
sql_path.push_str(key);
}
JsonPathSegment::Index(index) => {
sql_path.push('[');
sql_path.push_str(index);
sql_path.push(']');
}
}
}
self.write_string_literal(&sql_path)?;
self.writer.write_char(')')
}
}
}
fn emit_json_length(
&mut self,
cursor: &mut &[RpnInstr],
inner: usize,
path: &str,
) -> std::fmt::Result {
match self.dialect {
Dialect::Sqlite => {
self.writer.write_str("json_array_length(")?;
self.emit_json_extract_text(cursor, inner, path)?;
self.writer.write_char(')')
}
Dialect::MariaDb => {
self.writer.write_str("json_length(json_extract(")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(", ")?;
self.write_json_sql_path(path)?;
self.writer.write_str("))")
}
Dialect::Postgres => {
self.writer.write_str("jsonb_array_length(")?;
self.emit_postgres_json_path_expr(cursor, inner, path, false)?;
self.writer.write_char(')')
}
}
}
fn emit_temporal(
&mut self,
cursor: &mut &[RpnInstr],
inner: usize,
kind: TemporalKind,
) -> std::fmt::Result {
match (self.dialect, kind) {
(Dialect::Sqlite, TemporalKind::Date) => {
self.writer.write_str("date(")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_char(')')
}
(Dialect::Sqlite, TemporalKind::Time) => {
self.writer.write_str("time(")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_char(')')
}
(Dialect::Sqlite, TemporalKind::Year) => {
self.writer.write_str("cast(strftime('%Y', ")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(") as integer)")
}
(Dialect::Sqlite, TemporalKind::Month) => {
self.writer.write_str("cast(strftime('%m', ")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(") as integer)")
}
(Dialect::Sqlite, TemporalKind::Day) => {
self.writer.write_str("cast(strftime('%d', ")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(") as integer)")
}
(Dialect::MariaDb, TemporalKind::Date) => {
self.writer.write_str("date(")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_char(')')
}
(Dialect::MariaDb, TemporalKind::Time) => {
self.writer.write_str("time(")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_char(')')
}
(Dialect::MariaDb, TemporalKind::Year) => {
self.writer.write_str("year(")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_char(')')
}
(Dialect::MariaDb, TemporalKind::Month) => {
self.writer.write_str("month(")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_char(')')
}
(Dialect::MariaDb, TemporalKind::Day) => {
self.writer.write_str("day(")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_char(')')
}
(Dialect::Postgres, TemporalKind::Date) => {
self.writer.write_str("cast(")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(" as date)")
}
(Dialect::Postgres, TemporalKind::Time) => {
self.writer.write_str("cast(")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(" as time)")
}
(Dialect::Postgres, TemporalKind::Year) => {
self.writer.write_str("cast(extract(year from ")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(") as bigint)")
}
(Dialect::Postgres, TemporalKind::Month) => {
self.writer.write_str("cast(extract(month from ")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(") as bigint)")
}
(Dialect::Postgres, TemporalKind::Day) => {
self.writer.write_str("cast(extract(day from ")?;
let mut inner = take_slice(cursor, inner);
self.emit_node(&mut inner)?;
self.writer.write_str(") as bigint)")
}
}
}
pub(crate) fn write_placeholder(&mut self, index: u32) -> std::fmt::Result {
match self.dialect {
Dialect::Postgres => write!(self.writer, "${}", index),
Dialect::MariaDb | Dialect::Sqlite => self.writer.write_char('?'),
}
}
fn emit_precedence(
&mut self,
cursor: &mut &[RpnInstr],
min_prec: u8,
parent_op: Option<&Operator>,
side: Side,
) -> std::fmt::Result {
let root = *cursor.last().expect("underflow");
let p = rpn_prec(&root, self.dialect);
let needs = if p < min_prec {
true
} else if p == min_prec {
match parent_op {
Some(op) => !assoc_ok(op, side, &root),
None => false,
}
} else {
false
};
if needs {
self.writer.write_char('(')?;
self.emit_node(cursor)?;
self.writer.write_char(')')?;
Ok(())
} else {
self.emit_node(cursor)
}
}
fn emit_node(&mut self, cursor: &mut &[RpnInstr]) -> std::fmt::Result {
let (instr, rest) = cursor.split_last().expect("underflow");
*cursor = rest;
match instr {
RpnInstr::Column { span, table } => {
if let Some(tspan) = table {
let text = self.data.text(*tspan);
self.write_ident(text)?;
self.writer.write_char('.')?;
}
let text = self.data.text(*span);
self.write_table(text)
}
RpnInstr::Table { span } => {
let text = self.data.text(*span);
self.write_table(text)
}
RpnInstr::Param { id } => {
self.write_placeholder(id + 1)?;
self.params.push(*id as usize);
Ok(())
}
RpnInstr::Null => self.write_str("null"),
RpnInstr::Raw { span, params } => {
let raw_params = take_slice(cursor, *params);
for instr in raw_params {
match instr {
RpnInstr::Param { id } => self.params.push(*id as usize),
_ => panic!("raw fragment expected bound params"),
}
}
let text = self.data.text(*span);
self.writer.write_str(text)
}
RpnInstr::Alias { span, inner } => {
let inner = take_slice(cursor, *inner);
self.emit_instrs(inner)?;
let text = self.data.text(*span);
self.write_alias(text)
}
RpnInstr::Seperated { count } => {
self.emit_seperated(cursor, 0, *count, ", ")?;
Ok(())
}
RpnInstr::Binary { op, lhs, rhs } => {
let mut rhs = take_slice(cursor, *rhs);
let mut lhs = take_slice(cursor, *lhs);
let parent_prec = binding_rank(instr, self.dialect).expect("binary binding rank");
let lhs_postfix_cast = matches!(op, Operator::Like { .. })
&& matches!(self.dialect, Dialect::Postgres);
let lhs_min = if lhs_postfix_cast { 98 } else { parent_prec };
self.emit_precedence(&mut lhs, lhs_min, Some(op), Side::Left)?;
if lhs_postfix_cast {
self.writer.write_str("::text")?;
}
match op {
Operator::And => self.writer.write_str(" and ")?,
Operator::Or => self.writer.write_str(" or ")?,
Operator::Eq => self.writer.write_str(" = ")?,
Operator::Neq => self.writer.write_str(" != ")?,
Operator::Lt => self.writer.write_str(" < ")?,
Operator::Lte => self.writer.write_str(" <= ")?,
Operator::Gt => self.writer.write_str(" > ")?,
Operator::Gte => self.writer.write_str(" >= ")?,
Operator::Like { sensitive, negated } => {
if *negated {
self.writer.write_str(" not")?;
}
match (self.dialect, sensitive) {
(Dialect::Postgres, true) => {
self.writer.write_str(" like ")?;
}
(Dialect::MariaDb, true) => self.writer.write_str(" like binary ")?,
(Dialect::Sqlite, true) => {
self.writer.write_str(" glob ")?;
if let Some(RpnInstr::Param { id }) = rhs.last() {
self.value.push(Directive::RewriteGlob { id: *id as usize });
}
}
(Dialect::Postgres, false) => {
self.writer.write_str(" ilike ")?;
}
(Dialect::MariaDb, false) | (Dialect::Sqlite, false) => {
self.writer.write_str(" like ")?
}
};
}
Operator::Add => self.writer.write_str(" + ")?,
Operator::Mul => self.writer.write_str(" * ")?,
Operator::Rem => self.writer.write_str(" % ")?,
Operator::Sub => self.writer.write_str(" - ")?,
Operator::Div => self.writer.write_str(" / ")?,
Operator::BitAnd => self.writer.write_str(" & ")?,
Operator::BitOr => self.writer.write_str(" | ")?,
Operator::Shl => self.writer.write_str(" << ")?,
Operator::Shr => self.writer.write_str(" >> ")?,
};
self.emit_precedence(&mut rhs, parent_prec, Some(op), Side::Right)?;
Ok(())
}
RpnInstr::Unary { op, rhs } => {
let mut rhs = take_slice(cursor, *rhs);
match op {
UnaryOperator::Not => self.writer.write_str("not ")?,
}
self.writer.write_char('(')?;
self.emit_node(&mut rhs)?;
self.writer.write_char(')')
}
RpnInstr::Between {
negated,
lhs,
low,
high,
} => {
let mut high = take_slice(cursor, *high);
let mut low = take_slice(cursor, *low);
let mut lhs = take_slice(cursor, *lhs);
self.emit_precedence(&mut lhs, 60, None, Side::Left)?;
if *negated {
self.writer.write_str(" not between ")?;
} else {
self.writer.write_str(" between ")?;
}
self.emit_node(&mut low)?;
self.writer.write_str(" and ")?;
self.emit_node(&mut high)?;
Ok(())
}
RpnInstr::Subquery(base) => {
let total = base.total();
let s = *cursor;
let n = s.len();
let start = n.checked_sub(total).expect("subquery underflow");
let (remaining, sub_all) = s.split_at(start);
*cursor = remaining;
let (sub_project, rest) = sub_all.split_at(base.project as usize);
let (sub_from, rest) = rest.split_at(base.from as usize);
let (sub_filters, rest) = rest.split_at(base.filters as usize);
let (sub_groupby, rest) = rest.split_at(base.group_by as usize);
let (sub_havings, rest) = rest.split_at(base.having as usize);
let (sub_orderby, rest) = rest.split_at(base.order_by as usize);
let (sub_limit, rest) = rest.split_at(base.limit as usize);
let (sub_offset, _rest) = rest.split_at(base.offset as usize);
self.writer.write_str("(")?;
self.emit_select(SelectEmit {
distinct: base.distinct,
project: sub_project,
from: sub_from,
filters: sub_filters,
group_by: sub_groupby,
havings: sub_havings,
order_by: sub_orderby,
limit: sub_limit,
offset: sub_offset,
lock: None,
})?;
self.writer.write_str(")")?;
Ok(())
}
RpnInstr::Star => self.writer.write_char('*'),
RpnInstr::CrossJoin { count } => {
self.writer.write_char('(')?;
self.emit_seperated(cursor, 0, *count, " cross join ")?;
self.writer.write_char(')')?;
Ok(())
}
RpnInstr::Join { kind, lhs, rhs } => {
let mut on = take_slice(cursor, *rhs);
let mut rhs = take_slice(cursor, *lhs);
self.emit_node(cursor)?;
match kind {
JoinKind::Left => {
self.writer.write_str(" left join ")?;
self.emit_node(&mut rhs)?;
self.writer.write_str(" on ")?;
self.emit_node(&mut on)?;
}
JoinKind::Right => {
self.writer.write_str(" right join ")?;
self.emit_node(&mut rhs)?;
self.writer.write_str(" on ")?;
self.emit_node(&mut on)?;
}
JoinKind::Inner => {
self.writer.write_str(" inner join ")?;
self.emit_node(&mut rhs)?;
self.writer.write_str(" on ")?;
self.emit_node(&mut on)?;
}
}
Ok(())
}
RpnInstr::OrderBy { sort, inner } => {
let mut inner = take_slice(cursor, *inner);
self.emit_node(&mut inner)?;
match sort {
SortKind::Asc => {
self.writer.write_str(" asc")?;
}
SortKind::Desc => {
self.writer.write_str(" desc")?;
}
}
Ok(())
}
RpnInstr::Exists { negated, inner } => {
let mut inner = take_slice(cursor, *inner);
if *negated {
self.writer.write_str("not ")?;
}
self.writer.write_str("exists ")?;
self.emit_node(&mut inner)?;
Ok(())
}
RpnInstr::LitInt { value } => write!(self.writer, "{}", value),
RpnInstr::Fn { name, args } => {
if matches!(*name, "current_timestamp" | "current_date" | "current_time") {
self.writer.write_str(name)?;
return Ok(());
}
if matches!(*name, "random") {
match (*name, self.dialect) {
("random", Dialect::Postgres | Dialect::Sqlite) => {
self.writer.write_str(name)?
}
("random", Dialect::MariaDb) => self.writer.write_str("rand")?,
_ => unreachable!("function mapping"),
}
} else {
self.writer.write_str(name)?;
}
self.writer.write_char('(')?;
if *args > 0 {
let args = take_slice(cursor, *args);
self.emit_instrs(args)?;
}
self.writer.write_char(')')?;
Ok(())
}
RpnInstr::JsonExtractText { inner, path } => {
let path = self.data.text(*path);
self.emit_json_extract_text(cursor, *inner, path)
}
RpnInstr::JsonContains {
inner,
rhs,
path,
negated,
compound,
} => {
let path = self.data.text(*path);
self.emit_json_contains(
cursor,
JsonContainsEmit {
inner: *inner,
rhs: *rhs,
path,
negated: *negated,
compound: *compound,
},
)
}
RpnInstr::JsonContainsKey {
inner,
path,
negated,
} => {
let path = self.data.text(*path);
self.emit_json_contains_key(cursor, *inner, path, *negated)
}
RpnInstr::JsonLength { inner, path } => {
let path = self.data.text(*path);
self.emit_json_length(cursor, *inner, path)
}
RpnInstr::Temporal { inner, kind } => self.emit_temporal(cursor, *inner, *kind),
RpnInstr::In { negated, lhs, rhs } => {
let mut in_expr = take_slice(cursor, *rhs);
let mut lhs = take_slice(cursor, *lhs);
self.emit_node(&mut lhs)?;
if *negated {
self.writer.write_str(" not in (")?;
} else {
self.writer.write_str(" in (")?;
}
self.emit_node(&mut in_expr)?;
self.writer.write_str(")")?;
Ok(())
}
RpnInstr::Nop => Ok(()),
RpnInstr::Postfix { op, lhs } => {
let mut lhs = take_slice(cursor, *lhs);
self.writer.write_char('(')?;
self.emit_node(&mut lhs)?;
self.writer.write_char(')')?;
match op {
PostfixOperator::Null { negated } => {
if *negated {
self.writer.write_str(" is not null")?;
} else {
self.writer.write_str(" is null")?;
}
}
PostfixOperator::False => {
self.writer.write_str(" is false")?;
}
PostfixOperator::True => {
self.writer.write_str(" is true")?;
}
}
Ok(())
}
RpnInstr::Assignement { lhs, rhs } => {
let mut rhs = take_slice(cursor, *rhs);
let mut lhs = take_slice(cursor, *lhs);
self.emit_precedence(
&mut lhs,
rpn_prec(&RpnInstr::Assignement { lhs: 0, rhs: 0 }, self.dialect),
None,
Side::Left,
)?;
self.writer.write_str(" = ")?;
self.emit_precedence(
&mut rhs,
rpn_prec(&RpnInstr::Assignement { lhs: 0, rhs: 0 }, self.dialect),
None,
Side::Right,
)?;
Ok(())
}
}
}
fn emit_seperated(
&mut self,
cursor: &mut &[RpnInstr],
current: usize,
total: usize,
seperated: &'static str,
) -> std::fmt::Result {
if current == total {
return Ok(());
}
let mut slice = take_node(cursor);
self.emit_seperated(cursor, current + 1, total, seperated)?;
self.emit_node(&mut slice)?;
if current > 0 {
self.writer.write_str(seperated)?;
}
Ok(())
}
fn emit_select(&mut self, parts: SelectEmit<'_>) -> std::fmt::Result {
if parts.distinct {
self.writer.write_str("select distinct ")?;
} else {
self.writer.write_str("select ")?;
}
if parts.project.is_empty() {
self.writer.write_char('*')?;
} else {
self.emit_instrs(parts.project)?;
}
if !parts.from.is_empty() {
self.writer.write_str(" from ")?;
self.emit_instrs(parts.from)?;
}
if !parts.filters.is_empty() {
self.writer.write_str(" where ")?;
self.emit_instrs(parts.filters)?;
}
if !parts.group_by.is_empty() {
self.writer.write_str(" group by ")?;
self.emit_instrs(parts.group_by)?;
}
if !parts.havings.is_empty() {
self.writer.write_str(" having ")?;
self.emit_instrs(parts.havings)?;
}
if !parts.order_by.is_empty() {
self.writer.write_str(" order by ")?;
self.emit_instrs(parts.order_by)?;
}
let has_limit = !parts.limit.is_empty();
if has_limit {
self.writer.write_str(" limit ")?;
self.emit_instrs(parts.limit)?;
}
if has_limit && !parts.offset.is_empty() {
self.writer.write_str(" offset ")?;
self.emit_instrs(parts.offset)?;
}
if let Some(lock) = parts.lock {
match (self.dialect, lock) {
(
Dialect::Postgres,
LockClause::ForUpdate {
skip_locked,
nowait,
},
)
| (
Dialect::MariaDb,
LockClause::ForUpdate {
skip_locked,
nowait,
},
) => {
self.writer.write_str(" for update")?;
if skip_locked {
self.writer.write_str(" skip locked")?;
}
if nowait {
self.writer.write_str(" nowait")?;
}
}
(
Dialect::Postgres,
LockClause::Shared {
skip_locked,
nowait,
},
) => {
self.writer.write_str(" for share")?;
if skip_locked {
self.writer.write_str(" skip locked")?;
}
if nowait {
self.writer.write_str(" nowait")?;
}
}
(
Dialect::MariaDb,
LockClause::Shared {
skip_locked,
nowait,
},
) => {
self.writer.write_str(" lock in share mode")?;
if skip_locked || nowait {
panic!("lock modifiers are not supported with shared_lock() for mariadb");
}
}
(Dialect::Sqlite, _) => {
panic!("locking clauses are not supported for sqlite");
}
}
}
Ok(())
}
fn emit_ctes(&mut self, ctes: &'a [Cte]) -> std::fmt::Result {
if ctes.is_empty() {
return Ok(());
}
let recursive = ctes.iter().any(|cte| cte.recursive);
if recursive {
self.writer.write_str("with recursive ")?;
} else {
self.writer.write_str("with ")?;
}
for (index, cte) in ctes.iter().enumerate() {
if index > 0 {
self.writer.write_str(", ")?;
}
self.write_ident(cte.name)?;
if !cte.columns.is_empty() {
self.writer.write_char('(')?;
for (i, column) in cte.columns.iter().enumerate() {
if i > 0 {
self.writer.write_str(", ")?;
}
self.write_ident(column)?;
}
self.writer.write_char(')')?;
}
self.writer.write_str(" as (")?;
self.emit_query(&cte.query)?;
self.writer.write_char(')')?;
}
self.writer.write_char(' ')?;
Ok(())
}
pub fn emit_update<M>(&mut self, update: &'a Update<M>) -> std::fmt::Result {
self.emit_ctes(&update.query.ctes)?;
self.writer.write_str("update ")?;
self.write_table(update.table)?;
if let Some(alias) = update.alias {
self.write_alias(alias)?;
}
self.writer.write_str(" set ")?;
self.emit_instrs(&update.set)?;
if !update.from.is_empty() {
self.writer.write_str(" from ")?;
self.emit_instrs(&update.from)?;
}
if !update.query.filters.is_empty() {
self.writer.write_str(" where ")?;
self.emit_instrs(&update.query.filters)?;
}
if !update.returning.is_empty() {
self.writer.write_str(" returning ")?;
self.emit_instrs(&update.returning)?;
}
Ok(())
}
pub fn emit_delete<T>(&mut self, delete: &'a Delete<T>) -> std::fmt::Result {
self.emit_ctes(&delete.query.ctes)?;
self.writer.write_str("delete from ")?;
self.write_table(delete.table.name())?;
if !delete.query.filters.is_empty() {
self.writer.write_str(" where ")?;
self.emit_instrs(&delete.query.filters)?;
}
Ok(())
}
pub fn emit_ctes_for_query(&mut self, query: &'a Query) -> std::fmt::Result {
self.emit_ctes(&query.ctes)
}
pub fn emit_query_body(&mut self, query: &'a Query) -> std::fmt::Result {
if let Some(compound) = &query.compound {
return self.emit_compound_query(query, compound);
}
self.emit_select(SelectEmit {
distinct: query.distinct,
project: &query.project,
from: &query.from,
filters: &query.filters,
group_by: &query.group_by,
havings: &query.havings,
order_by: &query.order_by,
limit: &query.limit,
offset: &query.offset,
lock: query.lock,
})
}
pub fn emit_query(&mut self, query: &'a Query) -> std::fmt::Result {
self.emit_ctes_for_query(query)?;
self.emit_query_body(query)
}
fn emit_compound_query(
&mut self,
left: &'a Query,
compound: &'a CompoundQuery,
) -> std::fmt::Result {
self.writer.write_char('(')?;
self.emit_select(SelectEmit {
distinct: left.distinct,
project: &left.project,
from: &left.from,
filters: &left.filters,
group_by: &left.group_by,
havings: &left.havings,
order_by: &left.order_by,
limit: &left.limit,
offset: &left.offset,
lock: left.lock,
})?;
self.writer.write_char(')')?;
match compound.operator {
SetOperator::Union => self.writer.write_str(" union ")?,
SetOperator::UnionAll => self.writer.write_str(" union all ")?,
}
self.writer.write_char('(')?;
self.emit_query_body(&compound.right)?;
self.writer.write_char(')')
}
}
fn take_node<'b>(cursor: &mut &'b [RpnInstr]) -> &'b [RpnInstr] {
let orig = *cursor;
let (instr, rest) = cursor.split_last().expect("underflow");
*cursor = rest;
let size = instr.len();
let start = cursor.len().checked_sub(size).expect("subtree underflow");
*cursor = &cursor[..start];
&orig[start..]
}
fn take_slice<'b>(cursor: &mut &'b [RpnInstr], size: usize) -> &'b [RpnInstr] {
let split = cursor.len().checked_sub(size).expect("subtree underflow");
let (rest, slice) = cursor.split_at(split);
*cursor = rest;
slice
}