1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7use use_sql_ident::{SqlIdentifier, SqlIdentifierError};
8
9#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
11pub struct SqlSchemaName(SqlIdentifier);
12
13impl SqlSchemaName {
14 pub fn new(input: impl AsRef<str>) -> Result<Self, SqlSchemaError> {
20 SqlIdentifier::new(input)
21 .map(Self)
22 .map_err(SqlSchemaError::Identifier)
23 }
24
25 #[must_use]
27 pub fn as_str(&self) -> &str {
28 self.0.as_str()
29 }
30}
31
32impl AsRef<str> for SqlSchemaName {
33 fn as_ref(&self) -> &str {
34 self.as_str()
35 }
36}
37
38impl fmt::Display for SqlSchemaName {
39 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
40 self.0.fmt(formatter)
41 }
42}
43
44impl FromStr for SqlSchemaName {
45 type Err = SqlSchemaError;
46
47 fn from_str(input: &str) -> Result<Self, Self::Err> {
48 Self::new(input)
49 }
50}
51
52#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
54pub struct SqlDatabaseName(SqlIdentifier);
55
56impl SqlDatabaseName {
57 pub fn new(input: impl AsRef<str>) -> Result<Self, SqlSchemaError> {
63 SqlIdentifier::new(input)
64 .map(Self)
65 .map_err(SqlSchemaError::Identifier)
66 }
67
68 #[must_use]
70 pub fn as_str(&self) -> &str {
71 self.0.as_str()
72 }
73}
74
75impl fmt::Display for SqlDatabaseName {
76 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
77 self.0.fmt(formatter)
78 }
79}
80
81#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
83pub struct SqlNamespace {
84 database: Option<SqlDatabaseName>,
85 schema: SqlSchemaName,
86}
87
88impl SqlNamespace {
89 #[must_use]
91 pub const fn new(schema: SqlSchemaName) -> Self {
92 Self {
93 database: None,
94 schema,
95 }
96 }
97
98 #[must_use]
100 pub fn with_database(mut self, database: SqlDatabaseName) -> Self {
101 self.database = Some(database);
102 self
103 }
104
105 #[must_use]
107 pub const fn database(&self) -> Option<&SqlDatabaseName> {
108 self.database.as_ref()
109 }
110
111 #[must_use]
113 pub const fn schema(&self) -> &SqlSchemaName {
114 &self.schema
115 }
116}
117
118impl fmt::Display for SqlNamespace {
119 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
120 if let Some(database) = &self.database {
121 write!(formatter, "{database}.")?;
122 }
123 write!(formatter, "{}", self.schema)
124 }
125}
126
127#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
129pub struct SqlSearchPath {
130 schemas: Vec<SqlSchemaName>,
131}
132
133impl SqlSearchPath {
134 #[must_use]
136 pub const fn new(schemas: Vec<SqlSchemaName>) -> Self {
137 Self { schemas }
138 }
139
140 #[must_use]
142 pub fn schemas(&self) -> &[SqlSchemaName] {
143 &self.schemas
144 }
145
146 pub fn push(&mut self, schema: SqlSchemaName) {
148 self.schemas.push(schema);
149 }
150}
151
152impl fmt::Display for SqlSearchPath {
153 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
154 let mut schemas = self.schemas.iter();
155 if let Some(first) = schemas.next() {
156 write!(formatter, "{first}")?;
157 }
158 for schema in schemas {
159 write!(formatter, ", {schema}")?;
160 }
161 Ok(())
162 }
163}
164
165#[derive(Clone, Debug, Eq, PartialEq)]
167pub enum SqlSchemaError {
168 Identifier(SqlIdentifierError),
169}
170
171impl fmt::Display for SqlSchemaError {
172 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
173 match self {
174 Self::Identifier(error) => write!(formatter, "invalid SQL schema identifier: {error}"),
175 }
176 }
177}
178
179impl Error for SqlSchemaError {}
180
181#[cfg(test)]
182mod tests {
183 use super::{SqlDatabaseName, SqlNamespace, SqlSchemaError, SqlSchemaName, SqlSearchPath};
184
185 #[test]
186 fn creates_schema_names_and_paths() -> Result<(), SqlSchemaError> {
187 let schema = SqlSchemaName::new("public")?;
188 let namespace =
189 SqlNamespace::new(schema.clone()).with_database(SqlDatabaseName::new("app")?);
190 let path = SqlSearchPath::new(vec![schema]);
191
192 assert_eq!(namespace.to_string(), "app.public");
193 assert_eq!(path.to_string(), "public");
194 Ok(())
195 }
196}