mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-03-20 00:54:18 +00:00
feat(citext): support postgres citext (#2478)
* feat(citext): implement citext for postgres * feat(citext): add citext -> String conversion test * feat(citext): fix ltree -> citree * feat(citext): add citext to the setup.sql * chore: address nits to #2478 * Rename `PgCitext` to `PgCiText` * Document when use of `PgCiText` is warranted * Document potentially surprising `PartialEq` behavior * Test that the macros consider `CITEXT` to be compatible with `String` and friends * doc: add `PgCiText` to `postgres::types` listing * chore: restore missing trailing line break to `tests/postgres/setup.sql` --------- Co-authored-by: Austin Bonander <austin@launchbadge.com>
This commit is contained in:
@@ -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);
|
||||
@@ -178,6 +179,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!("Any driver does not support the Postgres type {pg_type:?}").into(),
|
||||
|
||||
@@ -457,6 +457,7 @@ impl PgType {
|
||||
PgType::Int8RangeArray => Oid(3927),
|
||||
PgType::Jsonpath => Oid(4072),
|
||||
PgType::JsonpathArray => Oid(4073),
|
||||
|
||||
PgType::Custom(ty) => ty.oid,
|
||||
|
||||
PgType::DeclareWithOid(oid) => *oid,
|
||||
@@ -874,6 +875,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,
|
||||
|
||||
106
sqlx-postgres/src/types/citext.rs
Normal file
106
sqlx-postgres/src/types/citext.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
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;
|
||||
|
||||
/// Case-insensitive text (`citext`) support for Postgres.
|
||||
///
|
||||
/// Note that SQLx considers the `citext` type to be compatible with `String`
|
||||
/// and its various derivatives, so direct usage of this type is generally unnecessary.
|
||||
///
|
||||
/// However, it may be needed, for example, when binding a `citext[]` array,
|
||||
/// as Postgres will generally not accept a `text[]` array (mapped from `Vec<String>`) in its place.
|
||||
///
|
||||
/// See [the Postgres manual, Appendix F, Section 10][PG.F.10] for details on using `citext`.
|
||||
///
|
||||
/// [PG.F.10]: 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";
|
||||
/// ```
|
||||
///
|
||||
/// ### Note: `PartialEq` is Case-Sensitive
|
||||
/// This type derives `PartialEq` which forwards to the implementation on `String`, which
|
||||
/// is case-sensitive. This impl exists mainly for testing.
|
||||
///
|
||||
/// To properly emulate the case-insensitivity of `citext` would require use of locale-aware
|
||||
/// functions in `libc`, and even then would require querying the locale of the database server
|
||||
/// and setting it locally, which is unsafe.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct PgCiText(pub String);
|
||||
|
||||
impl Type<Postgres> for PgCiText {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
// Since `citext` 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(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PgCiText> for String {
|
||||
fn from(value: PgCiText) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
f.write_str(&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()))
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
//! | `i64` | BIGINT, BIGSERIAL, INT8 |
|
||||
//! | `f32` | REAL, FLOAT4 |
|
||||
//! | `f64` | DOUBLE PRECISION, FLOAT8 |
|
||||
//! | `&str`, [`String`] | VARCHAR, CHAR(N), TEXT, NAME |
|
||||
//! | `&str`, [`String`] | VARCHAR, CHAR(N), TEXT, NAME, CITEXT |
|
||||
//! | `&[u8]`, `Vec<u8>` | BYTEA |
|
||||
//! | `()` | VOID |
|
||||
//! | [`PgInterval`] | INTERVAL |
|
||||
@@ -19,6 +19,11 @@
|
||||
//! | [`PgMoney`] | MONEY |
|
||||
//! | [`PgLTree`] | LTREE |
|
||||
//! | [`PgLQuery`] | LQUERY |
|
||||
//! | [`PgCiText`] | CITEXT<sup>1</sup> |
|
||||
//!
|
||||
//! <sup>1</sup> SQLx generally considers `CITEXT` to be compatible with `String`, `&str`, etc.,
|
||||
//! but this wrapper type is available for edge cases, such as `CITEXT[]` which Postgres
|
||||
//! does not consider to be compatible with `TEXT[]`.
|
||||
//!
|
||||
//! ### [`bigdecimal`](https://crates.io/crates/bigdecimal)
|
||||
//! Requires the `bigdecimal` Cargo feature flag.
|
||||
@@ -175,6 +180,7 @@ pub(crate) use sqlx_core::types::{Json, Type};
|
||||
mod array;
|
||||
mod bool;
|
||||
mod bytes;
|
||||
mod citext;
|
||||
mod float;
|
||||
mod int;
|
||||
mod interval;
|
||||
@@ -224,6 +230,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;
|
||||
|
||||
@@ -18,6 +18,7 @@ impl Type<Postgres> for str {
|
||||
PgTypeInfo::BPCHAR,
|
||||
PgTypeInfo::VARCHAR,
|
||||
PgTypeInfo::UNKNOWN,
|
||||
PgTypeInfo::with_name("citext"),
|
||||
]
|
||||
.contains(ty)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user