#![allow(clippy::write_with_newline)]
use std::fmt::{self, Debug, Display, Write};
use std::iter::{once, repeat};
use rusqlite::types::ToSql;
use crate::driver::TypeTag;
use crate::network::{Field, Ordering, Triples, TriplesField};
use crate::retrieve::Select;
use crate::soup::Encoded;
#[derive(Debug)]
pub struct Query<P> {
string: String,
params: Vec<P>,
}
impl<P> Query<P> {
pub fn as_str(&self) -> &str {
&self.string
}
pub fn params(&self) -> &[P] {
self.params.as_slice()
}
}
impl<P> Default for Query<P> {
fn default() -> Self {
Query { string: String::new(), params: Vec::new() }
}
}
impl<P> Display for Query<P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.string, f)
}
}
impl<P> Write for Query<P> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.string.write_str(s)
}
}
impl<P> QueryWriter<P> for Query<P> {
fn push_param(&mut self, p: P) -> &mut Self {
self.params.push(p);
self
}
fn push_sql(&mut self, s: &str) -> &mut Self {
self.string.push_str(s);
self
}
fn nl(&mut self) -> &mut Self {
self.string.push('\n');
self
}
}
impl<W, P> QueryWriter<P> for &'_ mut W
where
W: QueryWriter<P>,
{
fn push_param(&mut self, p: P) -> &mut Self {
(*self).push_param(p);
self
}
fn push_sql(&mut self, s: &str) -> &mut Self {
(*self).push_sql(s);
self
}
fn nl(&mut self) -> &mut Self {
(*self).nl();
self
}
}
pub trait QueryWriter<P>: Write {
fn push_param(&mut self, p: P) -> &mut Self;
fn push_sql(&mut self, s: &str) -> &mut Self;
fn nl(&mut self) -> &mut Self;
fn with_indent<I>(&mut self, indent: I) -> IndentedQueryWriter<&mut Self, I>
where
Self: Sized,
I: Iterator<Item = &'static str>,
{
IndentedQueryWriter { writer: self, indent }
}
fn push<T>(&mut self, ptq: T) -> &mut Self
where
Self: Sized,
T: PushToQuery<P>,
{
ptq.push_to_query(self);
self
}
}
#[derive(Debug)]
pub struct IndentedQueryWriter<W, I> {
writer: W,
indent: I,
}
impl<W, I> IndentedQueryWriter<W, I> {
fn dedent(self) -> W {
self.writer
}
}
impl<W, I, P> QueryWriter<P> for IndentedQueryWriter<W, I>
where
W: QueryWriter<P>,
I: Iterator<Item = &'static str>,
{
fn push_param(&mut self, p: P) -> &mut Self {
self.writer.push_param(p);
self
}
fn push_sql(&mut self, s: &str) -> &mut Self {
self.writer.push_sql(s);
self
}
fn nl(&mut self) -> &mut Self {
self.writer.nl();
if let Some(i) = self.indent.next() {
self.writer.push_sql(i);
}
self
}
}
impl<W, I> Write for IndentedQueryWriter<W, I>
where
W: Write,
{
fn write_str(&mut self, s: &str) -> fmt::Result {
self.writer.write_str(s)
}
}
pub trait PushToQuery<P> {
fn push_to_query<W>(&self, _: &mut W)
where
W: QueryWriter<P>;
fn to_query(&self) -> Query<P> {
let mut q = Query::default();
self.push_to_query(&mut q);
q
}
}
impl<T, P> PushToQuery<P> for &'_ T
where
T: PushToQuery<P>,
{
fn push_to_query<W>(&self, w: &mut W)
where
W: QueryWriter<P>,
{
(*self).push_to_query(w)
}
}
impl<'n, V> PushToQuery<&'n dyn ToSql> for Select<'n, V>
where
V: ToSql + TypeTag,
{
fn push_to_query<W>(&self, writer: &mut W)
where
W: QueryWriter<&'n dyn ToSql>,
{
use crate::network::{Constraint, Match};
let mut soup = SoupLookup::with_length(self.network.triples());
let fromsoups = self
.selection
.iter()
.cloned()
.chain(self.network.constraints().iter().filter_map(|constraint| {
if let Constraint::Eq { lh, rh: Match::Value(_) } = *constraint {
Some(lh)
} else {
None
}
}))
.chain(self.order_by.iter().map(|&(tf, _ordering)| tf))
.filter(|tf| soup.mark(*tf))
.map(FromSoup)
.collect::<Vec<_>>();
let mut writer = writer.with_indent(once("SELECT ").chain(repeat(" , ")));
for tf in self.selection.iter() {
writer
.nl()
.push(FromSoup(*tf))
.push_sql(".t, ")
.push(FromSoup(*tf))
.push_sql(".v");
}
if self.selection.is_empty() {
writer.nl().push_sql("1");
}
let mut writer = writer
.dedent()
.with_indent(once(" FROM ").chain(repeat(" , ")));
for fromsoup in fromsoups.iter().cloned() {
writer.nl().push_sql(r#"soup "#).push(fromsoup);
}
for n in 0..self.network.triples() {
writer.nl().push_sql(&format!(r#"triples t{}"#, n));
}
let mut writer = writer
.dedent()
.with_indent(once(" WHERE ").chain(repeat(" AND ")));
for constraint in self.network.constraints().iter() {
if let Constraint::Eq { lh, rh: Match::Value(ref v) } = *constraint {
writer
.nl()
.push(FromSoup(lh))
.push_sql(&format!(".t = {} AND ", v.type_tag()))
.push(FromSoup(lh))
.push_sql(".v = ?")
.push_param(v as &dyn ToSql);
}
}
for fromsoup in fromsoups.iter().cloned() {
writer
.nl()
.push(fromsoup.0)
.push_sql(" = ")
.push(fromsoup)
.push_sql(".rowid");
}
for constraint in self.network.constraints().iter() {
match constraint {
&Constraint::Eq { lh, ref rh } => match rh {
&Match::Field(rh) => {
writer.nl().push(lh).push_sql(" = ").push(rh);
}
Match::Encoded(Encoded { rowid, .. }) => {
writer.nl().push(lh).push_sql(&format!(" = {}", rowid));
}
Match::Value(_) => {}
},
}
}
let indent = once("ORDER BY ").chain(repeat(" , "));
let mut writer = writer.dedent().with_indent(indent);
for &(tf, ordering) in self.order_by.iter() {
writer
.nl()
.push(FromSoup(tf))
.push_sql(".v ")
.push(ordering);
}
let writer = writer.dedent();
if 0 < self.limit {
writer.nl().push_sql(&format!(" LIMIT {}", self.limit));
}
return;
#[derive(Copy, Clone)]
struct FromSoup(TriplesField);
impl<P> PushToQuery<P> for FromSoup {
fn push_to_query<W>(&self, w: &mut W)
where
W: QueryWriter<P>,
{
let _ = match self.0.field() {
Field::Entity => write!(w, "s{}_e", self.0.triples().usize()),
Field::Attribute => write!(w, "s{}_a", self.0.triples().usize()),
Field::Value => write!(w, "s{}_v", self.0.triples().usize()),
};
}
}
struct SoupLookup(Vec<u8>);
impl SoupLookup {
fn with_length(len: usize) -> Self {
SoupLookup(vec![0; len])
}
fn lookup(&mut self, t: Triples) -> &mut u8 {
&mut self.0[t.usize()]
}
fn mark(&mut self, tf: TriplesField) -> bool {
let cell = self.lookup(tf.triples());
let flag: u8 = match tf.field() {
Field::Entity => 1,
Field::Attribute => 2,
Field::Value => 4,
};
if 0 == *cell & flag {
*cell |= flag;
true
} else {
false
}
}
}
}
}
impl<P> PushToQuery<P> for TriplesField {
fn push_to_query<W>(&self, w: &mut W)
where
W: QueryWriter<P>,
{
let _ = match self.field() {
Field::Entity => write!(w, "t{}.e", self.triples().usize()),
Field::Attribute => write!(w, "t{}.a", self.triples().usize()),
Field::Value => write!(w, "t{}.v", self.triples().usize()),
};
}
}
impl<P> PushToQuery<P> for Ordering {
fn push_to_query<W>(&self, w: &mut W)
where
W: QueryWriter<P>,
{
let _ = match self {
Ordering::Asc => write!(w, "ASC"),
Ordering::Desc => write!(w, "DESC"),
};
}
}
#[cfg(test)]
mod tests {
use crate::tests::rusqlite_in_memory;
use crate::{traits::*, AttributeRef, Network};
#[test]
fn test_select_default() {
let db = rusqlite_in_memory().expect("rusqlite_in_memory");
let network: Network = Network::default();
let query = network.select().to_query();
assert!(db.prepare(query.as_str()).is_ok());
}
#[test]
fn test_select_nothing_from() {
let db = rusqlite_in_memory().expect("rusqlite_in_memory");
let mut network: Network = Network::default();
network
.fluent_triples()
.match_attribute(AttributeRef::from_static(":db/attribute"));
let query = network.select().to_query();
assert!(db.prepare(query.as_str()).is_ok());
}
#[test]
fn test_select_just_limit() {
let db = rusqlite_in_memory().expect("rusqlite_in_memory");
let network: Network = Network::default();
let query = network.select().limit(123).to_query();
assert!(db.prepare(query.as_str()).is_ok());
}
}