1
1
use super :: csv_import:: { extract_csv_copy_statement, CsvImport } ;
2
2
use super :: sql_pseudofunctions:: { func_call_to_param, StmtParam } ;
3
3
use crate :: file_cache:: AsyncFromStrWithState ;
4
- use crate :: utils:: add_value_to_map;
5
4
use crate :: { AppState , Database } ;
6
5
use anyhow:: Context ;
7
6
use async_trait:: async_trait;
@@ -59,7 +58,7 @@ pub(super) struct StmtWithParams {
59
58
#[ derive( Debug ) ]
60
59
pub ( super ) enum ParsedStatement {
61
60
StmtWithParams ( StmtWithParams ) ,
62
- StaticSimpleSelect ( serde_json :: Map < String , serde_json :: Value > ) ,
61
+ StaticSimpleSelect ( Vec < ( String , SimpleSelectValue ) > ) ,
63
62
SetVariable {
64
63
variable : StmtParam ,
65
64
value : StmtWithParams ,
@@ -68,6 +67,12 @@ pub(super) enum ParsedStatement {
68
67
Error ( anyhow:: Error ) ,
69
68
}
70
69
70
+ #[ derive( Debug , PartialEq ) ]
71
+ pub ( super ) enum SimpleSelectValue {
72
+ Static ( serde_json:: Value ) ,
73
+ Dynamic ( StmtParam ) ,
74
+ }
75
+
71
76
fn parse_sql < ' a > (
72
77
dialect : & ' a dyn Dialect ,
73
78
sql : & ' a str ,
@@ -93,11 +98,6 @@ fn parse_single_statement(parser: &mut Parser<'_>, db_kind: AnyKind) -> Option<P
93
98
} ;
94
99
log:: debug!( "Parsed statement: {stmt}" ) ;
95
100
while parser. consume_token ( & SemiColon ) { }
96
- if let Some ( static_statement) = extract_static_simple_select ( & stmt) {
97
- log:: debug!( "Optimised a static simple select to avoid a trivial database query: {stmt} optimized to {static_statement:?}" ) ;
98
- return Some ( ParsedStatement :: StaticSimpleSelect ( static_statement) ) ;
99
- }
100
-
101
101
let params = ParameterExtractor :: extract_parameters ( & mut stmt, db_kind) ;
102
102
if let Some ( ( variable, query) ) = extract_set_variable ( & mut stmt) {
103
103
return Some ( ParsedStatement :: SetVariable {
@@ -108,6 +108,10 @@ fn parse_single_statement(parser: &mut Parser<'_>, db_kind: AnyKind) -> Option<P
108
108
if let Some ( csv_import) = extract_csv_copy_statement ( & mut stmt) {
109
109
return Some ( ParsedStatement :: CsvImport ( csv_import) ) ;
110
110
}
111
+ if let Some ( static_statement) = extract_static_simple_select ( & stmt, & params) {
112
+ log:: debug!( "Optimised a static simple select to avoid a trivial database query: {stmt} optimized to {static_statement:?}" ) ;
113
+ return Some ( ParsedStatement :: StaticSimpleSelect ( static_statement) ) ;
114
+ }
111
115
let query = stmt. to_string ( ) ;
112
116
log:: debug!( "Final transformed statement: {stmt}" ) ;
113
117
Some ( ParsedStatement :: StmtWithParams ( StmtWithParams {
@@ -174,7 +178,8 @@ fn map_param(mut name: String) -> StmtParam {
174
178
175
179
fn extract_static_simple_select (
176
180
stmt : & Statement ,
177
- ) -> Option < serde_json:: Map < String , serde_json:: Value > > {
181
+ params : & [ StmtParam ] ,
182
+ ) -> Option < Vec < ( String , SimpleSelectValue ) > > {
178
183
let set_expr = match stmt {
179
184
Statement :: Query ( q)
180
185
if q. limit . is_none ( )
@@ -208,22 +213,52 @@ fn extract_static_simple_select(
208
213
}
209
214
_ => return None ,
210
215
} ;
211
- let mut map = serde_json:: Map :: with_capacity ( select_items. len ( ) ) ;
216
+ let mut items = Vec :: with_capacity ( select_items. len ( ) ) ;
217
+ let mut params_iter = params. iter ( ) . cloned ( ) ;
212
218
for select_item in select_items {
213
219
let sqlparser:: ast:: SelectItem :: ExprWithAlias { expr, alias } = select_item else {
214
220
return None ;
215
221
} ;
222
+ use serde_json:: Value :: * ;
223
+ use SimpleSelectValue :: * ;
216
224
let value = match expr {
217
- Expr :: Value ( Value :: Boolean ( b) ) => serde_json:: Value :: Bool ( * b) ,
218
- Expr :: Value ( Value :: Number ( n, _) ) => serde_json:: Value :: Number ( n. parse ( ) . ok ( ) ?) ,
219
- Expr :: Value ( Value :: SingleQuotedString ( s) ) => serde_json:: Value :: String ( s. clone ( ) ) ,
220
- Expr :: Value ( Value :: Null ) => serde_json:: Value :: Null ,
221
- _ => return None ,
225
+ Expr :: Value ( Value :: Boolean ( b) ) => Static ( Bool ( * b) ) ,
226
+ Expr :: Value ( Value :: Number ( n, _) ) => Static ( Number ( n. parse ( ) . ok ( ) ?) ) ,
227
+ Expr :: Value ( Value :: SingleQuotedString ( s) ) => Static ( String ( s. clone ( ) ) ) ,
228
+ Expr :: Value ( Value :: Null ) => Static ( Null ) ,
229
+ e if is_simple_select_placeholder ( e) => {
230
+ if let Some ( p) = params_iter. next ( ) {
231
+ Dynamic ( p)
232
+ } else {
233
+ log:: error!( "Parameter not extracted for placehorder: {expr:?}" ) ;
234
+ return None ;
235
+ }
236
+ }
237
+ other => {
238
+ log:: trace!( "Cancelling simple select optimization because of expr: {other:?}" ) ;
239
+ return None ;
240
+ }
222
241
} ;
223
242
let key = alias. value . clone ( ) ;
224
- map = add_value_to_map ( map, ( key, value) ) ;
243
+ items. push ( ( key, value) ) ;
244
+ }
245
+ if let Some ( p) = params_iter. next ( ) {
246
+ log:: error!( "static select extraction failed because of extraneous parameter: {p:?}" ) ;
247
+ return None ;
248
+ }
249
+ Some ( items)
250
+ }
251
+
252
+ fn is_simple_select_placeholder ( e : & Expr ) -> bool {
253
+ match e {
254
+ Expr :: Value ( Value :: Placeholder ( _) ) => true ,
255
+ Expr :: Cast {
256
+ expr,
257
+ data_type : DataType :: Text | DataType :: Varchar ( _) | DataType :: Char ( _) ,
258
+ format : None ,
259
+ } if is_simple_select_placeholder ( expr) => true ,
260
+ _ => false ,
225
261
}
226
- Some ( map)
227
262
}
228
263
229
264
fn extract_set_variable ( stmt : & mut Statement ) -> Option < ( StmtParam , String ) > {
@@ -705,59 +740,109 @@ mod test {
705
740
706
741
#[ test]
707
742
fn test_static_extract ( ) {
743
+ use SimpleSelectValue :: Static ;
744
+
708
745
assert_eq ! (
709
- extract_static_simple_select( & parse_postgres_stmt(
710
- "select 'hello' as hello, 42 as answer, null as nothing, 'world' as hello"
711
- ) ) ,
712
- Some (
713
- serde_json:: json!( {
714
- "hello" : [ "hello" , "world" ] ,
715
- "answer" : 42 ,
716
- "nothing" : ( ) ,
717
- } )
718
- . as_object( )
719
- . unwrap( )
720
- . clone( )
721
- )
746
+ extract_static_simple_select(
747
+ & parse_postgres_stmt(
748
+ "select 'hello' as hello, 42 as answer, null as nothing, 'world' as hello"
749
+ ) ,
750
+ & [ ]
751
+ ) ,
752
+ Some ( vec![
753
+ ( "hello" . into( ) , Static ( "hello" . into( ) ) ) ,
754
+ ( "answer" . into( ) , Static ( 42 . into( ) ) ) ,
755
+ ( "nothing" . into( ) , Static ( ( ) . into( ) ) ) ,
756
+ ( "hello" . into( ) , Static ( "world" . into( ) ) ) ,
757
+ ] )
758
+ ) ;
759
+ }
760
+
761
+ #[ test]
762
+ fn test_simple_select_with_sqlpage_pseudofunction ( ) {
763
+ let sql = "select 'text' as component, $x as contents, $y as title" ;
764
+ let dialects: & [ & dyn Dialect ] = & [
765
+ & PostgreSqlDialect { } ,
766
+ & SQLiteDialect { } ,
767
+ & MySqlDialect { } ,
768
+ & MsSqlDialect { } ,
769
+ ] ;
770
+ for & dialect in dialects {
771
+ let parsed: Vec < ParsedStatement > = parse_sql ( dialect, sql) . unwrap ( ) . collect ( ) ;
772
+ use SimpleSelectValue :: { Dynamic , Static } ;
773
+ use StmtParam :: GetOrPost ;
774
+ match & parsed[ ..] {
775
+ [ ParsedStatement :: StaticSimpleSelect ( q) ] => assert_eq ! (
776
+ q,
777
+ & [
778
+ ( "component" . into( ) , Static ( "text" . into( ) ) ) ,
779
+ ( "contents" . into( ) , Dynamic ( GetOrPost ( "x" . into( ) ) ) ) ,
780
+ ( "title" . into( ) , Dynamic ( GetOrPost ( "y" . into( ) ) ) ) ,
781
+ ]
782
+ ) ,
783
+ other => panic ! ( "failed to extract simple select in {dialect:?}: {other:?}" ) ,
784
+ }
785
+ }
786
+ }
787
+
788
+ #[ test]
789
+ fn test_simple_select_only_extraction ( ) {
790
+ use SimpleSelectValue :: { Dynamic , Static } ;
791
+ use StmtParam :: Cookie ;
792
+ assert_eq ! (
793
+ extract_static_simple_select(
794
+ & parse_postgres_stmt( "select 'text' as component, $1 as contents" ) ,
795
+ & [ Cookie ( "cook" . into( ) ) ]
796
+ ) ,
797
+ Some ( vec![
798
+ ( "component" . into( ) , Static ( "text" . into( ) ) ) ,
799
+ ( "contents" . into( ) , Dynamic ( Cookie ( "cook" . into( ) ) ) ) ,
800
+ ] )
722
801
) ;
723
802
}
724
803
725
804
#[ test]
726
805
fn test_static_extract_doesnt_match ( ) {
727
806
assert_eq ! (
728
- extract_static_simple_select( & parse_postgres_stmt(
729
- "select 'hello' as hello, 42 as answer limit 0"
730
- ) ) ,
807
+ extract_static_simple_select(
808
+ & parse_postgres_stmt( "select 'hello' as hello, 42 as answer limit 0" ) ,
809
+ & [ ]
810
+ ) ,
731
811
None
732
812
) ;
733
813
assert_eq ! (
734
- extract_static_simple_select( & parse_postgres_stmt(
735
- "select 'hello' as hello, 42 as answer order by 1"
736
- ) ) ,
814
+ extract_static_simple_select(
815
+ & parse_postgres_stmt( "select 'hello' as hello, 42 as answer order by 1" ) ,
816
+ & [ ]
817
+ ) ,
737
818
None
738
819
) ;
739
820
assert_eq ! (
740
- extract_static_simple_select( & parse_postgres_stmt(
741
- "select 'hello' as hello, 42 as answer offset 1"
742
- ) ) ,
821
+ extract_static_simple_select(
822
+ & parse_postgres_stmt( "select 'hello' as hello, 42 as answer offset 1" ) ,
823
+ & [ ]
824
+ ) ,
743
825
None
744
826
) ;
745
827
assert_eq ! (
746
- extract_static_simple_select( & parse_postgres_stmt(
747
- "select 'hello' as hello, 42 as answer where 1 = 0"
748
- ) ) ,
828
+ extract_static_simple_select(
829
+ & parse_postgres_stmt( "select 'hello' as hello, 42 as answer where 1 = 0" ) ,
830
+ & [ ]
831
+ ) ,
749
832
None
750
833
) ;
751
834
assert_eq ! (
752
- extract_static_simple_select( & parse_postgres_stmt(
753
- "select 'hello' as hello, 42 as answer FROM t"
754
- ) ) ,
835
+ extract_static_simple_select(
836
+ & parse_postgres_stmt( "select 'hello' as hello, 42 as answer FROM t" ) ,
837
+ & [ ]
838
+ ) ,
755
839
None
756
840
) ;
757
841
assert_eq ! (
758
- extract_static_simple_select( & parse_postgres_stmt(
759
- "select x'CAFEBABE' as hello, 42 as answer"
760
- ) ) ,
842
+ extract_static_simple_select(
843
+ & parse_postgres_stmt( "select x'CAFEBABE' as hello, 42 as answer" ) ,
844
+ & [ ]
845
+ ) ,
761
846
None
762
847
) ;
763
848
}
0 commit comments