Skip to content

Commit

Permalink
feat(citext): implement citext for postgres
Browse files Browse the repository at this point in the history
  • Loading branch information
hgranthorner committed May 14, 2023
1 parent 253d8c9 commit 133c251
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 2 deletions.
2 changes: 2 additions & 0 deletions sqlx-postgres/src/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use sqlx_core::connection::Connection;
use sqlx_core::database::Database;
use sqlx_core::describe::Describe;
use sqlx_core::executor::Executor;
use sqlx_core::ext::ustr::UStr;
use sqlx_core::transaction::TransactionManager;

sqlx_core::declare_driver_with_optional_migrate!(DRIVER = Postgres);
Expand Down Expand Up @@ -179,6 +180,7 @@ impl<'a> TryFrom<&'a PgTypeInfo> for AnyTypeInfo {
PgType::Float8 => AnyTypeInfoKind::Double,
PgType::Bytea => AnyTypeInfoKind::Blob,
PgType::Text => AnyTypeInfoKind::Text,
PgType::DeclareWithName(UStr::Static("citext")) => AnyTypeInfoKind::Text,
_ => {
return Err(sqlx_core::Error::AnyDriverError(
format!(
Expand Down
2 changes: 2 additions & 0 deletions sqlx-postgres/src/type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ impl PgType {
PgType::Int8RangeArray => Oid(3927),
PgType::Jsonpath => Oid(4072),
PgType::JsonpathArray => Oid(4073),

PgType::Custom(ty) => ty.oid,

PgType::DeclareWithOid(oid) => *oid,
Expand Down Expand Up @@ -855,6 +856,7 @@ impl PgType {
PgType::Unknown => None,
// There is no `VoidArray`
PgType::Void => None,

PgType::Custom(ty) => match &ty.kind {
PgTypeKind::Simple => None,
PgTypeKind::Pseudo => None,
Expand Down
91 changes: 91 additions & 0 deletions sqlx-postgres/src/types/citext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use crate::types::array_compatible;
use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres};
use sqlx_core::decode::Decode;
use sqlx_core::encode::{Encode, IsNull};
use sqlx_core::error::BoxDynError;
use sqlx_core::types::Type;
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::ops::Deref;
use std::str::FromStr;

/// Text type for case insensitive searching in Postgres.
///
/// See https://www.postgresql.org/docs/current/citext.html
///
/// ### Note: Extension Required
/// The `citext` extension is not enabled by default in Postgres. You will need to do so explicitly:
///
/// ```ignore
/// CREATE EXTENSION IF NOT EXISTS "citext";
/// ```
#[derive(Clone, Debug, Default, PartialEq)]
pub struct PgCitext(String);

impl PgCitext {
pub fn new(s: String) -> Self {
Self(s)
}
}

impl Type<Postgres> for PgCitext {
fn type_info() -> PgTypeInfo {
// Since `ltree` is enabled by an extension, it does not have a stable OID.
PgTypeInfo::with_name("citext")
}

fn compatible(ty: &PgTypeInfo) -> bool {
<&str as Type<Postgres>>::compatible(ty)
}
}

impl Deref for PgCitext {
type Target = str;

fn deref(&self) -> &Self::Target {
self.0.as_str()
}
}

impl From<String> for PgCitext {
fn from(value: String) -> Self {
Self::new(value)
}
}

impl FromStr for PgCitext {
type Err = core::convert::Infallible;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(PgCitext(s.parse()?))
}
}

impl Display for PgCitext {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

impl PgHasArrayType for PgCitext {
fn array_type_info() -> PgTypeInfo {
PgTypeInfo::with_name("_citext")
}

fn array_compatible(ty: &PgTypeInfo) -> bool {
array_compatible::<&str>(ty)
}
}

impl Encode<'_, Postgres> for PgCitext {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
<&str as Encode<Postgres>>::encode(&**self, buf)
}
}

impl Decode<'_, Postgres> for PgCitext {
fn decode(value: PgValueRef<'_>) -> Result<Self, BoxDynError> {
Ok(PgCitext(value.as_str()?.to_owned()))
}
}
2 changes: 2 additions & 0 deletions sqlx-postgres/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ pub(crate) use sqlx_core::types::{Json, Type};
mod array;
mod bool;
mod bytes;
mod citext;
mod float;
mod int;
mod interval;
Expand Down Expand Up @@ -224,6 +225,7 @@ mod mac_address;
mod bit_vec;

pub use array::PgHasArrayType;
pub use citext::PgCitext;
pub use interval::PgInterval;
pub use lquery::PgLQuery;
pub use lquery::PgLQueryLevel;
Expand Down
1 change: 1 addition & 0 deletions sqlx-postgres/src/types/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ impl Type<Postgres> for str {
PgTypeInfo::BPCHAR,
PgTypeInfo::VARCHAR,
PgTypeInfo::UNKNOWN,
PgTypeInfo::with_name("citext"),
]
.contains(ty)
}
Expand Down
12 changes: 10 additions & 2 deletions tests/postgres/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ extern crate time_ as time;

use std::ops::Bound;

use sqlx::postgres::types::{Oid, PgInterval, PgMoney, PgRange};
use sqlx::postgres::types::{Oid, PgCitext, PgInterval, PgMoney, PgRange};
use sqlx::postgres::Postgres;
use sqlx_test::{test_decode_type, test_prepared_type, test_type};

Expand Down Expand Up @@ -79,7 +79,7 @@ test_type!(string_vec<Vec<String>>(Postgres,
== vec!["", "\""],

"array['Hello, World', '', 'Goodbye']::text[]"
== vec!["Hello, World", "", "Goodbye"]
== vec!["Hello, World", "", "Goodbye"],
));

test_type!(string_array<[String; 3]>(Postgres,
Expand Down Expand Up @@ -549,6 +549,14 @@ test_prepared_type!(money_vec<Vec<PgMoney>>(Postgres,
"array[123.45,420.00,666.66]::money[]" == vec![PgMoney(12345), PgMoney(42000), PgMoney(66666)],
));

test_prepared_type!(citext_array<Vec<PgCitext>>(Postgres,
"array['one','two','three']::citext[]" == vec![
PgCitext::new("one".to_string()),
PgCitext::new("two".to_string()),
PgCitext::new("three".to_string()),
],
));

// FIXME: needed to disable `ltree` tests in version that don't have a binary format for it
// but `PgLTree` should just fall back to text format
#[cfg(any(postgres_14, postgres_15))]
Expand Down

0 comments on commit 133c251

Please sign in to comment.