Skip to content

Commit

Permalink
Postgres ltree support
Browse files Browse the repository at this point in the history
  • Loading branch information
halfmatthalfcat committed Mar 28, 2022
1 parent 479e08a commit d4b3ee2
Show file tree
Hide file tree
Showing 14 changed files with 1,478 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
Cargo.lock
.direnv/
.vscode/
.idea/
1 change: 1 addition & 0 deletions db/scripts/postgres/ltree.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE EXTENSION IF NOT EXISTS "ltree";
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ services:
networks:
- databases
tmpfs: /pgtmpfs12
volumes:
- ./db/scripts/postgres:/docker-entrypoint-initdb.d

mysql57:
image: mysql:5.7
Expand Down
2 changes: 2 additions & 0 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ mod update;
mod values;

pub use column::{Column, DefaultValue, TypeDataLength, TypeFamily};
#[cfg(feature = "postgresql")]
pub use compare::{LtreeCompare, LtreeQuery};
pub use compare::{Comparable, Compare, JsonCompare, JsonType};
pub use conditions::ConditionTree;
pub use conjunctive::Conjunctive;
Expand Down
307 changes: 307 additions & 0 deletions src/ast/compare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub enum Compare<'a> {
/// (NOT `left` @@ to_tsquery(`value`))
#[cfg(feature = "postgresql")]
NotMatches(Box<Expression<'a>>, Cow<'a, str>),
#[cfg(feature = "postgresql")]
LtreeCompare(LtreeCompare<'a>),
}

#[derive(Debug, Clone, PartialEq)]
Expand All @@ -72,6 +74,44 @@ pub enum JsonType {
Null,
}

#[derive(Debug, Clone, PartialEq)]
#[cfg(feature = "postgresql")]
pub enum LtreeQuery<'a> {
String(Cow<'a, str>),
Array(Vec<Cow<'a, str>>),
}

#[cfg(feature = "postgresql")]
impl<'a> LtreeQuery<'a> {
pub fn string<S>(string: S) -> LtreeQuery<'a>
where
S: Into<Cow<'a, str>>,
{
LtreeQuery::String(string.into())
}

pub fn array<A, V>(array: A) -> LtreeQuery<'a>
where
V: Into<Cow<'a, str>>,
A: Into<Vec<V>>,
{
LtreeQuery::Array(array.into().into_iter().map(|v| v.into()).collect())
}
}

#[derive(Debug, Clone, PartialEq)]
#[cfg(feature = "postgresql")]
pub enum LtreeCompare<'a> {
IsAncestor(Box<Expression<'a>>, LtreeQuery<'a>),
IsNotAncestor(Box<Expression<'a>>, LtreeQuery<'a>),
IsDescendant(Box<Expression<'a>>, LtreeQuery<'a>),
IsNotDescendant(Box<Expression<'a>>, LtreeQuery<'a>),
Matches(Box<Expression<'a>>, LtreeQuery<'a>),
DoesNotMatch(Box<Expression<'a>>, LtreeQuery<'a>),
MatchesFullText(Box<Expression<'a>>, LtreeQuery<'a>),
DoesNotMatchFullText(Box<Expression<'a>>, LtreeQuery<'a>),
}

