From 6aa7812a9d3682b523ac27f7494836bfd2c597ee Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Thu, 10 Feb 2022 11:19:12 +0100 Subject: [PATCH 01/20] support ltree --- Cargo.toml | 1 + sqlx-core/Cargo.toml | 1 + sqlx-core/src/postgres/type_info.rs | 12 ++++++ sqlx-core/src/postgres/types/ltree.rs | 61 +++++++++++++++++++++++++++ sqlx-core/src/postgres/types/mod.rs | 6 +++ sqlx-macros/Cargo.toml | 1 + sqlx-macros/src/database/postgres.rs | 3 ++ 7 files changed, 85 insertions(+) create mode 100644 sqlx-core/src/postgres/types/ltree.rs diff --git a/Cargo.toml b/Cargo.toml index 7a2ea8ae28..7610f72867 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,6 +130,7 @@ time = ["sqlx-core/time", "sqlx-macros/time"] bit-vec = ["sqlx-core/bit-vec", "sqlx-macros/bit-vec"] bstr = ["sqlx-core/bstr"] git2 = ["sqlx-core/git2"] +ltree = ["sqlx-core/ltree", "sqlx-macros/ltree"] [dependencies] sqlx-core = { version = "0.5.10", path = "sqlx-core", default-features = false } diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index e8912206fa..febffe6da9 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -62,6 +62,7 @@ all-types = [ bigdecimal = ["bigdecimal_", "num-bigint"] decimal = ["rust_decimal", "num-bigint"] json = ["serde", "serde_json"] +ltree = ["postgres"] # runtimes runtime-actix-native-tls = [ diff --git a/sqlx-core/src/postgres/type_info.rs b/sqlx-core/src/postgres/type_info.rs index 97d5efa0cc..d578a8af94 100644 --- a/sqlx-core/src/postgres/type_info.rs +++ b/sqlx-core/src/postgres/type_info.rs @@ -116,6 +116,7 @@ pub enum PgType { JsonpathArray, Money, MoneyArray, + Ltree, // https://www.postgresql.org/docs/9.3/datatype-pseudo.html Void, @@ -328,6 +329,7 @@ impl PgType { 3927 => PgType::Int8RangeArray, 4072 => PgType::Jsonpath, 4073 => PgType::JsonpathArray, + 16394 => PgType::Ltree, _ => { return None; @@ -436,6 +438,7 @@ impl PgType { PgType::Int8RangeArray => 3927, PgType::Jsonpath => 4072, PgType::JsonpathArray => 4073, + PgType::Ltree => 16394, PgType::Custom(ty) => ty.oid, PgType::DeclareWithOid(oid) => *oid, @@ -538,6 +541,7 @@ impl PgType { PgType::JsonpathArray => "JSONPATH[]", PgType::Money => "MONEY", PgType::MoneyArray => "MONEY[]", + PgType::Ltree => "LTREE", PgType::Void => "VOID", PgType::Custom(ty) => &*ty.name, PgType::DeclareWithOid(_) => "?", @@ -638,6 +642,7 @@ impl PgType { PgType::JsonpathArray => "_jsonpath", PgType::Money => "money", PgType::MoneyArray => "_money", + PgType::Ltree => "ltree", PgType::Void => "void", PgType::Custom(ty) => &*ty.name, PgType::DeclareWithOid(_) => "?", @@ -738,6 +743,7 @@ impl PgType { PgType::JsonpathArray => &PgTypeKind::Array(PgTypeInfo(PgType::Jsonpath)), PgType::Money => &PgTypeKind::Simple, PgType::MoneyArray => &PgTypeKind::Array(PgTypeInfo(PgType::Money)), + PgType::Ltree => &PgTypeKind::Simple, PgType::Void => &PgTypeKind::Pseudo, @@ -849,6 +855,7 @@ impl PgType { PgType::Int8RangeArray => Some(Cow::Owned(PgTypeInfo(PgType::Int8Range))), PgType::Jsonpath => None, PgType::JsonpathArray => Some(Cow::Owned(PgTypeInfo(PgType::Jsonpath))), + PgType::Ltree => None, // There is no `UnknownArray` PgType::Unknown => None, // There is no `VoidArray` @@ -1101,6 +1108,11 @@ impl PgTypeInfo { pub(crate) const INT8_RANGE: Self = Self(PgType::Int8Range); pub(crate) const INT8_RANGE_ARRAY: Self = Self(PgType::Int8RangeArray); + // + // ltree + + pub(crate) const LTREE: Self = Self(PgType::Ltree); + // // pseudo types // https://www.postgresql.org/docs/9.3/datatype-pseudo.html diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs new file mode 100644 index 0000000000..9f8f7f4b97 --- /dev/null +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -0,0 +1,61 @@ +use std::fmt::{self, Display, Formatter}; +use std::str::FromStr; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::{Error, BoxDynError}; +use crate::postgres::{PgArgumentBuffer, PgValueFormat, PgTypeInfo, PgValueRef, Postgres}; +use crate::types::Type; + +#[derive(Debug)] +pub struct PgLTree { + labels: Vec<String> +} + +impl FromStr for PgLTree { + type Err = Error; + + fn from_str(s: &str) -> Result<Self, Error> { + Ok( + Self { + labels: s.split('.').map(|s| s.to_owned()).collect() + } + ) + } +} + +impl Display for PgLTree { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.labels.join(".")) + } +} + +impl Type<Postgres> for PgLTree { + fn type_info() -> PgTypeInfo { + PgTypeInfo::LTREE + } +} + +impl Encode<'_, Postgres> for PgLTree { + fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { + buf.extend(1i8.to_le_bytes()); + buf.extend(self.to_string().as_bytes()); + + IsNull::No + } +} + +impl<'r> Decode<'r, Postgres> for PgLTree { + fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> { + match value.format() { + PgValueFormat::Binary => { + let bytes = value.as_bytes()?; + let version = i8::from_le_bytes([bytes[0]; 1]); + if version != 1 { + todo!("add error here") + } + Ok(Self::from_str(&String::from_utf8(bytes[1..].to_vec())?)?) + }, + PgValueFormat::Text => Ok(Self::from_str(value.as_str()?)?) + } + } +} diff --git a/sqlx-core/src/postgres/types/mod.rs b/sqlx-core/src/postgres/types/mod.rs index eaf11f1093..2aebcdd06a 100644 --- a/sqlx-core/src/postgres/types/mod.rs +++ b/sqlx-core/src/postgres/types/mod.rs @@ -208,6 +208,12 @@ mod mac_address; #[cfg(feature = "bit-vec")] mod bit_vec; +#[cfg(feature = "ltree")] +mod ltree; + +#[cfg(feature = "ltree")] +pub use ltree::PgLTree; + pub use array::PgHasArrayType; pub use interval::PgInterval; pub use money::PgMoney; diff --git a/sqlx-macros/Cargo.toml b/sqlx-macros/Cargo.toml index b036aa2f46..52f849cb5a 100644 --- a/sqlx-macros/Cargo.toml +++ b/sqlx-macros/Cargo.toml @@ -76,6 +76,7 @@ mac_address = ["sqlx-core/mac_address"] uuid = ["sqlx-core/uuid"] bit-vec = ["sqlx-core/bit-vec"] json = ["sqlx-core/json", "serde_json"] +ltree = ["sqlx-core/ltree"] [dependencies] dotenv = { version = "0.15.0", default-features = false } diff --git a/sqlx-macros/src/database/postgres.rs b/sqlx-macros/src/database/postgres.rs index 5330bb3cd9..e3a554ecaf 100644 --- a/sqlx-macros/src/database/postgres.rs +++ b/sqlx-macros/src/database/postgres.rs @@ -69,6 +69,9 @@ impl_database_ext! { #[cfg(feature = "bit-vec")] sqlx::types::BitVec, + #[cfg(feature = "ltree")] + sqlx::postgres::types::PgLTree, + // Arrays Vec<bool> | &[bool], From 81ba1b80f52c80f16c8e364a4fd95cc86921b54d Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Thu, 10 Feb 2022 13:07:42 +0100 Subject: [PATCH 02/20] add default and push to PgLTree --- sqlx-core/src/error.rs | 5 +++++ sqlx-core/src/postgres/types/ltree.rs | 13 ++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/sqlx-core/src/error.rs b/sqlx-core/src/error.rs index 0659375846..4761fbb2c9 100644 --- a/sqlx-core/src/error.rs +++ b/sqlx-core/src/error.rs @@ -103,6 +103,11 @@ pub enum Error { #[cfg(feature = "migrate")] #[error("{0}")] Migrate(#[source] Box<crate::migrate::MigrateError>), + + /// A background worker has crashed. + #[cfg(feature = "ltree")] + #[error("ltree label cotains invalid characters")] + InvalidLtreeLabel, } impl StdError for Box<dyn DatabaseError> {} diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index 9f8f7f4b97..aaf5962151 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -6,11 +6,22 @@ use crate::error::{Error, BoxDynError}; use crate::postgres::{PgArgumentBuffer, PgValueFormat, PgTypeInfo, PgValueRef, Postgres}; use crate::types::Type; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct PgLTree { labels: Vec<String> } +impl PgLTree { + pub fn push(&mut self, label: String) -> Result<(), Error> { + if label.chars().all(|c| c.is_ascii_alphabetic() || c.is_ascii_digit() || c == '_') { + self.labels.push(label); + Ok(()) + } else { + Err(Error::InvalidLtreeLabel) + } + } +} + impl FromStr for PgLTree { type Err = Error; From 0c8a5a8c4c2fb940304d0adb47e117fde3761470 Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Thu, 10 Feb 2022 13:36:40 +0100 Subject: [PATCH 03/20] add more derived for ltree --- sqlx-core/src/postgres/types/ltree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index aaf5962151..0f2bb4913e 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -6,7 +6,7 @@ use crate::error::{Error, BoxDynError}; use crate::postgres::{PgArgumentBuffer, PgValueFormat, PgTypeInfo, PgValueRef, Postgres}; use crate::types::Type; -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default, PartialEq)] pub struct PgLTree { labels: Vec<String> } From 5e5dd75375da9429f6e7b69ae569548d24f4ebae Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Thu, 10 Feb 2022 13:53:28 +0100 Subject: [PATCH 04/20] fix copy/paste --- sqlx-core/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlx-core/src/error.rs b/sqlx-core/src/error.rs index 4761fbb2c9..8aa47962ca 100644 --- a/sqlx-core/src/error.rs +++ b/sqlx-core/src/error.rs @@ -104,7 +104,7 @@ pub enum Error { #[error("{0}")] Migrate(#[source] Box<crate::migrate::MigrateError>), - /// A background worker has crashed. + /// LTree labels can only contain [A-Za-z_] #[cfg(feature = "ltree")] #[error("ltree label cotains invalid characters")] InvalidLtreeLabel, From 98c99012f8beb0714798679b638d604c16508253 Mon Sep 17 00:00:00 2001 From: Bastian <b.schubert82@gmail.com> Date: Thu, 10 Feb 2022 17:09:29 +0100 Subject: [PATCH 05/20] Update sqlx-core/src/error.rs Co-authored-by: Paolo Barbolini <paolo@paolo565.org> --- sqlx-core/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlx-core/src/error.rs b/sqlx-core/src/error.rs index 8aa47962ca..00333015b4 100644 --- a/sqlx-core/src/error.rs +++ b/sqlx-core/src/error.rs @@ -104,7 +104,7 @@ pub enum Error { #[error("{0}")] Migrate(#[source] Box<crate::migrate::MigrateError>), - /// LTree labels can only contain [A-Za-z_] + /// LTree labels can only contain [A-Za-z0-9_] #[cfg(feature = "ltree")] #[error("ltree label cotains invalid characters")] InvalidLtreeLabel, From b5ff590596c1678cafd051ed5a79fdeb7096e94d Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Thu, 10 Feb 2022 19:49:45 +0100 Subject: [PATCH 06/20] PR fixes --- sqlx-core/src/error.rs | 5 ++++ sqlx-core/src/postgres/types/ltree.rs | 40 ++++++++++++++++----------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/sqlx-core/src/error.rs b/sqlx-core/src/error.rs index 00333015b4..528ad5ae04 100644 --- a/sqlx-core/src/error.rs +++ b/sqlx-core/src/error.rs @@ -108,6 +108,11 @@ pub enum Error { #[cfg(feature = "ltree")] #[error("ltree label cotains invalid characters")] InvalidLtreeLabel, + + /// LTree version not supported + #[cfg(feature = "ltree")] + #[error("ltree version not supported")] + InvalidLtreeVersion, } impl StdError for Box<dyn DatabaseError> {} diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index 0f2bb4913e..2cf15a1927 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -1,19 +1,22 @@ -use std::fmt::{self, Display, Formatter}; -use std::str::FromStr; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; -use crate::error::{Error, BoxDynError}; -use crate::postgres::{PgArgumentBuffer, PgValueFormat, PgTypeInfo, PgValueRef, Postgres}; +use crate::error::{BoxDynError, Error}; +use crate::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use crate::types::Type; +use std::fmt::{self, Display, Formatter}; +use std::str::FromStr; #[derive(Clone, Debug, Default, PartialEq)] pub struct PgLTree { - labels: Vec<String> + labels: Vec<String>, } impl PgLTree { pub fn push(&mut self, label: String) -> Result<(), Error> { - if label.chars().all(|c| c.is_ascii_alphabetic() || c.is_ascii_digit() || c == '_') { + if label + .chars() + .all(|c| c.is_ascii_alphabetic() || c.is_ascii_digit() || c == '_') + { self.labels.push(label); Ok(()) } else { @@ -26,17 +29,22 @@ impl FromStr for PgLTree { type Err = Error; fn from_str(s: &str) -> Result<Self, Error> { - Ok( - Self { - labels: s.split('.').map(|s| s.to_owned()).collect() - } - ) + Ok(Self { + labels: s.split('.').map(|s| s.to_owned()).collect(), + }) } } impl Display for PgLTree { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.labels.join(".")) + let mut iter = self.labels.iter(); + if let Some(label) = iter.next() { + write!(f, "{}", label)?; + while let Some(label) = iter.next() { + write!(f, ".{}", label)?; + } + } + Ok(()) } } @@ -62,11 +70,11 @@ impl<'r> Decode<'r, Postgres> for PgLTree { let bytes = value.as_bytes()?; let version = i8::from_le_bytes([bytes[0]; 1]); if version != 1 { - todo!("add error here") + return Err(Box::new(Error::InvalidLtreeVersion)); } - Ok(Self::from_str(&String::from_utf8(bytes[1..].to_vec())?)?) - }, - PgValueFormat::Text => Ok(Self::from_str(value.as_str()?)?) + Ok(Self::from_str(std::str::from_utf8(&bytes[1..])?)?) + } + PgValueFormat::Text => Ok(Self::from_str(value.as_str()?)?), } } } From 95bf07b6f612fa6357b4e6d3bbd61eb17e60d345 Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Fri, 11 Feb 2022 08:55:33 +0100 Subject: [PATCH 07/20] ltree with name instead of OID --- sqlx-core/src/postgres/type_info.rs | 12 ------------ sqlx-core/src/postgres/types/ltree.rs | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/sqlx-core/src/postgres/type_info.rs b/sqlx-core/src/postgres/type_info.rs index d578a8af94..97d5efa0cc 100644 --- a/sqlx-core/src/postgres/type_info.rs +++ b/sqlx-core/src/postgres/type_info.rs @@ -116,7 +116,6 @@ pub enum PgType { JsonpathArray, Money, MoneyArray, - Ltree, // https://www.postgresql.org/docs/9.3/datatype-pseudo.html Void, @@ -329,7 +328,6 @@ impl PgType { 3927 => PgType::Int8RangeArray, 4072 => PgType::Jsonpath, 4073 => PgType::JsonpathArray, - 16394 => PgType::Ltree, _ => { return None; @@ -438,7 +436,6 @@ impl PgType { PgType::Int8RangeArray => 3927, PgType::Jsonpath => 4072, PgType::JsonpathArray => 4073, - PgType::Ltree => 16394, PgType::Custom(ty) => ty.oid, PgType::DeclareWithOid(oid) => *oid, @@ -541,7 +538,6 @@ impl PgType { PgType::JsonpathArray => "JSONPATH[]", PgType::Money => "MONEY", PgType::MoneyArray => "MONEY[]", - PgType::Ltree => "LTREE", PgType::Void => "VOID", PgType::Custom(ty) => &*ty.name, PgType::DeclareWithOid(_) => "?", @@ -642,7 +638,6 @@ impl PgType { PgType::JsonpathArray => "_jsonpath", PgType::Money => "money", PgType::MoneyArray => "_money", - PgType::Ltree => "ltree", PgType::Void => "void", PgType::Custom(ty) => &*ty.name, PgType::DeclareWithOid(_) => "?", @@ -743,7 +738,6 @@ impl PgType { PgType::JsonpathArray => &PgTypeKind::Array(PgTypeInfo(PgType::Jsonpath)), PgType::Money => &PgTypeKind::Simple, PgType::MoneyArray => &PgTypeKind::Array(PgTypeInfo(PgType::Money)), - PgType::Ltree => &PgTypeKind::Simple, PgType::Void => &PgTypeKind::Pseudo, @@ -855,7 +849,6 @@ impl PgType { PgType::Int8RangeArray => Some(Cow::Owned(PgTypeInfo(PgType::Int8Range))), PgType::Jsonpath => None, PgType::JsonpathArray => Some(Cow::Owned(PgTypeInfo(PgType::Jsonpath))), - PgType::Ltree => None, // There is no `UnknownArray` PgType::Unknown => None, // There is no `VoidArray` @@ -1108,11 +1101,6 @@ impl PgTypeInfo { pub(crate) const INT8_RANGE: Self = Self(PgType::Int8Range); pub(crate) const INT8_RANGE_ARRAY: Self = Self(PgType::Int8RangeArray); - // - // ltree - - pub(crate) const LTREE: Self = Self(PgType::Ltree); - // // pseudo types // https://www.postgresql.org/docs/9.3/datatype-pseudo.html diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index 2cf15a1927..5d086e0d34 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -50,7 +50,7 @@ impl Display for PgLTree { impl Type<Postgres> for PgLTree { fn type_info() -> PgTypeInfo { - PgTypeInfo::LTREE + PgTypeInfo::with_name("ltree") } } From 4f19c9b816e34c7dc46be03117a1caf495cd6467 Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Fri, 11 Feb 2022 09:02:49 +0100 Subject: [PATCH 08/20] custom ltree errors --- sqlx-core/src/error.rs | 10 ---------- sqlx-core/src/postgres/types/ltree.rs | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/sqlx-core/src/error.rs b/sqlx-core/src/error.rs index 528ad5ae04..0659375846 100644 --- a/sqlx-core/src/error.rs +++ b/sqlx-core/src/error.rs @@ -103,16 +103,6 @@ pub enum Error { #[cfg(feature = "migrate")] #[error("{0}")] Migrate(#[source] Box<crate::migrate::MigrateError>), - - /// LTree labels can only contain [A-Za-z0-9_] - #[cfg(feature = "ltree")] - #[error("ltree label cotains invalid characters")] - InvalidLtreeLabel, - - /// LTree version not supported - #[cfg(feature = "ltree")] - #[error("ltree version not supported")] - InvalidLtreeVersion, } impl StdError for Box<dyn DatabaseError> {} diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index 5d086e0d34..b344b41d84 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -1,11 +1,28 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; -use crate::error::{BoxDynError, Error}; +use crate::error::BoxDynError; use crate::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use crate::types::Type; use std::fmt::{self, Display, Formatter}; use std::str::FromStr; + +/// Represents ltree specific errors +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + /// LTree labels can only contain [A-Za-z0-9_] + #[cfg(feature = "ltree")] + #[error("ltree label cotains invalid characters")] + InvalidLtreeLabel, + + /// LTree version not supported + #[cfg(feature = "ltree")] + #[error("ltree version not supported")] + InvalidLtreeVersion, +} + + #[derive(Clone, Debug, Default, PartialEq)] pub struct PgLTree { labels: Vec<String>, From b611e5f75cd8326db7bf99ae28dce97d95f65fd5 Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Fri, 11 Feb 2022 09:15:48 +0100 Subject: [PATCH 09/20] add pop ot PgLTree --- sqlx-core/src/postgres/types/ltree.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index b344b41d84..683482074d 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -40,6 +40,10 @@ impl PgLTree { Err(Error::InvalidLtreeLabel) } } + + pub fn pop(&mut self) -> Option<String> { + self.labels.pop() + } } impl FromStr for PgLTree { From b9bced314b85bcad1bdc0670fd9a8ed80dddd0c5 Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Fri, 11 Feb 2022 09:25:31 +0100 Subject: [PATCH 10/20] do not hide ltree behind feature flag --- Cargo.toml | 1 - sqlx-core/Cargo.toml | 1 - sqlx-core/src/postgres/types/ltree.rs | 3 +-- sqlx-core/src/postgres/types/mod.rs | 6 +----- sqlx-macros/Cargo.toml | 1 - sqlx-macros/src/database/postgres.rs | 5 ++--- 6 files changed, 4 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7610f72867..7a2ea8ae28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,7 +130,6 @@ time = ["sqlx-core/time", "sqlx-macros/time"] bit-vec = ["sqlx-core/bit-vec", "sqlx-macros/bit-vec"] bstr = ["sqlx-core/bstr"] git2 = ["sqlx-core/git2"] -ltree = ["sqlx-core/ltree", "sqlx-macros/ltree"] [dependencies] sqlx-core = { version = "0.5.10", path = "sqlx-core", default-features = false } diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index febffe6da9..e8912206fa 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -62,7 +62,6 @@ all-types = [ bigdecimal = ["bigdecimal_", "num-bigint"] decimal = ["rust_decimal", "num-bigint"] json = ["serde", "serde_json"] -ltree = ["postgres"] # runtimes runtime-actix-native-tls = [ diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index 683482074d..37c6b4dd5f 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -12,17 +12,16 @@ use std::str::FromStr; #[non_exhaustive] pub enum Error { /// LTree labels can only contain [A-Za-z0-9_] - #[cfg(feature = "ltree")] #[error("ltree label cotains invalid characters")] InvalidLtreeLabel, /// LTree version not supported - #[cfg(feature = "ltree")] #[error("ltree version not supported")] InvalidLtreeVersion, } +/// Represents an postgres ltree. Not that this is an EXTENSION! #[derive(Clone, Debug, Default, PartialEq)] pub struct PgLTree { labels: Vec<String>, diff --git a/sqlx-core/src/postgres/types/mod.rs b/sqlx-core/src/postgres/types/mod.rs index 2aebcdd06a..8c40360c78 100644 --- a/sqlx-core/src/postgres/types/mod.rs +++ b/sqlx-core/src/postgres/types/mod.rs @@ -174,6 +174,7 @@ mod record; mod str; mod tuple; mod void; +mod ltree; #[cfg(any(feature = "chrono", feature = "time"))] mod time_tz; @@ -208,12 +209,7 @@ mod mac_address; #[cfg(feature = "bit-vec")] mod bit_vec; -#[cfg(feature = "ltree")] -mod ltree; - -#[cfg(feature = "ltree")] pub use ltree::PgLTree; - pub use array::PgHasArrayType; pub use interval::PgInterval; pub use money::PgMoney; diff --git a/sqlx-macros/Cargo.toml b/sqlx-macros/Cargo.toml index 52f849cb5a..b036aa2f46 100644 --- a/sqlx-macros/Cargo.toml +++ b/sqlx-macros/Cargo.toml @@ -76,7 +76,6 @@ mac_address = ["sqlx-core/mac_address"] uuid = ["sqlx-core/uuid"] bit-vec = ["sqlx-core/bit-vec"] json = ["sqlx-core/json", "serde_json"] -ltree = ["sqlx-core/ltree"] [dependencies] dotenv = { version = "0.15.0", default-features = false } diff --git a/sqlx-macros/src/database/postgres.rs b/sqlx-macros/src/database/postgres.rs index e3a554ecaf..3d51641d34 100644 --- a/sqlx-macros/src/database/postgres.rs +++ b/sqlx-macros/src/database/postgres.rs @@ -18,6 +18,8 @@ impl_database_ext! { sqlx::postgres::types::PgMoney, + sqlx::postgres::types::PgLTree, + #[cfg(feature = "uuid")] sqlx::types::Uuid, @@ -69,9 +71,6 @@ impl_database_ext! { #[cfg(feature = "bit-vec")] sqlx::types::BitVec, - #[cfg(feature = "ltree")] - sqlx::postgres::types::PgLTree, - // Arrays Vec<bool> | &[bool], From 6bb8b26e58bc34e7c98dbcd73f742707907e33da Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Fri, 11 Feb 2022 10:26:25 +0100 Subject: [PATCH 11/20] bytes() instead of chars() --- sqlx-core/src/postgres/types/ltree.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index 37c6b4dd5f..f800cf6d7a 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -30,8 +30,8 @@ pub struct PgLTree { impl PgLTree { pub fn push(&mut self, label: String) -> Result<(), Error> { if label - .chars() - .all(|c| c.is_ascii_alphabetic() || c.is_ascii_digit() || c == '_') + .bytes() + .all(|c| c.is_ascii_alphabetic() || c.is_ascii_digit() || c == b'_') { self.labels.push(label); Ok(()) From e12110ec4b289a0e17102973deca418fc7222d0a Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Fri, 11 Feb 2022 10:31:22 +0100 Subject: [PATCH 12/20] apply extend_display suggestion --- sqlx-core/src/postgres/arguments.rs | 19 +++++++++++++++++++ sqlx-core/src/postgres/types/ltree.rs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/sqlx-core/src/postgres/arguments.rs b/sqlx-core/src/postgres/arguments.rs index 9bd60dbbbd..83a22a99e4 100644 --- a/sqlx-core/src/postgres/arguments.rs +++ b/sqlx-core/src/postgres/arguments.rs @@ -1,4 +1,5 @@ use std::ops::{Deref, DerefMut}; +use std::fmt::{self, Write}; use crate::arguments::Arguments; use crate::encode::{Encode, IsNull}; @@ -161,6 +162,24 @@ impl PgArgumentBuffer { self.extend_from_slice(&0_u32.to_be_bytes()); self.type_holes.push((offset, type_name.clone())); } + + pub(crate) fn extend_display<T: fmt::Display>(&mut self, value: T) { + struct VecFmt<'a>(&'a mut Vec<u8>); + + impl<'a> fmt::Write for VecFmt<'a> { + fn write_char(&mut self, c: char) -> fmt::Result { + self.write_str(c.encode_utf8(&mut [0u8; 4])) + } + + fn write_str(&mut self, s: &str) -> fmt::Result { + self.0.extend_from_slice(s.as_bytes()); + Ok(()) + } + } + + write!(VecFmt(&mut self.buffer), "{}", value) + .expect("Display implementation panicked while writing to PgArgumentBuffer"); + } } impl Deref for PgArgumentBuffer { diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index f800cf6d7a..5c0b643972 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -77,7 +77,7 @@ impl Type<Postgres> for PgLTree { impl Encode<'_, Postgres> for PgLTree { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { buf.extend(1i8.to_le_bytes()); - buf.extend(self.to_string().as_bytes()); + buf.extend_display(self); IsNull::No } From 1bb6d9928e4c02b6c23e9dd500f5ded8938b283f Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Fri, 11 Feb 2022 10:33:44 +0100 Subject: [PATCH 13/20] add more functions to PgLTree --- sqlx-core/src/postgres/arguments.rs | 2 +- sqlx-core/src/postgres/types/ltree.rs | 37 ++++++++++++++++++++++++--- sqlx-core/src/postgres/types/mod.rs | 4 +-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/sqlx-core/src/postgres/arguments.rs b/sqlx-core/src/postgres/arguments.rs index 83a22a99e4..b8ba8bbf42 100644 --- a/sqlx-core/src/postgres/arguments.rs +++ b/sqlx-core/src/postgres/arguments.rs @@ -1,5 +1,5 @@ -use std::ops::{Deref, DerefMut}; use std::fmt::{self, Write}; +use std::ops::{Deref, DerefMut}; use crate::arguments::Arguments; use crate::encode::{Encode, IsNull}; diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index 5c0b643972..f8fad397c3 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -4,14 +4,14 @@ use crate::error::BoxDynError; use crate::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use crate::types::Type; use std::fmt::{self, Display, Formatter}; +use std::iter::FromIterator; use std::str::FromStr; - /// Represents ltree specific errors #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum Error { - /// LTree labels can only contain [A-Za-z0-9_] + /// LTree labels can only contain [A-Za-z0-9_] #[error("ltree label cotains invalid characters")] InvalidLtreeLabel, @@ -20,7 +20,6 @@ pub enum Error { InvalidLtreeVersion, } - /// Represents an postgres ltree. Not that this is an EXTENSION! #[derive(Clone, Debug, Default, PartialEq)] pub struct PgLTree { @@ -28,6 +27,28 @@ pub struct PgLTree { } impl PgLTree { + /// creates default/empty ltree + pub fn new() -> Self { + Self::default() + } + + /// creates ltree from a [Vec<String>] without checking labels + pub fn new_unchecked(labels: Vec<String>) -> Self { + Self { labels } + } + + /// creates ltree from an iterator with checking labels + pub fn from_iter( + labels: impl IntoIterator<Item = String, IntoIter = std::vec::IntoIter<String>>, + ) -> Result<Self, Error> { + let mut ltree = Self::default(); + for label in labels { + ltree.push(label)?; + } + Ok(ltree) + } + + /// push a label to ltree pub fn push(&mut self, label: String) -> Result<(), Error> { if label .bytes() @@ -40,11 +61,21 @@ impl PgLTree { } } + /// pop a label from ltree pub fn pop(&mut self) -> Option<String> { self.labels.pop() } } +impl IntoIterator for PgLTree { + type Item = String; + type IntoIter = std::vec::IntoIter<Self::Item>; + + fn into_iter(self) -> Self::IntoIter { + self.labels.into_iter() + } +} + impl FromStr for PgLTree { type Err = Error; diff --git a/sqlx-core/src/postgres/types/mod.rs b/sqlx-core/src/postgres/types/mod.rs index 8c40360c78..c67658e043 100644 --- a/sqlx-core/src/postgres/types/mod.rs +++ b/sqlx-core/src/postgres/types/mod.rs @@ -168,13 +168,13 @@ mod bytes; mod float; mod int; mod interval; +mod ltree; mod money; mod range; mod record; mod str; mod tuple; mod void; -mod ltree; #[cfg(any(feature = "chrono", feature = "time"))] mod time_tz; @@ -209,9 +209,9 @@ mod mac_address; #[cfg(feature = "bit-vec")] mod bit_vec; -pub use ltree::PgLTree; pub use array::PgHasArrayType; pub use interval::PgInterval; +pub use ltree::PgLTree; pub use money::PgMoney; pub use range::PgRange; From cd29037072ef46c04a0c1308d58f5b7c5905a536 Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Fri, 11 Feb 2022 11:13:58 +0100 Subject: [PATCH 14/20] fix IntoIter --- sqlx-core/src/postgres/types/ltree.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index f8fad397c3..6510a922e1 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -4,7 +4,6 @@ use crate::error::BoxDynError; use crate::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use crate::types::Type; use std::fmt::{self, Display, Formatter}; -use std::iter::FromIterator; use std::str::FromStr; /// Represents ltree specific errors @@ -39,10 +38,10 @@ impl PgLTree { /// creates ltree from an iterator with checking labels pub fn from_iter( - labels: impl IntoIterator<Item = String, IntoIter = std::vec::IntoIter<String>>, + labels: impl IntoIterator<Item = String>, ) -> Result<Self, Error> { let mut ltree = Self::default(); - for label in labels { + for label in labels.into_iter() { ltree.push(label)?; } Ok(ltree) From e83d38016ebd1e2452c0cf5e44ee07e921ee0383 Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Sat, 12 Feb 2022 07:19:54 +0100 Subject: [PATCH 15/20] resolve PR annotation --- sqlx-core/src/postgres/arguments.rs | 5 ++- sqlx-core/src/postgres/types/ltree.rs | 54 +++++++++++++++++++-------- sqlx-core/src/postgres/types/mod.rs | 1 + 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/sqlx-core/src/postgres/arguments.rs b/sqlx-core/src/postgres/arguments.rs index b8ba8bbf42..c94d4c45fe 100644 --- a/sqlx-core/src/postgres/arguments.rs +++ b/sqlx-core/src/postgres/arguments.rs @@ -1,4 +1,5 @@ -use std::fmt::{self, Write}; +use std::fmt; +use std::io::Write; use std::ops::{Deref, DerefMut}; use crate::arguments::Arguments; @@ -177,7 +178,7 @@ impl PgArgumentBuffer { } } - write!(VecFmt(&mut self.buffer), "{}", value) + write!(self.buffer, "{}", value) .expect("Display implementation panicked while writing to PgArgumentBuffer"); } } diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index 6510a922e1..6e2a512b0a 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -4,12 +4,13 @@ use crate::error::BoxDynError; use crate::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use crate::types::Type; use std::fmt::{self, Display, Formatter}; +use std::ops::Deref; use std::str::FromStr; /// Represents ltree specific errors #[derive(Debug, thiserror::Error)] #[non_exhaustive] -pub enum Error { +pub enum PgLTreeParseError { /// LTree labels can only contain [A-Za-z0-9_] #[error("ltree label cotains invalid characters")] InvalidLtreeLabel, @@ -19,7 +20,16 @@ pub enum Error { InvalidLtreeVersion, } -/// Represents an postgres ltree. Not that this is an EXTENSION! +/// Container for a Label Tree (`ltree`) in Postgres. +/// +/// See https://www.postgresql.org/docs/current/ltree.html +/// +/// ### Note: Extension Required +/// The `ltree` extension is not enabled by default in Postgres. You will need to do so explicitly: +/// +/// ```ignore +/// CREATE EXTENSION IF NOT EXISTS "ltree"; +/// ``` #[derive(Clone, Debug, Default, PartialEq)] pub struct PgLTree { labels: Vec<String>, @@ -37,26 +47,29 @@ impl PgLTree { } /// creates ltree from an iterator with checking labels - pub fn from_iter( - labels: impl IntoIterator<Item = String>, - ) -> Result<Self, Error> { + pub fn from_iter<I, S>(labels: I) -> Result<Self, PgLTreeParseError> + where + S: Into<String>, + I: IntoIterator<Item = S>, + { let mut ltree = Self::default(); - for label in labels.into_iter() { - ltree.push(label)?; + for label in labels { + ltree.push(label.into())?; } Ok(ltree) } /// push a label to ltree - pub fn push(&mut self, label: String) -> Result<(), Error> { - if label - .bytes() - .all(|c| c.is_ascii_alphabetic() || c.is_ascii_digit() || c == b'_') + pub fn push(&mut self, label: String) -> Result<(), PgLTreeParseError> { + if label.len() <= 256 + && label + .bytes() + .all(|c| c.is_ascii_alphabetic() || c.is_ascii_digit() || c == b'_') { self.labels.push(label); Ok(()) } else { - Err(Error::InvalidLtreeLabel) + Err(PgLTreeParseError::InvalidLtreeLabel) } } @@ -76,9 +89,9 @@ impl IntoIterator for PgLTree { } impl FromStr for PgLTree { - type Err = Error; + type Err = PgLTreeParseError; - fn from_str(s: &str) -> Result<Self, Error> { + fn from_str(s: &str) -> Result<Self, Self::Err> { Ok(Self { labels: s.split('.').map(|s| s.to_owned()).collect(), }) @@ -90,7 +103,7 @@ impl Display for PgLTree { let mut iter = self.labels.iter(); if let Some(label) = iter.next() { write!(f, "{}", label)?; - while let Some(label) = iter.next() { + for label in iter { write!(f, ".{}", label)?; } } @@ -98,8 +111,17 @@ impl Display for PgLTree { } } +impl Deref for PgLTree { + type Target = [String]; + + fn deref(&self) -> &Self::Target { + &self.labels + } +} + impl Type<Postgres> for PgLTree { fn type_info() -> PgTypeInfo { + // Since `ltree` is enabled by an extension, it does not have a stable OID. PgTypeInfo::with_name("ltree") } } @@ -120,7 +142,7 @@ impl<'r> Decode<'r, Postgres> for PgLTree { let bytes = value.as_bytes()?; let version = i8::from_le_bytes([bytes[0]; 1]); if version != 1 { - return Err(Box::new(Error::InvalidLtreeVersion)); + return Err(Box::new(PgLTreeParseError::InvalidLtreeVersion)); } Ok(Self::from_str(std::str::from_utf8(&bytes[1..])?)?) } diff --git a/sqlx-core/src/postgres/types/mod.rs b/sqlx-core/src/postgres/types/mod.rs index c67658e043..26524f85d9 100644 --- a/sqlx-core/src/postgres/types/mod.rs +++ b/sqlx-core/src/postgres/types/mod.rs @@ -212,6 +212,7 @@ mod bit_vec; pub use array::PgHasArrayType; pub use interval::PgInterval; pub use ltree::PgLTree; +pub use ltree::PgLTreeParseError; pub use money::PgMoney; pub use range::PgRange; From 25d76349c134fced74cd52eedcb95442613e40de Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Sat, 12 Feb 2022 07:44:27 +0100 Subject: [PATCH 16/20] add tests --- sqlx-core/src/postgres/types/ltree.rs | 10 +++++++++- tests/postgres/setup.sql | 3 +++ tests/postgres/types.rs | 16 ++++++++++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index 6e2a512b0a..96f7de4f97 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -1,7 +1,9 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; +use crate::postgres::{ + PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, +}; use crate::types::Type; use std::fmt::{self, Display, Formatter}; use std::ops::Deref; @@ -126,6 +128,12 @@ impl Type<Postgres> for PgLTree { } } +impl PgHasArrayType for PgLTree { + fn array_type_info() -> PgTypeInfo { + PgTypeInfo::with_name("ltree[]") + } +} + impl Encode<'_, Postgres> for PgLTree { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { buf.extend(1i8.to_le_bytes()); diff --git a/tests/postgres/setup.sql b/tests/postgres/setup.sql index 9818d139ba..d0abd16612 100644 --- a/tests/postgres/setup.sql +++ b/tests/postgres/setup.sql @@ -1,3 +1,6 @@ +-- https://www.postgresql.org/docs/current/ltree.html +CREATE EXTENSION IF NOT EXISTS 'ltree'; + -- https://www.postgresql.org/docs/current/sql-createtype.html CREATE TYPE status AS ENUM ('new', 'open', 'closed'); diff --git a/tests/postgres/types.rs b/tests/postgres/types.rs index 0b58421448..003d94cac5 100644 --- a/tests/postgres/types.rs +++ b/tests/postgres/types.rs @@ -1,12 +1,11 @@ extern crate time_ as time; use std::ops::Bound; -#[cfg(feature = "decimal")] -use std::str::FromStr; use sqlx::postgres::types::{PgInterval, PgMoney, PgRange}; use sqlx::postgres::Postgres; use sqlx_test::{test_decode_type, test_prepared_type, test_type}; +use std::str::FromStr; test_type!(null<Option<i16>>(Postgres, "NULL::int2" == None::<i16> @@ -513,3 +512,16 @@ test_prepared_type!(money<PgMoney>(Postgres, "123.45::money" == PgMoney(12345))) test_prepared_type!(money_vec<Vec<PgMoney>>(Postgres, "array[123.45,420.00,666.66]::money[]" == vec![PgMoney(12345), PgMoney(42000), PgMoney(66666)], )); + +test_type!(ltree<sqlx::postgres::types::PgLTree>(Postgres, + "'Foo.Bar.Baz.Quux'::ltree" == sqlx::postgres::types::PgLTree::from_str("Foo.Bar.Baz.Quux").unwrap(), + "'Alpha.Beta.Delta.Gamma'::ltree" == sqlx::postgres::types::PgLTree::from_iter(["Alpha", "Beta", "Delta", "Gamma"]).unwrap(), +)); + +test_type!(ltree_vec<Vec<sqlx::postgres::types::PgLTree>>(Postgres, + "['Foo.Bar.Baz.Quux', 'Alpha.Beta.Delta.Gamma']::ltree[]" == + vec![ + sqlx::postgres::types::PgLTree::from_str("Foo.Bar.Baz.Quux").unwrap(), + sqlx::postgres::types::PgLTree::from_iter(["Alpha", "Beta", "Delta", "Gamma"]).unwrap() + ] +)); From 75fe431b43b76b1e540197d68562915fb94c9ce3 Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Sat, 12 Feb 2022 09:13:09 +0100 Subject: [PATCH 17/20] remove code from arguments --- sqlx-core/src/postgres/arguments.rs | 20 -------------------- sqlx-core/src/postgres/types/ltree.rs | 4 +++- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/sqlx-core/src/postgres/arguments.rs b/sqlx-core/src/postgres/arguments.rs index c94d4c45fe..9bd60dbbbd 100644 --- a/sqlx-core/src/postgres/arguments.rs +++ b/sqlx-core/src/postgres/arguments.rs @@ -1,5 +1,3 @@ -use std::fmt; -use std::io::Write; use std::ops::{Deref, DerefMut}; use crate::arguments::Arguments; @@ -163,24 +161,6 @@ impl PgArgumentBuffer { self.extend_from_slice(&0_u32.to_be_bytes()); self.type_holes.push((offset, type_name.clone())); } - - pub(crate) fn extend_display<T: fmt::Display>(&mut self, value: T) { - struct VecFmt<'a>(&'a mut Vec<u8>); - - impl<'a> fmt::Write for VecFmt<'a> { - fn write_char(&mut self, c: char) -> fmt::Result { - self.write_str(c.encode_utf8(&mut [0u8; 4])) - } - - fn write_str(&mut self, s: &str) -> fmt::Result { - self.0.extend_from_slice(s.as_bytes()); - Ok(()) - } - } - - write!(self.buffer, "{}", value) - .expect("Display implementation panicked while writing to PgArgumentBuffer"); - } } impl Deref for PgArgumentBuffer { diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index 96f7de4f97..f864aba861 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -6,6 +6,7 @@ use crate::postgres::{ }; use crate::types::Type; use std::fmt::{self, Display, Formatter}; +use std::io::Write; use std::ops::Deref; use std::str::FromStr; @@ -137,7 +138,8 @@ impl PgHasArrayType for PgLTree { impl Encode<'_, Postgres> for PgLTree { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { buf.extend(1i8.to_le_bytes()); - buf.extend_display(self); + write!(buf, "{}", self) + .expect("Display implementation panicked while writing to PgArgumentBuffer"); IsNull::No } From b5c8def72dbef5fd4eafedf7cedb3bc2174a9f42 Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Sat, 12 Feb 2022 09:27:20 +0100 Subject: [PATCH 18/20] fix array --- sqlx-core/src/postgres/types/ltree.rs | 2 +- sqlx-test/src/lib.rs | 1 + tests/postgres/types.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index f864aba861..d3e525eb1f 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -131,7 +131,7 @@ impl Type<Postgres> for PgLTree { impl PgHasArrayType for PgLTree { fn array_type_info() -> PgTypeInfo { - PgTypeInfo::with_name("ltree[]") + PgTypeInfo::with_name("_ltree") } } diff --git a/sqlx-test/src/lib.rs b/sqlx-test/src/lib.rs index 052e157271..4efa9d298c 100644 --- a/sqlx-test/src/lib.rs +++ b/sqlx-test/src/lib.rs @@ -158,6 +158,7 @@ macro_rules! __test_prepared_type { $( let query = format!($sql, $text); + println!("{query}"); let row = sqlx::query(&query) .bind($value) diff --git a/tests/postgres/types.rs b/tests/postgres/types.rs index 003d94cac5..e9dd35698e 100644 --- a/tests/postgres/types.rs +++ b/tests/postgres/types.rs @@ -519,7 +519,7 @@ test_type!(ltree<sqlx::postgres::types::PgLTree>(Postgres, )); test_type!(ltree_vec<Vec<sqlx::postgres::types::PgLTree>>(Postgres, - "['Foo.Bar.Baz.Quux', 'Alpha.Beta.Delta.Gamma']::ltree[]" == + "array['Foo.Bar.Baz.Quux', 'Alpha.Beta.Delta.Gamma']::ltree[]" == vec![ sqlx::postgres::types::PgLTree::from_str("Foo.Bar.Baz.Quux").unwrap(), sqlx::postgres::types::PgLTree::from_iter(["Alpha", "Beta", "Delta", "Gamma"]).unwrap() From 580148b994ee46cd2b7832fd501320599eff6495 Mon Sep 17 00:00:00 2001 From: Bastian Schubert <bastian.schubert@crosscard.com> Date: Mon, 14 Feb 2022 11:30:31 +0100 Subject: [PATCH 19/20] fix setup isse --- tests/postgres/setup.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/postgres/setup.sql b/tests/postgres/setup.sql index d0abd16612..1a42d0b899 100644 --- a/tests/postgres/setup.sql +++ b/tests/postgres/setup.sql @@ -1,5 +1,5 @@ -- https://www.postgresql.org/docs/current/ltree.html -CREATE EXTENSION IF NOT EXISTS 'ltree'; +CREATE EXTENSION IF NOT EXISTS ltree; -- https://www.postgresql.org/docs/current/sql-createtype.html CREATE TYPE status AS ENUM ('new', 'open', 'closed'); From 74d075304625c68730a3a1baba0ef3ed0e0d1ffe Mon Sep 17 00:00:00 2001 From: Austin Bonander <austin@launchbadge.com> Date: Tue, 15 Feb 2022 20:06:32 -0800 Subject: [PATCH 20/20] fix(postgres): disable `ltree` tests on Postgres 9.6 --- .github/workflows/sqlx.yml | 10 ++++++++++ sqlx-core/src/postgres/types/ltree.rs | 10 ++++++++++ tests/postgres/types.rs | 6 ++++++ 3 files changed, 26 insertions(+) diff --git a/.github/workflows/sqlx.yml b/.github/workflows/sqlx.yml index 44964b509b..f325cb7c3f 100644 --- a/.github/workflows/sqlx.yml +++ b/.github/workflows/sqlx.yml @@ -208,6 +208,10 @@ jobs: key: ${{ runner.os }}-postgres-${{ matrix.runtime }}-${{ matrix.tls }}-${{ hashFiles('**/Cargo.lock') }} - uses: actions-rs/cargo@v1 + env: + # FIXME: needed to disable `ltree` tests in Postgres 9.6 + # but `PgLTree` should just fall back to text format + RUSTFLAGS: --cfg postgres_${{ matrix.postgres }} with: command: build args: > @@ -225,6 +229,9 @@ jobs: --features any,postgres,macros,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }} env: DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx + # FIXME: needed to disable `ltree` tests in Postgres 9.6 + # but `PgLTree` should just fall back to text format + RUSTFLAGS: --cfg postgres_${{ matrix.postgres }} - uses: actions-rs/cargo@v1 with: @@ -234,6 +241,9 @@ jobs: --features any,postgres,macros,migrate,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }} env: DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt + # FIXME: needed to disable `ltree` tests in Postgres 9.6 + # but `PgLTree` should just fall back to text format + RUSTFLAGS: --cfg postgres_${{ matrix.postgres }} mysql: name: MySQL diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-core/src/postgres/types/ltree.rs index d3e525eb1f..23173f528e 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-core/src/postgres/types/ltree.rs @@ -27,6 +27,16 @@ pub enum PgLTreeParseError { /// /// See https://www.postgresql.org/docs/current/ltree.html /// +/// ### Note: Requires Postgres 13+ +/// +/// This integration requires that the `ltree` type support the binary format in the Postgres +/// wire protocol, which only became available in Postgres 13. +/// ([Postgres 13.0 Release Notes, Additional Modules][https://www.postgresql.org/docs/13/release-13.html#id-1.11.6.11.5.14]) +/// +/// Ideally, SQLx's Postgres driver should support falling back to text format for types +/// which don't have `typsend` and `typrecv` entries in `pg_type`, but that work still needs +/// to be done. +/// /// ### Note: Extension Required /// The `ltree` extension is not enabled by default in Postgres. You will need to do so explicitly: /// diff --git a/tests/postgres/types.rs b/tests/postgres/types.rs index e9dd35698e..21ebbe6120 100644 --- a/tests/postgres/types.rs +++ b/tests/postgres/types.rs @@ -513,11 +513,17 @@ test_prepared_type!(money_vec<Vec<PgMoney>>(Postgres, "array[123.45,420.00,666.66]::money[]" == vec![PgMoney(12345), PgMoney(42000), PgMoney(66666)], )); +// FIXME: needed to disable `ltree` tests in Postgres 9.6 +// but `PgLTree` should just fall back to text format +#[cfg(postgres_14)] test_type!(ltree<sqlx::postgres::types::PgLTree>(Postgres, "'Foo.Bar.Baz.Quux'::ltree" == sqlx::postgres::types::PgLTree::from_str("Foo.Bar.Baz.Quux").unwrap(), "'Alpha.Beta.Delta.Gamma'::ltree" == sqlx::postgres::types::PgLTree::from_iter(["Alpha", "Beta", "Delta", "Gamma"]).unwrap(), )); +// FIXME: needed to disable `ltree` tests in Postgres 9.6 +// but `PgLTree` should just fall back to text format +#[cfg(postgres_14)] test_type!(ltree_vec<Vec<sqlx::postgres::types::PgLTree>>(Postgres, "array['Foo.Bar.Baz.Quux', 'Alpha.Beta.Delta.Gamma']::ltree[]" == vec![