1use std::fmt;
10use std::hash::{Hash, Hasher};
11use std::str::FromStr;
12
13use smallvec::SmallVec;
14
15use crate::error::{Error, Result};
16
17pub(crate) const IDENTIFIER_LIMIT: usize = 1024;
26
27pub fn escape_name(name: &str) -> Result<String> {
58 let len = name.chars().count();
59 if len > IDENTIFIER_LIMIT {
60 return Err(Error::invalid_name(format!(
61 "Name exceeds identifier limit ({len} > {IDENTIFIER_LIMIT})"
62 )));
63 }
64
65 let escaped_inner = name.replace('"', "\"\"");
66 Ok(format!("\"{escaped_inner}\""))
67}
68
69#[must_use]
89pub fn escape_sql_path(path: &str) -> String {
90 let escaped_inner = path.replace('"', "\"\"");
91 format!("\"{escaped_inner}\"")
92}
93
94#[must_use]
111pub fn escape_string_literal(value: &str) -> String {
112 format!("'{}'", value.replace('\'', "''"))
113}
114
115#[derive(Clone, Debug)]
131#[must_use = "Name represents a validated SQL identifier that should not be discarded. Use it in your SQL queries or table definitions"]
132pub struct Name {
133 escaped: String,
135 unescaped: String,
137}
138
139impl Name {
140 pub fn try_new(name: impl Into<String>) -> Result<Self> {
156 let unescaped = name.into();
157 if unescaped.is_empty() {
158 return Err(Error::invalid_name("Name must not be empty"));
159 }
160 let escaped = escape_name(&unescaped)?;
162 Ok(Name { escaped, unescaped })
163 }
164
165 #[must_use]
169 pub fn as_str(&self) -> &str {
170 &self.escaped
171 }
172
173 #[must_use]
178 pub fn unescaped(&self) -> &str {
179 &self.unescaped
180 }
181}
182
183fn parse_qualified_identifier(s: &str) -> SmallVec<[String; 3]> {
188 let mut parts = SmallVec::new();
189 let mut current = String::new();
190 let mut in_quotes = false;
191 let mut chars = s.chars().peekable();
192
193 while let Some(c) = chars.next() {
194 match c {
195 '"' => {
197 if in_quotes && chars.peek() == Some(&'"') {
199 current.push('"');
200 chars.next(); } else {
202 in_quotes = !in_quotes;
203 }
205 }
206 '.' if !in_quotes => {
208 if !current.is_empty() {
209 parts.push(current.split_off(0));
210 }
211 }
212 _ => current.push(c),
213 }
214 }
215 if !current.is_empty() {
216 parts.push(current);
217 }
218 parts
219}
220
221impl fmt::Display for Name {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 write!(f, "{}", self.escaped)
224 }
225}
226
227impl PartialEq for Name {
228 fn eq(&self, other: &Self) -> bool {
229 self.unescaped == other.unescaped
230 }
231}
232
233impl Eq for Name {}
234
235impl PartialOrd for Name {
236 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
237 Some(self.cmp(other))
238 }
239}
240
241impl Ord for Name {
242 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
243 self.unescaped.cmp(&other.unescaped)
244 }
245}
246
247impl Hash for Name {
248 fn hash<H: Hasher>(&self, state: &mut H) {
249 self.unescaped.hash(state);
250 }
251}
252
253impl TryFrom<&str> for Name {
254 type Error = Error;
255
256 fn try_from(s: &str) -> Result<Self> {
257 Self::try_new(s)
258 }
259}
260
261impl TryFrom<&String> for Name {
262 type Error = Error;
263
264 fn try_from(s: &String) -> Result<Self> {
265 Self::try_new(s.as_str())
266 }
267}
268
269impl TryFrom<String> for Name {
270 type Error = Error;
271
272 fn try_from(s: String) -> Result<Self> {
273 Self::try_new(s)
274 }
275}
276
277impl FromStr for Name {
278 type Err = Error;
279
280 fn from_str(s: &str) -> Result<Self> {
281 Self::try_new(s)
282 }
283}
284
285#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
299#[must_use = "DatabaseName represents a validated database identifier that should not be discarded. Use it in your connection or table definitions"]
300pub struct DatabaseName {
301 name: Name,
302}
303
304impl DatabaseName {
305 pub fn try_new(name: impl Into<String>) -> Result<Self> {
311 Ok(DatabaseName {
312 name: Name::try_new(name)?,
313 })
314 }
315
316 pub fn name(&self) -> &Name {
318 &self.name
319 }
320
321 #[must_use]
323 pub fn unescaped(&self) -> &str {
324 self.name.unescaped()
325 }
326}
327
328impl fmt::Display for DatabaseName {
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 write!(f, "{}", self.name)
331 }
332}
333
334impl TryFrom<&str> for DatabaseName {
335 type Error = Error;
336
337 fn try_from(s: &str) -> Result<Self> {
338 Self::try_new(s)
339 }
340}
341
342impl TryFrom<&String> for DatabaseName {
343 type Error = Error;
344
345 fn try_from(s: &String) -> Result<Self> {
346 Self::try_new(s.as_str())
347 }
348}
349
350impl TryFrom<String> for DatabaseName {
351 type Error = Error;
352
353 fn try_from(s: String) -> Result<Self> {
354 Self::try_new(s)
355 }
356}
357
358impl From<Name> for DatabaseName {
359 fn from(name: Name) -> Self {
360 DatabaseName { name }
361 }
362}
363
364impl FromStr for DatabaseName {
365 type Err = Error;
366
367 fn from_str(s: &str) -> Result<Self> {
368 Self::try_new(s)
369 }
370}
371
372#[derive(Clone, Debug, PartialEq, Eq, Hash)]
393#[must_use = "SchemaName represents a validated schema identifier that should not be discarded. Use it in your table definitions or queries"]
394pub struct SchemaName {
395 database: Option<DatabaseName>,
396 schema: Name,
397}
398
399impl SchemaName {
400 pub fn try_new(schema: impl Into<String>) -> Result<Self> {
416 Ok(SchemaName {
417 database: None,
418 schema: Name::try_new(schema)?,
419 })
420 }
421
422 pub fn with_database(mut self, database: impl Into<String>) -> Result<Self> {
441 self.database = Some(DatabaseName::try_new(database)?);
442 Ok(self)
443 }
444
445 #[must_use]
447 pub fn database(&self) -> Option<&DatabaseName> {
448 self.database.as_ref()
449 }
450
451 pub fn schema(&self) -> &Name {
453 &self.schema
454 }
455
456 #[must_use]
458 pub fn unescaped(&self) -> &str {
459 self.schema.unescaped()
460 }
461}
462
463impl fmt::Display for SchemaName {
464 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
465 if let Some(ref db) = self.database {
466 write!(f, "{}.{}", db, self.schema)
467 } else {
468 write!(f, "{}", self.schema)
469 }
470 }
471}
472
473impl TryFrom<&str> for SchemaName {
474 type Error = Error;
475
476 fn try_from(s: &str) -> Result<Self> {
477 s.parse()
478 }
479}
480
481impl TryFrom<&String> for SchemaName {
482 type Error = Error;
483
484 fn try_from(s: &String) -> Result<Self> {
485 s.as_str().parse()
486 }
487}
488
489impl TryFrom<String> for SchemaName {
490 type Error = Error;
491
492 fn try_from(s: String) -> Result<Self> {
493 s.parse()
494 }
495}
496
497impl From<Name> for SchemaName {
498 fn from(name: Name) -> Self {
499 SchemaName {
500 database: None,
501 schema: name,
502 }
503 }
504}
505
506impl FromStr for SchemaName {
507 type Err = Error;
508
509 fn from_str(s: &str) -> Result<Self> {
510 let parts = parse_qualified_identifier(s);
511
512 match parts.as_slice() {
514 [s] => SchemaName::try_new(s),
515 [d, s] => SchemaName::try_new(s)?.with_database(d),
516 _ => Err(Error::invalid_name(format!("Invalid SQL identifier: {s}"))),
517 }
518 }
519}
520
521#[derive(Clone, Debug, PartialEq, Eq, Hash)]
549#[must_use = "TableName represents a validated table identifier that should not be discarded. Use it in your queries or table operations"]
550pub struct TableName {
551 database: Option<DatabaseName>,
552 schema: Option<Name>,
553 table: Name,
554}
555
556impl TableName {
557 pub fn try_new(table: impl Into<String>) -> Result<Self> {
573 Ok(TableName {
574 database: None,
575 schema: None,
576 table: Name::try_new(table)?,
577 })
578 }
579
580 pub fn with_schema(mut self, schema: impl Into<String>) -> Result<Self> {
599 self.schema = Some(Name::try_new(schema)?);
600 Ok(self)
601 }
602
603 pub fn with_database(mut self, database: impl Into<String>) -> Result<Self> {
624 self.database = Some(DatabaseName::try_new(database)?);
625 Ok(self)
626 }
627
628 #[must_use]
630 pub fn database(&self) -> Option<&DatabaseName> {
631 self.database.as_ref()
632 }
633
634 #[must_use]
636 pub fn schema(&self) -> Option<&Name> {
637 self.schema.as_ref()
638 }
639
640 pub fn table(&self) -> &Name {
642 &self.table
643 }
644
645 #[must_use]
647 pub fn unescaped(&self) -> &str {
648 self.table.unescaped()
649 }
650}
651
652impl fmt::Display for TableName {
653 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
654 if let Some(ref db) = self.database {
655 write!(f, "{db}.")?;
656 }
657 if let Some(ref schema) = self.schema {
658 write!(f, "{schema}.")?;
659 }
660 write!(f, "{}", self.table)
661 }
662}
663
664impl TryFrom<&str> for TableName {
665 type Error = Error;
666
667 fn try_from(s: &str) -> Result<Self> {
668 s.parse()
669 }
670}
671
672impl TryFrom<&String> for TableName {
673 type Error = Error;
674
675 fn try_from(s: &String) -> Result<Self> {
676 s.as_str().parse()
677 }
678}
679
680impl TryFrom<String> for TableName {
681 type Error = Error;
682
683 fn try_from(s: String) -> Result<Self> {
684 s.parse()
685 }
686}
687
688impl From<Name> for TableName {
689 fn from(name: Name) -> Self {
690 TableName {
691 database: None,
692 schema: None,
693 table: name,
694 }
695 }
696}
697
698impl FromStr for TableName {
699 type Err = Error;
700
701 fn from_str(s: &str) -> Result<Self> {
702 let mut parts = Vec::new();
703 let mut current = String::new();
704 let mut in_quotes = false;
705 let mut chars = s.chars().peekable();
706
707 while let Some(c) = chars.next() {
708 match c {
709 '"' => {
711 if in_quotes && chars.peek() == Some(&'"') {
713 current.push('"');
714 chars.next(); } else {
716 in_quotes = !in_quotes;
717 }
719 }
720 '.' if !in_quotes => {
722 if !current.is_empty() {
723 parts.push(current.split_off(0));
724 }
725 }
726 _ => current.push(c),
727 }
728 }
729 if !current.is_empty() {
730 parts.push(current);
731 }
732
733 match parts.as_slice() {
735 [t] => TableName::try_new(t),
736 [s, t] => TableName::try_new(t)?.with_schema(s),
737 [d, s, t] => TableName::try_new(t)?.with_schema(s)?.with_database(d),
738 _ => Err(Error::invalid_name(format!("Invalid SQL identifier: {s}"))),
739 }
740 }
741}
742
743#[macro_export]
767macro_rules! table_name {
768 ($db:expr, $schema:expr, $table:expr) => {
770 $crate::TableName::try_new($table)?
771 .with_schema($schema)?
772 .with_database($db)
773 };
774
775 ($schema:expr, $table:expr) => {
777 $crate::TableName::try_new($table)?.with_schema($schema)
778 };
779
780 ($table:expr) => {
782 $crate::TableName::try_new($table)
783 };
784}
785
786#[macro_export]
806macro_rules! schema_name {
807 ($db:expr, $schema:expr) => {
809 $crate::SchemaName::try_new($schema)?.with_database($db)
810 };
811
812 ($schema:expr) => {
814 $crate::SchemaName::try_new($schema)
815 };
816}
817
818#[cfg(test)]
819mod tests {
820 use super::*;
821
822 #[test]
823 fn test_escape_name() {
824 assert_eq!(escape_name("table").unwrap(), "\"table\"");
825 assert_eq!(escape_name("my_table").unwrap(), "\"my_table\"");
826 assert_eq!(escape_name("table\"quote").unwrap(), "\"table\"\"quote\"");
827 assert_eq!(escape_name("").unwrap(), "\"\"");
828 }
829
830 #[test]
831 fn test_escape_name_too_long() {
832 let max_name = "a".repeat(IDENTIFIER_LIMIT);
833 assert!(escape_name(&max_name).is_ok());
834
835 let too_long = "a".repeat(IDENTIFIER_LIMIT + 1);
836 let err = escape_name(&too_long).unwrap_err();
837 assert!(err.to_string().contains("identifier limit"));
838 }
839
840 #[test]
841 fn names_longer_than_postgresql_namedatalen_are_accepted() {
842 let name = "a".repeat(93);
847 assert!(escape_name(&name).is_ok());
848 assert!(Name::try_new(name.clone()).is_ok());
849 assert!(TableName::try_new(name).is_ok());
850 }
851
852 #[test]
853 fn test_escape_sql_path() {
854 assert_eq!(escape_sql_path("/tmp/data.hyper"), "\"/tmp/data.hyper\"");
855 assert_eq!(
856 escape_sql_path("/tmp/my \"db\".hyper"),
857 "\"/tmp/my \"\"db\"\".hyper\""
858 );
859 assert_eq!(escape_sql_path(""), "\"\"");
860
861 let long_path = format!("/very/long/path/{}.hyper", "a".repeat(100));
863 let escaped = escape_sql_path(&long_path);
864 assert!(escaped.starts_with('"'));
865 assert!(escaped.ends_with('"'));
866 }
867
868 #[test]
869 fn test_escape_string_literal() {
870 assert_eq!(escape_string_literal("hello"), "'hello'");
871 assert_eq!(escape_string_literal("it's"), "'it''s'");
872 assert_eq!(escape_string_literal(""), "''");
873 }
874
875 #[test]
876 fn test_name() {
877 let name = Name::try_new("users").unwrap();
878 assert_eq!(name.to_string(), "\"users\"");
879 assert_eq!(name.unescaped(), "users");
880 assert!(!name.unescaped().is_empty());
881 }
882
883 #[test]
884 fn test_name_with_quotes() {
885 let name = Name::try_new("table\"name").unwrap();
886 assert_eq!(name.to_string(), "\"table\"\"name\"");
887 assert_eq!(name.unescaped(), "table\"name");
888 }
889
890 #[test]
891 fn test_database_name() {
892 let db = DatabaseName::try_new("mydb").unwrap();
893 assert_eq!(db.to_string(), "\"mydb\"");
894 assert_eq!(db.unescaped(), "mydb");
895 }
896
897 #[test]
898 fn test_schema_name() {
899 let schema = SchemaName::try_new("public").unwrap();
900 assert_eq!(schema.to_string(), "\"public\"");
901
902 let qualified = SchemaName::try_new("public")
903 .unwrap()
904 .with_database("mydb")
905 .unwrap();
906 assert_eq!(qualified.to_string(), "\"mydb\".\"public\"");
907 }
908
909 #[test]
910 fn test_table_name() {
911 let simple = TableName::try_new("users").unwrap();
912 assert_eq!(simple.to_string(), "\"users\"");
913
914 let with_schema = TableName::try_new("users")
915 .unwrap()
916 .with_schema("public")
917 .unwrap();
918 assert_eq!(with_schema.to_string(), "\"public\".\"users\"");
919
920 let full = TableName::try_new("users")
921 .unwrap()
922 .with_schema("public")
923 .unwrap()
924 .with_database("mydb")
925 .unwrap();
926 assert_eq!(full.to_string(), "\"mydb\".\"public\".\"users\"");
927 }
928
929 #[test]
930 fn test_name_equality() {
931 let name1 = Name::try_new("test").unwrap();
932 let name2 = Name::try_new("test").unwrap();
933 let name3 = Name::try_new("other").unwrap();
934
935 assert_eq!(name1, name2);
936 assert_ne!(name1, name3);
937 }
938
939 #[test]
940 fn test_schema_name_from_str() {
941 let schema: SchemaName = "public".parse().unwrap();
943 assert_eq!(schema.to_string(), "\"public\"");
944 assert_eq!(schema.unescaped(), "public");
945
946 let qualified: SchemaName = "mydb.public".parse().unwrap();
948 assert_eq!(qualified.to_string(), "\"mydb\".\"public\"");
949 assert_eq!(qualified.unescaped(), "public");
950 assert_eq!(qualified.database().unwrap().unescaped(), "mydb");
951
952 let quoted: SchemaName = "\"my db\".\"my schema\"".parse().unwrap();
954 assert_eq!(quoted.to_string(), "\"my db\".\"my schema\"");
955 assert_eq!(quoted.unescaped(), "my schema");
956
957 let escaped: SchemaName = "\"schema\"\"name\"".parse().unwrap();
959 assert_eq!(escaped.to_string(), "\"schema\"\"name\"");
960 assert_eq!(escaped.unescaped(), "schema\"name");
961
962 assert!("db.schema.table".parse::<SchemaName>().is_err());
964 assert!("".parse::<SchemaName>().is_err());
965 }
966
967 #[test]
968 fn test_table_name_from_str() {
969 let table: TableName = "users".parse().unwrap();
971 assert_eq!(table.to_string(), "\"users\"");
972 assert_eq!(table.unescaped(), "users");
973
974 let with_schema: TableName = "public.users".parse().unwrap();
976 assert_eq!(with_schema.to_string(), "\"public\".\"users\"");
977 assert_eq!(with_schema.unescaped(), "users");
978 assert_eq!(with_schema.schema().unwrap().unescaped(), "public");
979
980 let full: TableName = "mydb.public.users".parse().unwrap();
982 assert_eq!(full.to_string(), "\"mydb\".\"public\".\"users\"");
983 assert_eq!(full.unescaped(), "users");
984 assert_eq!(full.schema().unwrap().unescaped(), "public");
985 assert_eq!(full.database().unwrap().unescaped(), "mydb");
986
987 let quoted: TableName = "\"my db\".\"my schema\".\"my table\"".parse().unwrap();
989 assert_eq!(quoted.to_string(), "\"my db\".\"my schema\".\"my table\"");
990 assert_eq!(quoted.unescaped(), "my table");
991
992 let escaped: TableName = "\"table\"\"name\"".parse().unwrap();
994 assert_eq!(escaped.to_string(), "\"table\"\"name\"");
995 assert_eq!(escaped.unescaped(), "table\"name");
996
997 let with_dots: TableName = "\"schema.name\".\"table.name\"".parse().unwrap();
999 assert_eq!(with_dots.to_string(), "\"schema.name\".\"table.name\"");
1000 assert_eq!(with_dots.schema().unwrap().unescaped(), "schema.name");
1001 assert_eq!(with_dots.unescaped(), "table.name");
1002
1003 assert!("db.schema.table.extra".parse::<TableName>().is_err());
1005 assert!("".parse::<TableName>().is_err());
1006 }
1007
1008 #[test]
1009 fn test_schema_name_macro() -> Result<()> {
1010 let schema = schema_name!("public")?;
1012 assert_eq!(schema.to_string(), "\"public\"");
1013 assert_eq!(schema.unescaped(), "public");
1014
1015 let qualified = schema_name!("mydb", "public")?;
1017 assert_eq!(qualified.to_string(), "\"mydb\".\"public\"");
1018 assert_eq!(qualified.unescaped(), "public");
1019 assert_eq!(qualified.database().unwrap().unescaped(), "mydb");
1020 Ok(())
1021 }
1022
1023 #[test]
1024 fn test_table_name_macro() -> Result<()> {
1025 let table = table_name!("users")?;
1027 assert_eq!(table.to_string(), "\"users\"");
1028 assert_eq!(table.unescaped(), "users");
1029
1030 let with_schema = table_name!("public", "users")?;
1032 assert_eq!(with_schema.to_string(), "\"public\".\"users\"");
1033 assert_eq!(with_schema.unescaped(), "users");
1034 assert_eq!(with_schema.schema().unwrap().unescaped(), "public");
1035
1036 let full = table_name!("mydb", "public", "users")?;
1038 assert_eq!(full.to_string(), "\"mydb\".\"public\".\"users\"");
1039 assert_eq!(full.unescaped(), "users");
1040 assert_eq!(full.schema().unwrap().unescaped(), "public");
1041 assert_eq!(full.database().unwrap().unescaped(), "mydb");
1042 Ok(())
1043 }
1044
1045 #[test]
1046 fn test_schema_name_try_from() {
1047 let schema: SchemaName = "public".try_into().unwrap();
1049 assert_eq!(schema.to_string(), "\"public\"");
1050 assert_eq!(schema.unescaped(), "public");
1051
1052 let qualified: SchemaName = "mydb.public".try_into().unwrap();
1054 assert_eq!(qualified.to_string(), "\"mydb\".\"public\"");
1055 assert_eq!(qualified.unescaped(), "public");
1056 assert_eq!(qualified.database().unwrap().unescaped(), "mydb");
1057
1058 let schema_string: SchemaName = String::from("public").try_into().unwrap();
1060 assert_eq!(schema_string.to_string(), "\"public\"");
1061 }
1062
1063 #[test]
1064 fn test_table_name_try_from() {
1065 let table: TableName = "users".try_into().unwrap();
1067 assert_eq!(table.to_string(), "\"users\"");
1068 assert_eq!(table.unescaped(), "users");
1069
1070 let with_schema: TableName = "public.users".try_into().unwrap();
1072 assert_eq!(with_schema.to_string(), "\"public\".\"users\"");
1073 assert_eq!(with_schema.unescaped(), "users");
1074 assert_eq!(with_schema.schema().unwrap().unescaped(), "public");
1075
1076 let full: TableName = "mydb.public.users".try_into().unwrap();
1078 assert_eq!(full.to_string(), "\"mydb\".\"public\".\"users\"");
1079 assert_eq!(full.unescaped(), "users");
1080 assert_eq!(full.schema().unwrap().unescaped(), "public");
1081 assert_eq!(full.database().unwrap().unescaped(), "mydb");
1082
1083 let table_string: TableName = String::from("users").try_into().unwrap();
1085 assert_eq!(table_string.to_string(), "\"users\"");
1086
1087 let invalid: std::result::Result<TableName, _> = "db.schema.table.extra".try_into();
1089 assert!(invalid.is_err());
1090 }
1091}