Skip to content

Commit 5c84465

Browse files
committed
Implemented schema parsing and objectname error handling
1 parent f79a7fc commit 5c84465

File tree

3 files changed

+140
-113
lines changed

3 files changed

+140
-113
lines changed

src/docs.rs

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
//! Module for parsing sql and comments and returning `table` and `column`
22
//! information, including comments
3-
use sqlparser::ast::{Spanned, Statement};
3+
use sqlparser::ast::{Ident, ObjectName, ObjectNamePart, Spanned, Statement};
44

5-
use crate::{ast::ParsedSqlFile, comments::Comments};
5+
use crate::{ast::ParsedSqlFile, comments::Comments, error::DocError};
66

77
/// Structure for containing the `name` of the `Column` and an [`Option`] for
88
/// the comment as a [`String`]
@@ -38,12 +38,12 @@ impl ColumnDoc {
3838
}
3939

4040
/// Structure for containing the `name` of the `Table`, an [`Option`] for if the
41-
/// table has a schema, an [`Option`] for the comment as a [`String`], and a
41+
/// table has a schema, an [`Option`] for the comment as a [`String`], and a
4242
/// `Vec` of [`ColumnDoc`] contained in the table
4343
#[derive(Clone, Debug, Eq, PartialEq)]
4444
pub struct TableDoc {
45-
name: String,
4645
schema: Option<String>,
46+
name: String,
4747
doc: Option<String>,
4848
columns: Vec<ColumnDoc>,
4949
}
@@ -57,8 +57,13 @@ impl TableDoc {
5757
/// - columns: the `Vec<ColumnDoc>` of all [`ColumnDoc`] for this table
5858
#[must_use]
5959
#[allow(clippy::missing_const_for_fn)]
60-
pub fn new(name: String, doc: Option<String>, columns: Vec<ColumnDoc>) -> Self {
61-
Self { name, doc, columns }
60+
pub fn new(
61+
schema: Option<String>,
62+
name: String,
63+
doc: Option<String>,
64+
columns: Vec<ColumnDoc>,
65+
) -> Self {
66+
Self { schema, name, doc, columns }
6267
}
6368

6469
/// Getter for the `name` field
@@ -109,8 +114,11 @@ impl SqlDocs {
109114
/// # Parameters
110115
/// - `file`: the [`ParsedSqlFile`]
111116
/// - `comments`: the parsed [`Comments`]
112-
#[must_use]
113-
pub fn from_parsed_file(file: &ParsedSqlFile, comments: &Comments) -> Self {
117+
///
118+
/// # Errors
119+
/// - Returns [`DocError::InvalidObjectName`] if the table name has no identifier components.
120+
/// - May also propagate other [`DocError`] variants from lower layers in the future.
121+
pub fn from_parsed_file(file: &ParsedSqlFile, comments: &Comments) -> Result<Self, DocError> {
114122
let mut tables = Vec::new();
115123
for statement in file.statements() {
116124
#[allow(clippy::single_match)]
@@ -131,13 +139,15 @@ impl SqlDocs {
131139
column_docs.push(column_doc);
132140
}
133141
let table_leading = comments.leading_comment(table_start);
142+
let (schema, name) = schema_and_table(&table.name)?;
134143
let table_doc = match table_leading {
135144
Some(comment) => TableDoc::new(
136-
table.name.to_string(),
145+
schema,
146+
name,
137147
Some(comment.text().to_string()),
138148
column_docs,
139149
),
140-
None => TableDoc::new(table.name.to_string(), None, column_docs),
150+
None => TableDoc::new(schema, name, None, column_docs),
141151
};
142152
tables.push(table_doc);
143153
}
@@ -146,7 +156,7 @@ impl SqlDocs {
146156
}
147157
}
148158

149-
Self { tables }
159+
Ok(Self { tables })
150160
}
151161

152162
/// Getter function to get a slice of [`TableDoc`]
@@ -156,8 +166,37 @@ impl SqlDocs {
156166
}
157167
}
158168

169+
/// Helper function that will parse the table's schema and table name.
170+
/// Easily extensible for catalog if neeeded as well.
171+
///
172+
/// # Parameters
173+
/// - `name` the [`ObjectName`] structure for the statement
174+
///
175+
/// # Errors
176+
/// - [`DocError`] will return the location of the statement if there is a statement without a schema and table name.
177+
fn schema_and_table(name: &ObjectName) -> Result<(Option<String>, String), DocError> {
178+
let idents: Vec<&Ident> = name
179+
.0
180+
.iter()
181+
.filter_map(|part| match part {
182+
ObjectNamePart::Identifier(ident) => Some(ident),
183+
ObjectNamePart::Function(_func) => None,
184+
})
185+
.collect();
159186

160-
187+
match idents.as_slice() {
188+
[] => {
189+
let span = name.span();
190+
Err(DocError::InvalidObjectName {
191+
message: "ObjectName had no identifier parts".to_string(),
192+
line: span.start.line,
193+
column: span.start.column,
194+
})
195+
}
196+
[only] => Ok((None, only.value.clone())),
197+
[.., schema, table] => Ok((Some(schema.value.clone()), table.value.clone())),
198+
}
199+
}
161200

162201
#[cfg(test)]
163202
mod tests {
@@ -167,8 +206,12 @@ mod tests {
167206
fn test_sql_docs_struct() {
168207
let column_doc = ColumnDoc::new("id".to_string(), Some("The ID for the table".to_string()));
169208
let columns = vec![column_doc];
170-
let table_doc =
171-
TableDoc::new("user".to_string(), Some("The table for users".to_string()), columns);
209+
let table_doc = TableDoc::new(
210+
None,
211+
"user".to_string(),
212+
Some("The table for users".to_string()),
213+
columns,
214+
);
172215
let tables = vec![table_doc];
173216
let sql_doc = SqlDocs::new(tables);
174217
let sql_doc_val =
@@ -202,14 +245,14 @@ mod tests {
202245

203246
match filename {
204247
"with_single_line_comments.sql" | "with_mixed_comments.sql" => {
205-
assert_eq!(&docs, &expected_values[0]);
248+
assert_eq!(&docs?, &expected_values[0]);
206249
}
207250
"with_multiline_comments.sql" => {
208-
assert_eq!(&docs, &expected_values[1]);
251+
assert_eq!(&docs?, &expected_values[1]);
209252
}
210253
"without_comments.sql" => {
211254
let expected = expected_without_comments_docs();
212-
assert_eq!(&docs, &expected);
255+
assert_eq!(&docs?, &expected);
213256
}
214257
other => {
215258
unreachable!(
@@ -225,6 +268,7 @@ mod tests {
225268
fn expected_without_comments_docs() -> SqlDocs {
226269
SqlDocs::new(vec![
227270
TableDoc::new(
271+
None,
228272
"users".to_string(),
229273
None,
230274
vec![
@@ -235,6 +279,7 @@ mod tests {
235279
],
236280
),
237281
TableDoc::new(
282+
None,
238283
"posts".to_string(),
239284
None,
240285
vec![
@@ -253,6 +298,7 @@ mod tests {
253298

254299
let first_docs = SqlDocs::new(vec![
255300
TableDoc::new(
301+
None,
256302
"users".to_string(),
257303
Some("Users table stores user account information".to_string()),
258304
vec![
@@ -266,6 +312,7 @@ mod tests {
266312
],
267313
),
268314
TableDoc::new(
315+
None,
269316
"posts".to_string(),
270317
Some("Posts table stores blog posts".to_string()),
271318
vec![
@@ -287,6 +334,7 @@ mod tests {
287334

288335
let second_docs = SqlDocs::new(vec![
289336
TableDoc::new(
337+
None,
290338
"users".to_string(),
291339
Some("Users table stores user account information\nmultiline".to_string()),
292340
vec![
@@ -306,6 +354,7 @@ mod tests {
306354
],
307355
),
308356
TableDoc::new(
357+
None,
309358
"posts".to_string(),
310359
Some("Posts table stores blog posts\nmultiline".to_string()),
311360
vec![

src/error.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//! Module for managing Document Errors as [`DocError`]
2+
use crate::comments::CommentError;
3+
use core::fmt;
4+
use sqlparser::parser::ParserError;
5+
use std::{error, fmt::Debug};
6+
/// Error enum for returning relevant error based on error type
7+
#[derive(Debug)]
8+
pub enum DocError {
9+
/// Wrapper for standard [`std::io::Error`]
10+
FileReadError(std::io::Error),
11+
/// Wrapper for [`CommentError`]
12+
CommentError(CommentError),
13+
/// Wrapper for [`ParserError`]
14+
SqlParserError(ParserError),
15+
/// Indicates an invalid or unexpected object name in the [`sqlparser::ast::Statement`]
16+
InvalidObjectName {
17+
/// The message to accompany the invalid [`sqlparser::ast::ObjectName`]
18+
message: String,
19+
/// The line number for the invalid [`sqlparser::ast::ObjectName`]
20+
line: u64,
21+
/// The column number for the invalid [`sqlparser::ast::ObjectName`]
22+
column: u64,
23+
},
24+
}
25+
26+
impl fmt::Display for DocError {
27+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28+
match self {
29+
Self::FileReadError(error) => write!(f, "file read error: {error}"),
30+
Self::CommentError(comment_error) => {
31+
write!(f, "comment parse error: {comment_error}")
32+
}
33+
Self::SqlParserError(parser_error) => write!(f, "SQL parse error {parser_error}"),
34+
Self::InvalidObjectName { message, line, column } => {
35+
write!(f, "{message} at line {line}, column {column}")
36+
}
37+
}
38+
}
39+
}
40+
41+
impl error::Error for DocError {
42+
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
43+
match self {
44+
Self::FileReadError(e) => Some(e),
45+
Self::CommentError(e) => Some(e),
46+
Self::SqlParserError(e) => Some(e),
47+
Self::InvalidObjectName { .. } => None,
48+
}
49+
}
50+
}
51+
52+
impl From<std::io::Error> for DocError {
53+
fn from(e: std::io::Error) -> Self {
54+
Self::FileReadError(e)
55+
}
56+
}
57+
58+
impl From<CommentError> for DocError {
59+
fn from(e: CommentError) -> Self {
60+
Self::CommentError(e)
61+
}
62+
}
63+
64+
impl From<ParserError> for DocError {
65+
fn from(e: ParserError) -> Self {
66+
Self::SqlParserError(e)
67+
}
68+
}

src/lib.rs

Lines changed: 6 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -4,105 +4,15 @@
44
//! - [`ast`] : parse SQL into an AST using [`sqlparser`]
55
//! - [`comments`] : extract and model comments and spans
66
7-
use core::fmt;
8-
use std::{
9-
error,
10-
fmt::Debug,
11-
path::{Path, PathBuf},
12-
};
13-
14-
use sqlparser::parser::ParserError;
15-
16-
use crate::{
17-
ast::ParsedSqlFileSet,
18-
comments::{CommentError, Comments},
19-
docs::SqlDocs,
20-
files::SqlFileSet,
21-
};
7+
use crate::{ast::ParsedSqlFileSet, comments::Comments, docs::SqlDocs, files::SqlFileSet};
8+
pub use error::DocError;
9+
use std::path::{Path, PathBuf};
2210
pub mod ast;
2311
pub mod comments;
2412
pub mod docs;
13+
pub mod error;
2514
pub mod files;
2615

27-
/// Error enum for returning relevant error based on error type
28-
#[derive(Debug)]
29-
pub enum DocError {
30-
/// Wrapper for standard [`std::io::Error`]
31-
FileReadError(std::io::Error),
32-
/// Wrapper for [`CommentError`]
33-
CommentError(CommentError),
34-
/// Wrapper for [`ParserError`]
35-
SqlParserError(ParserError),
36-
}
37-
38-
impl fmt::Display for DocError {
39-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40-
match self {
41-
Self::FileReadError(error) => write!(f, "file read error: {error}"),
42-
Self::CommentError(comment_error) => {
43-
write!(f, "comment parse error: {comment_error}")
44-
}
45-
Self::SqlParserError(parser_error) => write!(f, "SQL parse error {parser_error}"),
46-
}
47-
}
48-
}
49-
50-
impl error::Error for DocError {
51-
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
52-
match self {
53-
Self::FileReadError(e) => Some(e),
54-
Self::CommentError(e) => Some(e),
55-
Self::SqlParserError(e) => Some(e),
56-
}
57-
}
58-
}
59-
60-
impl From<std::io::Error> for DocError {
61-
fn from(e: std::io::Error) -> Self {
62-
Self::FileReadError(e)
63-
}
64-
}
65-
66-
impl From<CommentError> for DocError {
67-
fn from(e: CommentError) -> Self {
68-
Self::CommentError(e)
69-
}
70-
}
71-
72-
impl From<ParserError> for DocError {
73-
fn from(e: ParserError) -> Self {
74-
Self::SqlParserError(e)
75-
}
76-
}
77-
78-
/// Struct for the SqlDoc
79-
pub struct SqlDoc {
80-
files: Vec<(PathBuf, SqlDocs)>,
81-
}
82-
83-
impl SqlDoc {
84-
pub fn table (&self, name: &str) -> Result<&TableDoc, DocError> {
85-
86-
}
87-
pub fn table_with_schema(&self, schema: &str, name: &str) -> Result<&TableDoc, DocError> {
88-
89-
}
90-
pub fn from_path(path: &Path) -> {
91-
92-
}
93-
}
94-
/// Builder struct for the [`SqlDoc`]
95-
pub struct SqlDocBuilder {
96-
sql_docs: SqlDoc,
97-
}
98-
impl SqlDocsBuilder {
99-
fn new() -> Self {
100-
Self {}
101-
}
102-
}
103-
104-
105-
10616
/// Primary Entry point. Returns a tuple of [`PathBuf`] and [`SqlDocs`].
10717
///
10818
/// # Parameters:
@@ -112,7 +22,7 @@ impl SqlDocsBuilder {
11222
/// other file types to ignore).
11323
///
11424
/// # Errors
115-
/// - Will return a `DocError` that specifies the error (io, comment parsing,
25+
/// - Will return a [`DocError`] that specifies the error (io, comment parsing,
11626
/// sql parsing)
11727
///
11828
/// # Example
@@ -136,7 +46,7 @@ pub fn generate_docs_from_dir<P: AsRef<Path>, S: AsRef<str>>(
13646
// iterate on each file and generate the `SqlDocs` and associate with the `Path`
13747
for file in parsed_files.files() {
13848
let comments = Comments::parse_all_comments_from_file(file)?;
139-
let docs = SqlDocs::from_parsed_file(file, &comments);
49+
let docs = SqlDocs::from_parsed_file(file, &comments)?;
14050
let path = file.file().path().to_path_buf();
14151
sql_docs.push((path, docs));
14252
}

0 commit comments

Comments
 (0)