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 #[must_use]
318 pub fn name(&self) -> &Name {
319 &self.name
320 }
321
322 #[must_use]
324 pub fn unescaped(&self) -> &str {
325 self.name.unescaped()
326 }
327}
328
329impl fmt::Display for DatabaseName {
330 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331 write!(f, "{}", self.name)
332 }
333}
334
335impl TryFrom<&str> for DatabaseName {
336 type Error = Error;
337
338 fn try_from(s: &str) -> Result<Self> {
339 Self::try_new(s)
340 }
341}
342
343impl TryFrom<&String> for DatabaseName {
344 type Error = Error;
345
346 fn try_from(s: &String) -> Result<Self> {
347 Self::try_new(s.as_str())
348 }
349}
350
351impl TryFrom<String> for DatabaseName {
352 type Error = Error;
353
354 fn try_from(s: String) -> Result<Self> {
355 Self::try_new(s)
356 }
357}
358
359impl From<Name> for DatabaseName {
360 fn from(name: Name) -> Self {
361 DatabaseName { name }
362 }
363}
364
365impl FromStr for DatabaseName {
366 type Err = Error;
367
368 fn from_str(s: &str) -> Result<Self> {
369 Self::try_new(s)
370 }
371}
372
373#[derive(Clone, Debug, PartialEq, Eq, Hash)]
394#[must_use = "SchemaName represents a validated schema identifier that should not be discarded. Use it in your table definitions or queries"]
395pub struct SchemaName {
396 database: Option<DatabaseName>,
397 schema: Name,
398}
399
400impl SchemaName {
401 pub fn try_new(schema: impl Into<String>) -> Result<Self> {
417 Ok(SchemaName {
418 database: None,
419 schema: Name::try_new(schema)?,
420 })
421 }
422
423 pub fn with_database(mut self, database: impl Into<String>) -> Result<Self> {
442 self.database = Some(DatabaseName::try_new(database)?);
443 Ok(self)
444 }
445
446 #[must_use]
448 pub fn database(&self) -> Option<&DatabaseName> {
449 self.database.as_ref()
450 }
451
452 #[must_use]
454 pub fn schema(&self) -> &Name {
455 &self.schema
456 }
457
458 #[must_use]
460 pub fn unescaped(&self) -> &str {
461 self.schema.unescaped()
462 }
463}
464
465impl fmt::Display for SchemaName {
466 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
467 if let Some(ref db) = self.database {
468 write!(f, "{}.{}", db, self.schema)
469 } else {
470 write!(f, "{}", self.schema)
471 }
472 }
473}
474
475impl TryFrom<&str> for SchemaName {
476 type Error = Error;
477
478 fn try_from(s: &str) -> Result<Self> {
479 s.parse()
480 }
481}
482
483impl TryFrom<&String> for SchemaName {
484 type Error = Error;
485
486 fn try_from(s: &String) -> Result<Self> {
487 s.as_str().parse()
488 }
489}
490
491impl TryFrom<String> for SchemaName {
492 type Error = Error;
493
494 fn try_from(s: String) -> Result<Self> {
495 s.parse()
496 }
497}
498
499impl From<Name> for SchemaName {
500 fn from(name: Name) -> Self {
501 SchemaName {
502 database: None,
503 schema: name,
504 }
505 }
506}
507
508impl FromStr for SchemaName {
509 type Err = Error;
510
511 fn from_str(s: &str) -> Result<Self> {
512 let parts = parse_qualified_identifier(s);
513
514 match parts.as_slice() {
516 [s] => SchemaName::try_new(s),
517 [d, s] => SchemaName::try_new(s)?.with_database(d),
518 _ => Err(Error::invalid_name(format!("Invalid SQL identifier: {s}"))),
519 }
520 }
521}
522
523#[derive(Clone, Debug, PartialEq, Eq, Hash)]
551#[must_use = "TableName represents a validated table identifier that should not be discarded. Use it in your queries or table operations"]
552pub struct TableName {
553 database: Option<DatabaseName>,
554 schema: Option<Name>,
555 table: Name,
556}
557
558impl TableName {
559 pub fn try_new(table: impl Into<String>) -> Result<Self> {
575 Ok(TableName {
576 database: None,
577 schema: None,
578 table: Name::try_new(table)?,
579 })
580 }
581
582 pub fn with_schema(mut self, schema: impl Into<String>) -> Result<Self> {
601 self.schema = Some(Name::try_new(schema)?);
602 Ok(self)
603 }
604
605 pub fn with_database(mut self, database: impl Into<String>) -> Result<Self> {
626 self.database = Some(DatabaseName::try_new(database)?);
627 Ok(self)
628 }
629
630 #[must_use]
632 pub fn database(&self) -> Option<&DatabaseName> {
633 self.database.as_ref()
634 }
635
636 #[must_use]
638 pub fn schema(&self) -> Option<&Name> {
639 self.schema.as_ref()
640 }
641
642 #[must_use]
644 pub fn table(&self) -> &Name {
645 &self.table
646 }
647
648 #[must_use]
650 pub fn unescaped(&self) -> &str {
651 self.table.unescaped()
652 }
653}
654
655impl fmt::Display for TableName {
656 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
657 if let Some(ref db) = self.database {
658 write!(f, "{db}.")?;
659 }
660 if let Some(ref schema) = self.schema {
661 write!(f, "{schema}.")?;
662 }
663 write!(f, "{}", self.table)
664 }
665}
666
667impl TryFrom<&str> for TableName {
668 type Error = Error;
669
670 fn try_from(s: &str) -> Result<Self> {
671 s.parse()
672 }
673}
674
675impl TryFrom<&String> for TableName {
676 type Error = Error;
677
678 fn try_from(s: &String) -> Result<Self> {
679 s.as_str().parse()
680 }
681}
682
683impl TryFrom<String> for TableName {
684 type Error = Error;
685
686 fn try_from(s: String) -> Result<Self> {
687 s.parse()
688 }
689}
690
691impl From<Name> for TableName {
692 fn from(name: Name) -> Self {
693 TableName {
694 database: None,
695 schema: None,
696 table: name,
697 }
698 }
699}
700
701impl FromStr for TableName {
702 type Err = Error;
703
704 fn from_str(s: &str) -> Result<Self> {
705 let mut parts = Vec::new();
706 let mut current = String::new();
707 let mut in_quotes = false;
708 let mut chars = s.chars().peekable();
709
710 while let Some(c) = chars.next() {
711 match c {
712 '"' => {
714 if in_quotes && chars.peek() == Some(&'"') {
716 current.push('"');
717 chars.next(); } else {
719 in_quotes = !in_quotes;
720 }
722 }
723 '.' if !in_quotes => {
725 if !current.is_empty() {
726 parts.push(current.split_off(0));
727 }
728 }
729 _ => current.push(c),
730 }
731 }
732 if !current.is_empty() {
733 parts.push(current);
734 }
735
736 match parts.as_slice() {
738 [t] => TableName::try_new(t),
739 [s, t] => TableName::try_new(t)?.with_schema(s),
740 [d, s, t] => TableName::try_new(t)?.with_schema(s)?.with_database(d),
741 _ => Err(Error::invalid_name(format!("Invalid SQL identifier: {s}"))),
742 }
743 }
744}
745
746#[macro_export]
770macro_rules! table_name {
771 ($db:expr, $schema:expr, $table:expr) => {
773 $crate::TableName::try_new($table)?
774 .with_schema($schema)?
775 .with_database($db)
776 };
777
778 ($schema:expr, $table:expr) => {
780 $crate::TableName::try_new($table)?.with_schema($schema)
781 };
782
783 ($table:expr) => {
785 $crate::TableName::try_new($table)
786 };
787}
788
789#[macro_export]
809macro_rules! schema_name {
810 ($db:expr, $schema:expr) => {
812 $crate::SchemaName::try_new($schema)?.with_database($db)
813 };
814
815 ($schema:expr) => {
817 $crate::SchemaName::try_new($schema)
818 };
819}
820
821#[cfg(test)]
822mod tests {
823 use super::*;
824
825 #[test]
826 fn test_escape_name() {
827 assert_eq!(escape_name("table").unwrap(), "\"table\"");
828 assert_eq!(escape_name("my_table").unwrap(), "\"my_table\"");
829 assert_eq!(escape_name("table\"quote").unwrap(), "\"table\"\"quote\"");
830 assert_eq!(escape_name("").unwrap(), "\"\"");
831 }
832
833 #[test]
834 fn test_escape_name_too_long() {
835 let max_name = "a".repeat(IDENTIFIER_LIMIT);
836 assert!(escape_name(&max_name).is_ok());
837
838 let too_long = "a".repeat(IDENTIFIER_LIMIT + 1);
839 let err = escape_name(&too_long).unwrap_err();
840 assert!(err.to_string().contains("identifier limit"));
841 }
842
843 #[test]
844 fn names_longer_than_postgresql_namedatalen_are_accepted() {
845 let name = "a".repeat(93);
850 assert!(escape_name(&name).is_ok());
851 assert!(Name::try_new(name.clone()).is_ok());
852 assert!(TableName::try_new(name).is_ok());
853 }
854
855 #[test]
856 fn test_escape_sql_path() {
857 assert_eq!(escape_sql_path("/tmp/data.hyper"), "\"/tmp/data.hyper\"");
858 assert_eq!(
859 escape_sql_path("/tmp/my \"db\".hyper"),
860 "\"/tmp/my \"\"db\"\".hyper\""
861 );
862 assert_eq!(escape_sql_path(""), "\"\"");
863
864 let long_path = format!("/very/long/path/{}.hyper", "a".repeat(100));
866 let escaped = escape_sql_path(&long_path);
867 assert!(escaped.starts_with('"'));
868 assert!(escaped.ends_with('"'));
869 }
870
871 #[test]
872 fn test_escape_string_literal() {
873 assert_eq!(escape_string_literal("hello"), "'hello'");
874 assert_eq!(escape_string_literal("it's"), "'it''s'");
875 assert_eq!(escape_string_literal(""), "''");
876 }
877
878 #[test]
879 fn test_name() {
880 let name = Name::try_new("users").unwrap();
881 assert_eq!(name.to_string(), "\"users\"");
882 assert_eq!(name.unescaped(), "users");
883 assert_ne!(name.unescaped(), "");
884 }
885
886 #[test]
887 fn test_name_with_quotes() {
888 let name = Name::try_new("table\"name").unwrap();
889 assert_eq!(name.to_string(), "\"table\"\"name\"");
890 assert_eq!(name.unescaped(), "table\"name");
891 }
892
893 #[test]
894 fn test_database_name() {
895 let db = DatabaseName::try_new("mydb").unwrap();
896 assert_eq!(db.to_string(), "\"mydb\"");
897 assert_eq!(db.unescaped(), "mydb");
898 }
899
900 #[test]
901 fn test_schema_name() {
902 let schema = SchemaName::try_new("public").unwrap();
903 assert_eq!(schema.to_string(), "\"public\"");
904
905 let qualified = SchemaName::try_new("public")
906 .unwrap()
907 .with_database("mydb")
908 .unwrap();
909 assert_eq!(qualified.to_string(), "\"mydb\".\"public\"");
910 }
911
912 #[test]
913 fn test_table_name() {
914 let simple = TableName::try_new("users").unwrap();
915 assert_eq!(simple.to_string(), "\"users\"");
916
917 let with_schema = TableName::try_new("users")
918 .unwrap()
919 .with_schema("public")
920 .unwrap();
921 assert_eq!(with_schema.to_string(), "\"public\".\"users\"");
922
923 let full = TableName::try_new("users")
924 .unwrap()
925 .with_schema("public")
926 .unwrap()
927 .with_database("mydb")
928 .unwrap();
929 assert_eq!(full.to_string(), "\"mydb\".\"public\".\"users\"");
930 }
931
932 #[test]
933 fn test_name_equality() {
934 let name1 = Name::try_new("test").unwrap();
935 let name2 = Name::try_new("test").unwrap();
936 let name3 = Name::try_new("other").unwrap();
937
938 assert_eq!(name1, name2);
939 assert_ne!(name1, name3);
940 }
941
942 #[test]
943 fn test_schema_name_from_str() {
944 let schema: SchemaName = "public".parse().unwrap();
946 assert_eq!(schema.to_string(), "\"public\"");
947 assert_eq!(schema.unescaped(), "public");
948
949 let qualified: SchemaName = "mydb.public".parse().unwrap();
951 assert_eq!(qualified.to_string(), "\"mydb\".\"public\"");
952 assert_eq!(qualified.unescaped(), "public");
953 assert_eq!(qualified.database().unwrap().unescaped(), "mydb");
954
955 let quoted: SchemaName = "\"my db\".\"my schema\"".parse().unwrap();
957 assert_eq!(quoted.to_string(), "\"my db\".\"my schema\"");
958 assert_eq!(quoted.unescaped(), "my schema");
959
960 let escaped: SchemaName = "\"schema\"\"name\"".parse().unwrap();
962 assert_eq!(escaped.to_string(), "\"schema\"\"name\"");
963 assert_eq!(escaped.unescaped(), "schema\"name");
964
965 assert!("db.schema.table".parse::<SchemaName>().is_err());
967 assert!("".parse::<SchemaName>().is_err());
968 }
969
970 #[test]
971 fn test_table_name_from_str() {
972 let table: TableName = "users".parse().unwrap();
974 assert_eq!(table.to_string(), "\"users\"");
975 assert_eq!(table.unescaped(), "users");
976
977 let with_schema: TableName = "public.users".parse().unwrap();
979 assert_eq!(with_schema.to_string(), "\"public\".\"users\"");
980 assert_eq!(with_schema.unescaped(), "users");
981 assert_eq!(with_schema.schema().unwrap().unescaped(), "public");
982
983 let full: TableName = "mydb.public.users".parse().unwrap();
985 assert_eq!(full.to_string(), "\"mydb\".\"public\".\"users\"");
986 assert_eq!(full.unescaped(), "users");
987 assert_eq!(full.schema().unwrap().unescaped(), "public");
988 assert_eq!(full.database().unwrap().unescaped(), "mydb");
989
990 let quoted: TableName = "\"my db\".\"my schema\".\"my table\"".parse().unwrap();
992 assert_eq!(quoted.to_string(), "\"my db\".\"my schema\".\"my table\"");
993 assert_eq!(quoted.unescaped(), "my table");
994
995 let escaped: TableName = "\"table\"\"name\"".parse().unwrap();
997 assert_eq!(escaped.to_string(), "\"table\"\"name\"");
998 assert_eq!(escaped.unescaped(), "table\"name");
999
1000 let with_dots: TableName = "\"schema.name\".\"table.name\"".parse().unwrap();
1002 assert_eq!(with_dots.to_string(), "\"schema.name\".\"table.name\"");
1003 assert_eq!(with_dots.schema().unwrap().unescaped(), "schema.name");
1004 assert_eq!(with_dots.unescaped(), "table.name");
1005
1006 assert!("db.schema.table.extra".parse::<TableName>().is_err());
1008 assert!("".parse::<TableName>().is_err());
1009 }
1010
1011 #[test]
1012 fn test_schema_name_macro() -> Result<()> {
1013 let schema = schema_name!("public")?;
1015 assert_eq!(schema.to_string(), "\"public\"");
1016 assert_eq!(schema.unescaped(), "public");
1017
1018 let qualified = schema_name!("mydb", "public")?;
1020 assert_eq!(qualified.to_string(), "\"mydb\".\"public\"");
1021 assert_eq!(qualified.unescaped(), "public");
1022 assert_eq!(qualified.database().unwrap().unescaped(), "mydb");
1023 Ok(())
1024 }
1025
1026 #[test]
1027 fn test_table_name_macro() -> Result<()> {
1028 let table = table_name!("users")?;
1030 assert_eq!(table.to_string(), "\"users\"");
1031 assert_eq!(table.unescaped(), "users");
1032
1033 let with_schema = table_name!("public", "users")?;
1035 assert_eq!(with_schema.to_string(), "\"public\".\"users\"");
1036 assert_eq!(with_schema.unescaped(), "users");
1037 assert_eq!(with_schema.schema().unwrap().unescaped(), "public");
1038
1039 let full = table_name!("mydb", "public", "users")?;
1041 assert_eq!(full.to_string(), "\"mydb\".\"public\".\"users\"");
1042 assert_eq!(full.unescaped(), "users");
1043 assert_eq!(full.schema().unwrap().unescaped(), "public");
1044 assert_eq!(full.database().unwrap().unescaped(), "mydb");
1045 Ok(())
1046 }
1047
1048 #[test]
1049 fn test_schema_name_try_from() {
1050 let schema: SchemaName = "public".try_into().unwrap();
1052 assert_eq!(schema.to_string(), "\"public\"");
1053 assert_eq!(schema.unescaped(), "public");
1054
1055 let qualified: SchemaName = "mydb.public".try_into().unwrap();
1057 assert_eq!(qualified.to_string(), "\"mydb\".\"public\"");
1058 assert_eq!(qualified.unescaped(), "public");
1059 assert_eq!(qualified.database().unwrap().unescaped(), "mydb");
1060
1061 let schema_string: SchemaName = String::from("public").try_into().unwrap();
1063 assert_eq!(schema_string.to_string(), "\"public\"");
1064 }
1065
1066 #[test]
1067 fn test_table_name_try_from() {
1068 let table: TableName = "users".try_into().unwrap();
1070 assert_eq!(table.to_string(), "\"users\"");
1071 assert_eq!(table.unescaped(), "users");
1072
1073 let with_schema: TableName = "public.users".try_into().unwrap();
1075 assert_eq!(with_schema.to_string(), "\"public\".\"users\"");
1076 assert_eq!(with_schema.unescaped(), "users");
1077 assert_eq!(with_schema.schema().unwrap().unescaped(), "public");
1078
1079 let full: TableName = "mydb.public.users".try_into().unwrap();
1081 assert_eq!(full.to_string(), "\"mydb\".\"public\".\"users\"");
1082 assert_eq!(full.unescaped(), "users");
1083 assert_eq!(full.schema().unwrap().unescaped(), "public");
1084 assert_eq!(full.database().unwrap().unescaped(), "mydb");
1085
1086 let table_string: TableName = String::from("users").try_into().unwrap();
1088 assert_eq!(table_string.to_string(), "\"users\"");
1089
1090 let invalid: std::result::Result<TableName, _> = "db.schema.table.extra".try_into();
1092 assert!(invalid.is_err());
1093 }
1094}