Skip to main content

umbral_core/orm/
tsvector.rs

1//! The `TsVector` newtype — umbral's Rust binding for Postgres
2//! `tsvector`.
3//!
4//! Postgres tsvector is the lexeme-vector representation used by the
5//! full-text search subsystem. It's typically read out of the
6//! database (via `to_tsvector(...)`) and queried via the `@@`
7//! operator against a `tsquery`. Users rarely construct one
8//! by hand — the column is usually populated by a Postgres trigger
9//! or a `GENERATED ALWAYS AS (to_tsvector(...)) STORED` clause.
10//!
11//! sqlx doesn't ship a native binding, so umbral defines a thin
12//! newtype around `String` with manual `Type`/`Encode`/`Decode`
13//! impls for the Postgres driver. The String holds the on-the-wire
14//! text representation, which is what Postgres returns when a
15//! `tsvector` is selected without an explicit cast.
16
17use sqlx::Postgres;
18use sqlx::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueRef};
19use sqlx::{Decode, Encode, Type};
20
21/// Postgres `tsvector` column value.
22///
23/// The inner string carries the lexeme-vector's text representation,
24/// the format Postgres uses for `tsvector::text` cast output —
25/// space-separated lexemes with optional positions (`'word':1
26/// 'phrase':2,3A`).
27///
28/// Most user code reads this from the database and queries with
29/// `FullTextCol::matches(...)`; constructing a TsVector by hand is
30/// unusual but supported via the `From<String>` impl.
31#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
32pub struct TsVector(pub String);
33
34impl TsVector {
35    /// Borrow the inner string.
36    pub fn as_str(&self) -> &str {
37        &self.0
38    }
39
40    /// Consume and return the inner string.
41    pub fn into_inner(self) -> String {
42        self.0
43    }
44}
45
46impl From<String> for TsVector {
47    fn from(s: String) -> Self {
48        Self(s)
49    }
50}
51
52impl From<&str> for TsVector {
53    fn from(s: &str) -> Self {
54        Self(s.to_string())
55    }
56}
57
58impl AsRef<str> for TsVector {
59    fn as_ref(&self) -> &str {
60        &self.0
61    }
62}
63
64// =========================================================================
65// sqlx bindings (Postgres only).
66//
67// TsVector decodes from the wire as text (the `tsvector::text` cast
68// output Postgres returns when the column is selected). Encoding
69// writes the same text — Postgres accepts implicit `text -> tsvector`
70// cast on INSERT/UPDATE so a bare-text bind works for typical paths.
71// Users with strict tsvector input semantics (e.g., must call
72// `to_tsvector('english', $1)` to apply a specific config) wrap
73// the value in a custom INSERT expression rather than binding through
74// the QuerySet.
75//
76// SQLite gets no impl — `TsVector` only makes sense on Postgres, and
77// the `field.backend` system check blocks models with FullText fields
78// from booting against SQLite.
79// =========================================================================
80
81impl Type<Postgres> for TsVector {
82    fn type_info() -> PgTypeInfo {
83        PgTypeInfo::with_name("tsvector")
84    }
85}
86
87impl<'r> Decode<'r, Postgres> for TsVector {
88    fn decode(value: PgValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
89        // The PG wire format for tsvector is the same text form Postgres
90        // returns for the `::text` cast. sqlx exposes it via the value's
91        // text view; fall back to the string decoder.
92        let s = <String as Decode<Postgres>>::decode(value)?;
93        Ok(TsVector(s))
94    }
95}
96
97impl Encode<'_, Postgres> for TsVector {
98    fn encode_by_ref(
99        &self,
100        buf: &mut PgArgumentBuffer,
101    ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
102        <String as Encode<Postgres>>::encode_by_ref(&self.0, buf)
103    }
104}