impl<'a> Compare<'a> {
/// Finds a possible `(a,y) IN (SELECT x,z FROM B)`, takes the select out and
/// converts the comparison into `a IN (SELECT x FROM cte_n where z = y)`.
Expand Down Expand Up @@ -873,6 +913,185 @@ pub trait Comparable<'a> {
where
T: Into<Cow<'a, str>>,
V: Into<Expression<'a>>;

/// Determines whether a given ltree is the ancestor of one or more lqueries
///
/// ```rust
/// # use quaint::{ast::*, visitor::{Visitor, Postgres}};
/// # fn main() -> Result<(), quaint::error::Error> {
/// let query = Select::from_table("paths").so_that("path".ltree_is_ancestor(LtreeQuery::string("a.b.c")));
/// let (sql, params) = Postgres::build(query)?;
///
/// assert_eq!("SELECT \"paths\".* FROM \"paths\" WHERE \"path\" @> $1", sql);
///
/// assert_eq!(vec![
/// Value::from("a.b.c")
/// ], params);
///
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "postgresql")]
fn ltree_is_ancestor<T>(self, ltree: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>;

/// Determines whether a given ltree is not the ancestor of one or more lqueries
///
/// ```rust
/// # use quaint::{ast::*, visitor::{Visitor, Postgres}};
/// # fn main() -> Result<(), quaint::error::Error> {
/// let query = Select::from_table("paths").so_that("path".ltree_is_not_ancestor(LtreeQuery::array(vec!["a.b.c", "d.e.f"])));
/// let (sql, params) = Postgres::build(query)?;
///
/// assert_eq!("SELECT \"paths\".* FROM \"paths\" WHERE (NOT \"path\" @> ARRAY[$1,$2]::lquery[])", sql);
///
/// assert_eq!(vec![
/// Value::from("a.b.c"),
/// Value::from("d.e.f")
/// ], params);
///
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "postgresql")]
fn ltree_is_not_ancestor<T>(self, ltree: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>;

/// Determines whether a given ltree is the descendent of one or more lqueries
///
/// ```rust
/// # use quaint::{ast::*, visitor::{Visitor, Postgres}};
/// # fn main() -> Result<(), quaint::error::Error> {
/// let query = Select::from_table("paths").so_that("path".ltree_is_descendant(LtreeQuery::string("a.b.c")));
/// let (sql, params) = Postgres::build(query)?;
///
/// assert_eq!("SELECT \"paths\".* FROM \"paths\" WHERE \"path\" <@ $1", sql);
///
/// assert_eq!(vec![
/// Value::from("a.b.c")
/// ], params);
///
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "postgresql")]
fn ltree_is_descendant<T>(self, ltree: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>;

/// Determines whether a given ltree is not the descendent of one or more lqueries
///
/// ```rust
/// # use quaint::{ast::*, visitor::{Visitor, Postgres}};
/// # fn main() -> Result<(), quaint::error::Error> {
/// let query = Select::from_table("paths").so_that("path".ltree_is_not_descendant(LtreeQuery::array(vec!["a.b.c", "d.e.f"])));
/// let (sql, params) = Postgres::build(query)?;
///
/// assert_eq!("SELECT \"paths\".* FROM \"paths\" WHERE (NOT \"path\" <@ ARRAY[$1,$2]::lquery[])", sql);
///
/// assert_eq!(vec![
/// Value::from("a.b.c"),
/// Value::from("d.e.f")
/// ], params);
///
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "postgresql")]
fn ltree_is_not_descendant<T>(self, ltree: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>;

/// Determines whether a given ltree matches one or more lqueries
///
/// ```rust
/// # use quaint::{ast::*, visitor::{Visitor, Postgres}};
/// # fn main() -> Result<(), quaint::error::Error> {
/// let query = Select::from_table("paths").so_that("path".ltree_match(LtreeQuery::string("a.b.c")));
/// let (sql, params) = Postgres::build(query)?;
///
/// assert_eq!("SELECT \"paths\".* FROM \"paths\" WHERE \"path\" ~ $1", sql);
///
/// assert_eq!(vec![
/// Value::from("a.b.c")
/// ], params);
///
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "postgresql")]
fn ltree_match<T>(self, lquery: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>;

/// Determines whether a given ltree does not match one or more lqueries
///
/// ```rust
/// # use quaint::{ast::*, visitor::{Visitor, Postgres}};
/// # fn main() -> Result<(), quaint::error::Error> {
/// let query = Select::from_table("paths").so_that("path".ltree_match(LtreeQuery::array(vec!["a.b.c", "d.e.f"])));
/// let (sql, params) = Postgres::build(query)?;
///
/// assert_eq!("SELECT \"paths\".* FROM \"paths\" WHERE (NOT \"path\" ? ARRAY[$1,$2]::lquery[])", sql);
///
/// assert_eq!(vec![
/// Value::from("a.b.c"),
/// Value::from("d.e.f")
/// ], params);
///
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "postgresql")]
fn ltree_does_not_match<T>(self, lquery: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>;

/// Determines whether a given ltree matches fulltext one or more ltxtqueries
///
/// ```rust
/// # use quaint::{ast::*, visitor::{Visitor, Postgres}};
/// # fn main() -> Result<(), quaint::error::Error> {
/// let query = Select::from_table("paths").so_that("path".ltree_match_fulltext(LtreeQuery::string("a.b.c")));
/// let (sql, params) = Postgres::build(query)?;
///
/// assert_eq!("SELECT \"paths\".* FROM \"paths\" WHERE \"path\" @ $1", sql);
///
/// assert_eq!(vec![
/// Value::from("a.b.c")
/// ], params);
///
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "postgresql")]
fn ltree_match_fulltext<T>(self, ltxtquery: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>;

/// Determines whether a given ltree does not match fulltext one or more ltxtqueries
///
/// ```rust
/// # use quaint::{ast::*, visitor::{Visitor, Postgres}};
/// # fn main() -> Result<(), quaint::error::Error> {
/// let query = Select::from_table("paths").so_that("path".ltree_does_not_match_fulltext(LtreeQuery::string("a.b.c")));
/// let (sql, params) = Postgres::build(query)?;
///
/// assert_eq!("SELECT \"paths\".* FROM \"paths\" WHERE (NOT \"path\" @ $1)", sql);
///
/// assert_eq!(vec![
/// Value::from("a.b.c")
/// ], params);
///
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "postgresql")]
fn ltree_does_not_match_fulltext<T>(self, ltxtquery: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>;
}

