Skip to content

Commit ededdcc

Browse files
committed
feat(citext): implement citext for postgres
1 parent 253d8c9 commit ededdcc

File tree

6 files changed

+110
-2
lines changed

6 files changed

+110
-2
lines changed

sqlx-postgres/src/any.rs

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use sqlx_core::connection::Connection;
1313
use sqlx_core::database::Database;
1414
use sqlx_core::describe::Describe;
1515
use sqlx_core::executor::Executor;
16+
use sqlx_core::ext::ustr::UStr;
1617
use sqlx_core::transaction::TransactionManager;
1718

1819
sqlx_core::declare_driver_with_optional_migrate!(DRIVER = Postgres);
@@ -179,6 +180,7 @@ impl<'a> TryFrom<&'a PgTypeInfo> for AnyTypeInfo {
179180
PgType::Float8 => AnyTypeInfoKind::Double,
180181
PgType::Bytea => AnyTypeInfoKind::Blob,
181182
PgType::Text => AnyTypeInfoKind::Text,
183+
PgType::DeclareWithName(UStr::Static("citext")) => AnyTypeInfoKind::Text,
182184
_ => {
183185
return Err(sqlx_core::Error::AnyDriverError(
184186
format!(

sqlx-postgres/src/type_info.rs

+2
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ impl PgType {
438438
PgType::Int8RangeArray => Oid(3927),
439439
PgType::Jsonpath => Oid(4072),
440440
PgType::JsonpathArray => Oid(4073),
441+
441442
PgType::Custom(ty) => ty.oid,
442443

443444
PgType::DeclareWithOid(oid) => *oid,
@@ -855,6 +856,7 @@ impl PgType {
855856
PgType::Unknown => None,
856857
// There is no `VoidArray`
857858
PgType::Void => None,
859+
858860
PgType::Custom(ty) => match &ty.kind {
859861
PgTypeKind::Simple => None,
860862
PgTypeKind::Pseudo => None,

sqlx-postgres/src/types/citext.rs

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use std::fmt;
2+
use std::fmt::{Debug, Display, Formatter};
3+
use std::ops::Deref;
4+
use std::str::FromStr;
5+
use sqlx_core::decode::Decode;
6+
use sqlx_core::encode::{Encode, IsNull};
7+
use sqlx_core::error::BoxDynError;
8+
use sqlx_core::types::Type;
9+
use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres};
10+
use crate::types::array_compatible;
11+
12+
/// Text type for case insensitive searching in Postgres.
13+
///
14+
/// See https://www.postgresql.org/docs/current/citext.html
15+
///
16+
/// ### Note: Extension Required
17+
/// The `citext` extension is not enabled by default in Postgres. You will need to do so explicitly:
18+
///
19+
/// ```ignore
20+
/// CREATE EXTENSION IF NOT EXISTS "citext";
21+
/// ```
22+
23+
#[derive(Clone, Debug, Default, PartialEq)]
24+
pub struct PgCitext(String);
25+
26+
impl PgCitext {
27+
pub fn new(s: String) -> Self {
28+
Self(s)
29+
}
30+
}
31+
32+
impl Type<Postgres> for PgCitext {
33+
fn type_info() -> PgTypeInfo {
34+
// Since `ltree` is enabled by an extension, it does not have a stable OID.
35+
PgTypeInfo::with_name("citext")
36+
}
37+
38+
fn compatible(ty: &PgTypeInfo) -> bool {
39+
<&str as Type<Postgres>>::compatible(ty)
40+
}
41+
}
42+
43+
impl Deref for PgCitext {
44+
type Target = str;
45+
46+
fn deref(&self) -> &Self::Target {
47+
self.0.as_str()
48+
}
49+
}
50+
51+
impl From<String> for PgCitext {
52+
fn from(value: String) -> Self {
53+
Self::new(value)
54+
}
55+
}
56+
57+
impl FromStr for PgCitext {
58+
type Err = core::convert::Infallible;
59+
60+
fn from_str(s: &str) -> Result<Self, Self::Err> {
61+
Ok(PgCitext(s.parse()?))
62+
}
63+
}
64+
65+
impl Display for PgCitext {
66+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
67+
write!(f, "{}", self.0)
68+
}
69+
}
70+
71+
impl PgHasArrayType for PgCitext {
72+
fn array_type_info() -> PgTypeInfo {
73+
PgTypeInfo::with_name("_citext")
74+
}
75+
76+
fn array_compatible(ty: &PgTypeInfo) -> bool {
77+
array_compatible::<&str>(ty)
78+
}
79+
}
80+
81+
82+
impl Encode<'_, Postgres> for PgCitext {
83+
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
84+
<&str as Encode<Postgres>>::encode(&**self, buf)
85+
}
86+
}
87+
88+
impl Decode<'_, Postgres> for PgCitext {
89+
fn decode(value: PgValueRef<'_>) -> Result<Self, BoxDynError> {
90+
Ok(PgCitext(value.as_str()?.to_owned()))
91+
}
92+
}

sqlx-postgres/src/types/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ mod int;
180180
mod interval;
181181
mod lquery;
182182
mod ltree;
183+
mod citext;
183184
// Not behind a Cargo feature because we require JSON in the driver implementation.
184185
mod json;
185186
mod money;
@@ -235,6 +236,7 @@ pub use ltree::PgLTreeParseError;
235236
pub use money::PgMoney;
236237
pub use oid::Oid;
237238
pub use range::PgRange;
239+
pub use citext::PgCitext;
238240

239241
#[cfg(any(feature = "chrono", feature = "time"))]
240242
pub use time_tz::PgTimeTz;

sqlx-postgres/src/types/str.rs

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ impl Type<Postgres> for str {
1818
PgTypeInfo::BPCHAR,
1919
PgTypeInfo::VARCHAR,
2020
PgTypeInfo::UNKNOWN,
21+
PgTypeInfo::with_name("citext")
2122
]
2223
.contains(ty)
2324
}

tests/postgres/types.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ extern crate time_ as time;
22

33
use std::ops::Bound;
44

5-
use sqlx::postgres::types::{Oid, PgInterval, PgMoney, PgRange};
5+
use sqlx::postgres::types::{Oid, PgInterval, PgMoney, PgRange, PgCitext};
66
use sqlx::postgres::Postgres;
77
use sqlx_test::{test_decode_type, test_prepared_type, test_type};
88

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

8181
"array['Hello, World', '', 'Goodbye']::text[]"
82-
== vec!["Hello, World", "", "Goodbye"]
82+
== vec!["Hello, World", "", "Goodbye"],
8383
));
8484

8585
test_type!(string_array<[String; 3]>(Postgres,
@@ -549,6 +549,15 @@ test_prepared_type!(money_vec<Vec<PgMoney>>(Postgres,
549549
"array[123.45,420.00,666.66]::money[]" == vec![PgMoney(12345), PgMoney(42000), PgMoney(66666)],
550550
));
551551

552+
test_prepared_type!(citext_array<Vec<PgCitext>>(Postgres,
553+
"array['one','two','three']::citext[]" == vec![
554+
PgCitext::new("one".to_string()),
555+
PgCitext::new("two".to_string()),
556+
PgCitext::new("three".to_string()),
557+
],
558+
));
559+
560+
552561
// FIXME: needed to disable `ltree` tests in version that don't have a binary format for it
553562
// but `PgLTree` should just fall back to text format
554563
#[cfg(any(postgres_14, postgres_15))]

0 commit comments

Comments
 (0)