surrealdb_core/sql/
regex.rs1use crate::cnf::REGEX_CACHE_SIZE;
2use quick_cache::sync::{Cache, GuardResult};
3use revision::revisioned;
4use serde::{
5 de::{self, Visitor},
6 Deserialize, Deserializer, Serialize, Serializer,
7};
8use std::cmp::Ordering;
9use std::fmt::Debug;
10use std::fmt::{self, Display, Formatter};
11use std::hash::{Hash, Hasher};
12use std::str;
13use std::str::FromStr;
14use std::sync::LazyLock;
15
16pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Regex";
17
18#[revisioned(revision = 1)]
19#[derive(Clone)]
20#[non_exhaustive]
21pub struct Regex(pub regex::Regex);
22
23impl Regex {
24 pub fn regex(&self) -> ®ex::Regex {
26 &self.0
27 }
28}
29
30fn regex_new(str: &str) -> Result<regex::Regex, regex::Error> {
31 static REGEX_CACHE: LazyLock<Cache<String, regex::Regex>> =
32 LazyLock::new(|| Cache::new(REGEX_CACHE_SIZE.max(10)));
33 match REGEX_CACHE.get_value_or_guard(str, None) {
34 GuardResult::Value(v) => Ok(v),
35 GuardResult::Guard(g) => {
36 let re = regex::Regex::new(str)?;
37 g.insert(re.clone()).ok();
38 Ok(re)
39 }
40 GuardResult::Timeout => {
41 warn!("Regex cache timeout");
42 regex::Regex::new(str)
43 }
44 }
45}
46
47impl FromStr for Regex {
48 type Err = <regex::Regex as FromStr>::Err;
49
50 fn from_str(s: &str) -> Result<Self, Self::Err> {
51 if s.contains('\0') {
52 Err(regex::Error::Syntax("regex contained NUL byte".to_owned()))
53 } else {
54 regex_new(&s.replace("\\/", "/")).map(Self)
55 }
56 }
57}
58
59impl PartialEq for Regex {
60 fn eq(&self, other: &Self) -> bool {
61 let str_left = self.0.as_str();
62 let str_right = other.0.as_str();
63 str_left == str_right
64 }
65}
66
67impl Eq for Regex {}
68
69impl Ord for Regex {
70 fn cmp(&self, other: &Self) -> Ordering {
71 self.0.as_str().cmp(other.0.as_str())
72 }
73}
74
75impl PartialOrd for Regex {
76 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
77 Some(self.cmp(other))
78 }
79}
80
81impl Hash for Regex {
82 fn hash<H: Hasher>(&self, state: &mut H) {
83 self.0.as_str().hash(state);
84 }
85}
86
87impl Debug for Regex {
88 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
89 Display::fmt(self, f)
90 }
91}
92
93impl Display for Regex {
94 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
95 let t = self.0.to_string().replace('/', "\\/");
96 write!(f, "/{}/", &t)
97 }
98}
99
100impl Serialize for Regex {
101 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
102 where
103 S: Serializer,
104 {
105 serializer.serialize_newtype_struct(TOKEN, self.0.as_str())
106 }
107}
108
109impl<'de> Deserialize<'de> for Regex {
110 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
111 where
112 D: Deserializer<'de>,
113 {
114 struct RegexNewtypeVisitor;
115
116 impl<'de> Visitor<'de> for RegexNewtypeVisitor {
117 type Value = Regex;
118
119 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
120 formatter.write_str("a regex newtype")
121 }
122
123 fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
124 where
125 D: Deserializer<'de>,
126 {
127 struct RegexVisitor;
128
129 impl Visitor<'_> for RegexVisitor {
130 type Value = Regex;
131
132 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
133 formatter.write_str("a regex str")
134 }
135
136 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
137 where
138 E: de::Error,
139 {
140 Regex::from_str(value).map_err(|_| de::Error::custom("invalid regex"))
141 }
142 }
143
144 deserializer.deserialize_str(RegexVisitor)
145 }
146 }
147
148 deserializer.deserialize_newtype_struct(TOKEN, RegexNewtypeVisitor)
149 }
150}