Skip to content

Commit 2ff6607

Browse files
committed
updated table to account for duplicates
1 parent 688bc66 commit 2ff6607

File tree

4 files changed

+131
-23
lines changed

4 files changed

+131
-23
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ CREATE TABLE users (
6464
// let docs = SqlDoc::from_dir(&base).build()?;
6565

6666
// Retrieve a specific table
67-
let users = docs.table("users")?;
67+
let users = docs.table("users", None)?;
6868

6969
// Table name
7070
assert_eq!(users.name(), "users");

src/docs.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,18 @@ impl TableDoc {
133133
///
134134
/// # Errors
135135
/// - Will return [`DocError::ColumnNotFound`] if the expected table is not found
136+
/// - Will return [`DocError::DuplicateColumnsFound`] if more than one column matches
136137
pub fn column(&self, name: &str) -> Result<&ColumnDoc, DocError> {
137-
self.columns().binary_search_by(|c| c.name().cmp(name)).map_or_else(
138-
|_| Err(DocError::ColumnNotFound { name: name.to_owned() }),
139-
|id| Ok(&self.columns()[id]),
140-
)
138+
let columns = self.columns();
139+
let start = columns.partition_point(|n| n.name() < name);
140+
if start == columns.len() || columns[start].name() != name {
141+
return Err(DocError::ColumnNotFound { name: name.to_owned() });
142+
}
143+
let end = columns.partition_point(|n| n.name() <= name);
144+
match &columns[start..end] {
145+
[single] => Ok(single),
146+
multiple => Err(DocError::DuplicateColumnsFound { columns: multiple.to_vec() }),
147+
}
141148
}
142149

143150
/// Getter method for retrieving the table's [`Path`]

src/error.rs

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ pub enum DocError {
4747
/// `Vec` of the [`crate::docs::ColumnDoc`] for each duplicate table found
4848
columns: Vec<ColumnDoc>,
4949
},
50+
/// Could not find table with `schema`
51+
TableWithSchemaNotFound {
52+
/// the name of the table not found
53+
name: String,
54+
/// the schema for the table not found
55+
schema: String,
56+
},
5057
}
5158

5259
impl fmt::Display for DocError {
@@ -76,6 +83,9 @@ impl fmt::Display for DocError {
7683
}
7784
Ok(())
7885
}
86+
Self::TableWithSchemaNotFound { name, schema } => {
87+
writeln!(f, "Table: {name} with schema: {schema} not found in SqlDoc")
88+
}
7989
}
8090
}
8191
}
@@ -90,7 +100,8 @@ impl error::Error for DocError {
90100
| Self::TableNotFound { .. }
91101
| Self::ColumnNotFound { .. }
92102
| Self::DuplicateTablesFound { .. }
93-
| Self::DuplicateColumnsFound { .. } => None,
103+
| Self::DuplicateColumnsFound { .. }
104+
| Self::TableWithSchemaNotFound { .. } => None,
94105
}
95106
}
96107
}
@@ -148,31 +159,32 @@ mod tests {
148159
fn test_doc_errors_from() {
149160
use crate::comments::Location;
150161
use std::fs;
162+
151163
let Err(io_error) = fs::read_dir("INVALID") else {
152164
panic!("there should not be a directory called INVALID")
153165
};
154166
let io_kind = io_error.kind();
155-
let doc_io_error = DocError::from(io_error);
156-
match doc_io_error {
157-
DocError::FileReadError(inner) => assert_eq!(inner.kind(), io_kind),
158-
_ => panic!("expected instance of DocError::FileReadError"),
167+
let doc_io_error: DocError = io_error.into();
168+
assert!(matches!(doc_io_error, DocError::FileReadError(_)));
169+
if let DocError::FileReadError(inner) = doc_io_error {
170+
assert_eq!(inner.kind(), io_kind);
159171
}
160172

161173
let comment_error =
162174
CommentError::UnmatchedMultilineCommentStart { location: Location::default() };
163175
let comment_error_str = comment_error.to_string();
164176
let doc_comment_error: DocError = comment_error.into();
165-
match doc_comment_error {
166-
DocError::CommentError(inner) => assert_eq!(inner.to_string(), comment_error_str),
167-
_ => panic!("expected instance of DocError::CommentError"),
177+
assert!(matches!(doc_comment_error, DocError::CommentError(_)));
178+
if let DocError::CommentError(inner) = doc_comment_error {
179+
assert_eq!(inner.to_string(), comment_error_str);
168180
}
169181

170182
let parser_error = ParserError::RecursionLimitExceeded;
171183
let parser_error_str = parser_error.to_string();
172184
let doc_parser_error: DocError = parser_error.into();
173-
match doc_parser_error {
174-
DocError::SqlParserError(inner) => assert_eq!(inner.to_string(), parser_error_str),
175-
_ => panic!("expected instance of DocError::SqlParserError"),
185+
assert!(matches!(doc_parser_error, DocError::SqlParserError(_)));
186+
if let DocError::SqlParserError(inner) = doc_parser_error {
187+
assert_eq!(inner.to_string(), parser_error_str);
176188
}
177189
}
178190

@@ -275,4 +287,23 @@ mod tests {
275287
let dup = DocError::DuplicateColumnsFound { columns: vec![] };
276288
assert!(dup.source().is_none());
277289
}
290+
291+
#[test]
292+
fn test_doc_error_display_table_with_schema_not_found() {
293+
let e = DocError::TableWithSchemaNotFound {
294+
name: "events".to_string(),
295+
schema: "analytics".to_string(),
296+
};
297+
assert_eq!(e.to_string(), "Table: events with schema: analytics not found in SqlDoc\n");
298+
}
299+
300+
#[test]
301+
fn test_doc_error_source_none_for_table_with_schema_not_found() {
302+
use std::error::Error as _;
303+
let e = DocError::TableWithSchemaNotFound {
304+
name: "events".to_string(),
305+
schema: "analytics".to_string(),
306+
};
307+
assert!(e.source().is_none());
308+
}
278309
}

src/sql_doc.rs

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,42 @@ impl SqlDoc {
101101
///
102102
/// # Parameters
103103
/// - the table `name` as a [`str`]
104+
/// - the table schema as `Option` of [`str`]
104105
///
105106
/// # Errors
106107
/// - Will return [`DocError::TableNotFound`] if the expected table is not found
107-
pub fn table(&self, name: &str) -> Result<&TableDoc, DocError> {
108-
self.tables().binary_search_by(|t| t.name().cmp(name)).map_or_else(
109-
|_| Err(DocError::TableNotFound { name: name.to_owned() }),
110-
|id| Ok(&self.tables()[id]),
111-
)
108+
/// - Will return [`DocError::TableWithSchemaNotFound`] if the table name exists but no table matches the given schema
109+
/// - Will return [`DocError::DuplicateTablesFound`] if more than one matching table is found
110+
pub fn table(&self, name: &str, schema: Option<&str>) -> Result<&TableDoc, DocError> {
111+
let tables = self.tables();
112+
let start = tables.partition_point(|n| n.name() < name);
113+
if start == tables.len() || tables[start].name() != name {
114+
return Err(DocError::TableNotFound { name: name.to_owned() });
115+
}
116+
let end = tables.partition_point(|t| t.name() <= name);
117+
match &tables[start..end] {
118+
[single] => Ok(single),
119+
multiple => {
120+
let mut schemas = multiple.iter().filter(|v| v.schema() == schema);
121+
let first = schemas.next().ok_or_else(|| DocError::TableWithSchemaNotFound {
122+
name: name.to_owned(),
123+
schema: schema.map_or_else(
124+
|| "No schema provided".to_owned(),
125+
std::borrow::ToOwned::to_owned,
126+
),
127+
})?;
128+
if schemas.next().is_some() {
129+
return Err(DocError::DuplicateTablesFound {
130+
tables: multiple
131+
.iter()
132+
.filter(|v| v.schema() == schema)
133+
.map(std::borrow::ToOwned::to_owned)
134+
.collect(),
135+
});
136+
}
137+
Ok(first)
138+
}
139+
}
112140
}
113141

114142
/// Method for finding a specific [`TableDoc`] from `schema` and table `name`
@@ -374,7 +402,7 @@ mod tests {
374402
let expected_doc = SqlDoc::new(expected_tables);
375403
assert_eq!(sql_doc, expected_doc);
376404
let table = "users";
377-
assert_eq!(sql_doc.table(table)?, expected_doc.table(table)?);
405+
assert_eq!(sql_doc.table(table, None)?, expected_doc.table(table, None)?);
378406
let schema = "analytics";
379407
let schema_table = "events";
380408
assert_eq!(
@@ -388,7 +416,7 @@ mod tests {
388416
#[test]
389417
fn test_table_err() {
390418
let empty_set = SqlDoc::new(vec![]);
391-
let empty_table_err = empty_set.table("name");
419+
let empty_table_err = empty_set.table("name", None);
392420
assert!(empty_table_err.is_err());
393421
assert!(matches!(
394422
empty_table_err,
@@ -769,4 +797,46 @@ mod tests {
769797
.unwrap_or_else(|_| panic!("expected to find table `users` via binary search"));
770798
assert_eq!(sql_doc.tables()[id].name(), "users");
771799
}
800+
801+
#[test]
802+
fn test_table_with_schema_not_found_when_name_exists() {
803+
let sql_doc = SqlDoc::new(vec![
804+
TableDoc::new(Some("analytics".to_owned()), "events".to_owned(), None, vec![], None),
805+
TableDoc::new(Some("public".to_owned()), "events".to_owned(), None, vec![], None),
806+
]);
807+
808+
match sql_doc.table("events", Some("missing")) {
809+
Err(DocError::TableWithSchemaNotFound { name, schema })
810+
if name == "events" && schema == "missing" => {}
811+
Err(e) => panic!("expected TableWithSchemaNotFound(events, missing), got: {e:?}"),
812+
Ok(_) => panic!("expected error, got Ok"),
813+
}
814+
}
815+
816+
#[test]
817+
fn test_table_duplicate_tables_found_for_same_name_and_schema() {
818+
let sql_doc = SqlDoc::new(vec![
819+
TableDoc::new(Some("analytics".to_owned()), "events".to_owned(), None, vec![], None),
820+
TableDoc::new(Some("analytics".to_owned()), "events".to_owned(), None, vec![], None),
821+
]);
822+
823+
match sql_doc.table("events", Some("analytics")) {
824+
Err(DocError::DuplicateTablesFound { .. }) => {}
825+
Err(e) => panic!("expected DuplicateTablesFound, got: {e:?}"),
826+
Ok(_) => panic!("expected error, got Ok"),
827+
}
828+
}
829+
830+
#[test]
831+
fn test_table_selects_correct_schema_when_multiple_exist()
832+
-> Result<(), Box<dyn std::error::Error>> {
833+
let sql_doc = SqlDoc::new(vec![
834+
TableDoc::new(Some("analytics".to_owned()), "events".to_owned(), None, vec![], None),
835+
TableDoc::new(Some("public".to_owned()), "events".to_owned(), None, vec![], None),
836+
]);
837+
838+
let t = sql_doc.table("events", Some("public"))?;
839+
assert_eq!(t.schema(), Some("public"));
840+
Ok(())
841+
}
772842
}

0 commit comments

Comments
 (0)