11use graph:: prelude:: BlockNumber ;
2+ use graph:: schema:: AggregationInterval ;
23use sqlparser:: ast:: {
3- Expr , Ident , ObjectName , Offset , Query , SetExpr , Statement , TableAlias , TableFactor , Value ,
4- VisitMut , VisitorMut ,
4+ Expr , FunctionArg , FunctionArgExpr , Ident , ObjectName , Offset , Query , SetExpr , Statement ,
5+ TableAlias , TableFactor , Value , VisitMut , VisitorMut ,
56} ;
67use sqlparser:: parser:: Parser ;
78use std:: result:: Result ;
@@ -22,12 +23,18 @@ pub enum Error {
2223 NotSelectQuery ,
2324 #[ error( "Unknown table {0}" ) ]
2425 UnknownTable ( String ) ,
26+ #[ error( "Unknown aggregation interval `{1}` for table {0}" ) ]
27+ UnknownAggregationInterval ( String , String ) ,
28+ #[ error( "Invalid syntax for aggregation {0}" ) ]
29+ InvalidAggregationSyntax ( String ) ,
2530 #[ error( "Only constant numbers are supported for LIMIT and OFFSET." ) ]
2631 UnsupportedLimitOffset ,
2732 #[ error( "The limit of {0} is greater than the maximum allowed limit of {1}." ) ]
2833 UnsupportedLimit ( u32 , u32 ) ,
2934 #[ error( "The offset of {0} is greater than the maximum allowed offset of {1}." ) ]
3035 UnsupportedOffset ( u32 , u32 ) ,
36+ #[ error( "Qualified table names are not supported: {0}" ) ]
37+ NoQualifiedTables ( String ) ,
3138}
3239
3340pub struct Validator < ' a > {
@@ -151,25 +158,79 @@ impl VisitorMut for Validator<'_> {
151158 & mut self ,
152159 table_factor : & mut TableFactor ,
153160 ) -> ControlFlow < Self :: Break > {
161+ /// Check whether `args` is a single string argument and return that
162+ /// string
163+ fn extract_string_arg ( args : & Vec < FunctionArg > ) -> Option < String > {
164+ if args. len ( ) != 1 {
165+ return None ;
166+ }
167+ match & args[ 0 ] {
168+ FunctionArg :: Unnamed ( FunctionArgExpr :: Expr ( Expr :: Value (
169+ Value :: SingleQuotedString ( s) ,
170+ ) ) ) => Some ( s. clone ( ) ) ,
171+ _ => None ,
172+ }
173+ }
174+
154175 if let TableFactor :: Table {
155176 name, args, alias, ..
156177 } = table_factor
157178 {
158- if args. is_some ( ) {
159- return self . validate_function_name ( name) ;
179+ if name. 0 . len ( ) != 1 {
180+ // We do not support schema qualified table names
181+ return ControlFlow :: Break ( Error :: NoQualifiedTables ( name. to_string ( ) ) ) ;
160182 }
161- let table = if let Some ( table_name) = name. 0 . last ( ) {
162- let name = & table_name. value ;
163- let Some ( table) = self . layout . table ( name) else {
164- if self . ctes . contains ( name) {
165- return ControlFlow :: Continue ( ( ) ) ;
166- } else {
167- return ControlFlow :: Break ( Error :: UnknownTable ( name. to_string ( ) ) ) ;
168- }
169- } ;
170- table
171- } else {
183+ let table_name = & name. 0 [ 0 ] . value ;
184+
185+ // CTES override subgraph tables
186+ if self . ctes . contains ( & table_name. to_lowercase ( ) ) && args. is_none ( ) {
172187 return ControlFlow :: Continue ( ( ) ) ;
188+ }
189+
190+ let table = match ( self . layout . table ( table_name) , args) {
191+ ( None , None ) => {
192+ return ControlFlow :: Break ( Error :: UnknownTable ( table_name. clone ( ) ) ) ;
193+ }
194+ ( Some ( _) , Some ( _) ) => {
195+ // Table exists but has args, must be a function
196+ return self . validate_function_name ( & name) ;
197+ }
198+ ( None , Some ( args) ) => {
199+ // Table does not exist but has args, is either an
200+ // aggregation table in the form <name>(<interval>) or
201+ // must be a function
202+
203+ if !self . layout . has_aggregation ( table_name) {
204+ // Not an aggregation, must be a function
205+ return self . validate_function_name ( & name) ;
206+ }
207+
208+ let Some ( intv) = extract_string_arg ( args) else {
209+ // Looks like an aggregation, but argument is not a single string
210+ return ControlFlow :: Break ( Error :: InvalidAggregationSyntax (
211+ table_name. clone ( ) ,
212+ ) ) ;
213+ } ;
214+ let Some ( intv) = intv. parse :: < AggregationInterval > ( ) . ok ( ) else {
215+ return ControlFlow :: Break ( Error :: UnknownAggregationInterval (
216+ table_name. clone ( ) ,
217+ intv,
218+ ) ) ;
219+ } ;
220+
221+ let Some ( table) = self . layout . aggregation_table ( table_name, intv) else {
222+ return self . validate_function_name ( & name) ;
223+ } ;
224+ table
225+ }
226+ ( Some ( table) , None ) => {
227+ if !table. object . is_object_type ( ) {
228+ // Interfaces and aggregations can not be queried
229+ // with the table name directly
230+ return ControlFlow :: Break ( Error :: UnknownTable ( table_name. clone ( ) ) ) ;
231+ }
232+ table
233+ }
173234 } ;
174235
175236 // Change 'from table [as alias]' to 'from (select {columns} from table) as alias'
0 commit comments