From cc8e78c84ac4394239b000688ec3ca7491e22e15 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Wed, 4 Oct 2023 16:30:38 -0300 Subject: [PATCH] feat(psql): introduce column type `ltree` (#604) * feat: introduce `LTree` column type * feat: the `ltree` type --- src/backend/mysql/table.rs | 1 + src/backend/postgres/table.rs | 1 + src/backend/sqlite/table.rs | 1 + src/extension/postgres/ltree.rs | 57 +++++++++++++++++++++++++++++++++ src/extension/postgres/mod.rs | 2 ++ src/table/column.rs | 8 +++++ tests/mysql/query.rs | 3 +- tests/postgres/table.rs | 24 ++++++++++++++ 8 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/extension/postgres/ltree.rs diff --git a/src/backend/mysql/table.rs b/src/backend/mysql/table.rs index 7c5b42a91..881922f4c 100644 --- a/src/backend/mysql/table.rs +++ b/src/backend/mysql/table.rs @@ -105,6 +105,7 @@ impl TableBuilder for MysqlQueryBuilder { ColumnType::Cidr => unimplemented!("Cidr is not available in MySQL."), ColumnType::Inet => unimplemented!("Inet is not available in MySQL."), ColumnType::MacAddr => unimplemented!("MacAddr is not available in MySQL."), + ColumnType::LTree => unimplemented!("LTree is not available in MySQL."), } ) .unwrap(); diff --git a/src/backend/postgres/table.rs b/src/backend/postgres/table.rs index a87a9c46c..11fc6a4f3 100644 --- a/src/backend/postgres/table.rs +++ b/src/backend/postgres/table.rs @@ -77,6 +77,7 @@ impl TableBuilder for PostgresQueryBuilder { ColumnType::Inet => "inet".into(), ColumnType::MacAddr => "macaddr".into(), ColumnType::Year(_) => unimplemented!("Year is not available in Postgres."), + ColumnType::LTree => "ltree".into(), } ) .unwrap() diff --git a/src/backend/sqlite/table.rs b/src/backend/sqlite/table.rs index 94491861a..996630045 100644 --- a/src/backend/sqlite/table.rs +++ b/src/backend/sqlite/table.rs @@ -90,6 +90,7 @@ impl TableBuilder for SqliteQueryBuilder { ColumnType::Year(_) => unimplemented!("Year is not available in Sqlite."), ColumnType::Bit(_) => unimplemented!("Bit is not available in Sqlite."), ColumnType::VarBit(_) => unimplemented!("VarBit is not available in Sqlite."), + ColumnType::LTree => unimplemented!("LTree is not available in Sqlite."), } ) .unwrap() diff --git a/src/extension/postgres/ltree.rs b/src/extension/postgres/ltree.rs new file mode 100644 index 000000000..1869cf731 --- /dev/null +++ b/src/extension/postgres/ltree.rs @@ -0,0 +1,57 @@ +use std::fmt; + +/// PostgreSQL `ltree` extension type. +/// +/// `ltree` stores a raber path which in this struct is represented as the +/// tuple's first value. +/// +/// # PostcreSQL Reference +/// +/// The following set of SQL statements can be used to create a table with +/// a `ltree` column. Here the `ltree` column is called `path`. +/// +/// The `path` column is then populated to generate the tree. +/// +/// ```ignore +/// CREATE TABLE test (path ltree); +/// INSERT INTO test VALUES ('Top'); +/// INSERT INTO test VALUES ('Top.Science'); +/// INSERT INTO test VALUES ('Top.Science.Astronomy'); +/// INSERT INTO test VALUES ('Top.Science.Astronomy.Astrophysics'); +/// INSERT INTO test VALUES ('Top.Science.Astronomy.Cosmology'); +/// INSERT INTO test VALUES ('Top.Hobbies'); +/// INSERT INTO test VALUES ('Top.Hobbies.Amateurs_Astronomy'); +/// INSERT INTO test VALUES ('Top.Collections'); +/// INSERT INTO test VALUES ('Top.Collections.Pictures'); +/// INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy'); +/// INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Stars'); +/// INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Galaxies'); +/// INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Astronauts'); +/// CREATE INDEX path_gist_idx ON test USING GIST (path); +/// CREATE INDEX path_idx ON test USING BTREE (path); +/// ``` +/// +/// The set of queries above will generate the following tree: +/// +/// ```ignore +/// Top +/// / | \ +/// Science Hobbies Collections +/// / | \ +/// Astronomy Amateurs_Astronomy Pictures +/// / \ | +/// Astrophysics Cosmology Astronomy +/// / | \ +/// Galaxies Stars Astronauts +/// ``` +/// [Source][1] +/// +/// [1]: https://www.postgresql.org/docs/current/ltree.html +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct PgLTree; + +impl fmt::Display for PgLTree { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ltree") + } +} diff --git a/src/extension/postgres/mod.rs b/src/extension/postgres/mod.rs index abe5424a8..acd0c7daa 100644 --- a/src/extension/postgres/mod.rs +++ b/src/extension/postgres/mod.rs @@ -2,6 +2,7 @@ pub use expr::*; pub use extension::*; pub use func::*; pub use interval::*; +pub use ltree::*; pub use types::*; use crate::types::BinOper; @@ -10,6 +11,7 @@ pub(crate) mod expr; pub(crate) mod extension; pub(crate) mod func; pub(crate) mod interval; +pub(crate) mod ltree; pub(crate) mod types; /// Binary operator diff --git a/src/table/column.rs b/src/table/column.rs index 7e267c7a0..ae0a6b5a3 100644 --- a/src/table/column.rs +++ b/src/table/column.rs @@ -52,6 +52,7 @@ pub enum ColumnType { Cidr, Inet, MacAddr, + LTree, } impl PartialEq for ColumnType { @@ -558,6 +559,13 @@ impl ColumnDef { self } + /// Set column type as `ltree` + /// This is only supported on Postgres. + pub fn ltree(&mut self) -> &mut Self { + self.types = Some(ColumnType::LTree); + self + } + /// Set constraints as SimpleExpr /// /// ``` diff --git a/tests/mysql/query.rs b/tests/mysql/query.rs index f8273b018..89ff19494 100644 --- a/tests/mysql/query.rs +++ b/tests/mysql/query.rs @@ -995,8 +995,7 @@ fn select_58() { .and_where(Expr::col(Char::Character).like(LikeExpr::new("A").escape('\\'))) .build(MysqlQueryBuilder), ( - r#"SELECT `character` FROM `character` WHERE `character` LIKE ? ESCAPE '\\'"# - .to_owned(), + r"SELECT `character` FROM `character` WHERE `character` LIKE ? ESCAPE '\\'".to_owned(), Values(vec!["A".into()]) ) ); diff --git a/tests/postgres/table.rs b/tests/postgres/table.rs index ece729c32..c63e9e54b 100644 --- a/tests/postgres/table.rs +++ b/tests/postgres/table.rs @@ -597,3 +597,27 @@ fn alter_with_check_constraint() { r#"ALTER TABLE "glyph" ADD COLUMN "aspect" integer NOT NULL DEFAULT 101 CHECK ("aspect" > 100)"#, ); } + +#[test] +fn create_16() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .col( + ColumnDef::new(Glyph::Id) + .integer() + .not_null() + .auto_increment() + .primary_key() + ) + .col(ColumnDef::new(Glyph::Tokens).ltree()) + .to_string(PostgresQueryBuilder), + [ + r#"CREATE TABLE "glyph" ("#, + r#""id" serial NOT NULL PRIMARY KEY,"#, + r#""tokens" ltree"#, + r#")"#, + ] + .join(" ") + ); +}