use itertools::Itertools;
use std::fmt::{self, Display};
use std::marker::PhantomData;
mod predicates;
pub use predicates::*;
mod data_type;
pub use data_type::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ObjectConcat<'i>(pub &'i [&'i str]);
impl fmt::Display for ObjectConcat<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.0.iter().any(|o| o.contains("'") || o.contains("\\")) {
return Err(fmt::Error);
}
if self.0.iter().any(|o| o.contains(" ") || o.contains("\"")) {
f.write_str("\"")?;
for part in self.0.iter().flat_map(|o| o.split("\"").intersperse("\"\"")) {
f.write_str(part)?;
}
f.write_str("\"")?;
} else {
for o in self.0.iter() {
f.write_str(o)?;
}
}
Ok(())
}
}
pub struct ObjectConcatDisplay<'i>(Box<[&'i str]>);
impl<'i> ObjectConcatDisplay<'i> {
pub fn as_quoted_data(self) -> QuotedDataConcatDisplay<'i> {
self.into()
}
}
impl fmt::Display for ObjectConcatDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
ObjectConcat(&self.0).fmt(f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct QuotedDataConcat<'i>(pub &'i [&'i str]);
impl fmt::Display for QuotedDataConcat<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("'")?;
for part in self.0.iter().flat_map(|o| o.split("'").intersperse("''")) {
for part in part.split("\\").intersperse("\\\\") {
f.write_str(part)?;
}
}
f.write_str("'")?;
Ok(())
}
}
pub struct QuotedDataConcatDisplay<'i>(Box<[&'i str]>);
impl fmt::Display for QuotedDataConcatDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
QuotedDataConcat(&self.0).fmt(f)
}
}
impl<'i> From<ObjectConcatDisplay<'i>> for QuotedDataConcatDisplay<'i> {
fn from(v: ObjectConcatDisplay<'i>) -> QuotedDataConcatDisplay<'i> {
QuotedDataConcatDisplay(v.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Object<'i>(pub &'i str);
impl<'i> Object<'i> {
pub fn as_str(&self) -> &'i str {
self.0
}
pub fn as_quoted_data(&self) -> QuotedDataConcatDisplay<'i> {
QuotedDataConcatDisplay(Box::new([self.as_str()]))
}
}
impl<'i> From<&'i str> for Object<'i> {
fn from(value: &'i str) -> Object<'i> {
Object(value.into())
}
}
impl fmt::Display for Object<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
ObjectConcat(&[self.0]).fmt(f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct QuotedData<'i>(pub &'i str);
impl<'i> From<&'i str> for QuotedData<'i> {
fn from(value: &'i str) -> QuotedData<'i> {
QuotedData(value)
}
}
impl<'i> QuotedData<'i> {
pub fn map<F>(self, f: F) -> MapQuotedData<'i, F>
where
F: Fn(&'i str) -> String,
{
MapQuotedData(self.0, f)
}
pub fn as_str(&self) -> &'i str {
self.0
}
}
impl fmt::Display for QuotedData<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
QuotedDataConcat(&[self.0]).fmt(f)
}
}
pub struct MapQuotedData<'i, F>(&'i str, F);
impl<'i, F> fmt::Display for MapQuotedData<'i, F>
where
F: Fn(&'i str) -> String,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let data = self.1(self.0);
QuotedData(&data).fmt(f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Schema<'i>(pub Object<'i>);
impl<'i> Schema<'i> {
pub fn as_str(&self) -> &'i str {
self.0.as_str()
}
pub fn as_quoted_data(&self) -> QuotedDataConcatDisplay<'i> {
self.0.as_quoted_data()
}
}
impl<'i, O: Into<Object<'i>> > From<O> for Schema<'i> {
fn from(value: O) -> Schema<'i> {
Schema(value.into())
}
}
impl fmt::Display for Schema<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Table<'i>(pub Object<'i>);
impl<'i> Table<'i> {
pub fn with_schema(self, schema: impl Into<Schema<'i>>) -> SchemaTable<'i> {
SchemaTable(schema.into(), self)
}
pub fn with_postfix(&self, postfix: &'i str) -> ObjectConcatDisplay<'i> {
ObjectConcatDisplay(Box::new([self.as_str(), postfix]))
}
pub fn with_postfix_sep(&self, postfix: &'i str, separator: &'i str) -> ObjectConcatDisplay<'i> {
ObjectConcatDisplay(Box::new([self.as_str(), separator, postfix]))
}
pub fn as_str(&self) -> &'i str {
self.0.as_str()
}
pub fn as_quoted_data(&self) -> QuotedDataConcatDisplay<'i> {
self.0.as_quoted_data()
}
}
impl<'i, O: Into<Object<'i>> > From<O> for Table<'i> {
fn from(table: O) -> Table<'i> {
Table(table.into())
}
}
impl fmt::Display for Table<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct SchemaTable<'i>(pub Schema<'i>, pub Table<'i>);
impl<'i> SchemaTable<'i> {
pub fn schema(&self) -> Schema<'i> {
self.0
}
pub fn table(&self) -> Table<'i> {
self.1
}
fn as_array(&self) -> [&'i str; 3] {
[self.0.as_str(), ".", self.1.as_str()]
}
pub fn with_postfix(&self, postfix: &'i str) -> impl Display + 'i {
let a = self.as_array();
ObjectConcatDisplay(Box::new([a[0], a[1], a[2], postfix]))
}
pub fn with_postfix_sep(&self, postfix: &'i str, separator: &'i str) -> ObjectConcatDisplay<'i> {
let a = self.as_array();
ObjectConcatDisplay(Box::new([a[0], a[1], a[2], separator, postfix]))
}
pub fn as_quoted_data(&self) -> QuotedDataConcatDisplay<'i> {
QuotedDataConcatDisplay(Box::new(self.as_array()))
}
}
impl<'i, S: Into<Schema<'i>>, T: Into<Table<'i>>> From<(S, T)> for SchemaTable<'i> {
fn from((schema, table): (S, T)) -> SchemaTable<'i> {
SchemaTable(schema.into(), table.into())
}
}
impl fmt::Display for SchemaTable<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
ObjectConcat(&self.as_array()).fmt(f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Column<'i>(pub Object<'i>);
impl<'i> Column<'i> {
pub fn as_str(&self) -> &'i str {
self.0.as_str()
}
pub fn as_quoted_data(&self) -> QuotedDataConcatDisplay<'i> {
self.0.as_quoted_data()
}
}
impl<'i, O: Into<Object<'i>>> From<O> for Column<'i> {
fn from(value: O) -> Column<'i> {
Column(value.into())
}
}
impl fmt::Display for Column<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ColumnType<D: Dialect>(pub Object<'static>, pub PhantomData<D>);
impl<D: Dialect> ColumnType<D> {
pub fn as_str(&self) -> &'static str {
self.0.as_str()
}
}
impl<D, O: Into<Object<'static>>> From<O> for ColumnType<D> where D: Dialect {
fn from(column_type: O) -> ColumnType<D> {
ColumnType(column_type.into(), PhantomData)
}
}
impl<D: Dialect> fmt::Display for ColumnType<D> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ColumnSchema<'i, D: Dialect>(pub Column<'i>, pub ColumnType<D>);
impl<'i, D: Dialect> ColumnSchema<'i, D> {
pub fn column(&self) -> &Column<'i> {
&self.0
}
pub fn column_type(&self) -> &ColumnType<D> {
&self.1
}
}
impl<'i, D: Dialect, C: Into<Column<'i>>, T: Into<ColumnType<D>>> From<(C, T)> for ColumnSchema<'i, D> {
fn from((name, r#type): (C, T)) -> ColumnSchema<'i, D> {
ColumnSchema(name.into(), r#type.into())
}
}
impl<D: Dialect> fmt::Display for ColumnSchema<'_, D> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", self.0, self.1)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_select() {
assert_eq!(
r#"SELECT "foo bar" FROM foo.baz_quix WHERE blah = 'hello ''world'' foo'"#,
&format!(
"SELECT {} FROM {} WHERE {} = {}",
Column("foo bar".into()),
SchemaTable("foo".into(), "baz".into()).with_postfix("_quix"),
Column("blah".into()),
QuotedData("hello 'world' foo")
)
)
}
#[test]
fn build_object_concat() {
assert_eq!(
r#""hello ""world"" foo_""quix""""#,
&format!(
"{}",
ObjectConcat(&[r#"hello "world" foo"#, r#"_"quix""#])
)
);
assert_eq!(
"foo_bar_baz",
&format!(
"{}",
ObjectConcat(&["foo_", "bar", "_baz"])
)
);
}
}