Skip to main content

surrealdb_sql/
table_name.rs

1use std::borrow::{Borrow, Cow};
2use std::fmt::{self, Display};
3
4use common::fmt::EscapeIdent;
5use surrealdb_strand::Strand;
6use surrealdb_types::{SqlFormat, ToSql};
7
8/// A table name as it appears in the syntax tree.
9///
10/// This is the form that renders: [`ToSql`] escapes the name when it would
11/// otherwise be read back as a reserved word, which is what makes
12/// `DEFINE TABLE ⟨select⟩` round-trip. It is also the form that crosses the
13/// public boundary, since the wire's `surrealdb_types::Table` is a language
14/// value rather than an engine or storage one.
15///
16/// Every AST node that renders a table name goes through that impl rather than
17/// naming an escaper itself, so how a table name escapes is decided in one
18/// place. A node that reaches for `EscapeIdent` directly is not wrong today,
19/// but it is a second place to change.
20///
21/// Lowering converts this to `surrealdb_strand::TableName`, which the engine
22/// passes around and which carries neither capability.
23#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
24#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
25#[repr(transparent)]
26pub struct TableName(Strand);
27
28impl TableName {
29	/// Create a new table name.
30	pub fn new(s: impl Into<Strand>) -> TableName {
31		TableName(s.into())
32	}
33
34	pub fn into_string(self) -> String {
35		self.0.into()
36	}
37
38	pub fn as_str(&self) -> &str {
39		self.0.as_str()
40	}
41
42	pub fn is_table_type(&self, tables: &[TableName]) -> bool {
43		tables.is_empty() || tables.contains(self)
44	}
45}
46
47impl From<String> for TableName {
48	fn from(value: String) -> Self {
49		TableName(value.into())
50	}
51}
52
53impl From<TableName> for String {
54	fn from(value: TableName) -> Self {
55		value.0.into()
56	}
57}
58
59impl From<&str> for TableName {
60	fn from(value: &str) -> Self {
61		TableName(Strand::from(value))
62	}
63}
64
65impl From<Strand> for TableName {
66	fn from(value: Strand) -> Self {
67		TableName(value)
68	}
69}
70
71impl From<TableName> for Strand {
72	fn from(value: TableName) -> Self {
73		value.0
74	}
75}
76
77impl<'a> From<TableName> for Cow<'a, str> {
78	fn from(value: TableName) -> Self {
79		Cow::Owned(value.into_string())
80	}
81}
82
83/// Lowering: the syntax tree's name becomes the engine's.
84impl From<TableName> for surrealdb_strand::TableName {
85	fn from(value: TableName) -> Self {
86		surrealdb_strand::TableName::from(value.0)
87	}
88}
89
90/// Rendering: the engine's name becomes the syntax tree's, so that it prints
91/// with the escaping this layer owns.
92impl From<surrealdb_strand::TableName> for TableName {
93	fn from(value: surrealdb_strand::TableName) -> Self {
94		TableName(Strand::from(value))
95	}
96}
97
98impl From<surrealdb_types::Table> for TableName {
99	fn from(value: surrealdb_types::Table) -> Self {
100		TableName(Strand::from(value.into_string()))
101	}
102}
103
104impl From<TableName> for surrealdb_types::Table {
105	fn from(value: TableName) -> Self {
106		surrealdb_types::Table::new(value.into_string())
107	}
108}
109
110impl ToSql for TableName {
111	fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
112		EscapeIdent(self.as_str()).fmt_sql(f, sql_fmt);
113	}
114}
115
116impl PartialEq<TableName> for &TableName {
117	fn eq(&self, other: &TableName) -> bool {
118		self.0 == other.0
119	}
120}
121
122impl PartialEq<str> for TableName {
123	fn eq(&self, other: &str) -> bool {
124		self.as_str() == other
125	}
126}
127
128impl PartialEq<TableName> for str {
129	fn eq(&self, other: &TableName) -> bool {
130		self == other.as_str()
131	}
132}
133
134impl PartialEq<&str> for TableName {
135	fn eq(&self, other: &&str) -> bool {
136		self.as_str() == *other
137	}
138}
139
140impl PartialEq<String> for TableName {
141	fn eq(&self, other: &String) -> bool {
142		self.as_str() == other.as_str()
143	}
144}
145
146impl AsRef<str> for TableName {
147	fn as_ref(&self) -> &str {
148		self.as_str()
149	}
150}
151
152impl Display for TableName {
153	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154		Display::fmt(self.as_str(), f)
155	}
156}
157
158impl Borrow<str> for TableName {
159	fn borrow(&self) -> &str {
160		self.as_str()
161	}
162}
163
164#[cfg(test)]
165mod tests {
166	use surrealdb_types::ToSql;
167
168	use super::TableName;
169
170	/// Escaping is what distinguishes this form from the engine's, so a name
171	/// that collides with a keyword must come back quoted.
172	#[test]
173	fn renders_with_keyword_escaping() {
174		assert_eq!(TableName::from("users").to_sql(), "users");
175		assert_eq!(TableName::from("select").to_sql(), "`select`");
176		assert_eq!(TableName::from("a b").to_sql(), "`a b`");
177	}
178
179	#[test]
180	fn lowering_round_trips_through_the_engine_form() {
181		let language = TableName::from("users");
182		let engine: surrealdb_strand::TableName = language.clone().into();
183		assert_eq!(TableName::from(engine), language);
184	}
185}