Skip to main content

qusql_parse/
identifier.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5// http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12
13use crate::{Span, Spanned};
14
15/// Compare `a` byte-for-byte against `b` with `b`'s bytes lowercased.
16fn ord_verbatim_vs_lowercased(a: &str, b: &str) -> core::cmp::Ordering {
17    for (a_byte, b_byte) in a.bytes().zip(b.bytes()) {
18        let b_lower = b_byte.to_ascii_lowercase();
19        if a_byte != b_lower {
20            return a_byte.cmp(&b_lower);
21        }
22    }
23    a.len().cmp(&b.len())
24}
25
26/// Compare two strings in ASCII case-insensitive manner, returning their ordering.
27fn ord_ignore_ascii_case(a: &str, b: &str) -> core::cmp::Ordering {
28    for (a_byte, b_byte) in a.bytes().zip(b.bytes()) {
29        let a_lower = a_byte.to_ascii_lowercase();
30        let b_lower = b_byte.to_ascii_lowercase();
31        if a_lower != b_lower {
32            return a_lower.cmp(&b_lower);
33        }
34    }
35    a.len().cmp(&b.len())
36}
37
38/// Simple identifier in code
39/// it derefs to its string value
40#[derive(Clone, Debug)]
41pub struct Identifier<'a> {
42    /// Identifier string
43    pub value: &'a str,
44    /// Span of the value
45    pub span: Span,
46    /// Whether the identifier is case-sensitive (e.g. double-quoted in PostgreSQL)
47    pub case_sensitive: bool,
48}
49
50impl<'a> PartialEq for Identifier<'a> {
51    fn eq(&self, other: &Self) -> bool {
52        self.cmp(other) == core::cmp::Ordering::Equal
53    }
54}
55impl<'a> Eq for Identifier<'a> {}
56
57impl<'a> PartialOrd for Identifier<'a> {
58    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
59        Some(self.cmp(other))
60    }
61}
62
63impl<'a> Ord for Identifier<'a> {
64    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
65        match (self.case_sensitive, other.case_sensitive) {
66            (false, false) => ord_ignore_ascii_case(self.value, other.value),
67            (true, true) => self.value.cmp(other.value),
68            (true, false) => ord_verbatim_vs_lowercased(self.value, other.value),
69            (false, true) => ord_verbatim_vs_lowercased(other.value, self.value).reverse(),
70        }
71    }
72}
73
74impl<'a> alloc::fmt::Display for Identifier<'a> {
75    fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
76        self.value.fmt(f)
77    }
78}
79
80impl<'a> Identifier<'a> {
81    /// Produce new identifier given value and span (unquoted, case-insensitive)
82    pub fn new(value: &'a str, span: Span) -> Self {
83        Identifier {
84            value,
85            span,
86            case_sensitive: false,
87        }
88    }
89
90    /// Produce a case-sensitive identifier (e.g. PostgreSQL `"Foo"`)
91    pub fn new_case_sensitive(value: &'a str, span: Span) -> Self {
92        Identifier {
93            value,
94            span,
95            case_sensitive: true,
96        }
97    }
98
99    /// Get the string representation of the identifier
100    pub fn as_str(&self) -> &'a str {
101        self.value
102    }
103}
104
105impl<'a> core::ops::Deref for Identifier<'a> {
106    type Target = str;
107
108    fn deref(&self) -> &'a Self::Target {
109        self.value
110    }
111}
112
113impl<'a> Spanned for Identifier<'a> {
114    fn span(&self) -> Span {
115        self.span.span()
116    }
117}