Skip to main content

dbkit_core/
func.rs

1use bitflags::bitflags;
2
3use crate::compile::CompiledSql;
4use crate::expr::{AggregateExpr, Expr, ExprNode, ExprOperand, IntoExpr, NumericExprType, TrimDirection, Value, VectorBinaryOp};
5use crate::query::Select;
6use crate::PgVector;
7
8pub trait StringUnaryExpr {
9    type Output;
10}
11
12impl StringUnaryExpr for String {
13    type Output = String;
14}
15
16impl StringUnaryExpr for Option<String> {
17    type Output = Option<String>;
18}
19
20pub trait StringLengthExpr {
21    type Output;
22}
23
24impl StringLengthExpr for String {
25    type Output = i32;
26}
27
28impl StringLengthExpr for Option<String> {
29    type Output = Option<i32>;
30}
31
32pub trait StringSplitExpr {
33    type Output;
34}
35
36impl StringSplitExpr for String {
37    type Output = Vec<String>;
38}
39
40impl StringSplitExpr for Option<String> {
41    type Output = Option<Vec<String>>;
42}
43
44pub trait StringBinaryExpr<Rhs, Result> {
45    type Output;
46}
47
48impl<Result> StringBinaryExpr<String, Result> for String {
49    type Output = Result;
50}
51
52impl<Result> StringBinaryExpr<Option<String>, Result> for String {
53    type Output = Option<Result>;
54}
55
56impl<Result> StringBinaryExpr<String, Result> for Option<String> {
57    type Output = Option<Result>;
58}
59
60impl<Result> StringBinaryExpr<Option<String>, Result> for Option<String> {
61    type Output = Option<Result>;
62}
63
64#[doc(hidden)]
65pub struct ConcatExpr {
66    node: ExprNode,
67}
68
69pub trait IntoConcatExpr {
70    fn into_concat_expr(self) -> ConcatExpr;
71}
72
73impl<T> IntoConcatExpr for T
74where
75    T: ExprOperand,
76    T::Value: StringUnaryExpr,
77{
78    fn into_concat_expr(self) -> ConcatExpr {
79        ConcatExpr {
80            node: self.into_operand_expr().node,
81        }
82    }
83}
84
85impl IntoConcatExpr for ConcatExpr {
86    fn into_concat_expr(self) -> ConcatExpr {
87        self
88    }
89}
90
91bitflags! {
92    /// Composable options for [`regex_replace`]; use [`empty`](Self::empty) for default behavior.
93    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
94    pub struct RegexReplaceFlags: u8 {
95        /// Uses case-insensitive matching. Maps to PostgreSQL's `i` flag.
96        const CASE_INSENSITIVE = 1 << 0;
97        /// Replaces every match instead of only the first. Maps to PostgreSQL's `g` flag.
98        const GLOBAL = 1 << 1;
99    }
100
101    /// Composable options for [`regex_split`]; use [`empty`](Self::empty) for default behavior.
102    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
103    pub struct RegexSplitFlags: u8 {
104        /// Uses case-insensitive matching. Maps to PostgreSQL's `i` flag.
105        const CASE_INSENSITIVE = 1 << 0;
106    }
107}
108
109/// A PostgreSQL Unicode normalization form for [`normalize`].
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum NormalizationForm {
112    /// PostgreSQL `NFC` normalization.
113    Nfc,
114    /// PostgreSQL `NFD` normalization.
115    Nfd,
116    /// PostgreSQL `NFKC` normalization.
117    Nfkc,
118    /// PostgreSQL `NFKD` normalization.
119    Nfkd,
120}
121
122/// Output type for boolean string functions.
123pub trait StringBoolExpr {
124    /// Result type preserving input nullability.
125    type Output;
126}
127
128impl StringBoolExpr for String {
129    type Output = bool;
130}
131
132impl StringBoolExpr for Option<String> {
133    type Output = Option<bool>;
134}
135
136/// Output type for integer-to-character functions.
137pub trait CodepointExpr {
138    /// Result type preserving input nullability.
139    type Output;
140}
141
142impl CodepointExpr for i32 {
143    type Output = String;
144}
145
146impl CodepointExpr for Option<i32> {
147    type Output = Option<String>;
148}
149
150impl RegexReplaceFlags {
151    fn as_postgres_str(self) -> &'static str {
152        match (self.contains(Self::GLOBAL), self.contains(Self::CASE_INSENSITIVE)) {
153            (false, false) => "",
154            (false, true) => "i",
155            (true, false) => "g",
156            (true, true) => "gi",
157        }
158    }
159}
160
161impl RegexSplitFlags {
162    fn as_postgres_str(self) -> &'static str {
163        if self.contains(Self::CASE_INSENSITIVE) {
164            "i"
165        } else {
166            ""
167        }
168    }
169}
170
171fn unary_string_fn<T>(name: &'static str, arg: impl IntoExpr<T>) -> Expr<<T as StringUnaryExpr>::Output>
172where
173    T: StringUnaryExpr,
174{
175    let expr = arg.into_expr();
176    Expr::new(ExprNode::Func {
177        name,
178        args: vec![expr.node],
179    })
180}
181
182fn string_length_fn<T>(name: &'static str, arg: impl IntoExpr<T>) -> Expr<<T as StringLengthExpr>::Output>
183where
184    T: StringLengthExpr,
185{
186    let expr = arg.into_expr();
187    Expr::new(ExprNode::Func {
188        name,
189        args: vec![expr.node],
190    })
191}
192
193fn string_fn<T>(name: &'static str, arg: impl IntoExpr<T>, extra_args: Vec<ExprNode>) -> Expr<<T as StringUnaryExpr>::Output>
194where
195    T: StringUnaryExpr,
196{
197    let mut args = vec![arg.into_expr().node];
198    args.extend(extra_args);
199    Expr::new(ExprNode::Func { name, args })
200}
201
202fn binary_string_fn<L, R, O>(name: &'static str, left: impl IntoExpr<L>, right: impl IntoExpr<R>) -> Expr<O> {
203    let left = left.into_expr();
204    let right = right.into_expr();
205    Expr::new(ExprNode::Func {
206        name,
207        args: vec![left.node, right.node],
208    })
209}
210
211fn ternary_string_fn<A, B, C, O>(
212    name: &'static str,
213    first: impl IntoExpr<A>,
214    second: impl IntoExpr<B>,
215    third: impl IntoExpr<C>,
216) -> Expr<O> {
217    Expr::new(ExprNode::Func {
218        name,
219        args: vec![first.into_expr().node, second.into_expr().node, third.into_expr().node],
220    })
221}
222
223fn string_expr_nodes<I, A>(args: I) -> Vec<ExprNode>
224where
225    I: IntoIterator<Item = A>,
226    A: IntoConcatExpr,
227{
228    args.into_iter().map(|arg| arg.into_concat_expr().node).collect()
229}
230
231fn directed_trim_fn<T>(
232    arg: impl IntoExpr<T>,
233    direction: TrimDirection,
234    characters: Option<Expr<String>>,
235) -> Expr<<T as StringUnaryExpr>::Output>
236where
237    T: StringUnaryExpr,
238{
239    Expr::new(ExprNode::Trim {
240        direction,
241        expr: Box::new(arg.into_expr().node),
242        characters: characters.map(|characters| Box::new(characters.node)),
243    })
244}
245
246/// Converts text to uppercase according to the database locale, preserving input nullability.
247/// Maps to PostgreSQL `UPPER`.
248pub fn upper<T>(arg: impl IntoExpr<T>) -> Expr<<T as StringUnaryExpr>::Output>
249where
250    T: StringUnaryExpr,
251{
252    unary_string_fn("UPPER", arg)
253}
254
255/// Converts text to lowercase according to the database locale, preserving input nullability.
256/// Maps to PostgreSQL `LOWER`.
257pub fn lower<T>(arg: impl IntoExpr<T>) -> Expr<<T as StringUnaryExpr>::Output>
258where
259    T: StringUnaryExpr,
260{
261    unary_string_fn("LOWER", arg)
262}
263
264/// Converts the first letter of each alphanumeric word to upper case and the rest to lower case.
265/// Maps to PostgreSQL `INITCAP`.
266pub fn title_case<T>(expression: impl IntoExpr<T>) -> Expr<<T as StringUnaryExpr>::Output>
267where
268    T: StringUnaryExpr,
269{
270    unary_string_fn("INITCAP", expression)
271}
272
273/// Replaces every exact occurrence of `from` with `to`.
274/// Returns NULL if any argument is NULL.
275/// Maps to PostgreSQL `REPLACE`.
276pub fn replace<S, F, T>(
277    expression: impl IntoExpr<S>,
278    from: impl IntoExpr<F>,
279    to: impl IntoExpr<T>,
280) -> Expr<<<S as StringBinaryExpr<F, String>>::Output as StringBinaryExpr<T, String>>::Output>
281where
282    S: StringBinaryExpr<F, String>,
283    <S as StringBinaryExpr<F, String>>::Output: StringBinaryExpr<T, String>,
284{
285    ternary_string_fn("REPLACE", expression, from, to)
286}
287
288/// Replaces `count` characters from the 1-based `start` with `replacement`.
289/// Returns NULL if either string argument is NULL.
290/// Maps to PostgreSQL's callable `OVERLAY` form.
291pub fn replace_range<S, R>(
292    expression: impl IntoExpr<S>,
293    replacement: impl IntoExpr<R>,
294    start: impl IntoExpr<i32>,
295    count: impl IntoExpr<i32>,
296) -> Expr<<S as StringBinaryExpr<R, String>>::Output>
297where
298    S: StringBinaryExpr<R, String>,
299{
300    Expr::new(ExprNode::Func {
301        name: "OVERLAY",
302        args: vec![
303            expression.into_expr().node,
304            replacement.into_expr().node,
305            start.into_expr().node,
306            count.into_expr().node,
307        ],
308    })
309}
310
311/// Replaces characters positionally, deleting `from` characters without a corresponding `to` character.
312/// Returns NULL if any argument is NULL.
313/// Maps to PostgreSQL `TRANSLATE`.
314pub fn translate_chars<S, F, T>(
315    expression: impl IntoExpr<S>,
316    from: impl IntoExpr<F>,
317    to: impl IntoExpr<T>,
318) -> Expr<<<S as StringBinaryExpr<F, String>>::Output as StringBinaryExpr<T, String>>::Output>
319where
320    S: StringBinaryExpr<F, String>,
321    <S as StringBinaryExpr<F, String>>::Output: StringBinaryExpr<T, String>,
322{
323    ternary_string_fn("TRANSLATE", expression, from, to)
324}
325
326/// Reverses the characters in a string.
327/// Maps to PostgreSQL `REVERSE`.
328pub fn reverse<T>(expression: impl IntoExpr<T>) -> Expr<<T as StringUnaryExpr>::Output>
329where
330    T: StringUnaryExpr,
331{
332    unary_string_fn("REVERSE", expression)
333}
334
335/// Removes spaces from both ends of a text expression, preserving input nullability.
336/// Maps to PostgreSQL `TRIM(expression)`.
337pub fn trim<T>(arg: impl IntoExpr<T>) -> Expr<<T as StringUnaryExpr>::Output>
338where
339    T: StringUnaryExpr,
340{
341    unary_string_fn("TRIM", arg)
342}
343
344/// Removes the longest span made only of characters in the `characters` set from both ends.
345/// For example, trimming `"xyxtrimyyx"` with `"xyz"` yields `"trim"`.
346/// Maps to PostgreSQL `TRIM(BOTH characters FROM expression)`.
347pub fn trim_chars<T>(arg: impl IntoExpr<T>, characters: impl IntoExpr<String>) -> Expr<<T as StringUnaryExpr>::Output>
348where
349    T: StringUnaryExpr,
350{
351    directed_trim_fn(arg, TrimDirection::Both, Some(characters.into_expr()))
352}
353
354/// Removes leading spaces from a text expression.
355/// Maps to PostgreSQL `TRIM(LEADING FROM expression)`.
356pub fn trim_start<T>(arg: impl IntoExpr<T>) -> Expr<<T as StringUnaryExpr>::Output>
357where
358    T: StringUnaryExpr,
359{
360    directed_trim_fn(arg, TrimDirection::Leading, None)
361}
362
363/// Removes the longest leading span made only of characters in the `characters` set.
364/// Maps to PostgreSQL `TRIM(LEADING characters FROM expression)`.
365pub fn trim_start_chars<T>(arg: impl IntoExpr<T>, characters: impl IntoExpr<String>) -> Expr<<T as StringUnaryExpr>::Output>
366where
367    T: StringUnaryExpr,
368{
369    directed_trim_fn(arg, TrimDirection::Leading, Some(characters.into_expr()))
370}
371
372/// Removes trailing spaces from a text expression.
373/// Maps to PostgreSQL `TRIM(TRAILING FROM expression)`.
374pub fn trim_end<T>(arg: impl IntoExpr<T>) -> Expr<<T as StringUnaryExpr>::Output>
375where
376    T: StringUnaryExpr,
377{
378    directed_trim_fn(arg, TrimDirection::Trailing, None)
379}
380
381/// Removes the longest trailing span made only of characters in the `characters` set.
382/// Maps to PostgreSQL `TRIM(TRAILING characters FROM expression)`.
383pub fn trim_end_chars<T>(arg: impl IntoExpr<T>, characters: impl IntoExpr<String>) -> Expr<<T as StringUnaryExpr>::Output>
384where
385    T: StringUnaryExpr,
386{
387    directed_trim_fn(arg, TrimDirection::Trailing, Some(characters.into_expr()))
388}
389
390/// Returns the number of characters in a text expression, preserving input nullability.
391/// Maps to PostgreSQL `CHAR_LENGTH`.
392pub fn char_length<T>(arg: impl IntoExpr<T>) -> Expr<<T as StringLengthExpr>::Output>
393where
394    T: StringLengthExpr,
395{
396    string_length_fn("CHAR_LENGTH", arg)
397}
398
399/// Returns the encoded byte length of a text expression, preserving input nullability.
400/// Maps to PostgreSQL `OCTET_LENGTH`.
401pub fn byte_length<T>(arg: impl IntoExpr<T>) -> Expr<<T as StringLengthExpr>::Output>
402where
403    T: StringLengthExpr,
404{
405    string_length_fn("OCTET_LENGTH", arg)
406}
407
408/// Returns eight times the encoded byte length, preserving input nullability.
409/// Maps to PostgreSQL `BIT_LENGTH`.
410pub fn bit_length<T>(arg: impl IntoExpr<T>) -> Expr<<T as StringLengthExpr>::Output>
411where
412    T: StringLengthExpr,
413{
414    string_length_fn("BIT_LENGTH", arg)
415}
416
417/// Returns the 1-based position of `substring` in `expression`, or zero when absent.
418/// Returns NULL if either argument is NULL; `position("banana", "ana")` evaluates to `2`.
419/// Maps to PostgreSQL `STRPOS`.
420pub fn position<L, R>(expression: impl IntoExpr<L>, substring: impl IntoExpr<R>) -> Expr<<L as StringBinaryExpr<R, i32>>::Output>
421where
422    L: StringBinaryExpr<R, i32>,
423{
424    binary_string_fn("STRPOS", expression, substring)
425}
426
427/// Tests whether `expression` begins with the exact, case-sensitive `prefix`.
428/// Returns NULL if either argument is NULL; `starts_with("PostgreSQL", "Post")` evaluates to `true`.
429/// Requires PostgreSQL 11 or newer.
430/// Maps to PostgreSQL `STARTS_WITH`.
431pub fn starts_with<L, R>(expression: impl IntoExpr<L>, prefix: impl IntoExpr<R>) -> Expr<<L as StringBinaryExpr<R, bool>>::Output>
432where
433    L: StringBinaryExpr<R, bool>,
434{
435    binary_string_fn("STARTS_WITH", expression, prefix)
436}
437
438/// Concatenates string expressions in order, ignoring NULL values.
439/// Maps to PostgreSQL `CONCAT`.
440pub fn concat<I, A>(values: I) -> Expr<String>
441where
442    I: IntoIterator<Item = A>,
443    A: IntoConcatExpr,
444{
445    let args = string_expr_nodes(values);
446    Expr::new(ExprNode::Func { name: "CONCAT", args })
447}
448
449/// Concatenates string expressions with `separator`, ignoring NULL values.
450/// Returns NULL when `separator` is NULL.
451/// Maps to PostgreSQL `CONCAT_WS`.
452pub fn concat_with_separator<S, I, A>(separator: impl IntoExpr<S>, values: I) -> Expr<<S as StringUnaryExpr>::Output>
453where
454    S: StringUnaryExpr,
455    I: IntoIterator<Item = A>,
456    A: IntoConcatExpr,
457{
458    let mut args = vec![separator.into_expr().node];
459    args.extend(string_expr_nodes(values));
460    Expr::new(ExprNode::Func { name: "CONCAT_WS", args })
461}
462
463/// Splits a string into a text array using a delimiter.
464/// A NULL delimiter splits the source into individual characters.
465/// Maps to PostgreSQL `STRING_TO_ARRAY`.
466pub fn split<S, D>(expression: impl IntoExpr<S>, delimiter: impl IntoExpr<D>) -> Expr<<S as StringSplitExpr>::Output>
467where
468    S: StringSplitExpr,
469    D: StringUnaryExpr,
470{
471    binary_string_fn("STRING_TO_ARRAY", expression, delimiter)
472}
473
474/// Returns the 1-based field from a delimited string.
475/// Negative indexes count from the end on PostgreSQL 14 or newer.
476/// PostgreSQL rejects an index of zero and returns an empty string for an out-of-range index.
477/// Maps to PostgreSQL `SPLIT_PART`.
478pub fn split_part<S, D>(
479    expression: impl IntoExpr<S>,
480    delimiter: impl IntoExpr<D>,
481    index: impl IntoExpr<i32>,
482) -> Expr<<S as StringBinaryExpr<D, String>>::Output>
483where
484    S: StringBinaryExpr<D, String>,
485{
486    let expression = expression.into_expr();
487    let delimiter = delimiter.into_expr();
488    let index = index.into_expr();
489    Expr::new(ExprNode::Func {
490        name: "SPLIT_PART",
491        args: vec![expression.node, delimiter.node, index.node],
492    })
493}
494
495/// Tests whether a POSIX regular expression matches anywhere in the text.
496/// Requires PostgreSQL 15 or newer.
497/// Maps to PostgreSQL `REGEXP_LIKE`.
498pub fn regex_is_match<L, R>(expression: impl IntoExpr<L>, pattern: impl IntoExpr<R>) -> Expr<<L as StringBinaryExpr<R, bool>>::Output>
499where
500    L: StringBinaryExpr<R, bool>,
501{
502    binary_string_fn("REGEXP_LIKE", expression, pattern)
503}
504
505/// Counts non-overlapping POSIX regular-expression matches.
506/// Requires PostgreSQL 15 or newer.
507/// Maps to PostgreSQL `REGEXP_COUNT`.
508pub fn regex_count<L, R>(expression: impl IntoExpr<L>, pattern: impl IntoExpr<R>) -> Expr<<L as StringBinaryExpr<R, i32>>::Output>
509where
510    L: StringBinaryExpr<R, i32>,
511{
512    binary_string_fn("REGEXP_COUNT", expression, pattern)
513}
514
515/// Returns the 1-based position of the first match, or zero when absent.
516/// Requires PostgreSQL 15 or newer.
517/// Maps to PostgreSQL `REGEXP_INSTR`.
518pub fn regex_position<L, R>(expression: impl IntoExpr<L>, pattern: impl IntoExpr<R>) -> Expr<<L as StringBinaryExpr<R, i32>>::Output>
519where
520    L: StringBinaryExpr<R, i32>,
521{
522    binary_string_fn("REGEXP_INSTR", expression, pattern)
523}
524
525/// Returns captures from the first match, or NULL when there is no match. Requires PostgreSQL 10 or newer.
526/// Capture elements are nullable because optional groups can be unmatched.
527/// Maps to PostgreSQL `REGEXP_MATCH`.
528pub fn regex_captures<L, R>(expression: impl IntoExpr<L>, pattern: impl IntoExpr<R>) -> Expr<Option<Vec<Option<String>>>>
529where
530    L: StringUnaryExpr,
531    R: StringUnaryExpr,
532{
533    binary_string_fn("REGEXP_MATCH", expression, pattern)
534}
535
536/// Returns the first matching substring, or NULL when there is no match.
537/// Requires PostgreSQL 15 or newer.
538/// Maps to PostgreSQL `REGEXP_SUBSTR`.
539pub fn regex_extract<L, R>(expression: impl IntoExpr<L>, pattern: impl IntoExpr<R>) -> Expr<Option<String>>
540where
541    L: StringUnaryExpr,
542    R: StringUnaryExpr,
543{
544    binary_string_fn("REGEXP_SUBSTR", expression, pattern)
545}
546
547/// Returns the first `count` characters, or all but the last `|count|` when negative.
548/// Maps to PostgreSQL `LEFT`.
549pub fn left<T>(arg: impl IntoExpr<T>, count: impl IntoExpr<i32>) -> Expr<<T as StringUnaryExpr>::Output>
550where
551    T: StringUnaryExpr,
552{
553    string_fn("LEFT", arg, vec![count.into_expr().node])
554}
555
556/// Returns the last `count` characters, or all but the first `|count|` when negative.
557/// Maps to PostgreSQL `RIGHT`.
558pub fn right<T>(arg: impl IntoExpr<T>, count: impl IntoExpr<i32>) -> Expr<<T as StringUnaryExpr>::Output>
559where
560    T: StringUnaryExpr,
561{
562    string_fn("RIGHT", arg, vec![count.into_expr().node])
563}
564
565/// Returns up to `count` characters from the 1-based `start`.
566/// From `"abcdef"`, `(2, 3)` yields `"bcd"` and `(0, 3)` yields `"ab"`; negative counts are rejected.
567/// Maps to PostgreSQL `SUBSTRING`.
568pub fn substring<T>(arg: impl IntoExpr<T>, start: impl IntoExpr<i32>, count: impl IntoExpr<i32>) -> Expr<<T as StringUnaryExpr>::Output>
569where
570    T: StringUnaryExpr,
571{
572    string_fn("SUBSTRING", arg, vec![start.into_expr().node, count.into_expr().node])
573}
574
575/// Repeats the text `count` times.
576/// Repeating `"ab"` three times yields `"ababab"`; non-positive counts yield an empty string.
577/// Maps to PostgreSQL `REPEAT`.
578pub fn repeat<T>(arg: impl IntoExpr<T>, count: impl IntoExpr<i32>) -> Expr<<T as StringUnaryExpr>::Output>
579where
580    T: StringUnaryExpr,
581{
582    string_fn("REPEAT", arg, vec![count.into_expr().node])
583}
584
585/// Pads on the left to `length` by cycling `fill`, truncating the source on the right if needed.
586/// Padding `"ab"` to 5 with `"xy"` yields `"xyxab"`; empty fill adds nothing and non-positive length yields `""`.
587/// Maps to PostgreSQL `LPAD`.
588pub fn pad_start<T>(arg: impl IntoExpr<T>, length: impl IntoExpr<i32>, fill: impl IntoExpr<String>) -> Expr<<T as StringUnaryExpr>::Output>
589where
590    T: StringUnaryExpr,
591{
592    string_fn("LPAD", arg, vec![length.into_expr().node, fill.into_expr().node])
593}
594
595/// Pads on the right to `length` by cycling `fill`, truncating the source on the right if needed.
596/// Padding `"ab"` to 5 with `"xy"` yields `"abxyx"`; empty fill adds nothing and non-positive length yields `""`.
597/// Maps to PostgreSQL `RPAD`.
598pub fn pad_end<T>(arg: impl IntoExpr<T>, length: impl IntoExpr<i32>, fill: impl IntoExpr<String>) -> Expr<<T as StringUnaryExpr>::Output>
599where
600    T: StringUnaryExpr,
601{
602    string_fn("RPAD", arg, vec![length.into_expr().node, fill.into_expr().node])
603}
604
605/// Replaces the first POSIX regular-expression match in `source`, or returns `source` unchanged when none exists.
606/// [`RegexReplaceFlags::GLOBAL`] replaces every match; [`RegexReplaceFlags::CASE_INSENSITIVE`] ignores case.
607/// `replacement` supports PostgreSQL backreferences (`\1` through `\9`, `\&`, and `\\` for a literal backslash).
608/// Returns NULL if `source`, `pattern`, or `replacement` is NULL.
609/// Maps to PostgreSQL `REGEXP_REPLACE`.
610pub fn regex_replace<S, P, R, SP, O>(
611    source: impl IntoExpr<S>,
612    pattern: impl IntoExpr<P>,
613    replacement: impl IntoExpr<R>,
614    flags: RegexReplaceFlags,
615) -> Expr<O>
616where
617    S: StringBinaryExpr<P, String, Output = SP>,
618    SP: StringBinaryExpr<R, String, Output = O>,
619{
620    Expr::new(ExprNode::Func {
621        name: "REGEXP_REPLACE",
622        args: vec![
623            source.into_expr().node,
624            pattern.into_expr().node,
625            replacement.into_expr().node,
626            ExprNode::Value(Value::String(flags.as_postgres_str().to_string())),
627        ],
628    })
629}
630
631/// Splits `source` around POSIX regular-expression matches into a text array.
632/// Returns `source` as the only element when no match exists.
633/// Zero-length matches at the start or end, or immediately after a previous match, are ignored.
634/// [`RegexSplitFlags::CASE_INSENSITIVE`] enables case-insensitive matching.
635/// Returns NULL if `source` or `pattern` is NULL.
636/// Maps to PostgreSQL `REGEXP_SPLIT_TO_ARRAY`.
637pub fn regex_split<S, P, O>(source: impl IntoExpr<S>, pattern: impl IntoExpr<P>, flags: RegexSplitFlags) -> Expr<O>
638where
639    S: StringBinaryExpr<P, Vec<String>, Output = O>,
640{
641    Expr::new(ExprNode::Func {
642        name: "REGEXP_SPLIT_TO_ARRAY",
643        args: vec![
644            source.into_expr().node,
645            pattern.into_expr().node,
646            ExprNode::Value(Value::String(flags.as_postgres_str().to_string())),
647        ],
648    })
649}
650
651/// Normalizes text using the selected Unicode form. Requires PostgreSQL 13 or newer and `UTF8`.
652/// Maps to PostgreSQL `NORMALIZE(expression, form)`.
653pub fn normalize<T>(arg: impl IntoExpr<T>, form: NormalizationForm) -> Expr<<T as StringUnaryExpr>::Output>
654where
655    T: StringUnaryExpr,
656{
657    Expr::new(ExprNode::Normalize {
658        expr: Box::new(arg.into_expr().node),
659        form,
660    })
661}
662
663/// Returns the numeric code of the first character, or zero for empty text.
664/// Other multibyte encodings accept only ASCII characters.
665/// Maps to PostgreSQL `ASCII`, which returns Unicode code points with `UTF8`.
666pub fn first_codepoint<T>(arg: impl IntoExpr<T>) -> Expr<<T as StringLengthExpr>::Output>
667where
668    T: StringLengthExpr,
669{
670    string_length_fn("ASCII", arg)
671}
672
673/// Returns the character for an integer code point. PostgreSQL rejects zero and invalid code points.
674/// Other multibyte encodings accept only ASCII characters.
675/// Maps to PostgreSQL `CHR`, which interprets Unicode code points with `UTF8`.
676pub fn from_codepoint<T>(arg: impl IntoExpr<T>) -> Expr<<T as CodepointExpr>::Output>
677where
678    T: CodepointExpr,
679{
680    let expr = arg.into_expr();
681    Expr::new(ExprNode::Func {
682        name: "CHR",
683        args: vec![expr.node],
684    })
685}
686
687/// Converts text to ASCII, primarily by removing accents.
688/// Supports only `LATIN1`, `LATIN2`, `LATIN9`, and `WIN1250` database encodings, not `UTF8`.
689/// Maps to PostgreSQL `TO_ASCII(expression)`.
690pub fn to_ascii<T>(arg: impl IntoExpr<T>) -> Expr<<T as StringUnaryExpr>::Output>
691where
692    T: StringUnaryExpr,
693{
694    unary_string_fn("TO_ASCII", arg)
695}
696
697/// Performs collation-dependent Unicode case folding. Requires PostgreSQL 18 or newer and `UTF8`.
698/// Maps to PostgreSQL `CASEFOLD`.
699pub fn case_fold<T>(arg: impl IntoExpr<T>) -> Expr<<T as StringUnaryExpr>::Output>
700where
701    T: StringUnaryExpr,
702{
703    unary_string_fn("CASEFOLD", arg)
704}
705
706/// Tests whether every character has an assigned Unicode code point. Requires PostgreSQL 18 or newer and `UTF8`.
707/// Maps to PostgreSQL `UNICODE_ASSIGNED`.
708pub fn is_unicode_assigned<T>(arg: impl IntoExpr<T>) -> Expr<<T as StringBoolExpr>::Output>
709where
710    T: StringBoolExpr,
711{
712    let expr = arg.into_expr();
713    Expr::new(ExprNode::Func {
714        name: "UNICODE_ASSIGNED",
715        args: vec![expr.node],
716    })
717}
718
719pub fn count<T>(arg: impl IntoExpr<T>) -> AggregateExpr<i64> {
720    let expr = arg.into_expr();
721    Expr::new(ExprNode::Func {
722        name: "COUNT",
723        args: vec![expr.node],
724    })
725}
726
727pub fn sum<T>(arg: impl IntoExpr<T>) -> AggregateExpr<T> {
728    let expr = arg.into_expr();
729    Expr::new(ExprNode::Func {
730        name: "SUM",
731        args: vec![expr.node],
732    })
733}
734
735pub trait NullableAggregateOutput {
736    type Output;
737}
738
739macro_rules! impl_nullable_aggregate_output {
740    ($($ty:ty),+ $(,)?) => {
741        $(
742            impl NullableAggregateOutput for $ty {
743                type Output = Option<$ty>;
744            }
745
746            impl NullableAggregateOutput for Option<$ty> {
747                type Output = Option<$ty>;
748            }
749        )+
750    };
751}
752
753impl_nullable_aggregate_output!(
754    String,
755    i16,
756    i32,
757    i64,
758    f32,
759    f64,
760    uuid::Uuid,
761    chrono::NaiveDateTime,
762    chrono::DateTime<chrono::Utc>,
763    chrono::NaiveDate,
764    chrono::NaiveTime,
765    crate::PgInterval,
766);
767
768pub fn min<T>(arg: impl IntoExpr<T>) -> AggregateExpr<<T as NullableAggregateOutput>::Output>
769where
770    T: NullableAggregateOutput,
771{
772    let expr = arg.into_expr();
773    Expr::new(ExprNode::Func {
774        name: "MIN",
775        args: vec![expr.node],
776    })
777}
778
779pub fn max<T>(arg: impl IntoExpr<T>) -> AggregateExpr<<T as NullableAggregateOutput>::Output>
780where
781    T: NullableAggregateOutput,
782{
783    let expr = arg.into_expr();
784    Expr::new(ExprNode::Func {
785        name: "MAX",
786        args: vec![expr.node],
787    })
788}
789
790pub fn coalesce<T>(a: impl IntoExpr<T>, b: impl IntoExpr<T>) -> Expr<T> {
791    let left = a.into_expr();
792    let right = b.into_expr();
793    Expr::new(ExprNode::Func {
794        name: "COALESCE",
795        args: vec![left.node, right.node],
796    })
797}
798
799pub fn least<T>(a: impl IntoExpr<T>, b: impl IntoExpr<T>) -> Expr<T> {
800    let left = a.into_expr();
801    let right = b.into_expr();
802    Expr::new(ExprNode::Func {
803        name: "LEAST",
804        args: vec![left.node, right.node],
805    })
806}
807
808pub fn greatest<T>(a: impl IntoExpr<T>, b: impl IntoExpr<T>) -> Expr<T> {
809    let left = a.into_expr();
810    let right = b.into_expr();
811    Expr::new(ExprNode::Func {
812        name: "GREATEST",
813        args: vec![left.node, right.node],
814    })
815}
816
817pub fn power<B, E>(base: impl IntoExpr<B>, exponent: impl IntoExpr<E>) -> Expr<f64>
818where
819    B: NumericExprType,
820    E: NumericExprType,
821{
822    let base = base.into_expr();
823    let exponent = exponent.into_expr();
824    Expr::new(ExprNode::Func {
825        name: "POWER",
826        args: vec![base.node, exponent.node],
827    })
828}
829
830pub fn date_trunc<T>(part: impl IntoExpr<String>, value: impl IntoExpr<T>) -> Expr<T> {
831    let part = part.into_expr();
832    let value = value.into_expr();
833    Expr::new(ExprNode::Func {
834        name: "DATE_TRUNC",
835        args: vec![part.node, value.node],
836    })
837}
838
839fn exists_expr(subquery: CompiledSql) -> Expr<bool> {
840    Expr::new(ExprNode::Exists { subquery })
841}
842
843pub fn exists<Out, Loads, Lock, DistinctState, GroupState>(subquery: Select<Out, Loads, Lock, DistinctState, GroupState>) -> Expr<bool> {
844    exists_expr(subquery.compile_for_exists())
845}
846
847/// Marker trait for values that can participate in vector distance/similarity expressions.
848pub trait VectorExpr<const N: usize> {}
849
850impl<const N: usize> VectorExpr<N> for PgVector<N> {}
851impl<const N: usize> VectorExpr<N> for Option<PgVector<N>> {}
852
853fn vector_binary_fn<const N: usize, L, R>(name: &'static str, left: impl IntoExpr<L>, right: impl IntoExpr<R>) -> Expr<f32>
854where
855    L: VectorExpr<N>,
856    R: VectorExpr<N>,
857{
858    let left = left.into_expr();
859    let right = right.into_expr();
860    Expr::new(ExprNode::Func {
861        name,
862        args: vec![left.node, right.node],
863    })
864}
865
866fn vector_binary_operator<const N: usize, L, R>(op: VectorBinaryOp, left: impl IntoExpr<L>, right: impl IntoExpr<R>) -> Expr<f32>
867where
868    L: VectorExpr<N>,
869    R: VectorExpr<N>,
870{
871    let left = left.into_expr();
872    let right = right.into_expr();
873    Expr::new(ExprNode::VectorBinary {
874        left: Box::new(left.node),
875        op,
876        right: Box::new(right.node),
877    })
878}
879
880/// Euclidean (L2) distance using pgvector's `<->` operator.
881///
882/// Lower is more similar.
883///
884/// ANN note:
885/// - This form is operator-based and can use pgvector ivfflat/hnsw indexes for
886///   `ORDER BY ... LIMIT` nearest-neighbor queries.
887pub fn l2_distance<const N: usize, L, R>(left: impl IntoExpr<L>, right: impl IntoExpr<R>) -> Expr<f32>
888where
889    L: VectorExpr<N>,
890    R: VectorExpr<N>,
891{
892    vector_binary_operator::<N, L, R>(VectorBinaryOp::L2Distance, left, right)
893}
894
895/// Cosine distance using pgvector's `<=>` operator.
896///
897/// Lower is more similar.
898///
899/// ANN note:
900/// - This form is operator-based and can use pgvector ivfflat/hnsw indexes for
901///   `ORDER BY ... LIMIT` nearest-neighbor queries.
902pub fn cosine_distance<const N: usize, L, R>(left: impl IntoExpr<L>, right: impl IntoExpr<R>) -> Expr<f32>
903where
904    L: VectorExpr<N>,
905    R: VectorExpr<N>,
906{
907    vector_binary_operator::<N, L, R>(VectorBinaryOp::CosineDistance, left, right)
908}
909
910/// True inner product as a function expression (`INNER_PRODUCT(a, b)`).
911///
912/// Higher is more similar (for normalized embeddings, identical vectors are `1.0`).
913///
914/// ANN warning:
915/// - This is intentionally a function call to preserve true inner-product semantics,
916///   but function expressions are generally not pgvector ANN index-compatible for
917///   `ORDER BY ... LIMIT`.
918/// - For ANN-indexed retrieval, use [`inner_product_distance`] with `ORDER BY ASC`.
919pub fn inner_product<const N: usize, L, R>(left: impl IntoExpr<L>, right: impl IntoExpr<R>) -> Expr<f32>
920where
921    L: VectorExpr<N>,
922    R: VectorExpr<N>,
923{
924    vector_binary_fn::<N, L, R>("INNER_PRODUCT", left, right)
925}
926
927/// L1 (Manhattan) distance using pgvector's `<+>` operator.
928///
929/// Lower is more similar.
930///
931/// ANN note:
932/// - This form is operator-based and can use pgvector ivfflat/hnsw indexes for
933///   `ORDER BY ... LIMIT` nearest-neighbor queries.
934pub fn l1_distance<const N: usize, L, R>(left: impl IntoExpr<L>, right: impl IntoExpr<R>) -> Expr<f32>
935where
936    L: VectorExpr<N>,
937    R: VectorExpr<N>,
938{
939    vector_binary_operator::<N, L, R>(VectorBinaryOp::L1Distance, left, right)
940}
941
942/// Negative inner-product distance using pgvector's `<#>` operator.
943///
944/// Lower is more similar, so nearest-neighbor queries should use `ORDER BY ASC`.
945///
946/// ANN note:
947/// - This form is operator-based and can use pgvector ivfflat/hnsw indexes for
948///   `ORDER BY ... LIMIT` nearest-neighbor queries.
949/// - Thresholds are inverted relative to true inner product
950///   (for example `inner_product > 0.9` corresponds to
951///   `inner_product_distance < -0.9`).
952pub fn inner_product_distance<const N: usize, L, R>(left: impl IntoExpr<L>, right: impl IntoExpr<R>) -> Expr<f32>
953where
954    L: VectorExpr<N>,
955    R: VectorExpr<N>,
956{
957    vector_binary_operator::<N, L, R>(VectorBinaryOp::InnerProductDistance, left, right)
958}