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 ) ]
4444pub 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) ]
163202mod 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\n multiline" . 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\n multiline" . to_string( ) ) ,
311360 vec![
0 commit comments