sea_query/types/
mod.rs

1//! Base types used throughout sea-query.
2
3use crate::{FunctionCall, ValueTuple, Values, expr::*, query::*};
4use std::{borrow::Cow, fmt::Debug, iter::Flatten};
5
6#[cfg(feature = "backend-postgres")]
7use crate::extension::postgres::PgBinOper;
8#[cfg(feature = "backend-sqlite")]
9use crate::extension::sqlite::SqliteBinOper;
10
11mod qualification;
12
13pub use qualification::{MaybeQualifiedOnce, MaybeQualifiedTwice};
14
15/// A reference counted pointer: either [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc],
16/// depending on the feature flags.
17///
18/// [`Arc`][std::sync::Arc] is used when `thread-safe` feature is activated.
19#[cfg(not(feature = "thread-safe"))]
20pub type RcOrArc<T> = std::rc::Rc<T>;
21/// A reference counted pointer: either [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc],
22/// depending on the feature flags.
23///
24/// [`Arc`][std::sync::Arc] is used when `thread-safe` feature is activated.
25#[cfg(feature = "thread-safe")]
26pub type RcOrArc<T> = std::sync::Arc<T>;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct Quote(pub(crate) u8, pub(crate) u8);
30
31/// Identifier
32pub trait Iden {
33    /// Return the to-be sanitized version of the identifier.
34    ///
35    /// For example, for MySQL "hel`lo`" would have to be escaped as "hel``lo".
36    /// Note that this method doesn't do the actual escape,
37    /// as it's backend specific.
38    /// It only indicates whether the identifier needs to be escaped.
39    ///
40    /// If the identifier doesn't need to be escaped, return `'static str`.
41    /// This can be deduced at compile-time by the `Iden` macro,
42    /// or using the [`is_static_iden`] function.
43    ///
44    /// `Cow::Owned` would always be escaped.
45    fn quoted(&self) -> Cow<'static, str> {
46        Cow::Owned(self.to_string())
47    }
48
49    /// A shortcut for writing an [`unquoted`][Iden::unquoted]
50    /// identifier into a [`String`].
51    ///
52    /// We can't reuse [`ToString`] for this, because [`ToString`] uses
53    /// the [`Display`][std::fmt::Display] representation. But [`Iden`]
54    /// representation is distinct from [`Display`][std::fmt::Display]
55    /// and can be different.
56    fn to_string(&self) -> String {
57        self.unquoted().to_owned()
58    }
59
60    /// Write a raw identifier string without quotes.
61    ///
62    /// We intentionally don't reuse [`Display`][std::fmt::Display] for
63    /// this, because we want to allow it to have a different logic.
64    fn unquoted(&self) -> &str;
65}
66
67/// Identifier statically known at compile-time.
68pub trait IdenStatic: Iden + Copy + 'static {
69    fn as_str(&self) -> &'static str;
70}
71
72/// A prepared (quoted) identifier string.
73///
74/// The naming is legacy and kept for compatibility.
75/// This used to be an alias for a `dyn Iden` object that's lazily rendered later.
76///
77/// Nowadays, it's an eagerly-rendered string.
78/// Most identifiers are static strings that aren't "rendered" at runtime anyway.
79#[derive(Debug, Clone, PartialEq, Eq, Hash)]
80pub struct DynIden(pub(crate) Cow<'static, str>);
81
82impl DynIden {
83    pub fn inner(&self) -> Cow<'static, str> {
84        self.0.clone()
85    }
86}
87
88/// A legacy namespace for compatibility.
89///
90/// It's needed, so that most existing [`SeaRc::new`][SeaRc::new] calls keep working.
91///
92/// This used to be an actual type
93/// (a reference-counted pointer with special impls for `dyn Iden` contents).
94/// It's not needed anymore.
95#[derive(Debug)]
96pub struct SeaRc;
97
98impl SeaRc {
99    /// A legacy method, kept for compatibility.
100    ///
101    /// Nowadays, instead of wrapping an `Iden` object,
102    /// it eagerly "renders" it into a string and then drops the object.
103    ///
104    /// Note that most `Iden`s are statically known
105    /// and their representations aren't actually "rendered" and allocated at runtime.
106    #[allow(clippy::new_ret_no_self)]
107    pub fn new<I>(i: I) -> DynIden
108    where
109        I: Iden,
110    {
111        DynIden(i.quoted())
112    }
113
114    pub fn clone(iden: &DynIden) -> DynIden {
115        iden.clone()
116    }
117}
118
119impl std::fmt::Display for DynIden {
120    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
121        f.write_str(&self.0)
122    }
123}
124
125pub trait IntoIden: Into<DynIden> {
126    fn into_iden(self) -> DynIden;
127}
128
129impl<T> IntoIden for T
130where
131    T: Into<DynIden>,
132{
133    fn into_iden(self) -> DynIden {
134        self.into()
135    }
136}
137
138pub trait IdenList {
139    type IntoIter: Iterator<Item = DynIden>;
140
141    fn into_iter(self) -> Self::IntoIter;
142}
143
144/// An identifier that represents a database name.
145#[derive(Debug, Clone, PartialEq, Eq, Hash)]
146pub struct DatabaseName(pub DynIden);
147
148/// A schema name, potentially qualified as `(database.)schema`.
149#[derive(Debug, Clone, PartialEq, Eq, Hash)]
150pub struct SchemaName(pub Option<DatabaseName>, pub DynIden);
151
152/// An SQL type name, potentially qualified as `(database.)(schema.)type`.
153#[derive(Debug, Clone, PartialEq, Eq, Hash)]
154pub struct TypeRef(pub Option<SchemaName>, pub DynIden);
155
156pub trait IntoTypeRef: Into<TypeRef> {
157    fn into_type_ref(self) -> TypeRef;
158}
159
160impl<T> IntoTypeRef for T
161where
162    T: Into<TypeRef>,
163{
164    fn into_type_ref(self) -> TypeRef {
165        self.into()
166    }
167}
168
169/// A table name, potentially qualified as `(database.)(schema.)table`.
170#[derive(Debug, Clone, PartialEq, Eq, Hash)]
171pub struct TableName(pub Option<SchemaName>, pub DynIden);
172
173impl TableName {
174    /// A flat `(db?, schema?, table)` tuple view, for quick pattern matching.
175    ///
176    /// Don't use this if you need exhaustiveness.
177    /// The return type is too lax and allows invalid shapes like `(Some(_), None, _)`.
178    pub(crate) fn as_iden_tuple(&self) -> (Option<&DynIden>, Option<&DynIden>, &DynIden) {
179        let TableName(schema_name, table) = self;
180        match schema_name {
181            None => (None, None, table),
182            Some(SchemaName(db_name, schema)) => match db_name {
183                None => (None, Some(schema), table),
184                Some(DatabaseName(db)) => (Some(db), Some(schema), table),
185            },
186        }
187    }
188}
189
190/// A column name, potentially qualified as `(database.)(schema.)(table.)column`.
191#[derive(Debug, Clone, PartialEq, Eq, Hash)]
192pub struct ColumnName(pub Option<TableName>, pub DynIden);
193
194/// Iteration over `[db?, schema?, table?, column]` identifiers.
195impl IdenList for ColumnName {
196    type IntoIter = Flatten<std::array::IntoIter<Option<DynIden>, 4>>;
197
198    /// Iteration over `[db?, schema?, table?, column]` identifiers.
199    fn into_iter(self) -> Self::IntoIter {
200        let ColumnName(table_name, column) = self;
201        let arr = match table_name {
202            None => [None, None, None, Some(column)],
203            Some(TableName(schema_name, table)) => match schema_name {
204                None => [None, None, Some(table), Some(column)],
205                Some(SchemaName(db_name, schema)) => {
206                    let db = db_name.map(|db| db.0);
207                    [db, Some(schema), Some(table), Some(column)]
208                }
209            },
210        };
211        arr.into_iter().flatten()
212    }
213}
214
215/// Column references.
216#[derive(Debug, Clone, PartialEq)]
217#[non_exhaustive]
218pub enum ColumnRef {
219    /// A column name, potentially qualified as `(database.)(schema.)(table.)column`.
220    Column(ColumnName),
221    /// An `*` expression, potentially qualified as `(database.)(schema.)(table.)*`.
222    Asterisk(Option<TableName>),
223}
224
225impl ColumnRef {
226    #[doc(hidden)]
227    /// Returns the unqualified column name if it's not an asterisk.
228    pub fn column(&self) -> Option<&DynIden> {
229        match self {
230            ColumnRef::Column(ColumnName(_table_ref, column_itself)) => Some(column_itself),
231            ColumnRef::Asterisk(..) => None,
232        }
233    }
234}
235
236pub trait IntoColumnRef: Into<ColumnRef> {
237    fn into_column_ref(self) -> ColumnRef;
238}
239
240impl<T> IntoColumnRef for T
241where
242    T: Into<ColumnRef>,
243{
244    fn into_column_ref(self) -> ColumnRef {
245        self.into()
246    }
247}
248
249impl<T> From<T> for ColumnRef
250where
251    T: Into<ColumnName>,
252{
253    fn from(value: T) -> Self {
254        ColumnRef::Column(value.into())
255    }
256}
257
258impl From<Asterisk> for ColumnRef {
259    fn from(_: Asterisk) -> Self {
260        ColumnRef::Asterisk(None)
261    }
262}
263
264impl<T> From<(T, Asterisk)> for ColumnRef
265where
266    T: IntoIden,
267{
268    fn from(value: (T, Asterisk)) -> Self {
269        ColumnRef::Asterisk(Some(value.0.into_iden().into()))
270    }
271}
272
273/// Table references
274#[derive(Debug, Clone, PartialEq)]
275#[non_exhaustive]
276pub enum TableRef {
277    /// A table identifier with optional Alias. Potentially qualified.
278    Table(TableName, Option<DynIden>),
279    /// Subquery with alias
280    SubQuery(Box<SelectStatement>, DynIden),
281    /// Values list with alias
282    ValuesList(Vec<ValueTuple>, DynIden),
283    /// Function call with alias
284    FunctionCall(FunctionCall, DynIden),
285}
286
287impl TableRef {
288    #[doc(hidden)]
289    pub fn sea_orm_table(&self) -> &DynIden {
290        match self {
291            TableRef::Table(TableName(_, tbl), _)
292            | TableRef::SubQuery(_, tbl)
293            | TableRef::ValuesList(_, tbl)
294            | TableRef::FunctionCall(_, tbl) => tbl,
295        }
296    }
297
298    #[doc(hidden)]
299    pub fn sea_orm_table_alias(&self) -> Option<&DynIden> {
300        match self {
301            TableRef::Table(_, None) | TableRef::SubQuery(_, _) | TableRef::ValuesList(_, _) => {
302                None
303            }
304            TableRef::Table(_, Some(alias)) | TableRef::FunctionCall(_, alias) => Some(alias),
305        }
306    }
307}
308
309pub trait IntoTableRef: Into<TableRef> {
310    fn into_table_ref(self) -> TableRef {
311        self.into()
312    }
313}
314
315impl<T> IntoTableRef for T where T: Into<TableRef> {}
316
317impl<T> From<T> for TableRef
318where
319    T: Into<TableName>,
320{
321    fn from(value: T) -> Self {
322        TableRef::Table(value.into(), None)
323    }
324}
325
326/// Unary operators.
327#[derive(Debug, Clone, Copy, PartialEq, Eq)]
328#[non_exhaustive]
329pub enum UnOper {
330    Not,
331}
332
333/// Binary operators.
334///
335/// If something is not supported here, you can use [`BinOper::Custom`].
336#[derive(Debug, Clone, Copy, PartialEq, Eq)]
337#[non_exhaustive]
338pub enum BinOper {
339    And,
340    Or,
341    Like,
342    NotLike,
343    Is,
344    IsNot,
345    In,
346    NotIn,
347    Between,
348    NotBetween,
349    Equal,
350    NotEqual,
351    SmallerThan,
352    GreaterThan,
353    SmallerThanOrEqual,
354    GreaterThanOrEqual,
355    Add,
356    Sub,
357    Mul,
358    Div,
359    Mod,
360    BitAnd,
361    BitOr,
362    LShift,
363    RShift,
364    As,
365    Escape,
366    Custom(&'static str),
367    #[cfg(feature = "backend-postgres")]
368    PgOperator(PgBinOper),
369    #[cfg(feature = "backend-sqlite")]
370    SqliteOperator(SqliteBinOper),
371}
372
373/// Logical chain operator: conjunction or disjunction.
374#[derive(Debug, Clone, PartialEq)]
375pub enum LogicalChainOper {
376    And(Expr),
377    Or(Expr),
378}
379
380/// Join types
381#[derive(Debug, Clone, Copy, PartialEq, Eq)]
382pub enum JoinType {
383    Join,
384    CrossJoin,
385    InnerJoin,
386    LeftJoin,
387    RightJoin,
388    FullOuterJoin,
389}
390
391/// Nulls order
392#[derive(Debug, Clone, Copy, PartialEq, Eq)]
393pub enum NullOrdering {
394    First,
395    Last,
396}
397
398/// Order expression
399#[derive(Debug, Clone, PartialEq)]
400pub struct OrderExpr {
401    pub(crate) expr: Expr,
402    pub(crate) order: Order,
403    pub(crate) nulls: Option<NullOrdering>,
404}
405
406/// Join on types
407#[derive(Debug, Clone, PartialEq)]
408#[non_exhaustive]
409pub enum JoinOn {
410    Condition(Box<ConditionHolder>),
411    Columns(Vec<Expr>),
412}
413
414/// Ordering options
415#[derive(Debug, Clone, PartialEq)]
416#[non_exhaustive]
417pub enum Order {
418    Asc,
419    Desc,
420    Field(Values),
421}
422
423/// An explicit wrapper for [`Iden`]s which are dynamic user-provided strings.
424///
425/// Nowadays, `&str` implements [`Iden`] and can be used directly.
426#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
427pub struct Alias(pub String);
428
429/// Null Alias
430#[derive(Default, Debug, Copy, Clone)]
431pub struct NullAlias;
432
433/// Asterisk ("*")
434///
435/// Express the asterisk without table prefix.
436///
437/// # Examples
438///
439/// ```
440/// use sea_query::{tests_cfg::*, *};
441///
442/// let query = Query::select()
443///     .column(Asterisk)
444///     .from(Char::Table)
445///     .to_owned();
446///
447/// assert_eq!(
448///     query.to_string(MysqlQueryBuilder),
449///     r#"SELECT * FROM `character`"#
450/// );
451/// assert_eq!(
452///     query.to_string(PostgresQueryBuilder),
453///     r#"SELECT * FROM "character""#
454/// );
455/// assert_eq!(
456///     query.to_string(SqliteQueryBuilder),
457///     r#"SELECT * FROM "character""#
458/// );
459/// ```
460///
461/// Express the asterisk with table prefix.
462///
463/// Examples
464///
465/// ```
466/// use sea_query::{tests_cfg::*, *};
467///
468/// let query = Query::select()
469///     .column((Char::Table, Asterisk))
470///     .from(Char::Table)
471///     .to_owned();
472///
473/// assert_eq!(
474///     query.to_string(MysqlQueryBuilder),
475///     r#"SELECT `character`.* FROM `character`"#
476/// );
477/// assert_eq!(
478///     query.to_string(PostgresQueryBuilder),
479///     r#"SELECT "character".* FROM "character""#
480/// );
481/// assert_eq!(
482///     query.to_string(SqliteQueryBuilder),
483///     r#"SELECT "character".* FROM "character""#
484/// );
485/// ```
486#[derive(Default, Debug, Clone, Copy)]
487pub struct Asterisk;
488
489/// Known SQL keywords that can be used as expressions.
490///
491/// If something is not supported here, you can use [`Keyword::Custom`].
492#[derive(Debug, Clone, PartialEq)]
493#[non_exhaustive]
494pub enum Keyword {
495    Null,
496    CurrentDate,
497    CurrentTime,
498    CurrentTimestamp,
499    Default,
500    Custom(DynIden),
501}
502
503/// Like Expression
504#[derive(Debug, Clone)]
505pub struct LikeExpr {
506    pub(crate) pattern: String,
507    pub(crate) escape: Option<char>,
508}
509
510pub trait IntoLikeExpr: Into<LikeExpr> {
511    fn into_like_expr(self) -> LikeExpr;
512}
513
514impl<T> IntoLikeExpr for T
515where
516    T: Into<LikeExpr>,
517{
518    fn into_like_expr(self) -> LikeExpr {
519        self.into()
520    }
521}
522
523/// SubQuery operators
524#[derive(Debug, Copy, Clone, PartialEq)]
525#[non_exhaustive]
526pub enum SubQueryOper {
527    Exists,
528    Any,
529    Some,
530    All,
531}
532
533// Impl begins
534
535impl Quote {
536    pub fn new(c: u8) -> Self {
537        Self(c, c)
538    }
539
540    pub fn left(&self) -> char {
541        char::from(self.0)
542    }
543
544    pub fn right(&self) -> char {
545        char::from(self.1)
546    }
547}
548
549impl From<char> for Quote {
550    fn from(c: char) -> Self {
551        (c as u8).into()
552    }
553}
554
555impl From<(char, char)> for Quote {
556    fn from((l, r): (char, char)) -> Self {
557        (l as u8, r as u8).into()
558    }
559}
560
561impl From<u8> for Quote {
562    fn from(u8: u8) -> Self {
563        Quote::new(u8)
564    }
565}
566
567impl From<(u8, u8)> for Quote {
568    fn from((l, r): (u8, u8)) -> Self {
569        Quote(l, r)
570    }
571}
572
573impl<T> From<T> for DynIden
574where
575    T: Iden,
576{
577    fn from(iden: T) -> Self {
578        DynIden(iden.quoted())
579    }
580}
581
582impl<I> IdenList for I
583where
584    I: IntoIden,
585{
586    type IntoIter = std::iter::Once<DynIden>;
587
588    fn into_iter(self) -> Self::IntoIter {
589        std::iter::once(self.into_iden())
590    }
591}
592
593impl<A, B> IdenList for (A, B)
594where
595    A: IntoIden,
596    B: IntoIden,
597{
598    type IntoIter = std::array::IntoIter<DynIden, 2>;
599
600    fn into_iter(self) -> Self::IntoIter {
601        [self.0.into_iden(), self.1.into_iden()].into_iter()
602    }
603}
604
605impl<A, B, C> IdenList for (A, B, C)
606where
607    A: IntoIden,
608    B: IntoIden,
609    C: IntoIden,
610{
611    type IntoIter = std::array::IntoIter<DynIden, 3>;
612
613    fn into_iter(self) -> Self::IntoIter {
614        [self.0.into_iden(), self.1.into_iden(), self.2.into_iden()].into_iter()
615    }
616}
617
618impl<T> From<T> for DatabaseName
619where
620    T: IntoIden,
621{
622    fn from(iden: T) -> Self {
623        DatabaseName(iden.into_iden())
624    }
625}
626
627impl TableRef {
628    /// Add or replace the current alias
629    pub fn alias<A>(self, alias: A) -> Self
630    where
631        A: IntoIden,
632    {
633        match self {
634            Self::Table(table, _) => Self::Table(table, Some(alias.into_iden())),
635            Self::SubQuery(statement, _) => Self::SubQuery(statement, alias.into_iden()),
636            Self::ValuesList(values, _) => Self::ValuesList(values, alias.into_iden()),
637            Self::FunctionCall(func, _) => Self::FunctionCall(func, alias.into_iden()),
638        }
639    }
640}
641
642impl Alias {
643    pub fn new<T>(n: T) -> Self
644    where
645        T: Into<String>,
646    {
647        Self(n.into())
648    }
649}
650
651impl Iden for Alias {
652    fn quoted(&self) -> Cow<'static, str> {
653        Cow::Owned(self.0.clone())
654    }
655
656    fn unquoted(&self) -> &str {
657        &self.0
658    }
659}
660
661impl From<String> for DynIden {
662    fn from(value: String) -> Self {
663        DynIden(Cow::Owned(value))
664    }
665}
666
667impl Iden for &'static str {
668    fn quoted(&self) -> Cow<'static, str> {
669        if is_static_iden(self) {
670            Cow::Borrowed(self)
671        } else {
672            Cow::Owned(String::from(*self))
673        }
674    }
675
676    fn unquoted(&self) -> &str {
677        self
678    }
679}
680
681/// Return whether this identifier needs to be escaped.
682/// Right now we're very safe and only return true for identifiers
683/// composed of `a-zA-Z0-9_`.
684///
685/// ```
686/// use sea_query::is_static_iden;
687///
688/// assert!(is_static_iden("abc"));
689/// assert!(is_static_iden("a_b_c"));
690/// assert!(!is_static_iden("a-b-c"));
691/// assert!(is_static_iden("abc123"));
692/// assert!(!is_static_iden("123abc"));
693/// assert!(!is_static_iden("a|b|c"));
694/// assert!(!is_static_iden("a'b'c"));
695/// ```
696pub const fn is_static_iden(string: &str) -> bool {
697    let bytes = string.as_bytes();
698    if bytes.is_empty() {
699        return true;
700    }
701
702    // can only begin with [a-z_]
703    if bytes[0] == b'_' || (bytes[0] as char).is_ascii_alphabetic() {
704        // good
705    } else {
706        return false;
707    }
708
709    let mut i = 1;
710    while i < bytes.len() {
711        if bytes[i] == b'_' || (bytes[i] as char).is_ascii_alphanumeric() {
712            // good
713        } else {
714            return false;
715        }
716        i += 1;
717    }
718
719    true
720}
721
722impl NullAlias {
723    pub fn new() -> Self {
724        Self
725    }
726}
727
728impl Iden for NullAlias {
729    fn unquoted(&self) -> &str {
730        ""
731    }
732}
733
734impl LikeExpr {
735    pub fn new<T>(pattern: T) -> Self
736    where
737        T: Into<String>,
738    {
739        Self {
740            pattern: pattern.into(),
741            escape: None,
742        }
743    }
744
745    #[deprecated(since = "0.29.0", note = "Please use the [`LikeExpr::new`] method")]
746    pub fn str<T>(pattern: T) -> Self
747    where
748        T: Into<String>,
749    {
750        Self {
751            pattern: pattern.into(),
752            escape: None,
753        }
754    }
755
756    pub fn escape(self, c: char) -> Self {
757        Self {
758            pattern: self.pattern,
759            escape: Some(c),
760        }
761    }
762}
763
764impl<T> From<T> for LikeExpr
765where
766    T: Into<String>,
767{
768    fn from(value: T) -> Self {
769        LikeExpr::new(value)
770    }
771}
772
773#[cfg(test)]
774mod tests {
775    pub use crate::{tests_cfg::*, *};
776    pub use Character as CharReexport;
777    use pretty_assertions::assert_eq;
778
779    #[test]
780    fn test_identifier() {
781        let query = Query::select().column("hello-World_").to_owned();
782
783        #[cfg(feature = "backend-mysql")]
784        assert_eq!(query.to_string(MysqlQueryBuilder), r"SELECT `hello-World_`");
785        #[cfg(feature = "backend-postgres")]
786        assert_eq!(
787            query.to_string(PostgresQueryBuilder),
788            r#"SELECT "hello-World_""#
789        );
790        #[cfg(feature = "backend-sqlite")]
791        assert_eq!(
792            query.to_string(SqliteQueryBuilder),
793            r#"SELECT "hello-World_""#
794        );
795    }
796
797    #[test]
798    fn test_quoted_identifier_1() {
799        let query = Query::select().column("hel`lo").to_owned();
800
801        #[cfg(feature = "backend-mysql")]
802        assert_eq!(query.to_string(MysqlQueryBuilder), r"SELECT `hel``lo`");
803        #[cfg(feature = "backend-sqlite")]
804        assert_eq!(query.to_string(SqliteQueryBuilder), r#"SELECT "hel`lo""#);
805
806        let query = Query::select().column("hel\"lo").to_owned();
807
808        #[cfg(feature = "backend-postgres")]
809        assert_eq!(query.to_string(PostgresQueryBuilder), r#"SELECT "hel""lo""#);
810    }
811
812    #[test]
813    fn test_quoted_identifier_2() {
814        let query = Query::select().column("hel``lo").to_owned();
815
816        #[cfg(feature = "backend-mysql")]
817        assert_eq!(query.to_string(MysqlQueryBuilder), r"SELECT `hel````lo`");
818        #[cfg(feature = "backend-sqlite")]
819        assert_eq!(query.to_string(SqliteQueryBuilder), r#"SELECT "hel``lo""#);
820
821        let query = Query::select().column("hel\"\"lo").to_owned();
822
823        #[cfg(feature = "backend-postgres")]
824        assert_eq!(
825            query.to_string(PostgresQueryBuilder),
826            r#"SELECT "hel""""lo""#
827        );
828    }
829
830    #[test]
831    fn test_cmp_identifier() {
832        type CharLocal = Character;
833
834        assert_eq!(
835            ColumnRef::Column(Character::Id.into()),
836            ColumnRef::Column(Character::Id.into())
837        );
838        assert_eq!(
839            ColumnRef::Column(Character::Id.into()),
840            ColumnRef::Column(Char::Id.into())
841        );
842        assert_eq!(
843            ColumnRef::Column(Character::Id.into()),
844            ColumnRef::Column(CharLocal::Id.into())
845        );
846        assert_eq!(
847            ColumnRef::Column(Character::Id.into()),
848            ColumnRef::Column(CharReexport::Id.into())
849        );
850        assert_eq!(
851            ColumnRef::Column("id".into()),
852            ColumnRef::Column("id".into())
853        );
854        assert_ne!(
855            ColumnRef::Column("id".into()),
856            ColumnRef::Column("id_".into())
857        );
858        assert_eq!(
859            ColumnRef::Column(Character::Id.into()),
860            ColumnRef::Column("id".into())
861        );
862        assert_ne!(
863            ColumnRef::Column(Character::Id.into()),
864            ColumnRef::Column(Character::Table.into())
865        );
866        assert_eq!(
867            ColumnRef::Column(Character::Id.into()),
868            ColumnRef::Column(Font::Id.into())
869        );
870    }
871}