Skip to main content

surrealdb_strand/
table.rs

1use std::borrow::{Borrow, Cow};
2use std::fmt::{self, Display};
3use std::io::{BufRead, Read, Write};
4
5use revision::{DeserializeRevisioned, Revisioned, SerializeRevisioned, SkipRevisioned};
6use storekey::{BorrowDecode, Decode, Encode};
7
8use crate::Strand;
9
10/// A table name, as the engine holds it and as it is encoded to storage.
11///
12/// Deliberately carries no rendering and no conversion to the public wire type.
13/// Both belong to the language layer, where a name is escaped if it would
14/// otherwise read back as a reserved word — see `surrealdb_sql::TableName`.
15/// Keeping them off this type is what lets this crate stay a string primitive
16/// rather than depending on the formatting and public-type crates.
17///
18/// It also deliberately does not deref to `str`. `str` implements `ToSql`, so
19/// while it did, writing `name.fmt_sql(..)` resolved to the unescaped string
20/// rendering instead of failing to compile, and a name that could not be read
21/// back was emitted. Reaching the text requires [`TableName::as_str`], which
22/// makes the choice of escaper explicit at every call site.
23///
24/// That absence is load-bearing rather than incidental, so it is pinned here:
25/// re-adding the impl makes this compile, and the test fails.
26///
27/// ```compile_fail
28/// let name = surrealdb_strand::TableName::from("person");
29/// let _: &str = &name;
30/// ```
31#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
32#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
33#[repr(transparent)]
34pub struct TableName(Strand);
35
36impl TableName {
37	/// Create a new table name.
38	pub fn new(s: impl Into<Strand>) -> TableName {
39		TableName(s.into())
40	}
41
42	pub fn into_string(self) -> String {
43		self.0.into()
44	}
45
46	pub fn as_str(&self) -> &str {
47		self.0.as_str()
48	}
49
50	pub fn is_table_type(&self, tables: &[TableName]) -> bool {
51		tables.is_empty() || tables.contains(self)
52	}
53}
54
55impl From<String> for TableName {
56	fn from(value: String) -> Self {
57		TableName(value.into())
58	}
59}
60
61impl From<TableName> for String {
62	fn from(value: TableName) -> Self {
63		value.0.into()
64	}
65}
66
67impl From<&str> for TableName {
68	fn from(value: &str) -> Self {
69		TableName(Strand::from(value))
70	}
71}
72
73impl From<Strand> for TableName {
74	fn from(value: Strand) -> Self {
75		TableName(value)
76	}
77}
78
79impl From<TableName> for Strand {
80	fn from(value: TableName) -> Self {
81		value.0
82	}
83}
84
85impl<'a> From<TableName> for Cow<'a, str> {
86	fn from(value: TableName) -> Self {
87		Cow::Owned(value.into_string())
88	}
89}
90
91impl PartialEq<TableName> for &TableName {
92	fn eq(&self, other: &TableName) -> bool {
93		self.0 == other.0
94	}
95}
96
97impl PartialEq<str> for TableName {
98	fn eq(&self, other: &str) -> bool {
99		self.as_str() == other
100	}
101}
102
103impl PartialEq<TableName> for str {
104	fn eq(&self, other: &TableName) -> bool {
105		self == other.as_str()
106	}
107}
108
109impl PartialEq<&str> for TableName {
110	fn eq(&self, other: &&str) -> bool {
111		self.as_str() == *other
112	}
113}
114
115impl PartialEq<String> for TableName {
116	fn eq(&self, other: &String) -> bool {
117		self.as_str() == other.as_str()
118	}
119}
120
121impl AsRef<str> for TableName {
122	fn as_ref(&self) -> &str {
123		self.as_str()
124	}
125}
126
127impl Display for TableName {
128	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129		Display::fmt(self.as_str(), f)
130	}
131}
132
133impl Borrow<str> for TableName {
134	fn borrow(&self) -> &str {
135		self.as_str()
136	}
137}
138
139impl Revisioned for TableName {
140	fn revision() -> u16 {
141		String::revision()
142	}
143}
144
145impl SerializeRevisioned for TableName {
146	fn serialize_revisioned<W: Write>(&self, w: &mut W) -> Result<(), revision::Error> {
147		<str as SerializeRevisioned>::serialize_revisioned(self.as_str(), w)
148	}
149}
150
151impl DeserializeRevisioned for TableName {
152	fn deserialize_revisioned<R: Read>(r: &mut R) -> Result<Self, revision::Error> {
153		// Route through `Strand` so we get its zero-/single-alloc
154		// decode paths directly, rather than materialising an
155		// intermediate `String` that would force an extra allocation
156		// on every long table name loaded from disk or the wire.
157		Ok(TableName(Strand::deserialize_revisioned(r)?))
158	}
159}
160
161impl SkipRevisioned for TableName {
162	fn skip_revisioned<R: Read>(r: &mut R) -> Result<(), revision::Error> {
163		<Strand as SkipRevisioned>::skip_revisioned(r)
164	}
165}
166
167impl revision::WalkRevisioned for TableName {
168	type Walker<'r, R: revision::BorrowedReader + 'r> = revision::LeafWalker<'r, TableName, R>;
169
170	fn walk_revisioned<'r, R: revision::BorrowedReader>(
171		reader: &'r mut R,
172	) -> Result<Self::Walker<'r, R>, revision::Error> {
173		Ok(revision::LeafWalker::new(reader))
174	}
175}
176
177impl revision::LengthPrefixedBytes for TableName {}
178
179impl<F> Encode<F> for TableName {
180	fn encode<W: Write>(&self, w: &mut storekey::Writer<W>) -> Result<(), storekey::EncodeError> {
181		<str as Encode<F>>::encode::<W>(self.as_str(), w)
182	}
183}
184
185impl<'de, F> BorrowDecode<'de, F> for TableName {
186	fn borrow_decode(r: &mut storekey::BorrowReader<'de>) -> Result<Self, storekey::DecodeError> {
187		Ok(TableName(<Strand as BorrowDecode<'de, F>>::borrow_decode(r)?))
188	}
189}
190
191impl<F> Decode<F> for TableName {
192	fn decode<R: BufRead>(r: &mut storekey::Reader<R>) -> Result<Self, storekey::DecodeError> {
193		Ok(TableName(<Strand as Decode<F>>::decode(r)?))
194	}
195}
196
197#[cfg(test)]
198mod tests {
199	use revision::{SerializeRevisioned, WalkRevisioned};
200
201	use super::TableName;
202
203	#[test]
204	fn table_name_with_bytes_matches_serialize() {
205		let name = TableName::from("users");
206		let mut bytes = Vec::new();
207		name.serialize_revisioned(&mut bytes).unwrap();
208		let mut r = bytes.as_slice();
209		let walker = TableName::walk_revisioned(&mut r).unwrap();
210		let observed = walker.with_bytes(|raw| raw.to_vec()).unwrap();
211		assert_eq!(observed.as_slice(), b"users");
212		assert!(r.is_empty());
213	}
214}