@@ -72,15 +72,16 @@ pub mod parser;
72
72
pub mod planner;
73
73
pub mod types;
74
74
75
+ /// SQL tests are implemented as goldenscripts under src/sql/testscripts.
75
76
#[ cfg( test) ]
76
77
mod tests {
77
78
use super :: engine:: { Catalog as _, Session } ;
78
79
use super :: parser:: Parser ;
79
- use super :: planner:: { Node , Plan , OPTIMIZERS } ;
80
+ use super :: planner:: { Plan , OPTIMIZERS } ;
80
81
use crate :: encoding:: format:: { self , Formatter as _} ;
81
82
use crate :: sql:: engine:: { Engine , Local , StatementResult } ;
82
83
use crate :: sql:: planner:: { Planner , Scope } ;
83
- use crate :: storage:: engine:: test:: { self , Emit , Mirror , Operation } ;
84
+ use crate :: storage:: engine:: test as testengine ;
84
85
use crate :: storage:: { self , Engine as _} ;
85
86
86
87
use crossbeam:: channel:: Receiver ;
@@ -92,24 +93,25 @@ mod tests {
92
93
use test_each_file:: test_each_path;
93
94
94
95
// Run goldenscript tests in src/sql/testscripts.
95
- test_each_path ! { in "src/sql/testscripts/optimizer" as optimizer => test_goldenscript }
96
+ test_each_path ! { in "src/sql/testscripts/expressions" as expressions => test_goldenscript_expr }
97
+ test_each_path ! { in "src/sql/testscripts/optimizers" as optimizers => test_goldenscript }
96
98
test_each_path ! { in "src/sql/testscripts/queries" as queries => test_goldenscript }
97
99
test_each_path ! { in "src/sql/testscripts/schema" as schema => test_goldenscript }
98
100
test_each_path ! { in "src/sql/testscripts/transactions" as transactions => test_goldenscript }
99
101
test_each_path ! { in "src/sql/testscripts/writes" as writes => test_goldenscript }
100
- test_each_path ! { in "src/sql/testscripts/expressions" as expressions => test_goldenscript_expr }
101
102
102
103
/// Runs SQL goldenscripts.
103
104
fn test_goldenscript ( path : & std:: path:: Path ) {
104
- // Since the runner's Session can't reference an Engine in the same
105
- // struct, borrow the engine. Use both a BitCask and a Memory engine,
106
- // and mirror operations across them. Emit engine operations to op_rx.
105
+ // The runner's Session can't borrow from an Engine in the same struct,
106
+ // so pass an engine reference . Use both BitCask and Memory engines and
107
+ // mirror operations across them. Emit engine operations to op_rx.
107
108
let ( op_tx, op_rx) = crossbeam:: channel:: unbounded ( ) ;
108
109
let tempdir = tempfile:: TempDir :: with_prefix ( "toydb" ) . expect ( "tempdir failed" ) ;
109
110
let bitcask =
110
111
storage:: BitCask :: new ( tempdir. path ( ) . join ( "bitcask" ) ) . expect ( "bitcask failed" ) ;
111
112
let memory = storage:: Memory :: new ( ) ;
112
- let engine = Local :: new ( Emit :: new ( Mirror :: new ( bitcask, memory) , op_tx) ) ;
113
+ let engine =
114
+ Local :: new ( testengine:: Emit :: new ( testengine:: Mirror :: new ( bitcask, memory) , op_tx) ) ;
113
115
let mut runner = SQLRunner :: new ( & engine, op_rx) ;
114
116
115
117
goldenscript:: run ( & mut runner, path) . expect ( "goldenscript failed" )
@@ -124,13 +126,14 @@ mod tests {
124
126
struct SQLRunner < ' a > {
125
127
engine : & ' a TestEngine ,
126
128
sessions : HashMap < String , Session < ' a , TestEngine > > ,
127
- op_rx : Receiver < Operation > ,
129
+ op_rx : Receiver < testengine :: Operation > ,
128
130
}
129
131
130
- type TestEngine = Local < test:: Emit < test:: Mirror < storage:: BitCask , storage:: Memory > > > ;
132
+ type TestEngine =
133
+ Local < testengine:: Emit < testengine:: Mirror < storage:: BitCask , storage:: Memory > > > ;
131
134
132
135
impl < ' a > SQLRunner < ' a > {
133
- fn new ( engine : & ' a TestEngine , op_rx : Receiver < Operation > ) -> Self {
136
+ fn new ( engine : & ' a TestEngine , op_rx : Receiver < testengine :: Operation > ) -> Self {
134
137
Self { engine, sessions : HashMap :: new ( ) , op_rx }
135
138
}
136
139
}
@@ -139,7 +142,7 @@ mod tests {
139
142
fn run ( & mut self , command : & goldenscript:: Command ) -> Result < String , Box < dyn Error > > {
140
143
let mut output = String :: new ( ) ;
141
144
142
- // Obtain a session for the command prefix.
145
+ // Obtain a session based on the command prefix ("" if none) .
143
146
let prefix = command. prefix . clone ( ) . unwrap_or_default ( ) ;
144
147
let session = self . sessions . entry ( prefix) . or_insert_with ( || self . engine . session ( ) ) ;
145
148
@@ -151,12 +154,9 @@ mod tests {
151
154
let mut engine = self . engine . mvcc . engine . lock ( ) . expect ( "mutex failed" ) ;
152
155
let mut iter = engine. scan ( ..) ;
153
156
while let Some ( ( key, value) ) = iter. next ( ) . transpose ( ) ? {
154
- writeln ! (
155
- output,
156
- "{} [{}]" ,
157
- format:: MVCC :: <format:: SQL >:: key_value( & key, & value) ,
158
- format:: Raw :: key_value( & key, & value)
159
- ) ?;
157
+ let fmtkv = format:: MVCC :: < format:: SQL > :: key_value ( & key, & value) ;
158
+ let rawkv = format:: Raw :: key_value ( & key, & value) ;
159
+ writeln ! ( output, "{fmtkv} [{rawkv}]" , ) ?;
160
160
}
161
161
return Ok ( output) ;
162
162
}
@@ -175,56 +175,71 @@ mod tests {
175
175
. map ( |t| session. with_txn ( true , |txn| txn. must_get_table ( & t) ) )
176
176
. try_collect ( ) ?
177
177
} ;
178
- return Ok ( schemas. into_iter ( ) . map ( |s| s . to_string ( ) ) . join ( "\n " ) ) ;
178
+ return Ok ( schemas. into_iter ( ) . join ( "\n " ) ) ;
179
179
}
180
180
181
181
// Otherwise, fall through to SQL execution.
182
182
_ => { }
183
183
}
184
184
185
- // The entire command is the statement to execute . There are no args.
185
+ // The entire command is the SQL statement . There are no args.
186
186
if !command. args . is_empty ( ) {
187
- return Err ( "expressions should be given as a command with no args" . into ( ) ) ;
187
+ return Err ( "SQL statements should be given as a command with no args" . into ( ) ) ;
188
188
}
189
189
let input = & command. name ;
190
190
let mut tags = command. tags . clone ( ) ;
191
191
192
- // Execute the statement.
193
- let result = session. execute ( input) ?;
192
+ // Output the plan if requested.
193
+ if tags. remove ( "plan" ) {
194
+ let ast = Parser :: new ( input) . parse ( ) ?;
195
+ let plan =
196
+ session. with_txn ( true , |txn| Planner :: new ( txn) . build ( ast) ?. optimize ( ) ) ?;
197
+ writeln ! ( output, "{plan}" ) ?;
198
+ }
194
199
195
- // Output optimizations if requested.
200
+ // Output plan optimizations if requested.
196
201
if tags. remove ( "opt" ) {
197
202
if tags. contains ( "plan" ) {
198
- return Err ( "no point using both plan and opt" . into ( ) ) ;
203
+ return Err ( "using both plan and opt is redundant " . into ( ) ) ;
199
204
}
200
205
let ast = Parser :: new ( input) . parse ( ) ?;
201
206
let plan = session. with_txn ( true , |txn| Planner :: new ( txn) . build ( ast) ) ?;
202
207
let Plan :: Select ( mut root) = plan else {
203
208
return Err ( "can only use opt with SELECT plans" . into ( ) ) ;
204
209
} ;
205
-
206
- let fmtplan = |name, node : & Node | format ! ( "{name}:\n {node}" ) . replace ( '\n' , "\n " ) ;
207
- writeln ! ( output, "{}" , fmtplan( "Initial" , & root) ) ?;
210
+ writeln ! ( output, "{}" , format!( "Initial:\n {root}" ) . replace( '\n' , "\n " ) ) ?;
208
211
for ( name, optimizer) in OPTIMIZERS {
209
- let old = root. clone ( ) ;
212
+ let prev = root. clone ( ) ;
210
213
root = optimizer ( root) ?;
211
- if root != old {
212
- writeln ! ( output, "{}" , fmtplan ( name, & root ) ) ?;
214
+ if root != prev {
215
+ writeln ! ( output, "{}" , format! ( "{ name}: \n {root}" ) . replace ( '\n' , " \n " ) ) ?;
213
216
}
214
217
}
215
218
}
216
219
217
- // Output the plan if requested.
218
- if tags. remove ( "plan" ) {
219
- let query = format ! ( "EXPLAIN {input}" ) ;
220
- let StatementResult :: Explain ( plan) = session. execute ( & query) ? else {
221
- return Err ( "unexpected explain response" . into ( ) ) ;
222
- } ;
223
- writeln ! ( output, "{plan}" ) ?;
220
+ // Execute the statement.
221
+ let result = session. execute ( input) ?;
222
+
223
+ // Output engine ops if requested.
224
+ if tags. remove ( "ops" ) {
225
+ while let Ok ( op) = self . op_rx . try_recv ( ) {
226
+ match op {
227
+ testengine:: Operation :: Delete { key } => {
228
+ let fmtkey = format:: MVCC :: < format:: SQL > :: key ( & key) ;
229
+ let rawkey = format:: Raw :: key ( & key) ;
230
+ writeln ! ( output, "delete {fmtkey} [{rawkey}]" ) ?;
231
+ }
232
+ testengine:: Operation :: Flush => writeln ! ( output, "flush" ) ?,
233
+ testengine:: Operation :: Set { key, value } => {
234
+ let fmtkv = format:: MVCC :: < format:: SQL > :: key_value ( & key, & value) ;
235
+ let rawkv = format:: Raw :: key_value ( & key, & value) ;
236
+ writeln ! ( output, "set {fmtkv} [{rawkv}]" ) ?;
237
+ }
238
+ }
239
+ }
224
240
}
225
241
226
242
// Output the result if requested. SELECT results are always output.
227
- let show_result = tags. remove ( "result" ) ;
228
243
match result {
229
244
StatementResult :: Select { columns, rows } => {
230
245
if tags. remove ( "header" ) {
@@ -234,37 +249,10 @@ mod tests {
234
249
writeln ! ( output, "{}" , row. into_iter( ) . join( ", " ) ) ?;
235
250
}
236
251
}
237
- StatementResult :: Begin { state } if show_result => {
238
- let version = state. version ;
239
- let kind = if state. read_only { "read-only" } else { "read-write" } ;
240
- let active = state. active . iter ( ) . join ( "," ) ;
241
- writeln ! ( output, "v{version} {kind} active={{{active}}}" ) ?;
242
- }
243
- result if show_result => writeln ! ( output, "{result:?}" ) ?,
252
+ result if tags. remove ( "result" ) => writeln ! ( output, "{result:?}" ) ?,
244
253
_ => { }
245
254
}
246
255
247
- // Output engine ops if requested.
248
- if tags. remove ( "ops" ) {
249
- while let Ok ( op) = self . op_rx . try_recv ( ) {
250
- match op {
251
- Operation :: Delete { key } => writeln ! (
252
- output,
253
- "storage delete {} [{}]" ,
254
- format:: MVCC :: <format:: SQL >:: key( & key) ,
255
- format:: Raw :: key( & key) ,
256
- ) ?,
257
- Operation :: Flush => writeln ! ( output, "storage flush" ) ?,
258
- Operation :: Set { key, value } => writeln ! (
259
- output,
260
- "storage set {} [{}]" ,
261
- format:: MVCC :: <format:: SQL >:: key_value( & key, & value) ,
262
- format:: Raw :: key_value( & key, & value) ,
263
- ) ?,
264
- }
265
- }
266
- }
267
-
268
256
// Reject unknown tags.
269
257
if let Some ( tag) = tags. iter ( ) . next ( ) {
270
258
return Err ( format ! ( "unknown tag {tag}" ) . into ( ) ) ;
@@ -280,14 +268,16 @@ mod tests {
280
268
}
281
269
}
282
270
283
- /// A test runner for expressions specifically . Evaluates expressions to
284
- /// values, and can optionally emit the expression tree.
271
+ /// A test runner for expressions. Evaluates expressions to values, and
272
+ /// optionally emits the expression tree.
285
273
struct ExpressionRunner ;
286
274
287
275
type Catalog < ' a > = <Local < storage:: Memory > as Engine < ' a > >:: Transaction ;
288
276
289
277
impl goldenscript:: Runner for ExpressionRunner {
290
278
fn run ( & mut self , command : & goldenscript:: Command ) -> Result < String , Box < dyn Error > > {
279
+ let mut output = String :: new ( ) ;
280
+
291
281
// The entire command is the expression to evaluate. There are no args.
292
282
if !command. args . is_empty ( ) {
293
283
return Err ( "expressions should be given as a command with no args" . into ( ) ) ;
@@ -301,30 +291,31 @@ mod tests {
301
291
if let Some ( next) = parser. lexer . next ( ) . transpose ( ) ? {
302
292
return Err ( format ! ( "unconsumed token {next}" ) . into ( ) ) ;
303
293
}
304
- let mut expr = Planner :: < Catalog > :: build_expression ( ast, & Scope :: new ( ) ) ?;
305
-
306
- // If requested, convert the expression to conjunctive normal form.
307
- if tags. remove ( "cnf" ) {
308
- expr = expr. into_cnf ( ) ;
309
- tags. insert ( "expr" . to_string ( ) ) ; // imply expr
310
- }
294
+ let expr = Planner :: < Catalog > :: build_expression ( ast, & Scope :: new ( ) ) ?;
311
295
312
296
// Evaluate the expression.
313
- let mut output = String :: new ( ) ;
314
297
let value = expr. evaluate ( None ) ?;
315
- write ! ( output, "{value:? }" ) ?;
298
+ write ! ( output, "{value}" ) ?;
316
299
317
- // If requested, dump the parsed expression.
300
+ // If requested, convert the expression to conjunctive normal form
301
+ // and dump it. Assert that it produces the same result.
302
+ if tags. remove ( "cnf" ) {
303
+ let cnf = expr. clone ( ) . into_cnf ( ) ;
304
+ assert_eq ! ( value, cnf. evaluate( None ) ?, "CNF result differs" ) ;
305
+ write ! ( output, " ← {}" , cnf. format_constant( ) ) ?;
306
+ }
307
+
308
+ // If requested, debug-dump the parsed expression.
318
309
if tags. remove ( "expr" ) {
319
- write ! ( output, " ← {}" , expr. format ( & Node :: Nothing { columns : vec! [ ] } ) ) ?;
310
+ write ! ( output, " ← {:? }" , expr) ?;
320
311
}
312
+ writeln ! ( output) ?;
321
313
322
314
// Reject unknown tags.
323
315
if let Some ( tag) = tags. iter ( ) . next ( ) {
324
316
return Err ( format ! ( "unknown tag {tag}" ) . into ( ) ) ;
325
317
}
326
318
327
- writeln ! ( output) ?;
328
319
Ok ( output)
329
320
}
330
321
}
0 commit comments