1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
mod lquery;
mod ltxtquery;

pub use lquery::*;
pub use ltxtquery::*;

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Ltree(Vec<String>);

impl From<Vec<String>> for Ltree {
    fn from(v: Vec<String>) -> Self {
        Self(v)
    }
}

impl From<Ltree> for Vec<String> {
    fn from(ltree: Ltree) -> Self {
        ltree.0
    }
}

impl ToString for Ltree {
    fn to_string(&self) -> String {
        self.0.join(".")
    }
}

impl std::str::FromStr for Ltree {
    type Err = std::convert::Infallible;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let ltree = if s.is_empty() {
            Self::default()
        } else {
            Self(s.split('.').map(ToString::to_string).collect())
        };

        Ok(ltree)
    }
}

impl std::ops::Deref for Ltree {
    type Target = Vec<String>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl std::ops::DerefMut for Ltree {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl crate::ToSql for Ltree {
    fn ty(&self) -> crate::pq::Type {
        crate::pq::Type {
            descr: "LTREE - data type for hierarchical tree-like structures",
            name: "ltree",

            ..crate::pq::types::UNKNOWN
        }
    }

    /*
     * https://github.com/postgres/postgres/blob/REL_14_STABLE/contrib/ltree/ltree_io.c#L172
     */
    fn to_text(&self) -> crate::Result<Option<String>> {
        self.to_string().to_text()
    }

    /*
     * https://github.com/postgres/postgres/blob/REL_14_STABLE/contrib/ltree/ltree_io.c#L223
     */
    fn to_binary(&self) -> crate::Result<Option<Vec<u8>>> {
        format!("\u{1}{}", self.to_string()).to_binary()
    }
}

impl crate::FromSql for Ltree {
    /*
     * https://github.com/postgres/postgres/blob/REL_14_STABLE/contrib/ltree/ltree_io.c#L181
     */
    fn from_text(ty: &crate::pq::Type, raw: Option<&str>) -> crate::Result<Self> {
        String::from_text(ty, raw).map(|x| x.parse().map_err(crate::Error::Infallible))?
    }

    /*
     * https://github.com/postgres/postgres/blob/REL_14_STABLE/contrib/ltree/ltree_io.c#L223
     */
    fn from_binary(ty: &crate::pq::Type, raw: Option<&[u8]>) -> crate::Result<Self> {
        let mut buf = crate::not_null(raw)?;

        let _version = crate::from_sql::read_i8(&mut buf)?;
        String::from_binary(ty, Some(buf)).map(|x| x.parse().map_err(crate::Error::Infallible))?
    }
}

impl crate::entity::Simple for Ltree {}

#[cfg(test)]
mod test {
    crate::sql_test!(
        ltree,
        crate::Ltree,
        [
            ("''", crate::Ltree::default()),
            (
                "'Top.Countries.Europe.Russia'",
                crate::Ltree::from(vec![
                    "Top".to_string(),
                    "Countries".to_string(),
                    "Europe".to_string(),
                    "Russia".to_string()
                ])
            ),
        ]
    );
}