impl<'a, U> Comparable<'a> for U
Expand Down Expand Up @@ -1150,4 +1369,92 @@ where

val.not_matches(query)
}

#[cfg(feature = "postgresql")]
fn ltree_is_ancestor<T>(self, ltree: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>,
{
let col: Column<'a> = self.into();
let val: Expression<'a> = col.into();

val.ltree_is_ancestor(ltree)
}

#[cfg(feature = "postgresql")]
fn ltree_is_not_ancestor<T>(self, ltree: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>,
{
let col: Column<'a> = self.into();
let val: Expression<'a> = col.into();

val.ltree_is_not_ancestor(ltree)
}

#[cfg(feature = "postgresql")]
fn ltree_is_descendant<T>(self, ltree: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>,
{
let col: Column<'a> = self.into();
let val: Expression<'a> = col.into();

val.ltree_is_descendant(ltree)
}

#[cfg(feature = "postgresql")]
fn ltree_is_not_descendant<T>(self, ltree: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>,
{
let col: Column<'a> = self.into();
let val: Expression<'a> = col.into();

val.ltree_is_not_descendant(ltree)
}

#[cfg(feature = "postgresql")]
fn ltree_match<T>(self, lquery: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>,
{
let col: Column<'a> = self.into();
let val: Expression<'a> = col.into();

val.ltree_match(lquery)
}

#[cfg(feature = "postgresql")]
fn ltree_does_not_match<T>(self, lquery: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>,
{
let col: Column<'a> = self.into();
let val: Expression<'a> = col.into();

val.ltree_does_not_match(lquery)
}

#[cfg(feature = "postgresql")]
fn ltree_match_fulltext<T>(self, ltxtquery: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>,
{
let col: Column<'a> = self.into();
let val: Expression<'a> = col.into();

val.ltree_match_fulltext(ltxtquery)
}

#[cfg(feature = "postgresql")]
fn ltree_does_not_match_fulltext<T>(self, ltxtquery: T) -> Compare<'a>
where
T: Into<LtreeQuery<'a>>,
{
let col: Column<'a> = self.into();
let val: Expression<'a> = col.into();

val.ltree_does_not_match_fulltext(ltxtquery)
}
}
Loading

0 comments on commit d4b3ee2

Please sign in to comment.