1
+ use super :: ast:: TableSchemaView ;
1
2
use super :: ast:: { compile_to_ast, Column , From , Join , Selection , SqlAst } ;
2
3
use super :: type_check:: TypeCheck ;
3
4
use crate :: db:: datastore:: locking_tx_datastore:: state_view:: StateView ;
@@ -8,13 +9,13 @@ use spacetimedb_data_structures::map::IntMap;
8
9
use spacetimedb_lib:: identity:: AuthCtx ;
9
10
use spacetimedb_lib:: relation:: { self , ColExpr , DbTable , FieldName , Header } ;
10
11
use spacetimedb_primitives:: ColId ;
12
+ use spacetimedb_sats:: satn:: PsqlType ;
13
+ use spacetimedb_sats:: { satn, ProductType , ProductValue , Typespace } ;
11
14
use spacetimedb_schema:: schema:: TableSchema ;
12
15
use spacetimedb_vm:: expr:: { CrudExpr , Expr , FieldExpr , QueryExpr , SourceExpr } ;
13
16
use spacetimedb_vm:: operator:: OpCmp ;
14
17
use std:: sync:: Arc ;
15
18
16
- use super :: ast:: TableSchemaView ;
17
-
18
19
/// DIRTY HACK ALERT: Maximum allowed length, in UTF-8 bytes, of SQL queries.
19
20
/// Any query longer than this will be rejected.
20
21
/// This prevents a stack overflow when compiling queries with deeply-nested `AND` and `OR` conditions.
@@ -227,18 +228,55 @@ fn compile_statement(db: &RelationalDB, statement: SqlAst) -> Result<CrudExpr, P
227
228
Ok ( q. optimize ( & |table_id, table_name| db. row_count ( table_id, table_name) ) )
228
229
}
229
230
231
+ /// Generates a [`tabled::Table`] from a schema and rows, using the style of a psql table.
232
+ pub fn build_table < E > (
233
+ schema : & ProductType ,
234
+ rows : impl Iterator < Item = Result < ProductValue , E > > ,
235
+ ) -> Result < tabled:: Table , E > {
236
+ let mut builder = tabled:: builder:: Builder :: default ( ) ;
237
+ builder. set_header (
238
+ schema
239
+ . elements
240
+ . iter ( )
241
+ . enumerate ( )
242
+ . map ( |( i, e) | e. name . clone ( ) . unwrap_or_else ( || format ! ( "column {i}" ) . into ( ) ) ) ,
243
+ ) ;
244
+
245
+ let ty = Typespace :: EMPTY . with_type ( schema) ;
246
+ for row in rows {
247
+ let row = row?;
248
+ builder. push_record ( ty. with_values ( & row) . enumerate ( ) . map ( |( idx, value) | {
249
+ let ty = PsqlType {
250
+ tuple : ty. ty ( ) ,
251
+ field : & ty. ty ( ) . elements [ idx] ,
252
+ idx,
253
+ } ;
254
+
255
+ satn:: PsqlWrapper { ty, value } . to_string ( )
256
+ } ) ) ;
257
+ }
258
+
259
+ let mut table = builder. build ( ) ;
260
+ table. with ( tabled:: settings:: Style :: psql ( ) ) ;
261
+
262
+ Ok ( table)
263
+ }
264
+
230
265
#[ cfg( test) ]
231
266
mod tests {
232
267
use super :: * ;
233
268
use crate :: db:: datastore:: traits:: IsolationLevel ;
234
269
use crate :: db:: relational_db:: tests_utils:: { insert, TestDB } ;
235
270
use crate :: execution_context:: Workload ;
236
271
use crate :: sql:: execute:: tests:: run_for_testing;
272
+ use itertools:: Itertools ;
237
273
use spacetimedb_lib:: error:: { ResultTest , TestError } ;
238
274
use spacetimedb_lib:: { ConnectionId , Identity } ;
239
275
use spacetimedb_primitives:: { col_list, ColList , TableId } ;
276
+ use spacetimedb_sats:: time_duration:: TimeDuration ;
277
+ use spacetimedb_sats:: timestamp:: Timestamp ;
240
278
use spacetimedb_sats:: {
241
- product, satn , AlgebraicType , AlgebraicValue , GroundSpacetimeType as _, ProductType , Typespace , ValueWithType ,
279
+ product, AlgebraicType , AlgebraicValue , GroundSpacetimeType as _, ProductType , ProductValue ,
242
280
} ;
243
281
use spacetimedb_vm:: expr:: { ColumnOp , IndexJoin , IndexScan , JoinExpr , Query } ;
244
282
use std:: convert:: From ;
@@ -403,54 +441,100 @@ mod tests {
403
441
Ok ( ( ) )
404
442
}
405
443
406
- // Verify the output of `sql` matches the inputs for `Identity`, 'ConnectionId' & binary data.
407
- #[ test]
408
- fn output_identity_connection_id ( ) -> ResultTest < ( ) > {
409
- let row = product ! [ AlgebraicValue :: from( Identity :: __dummy( ) ) ] ;
410
- let kind: ProductType = [ ( "i" , Identity :: get_type ( ) ) ] . into ( ) ;
411
- let ty = Typespace :: EMPTY . with_type ( & kind) ;
412
- let out = ty
413
- . with_values ( & row)
414
- . map ( |value| satn:: PsqlWrapper { ty : & kind, value } . to_string ( ) )
415
- . collect :: < Vec < _ > > ( )
416
- . join ( ", " ) ;
417
- assert_eq ! ( out, "0" ) ;
444
+ fn expect_psql_table ( ty : & ProductType , rows : Vec < ProductValue > , expected : & str ) {
445
+ let table = build_table ( ty, rows. into_iter ( ) . map ( Ok :: < _ , ( ) > ) ) . unwrap ( ) . to_string ( ) ;
446
+ let mut table = table. split ( '\n' ) . map ( |x| x. trim_end ( ) ) . join ( "\n " ) ;
447
+ table. insert ( 0 , '\n' ) ;
448
+ assert_eq ! ( expected, table) ;
449
+ }
418
450
451
+ // Verify the output of `sql` matches the inputs that return true for [`AlgebraicType::is_special()`]
452
+ #[ test]
453
+ fn output_special_types ( ) -> ResultTest < ( ) > {
419
454
// Check tuples
420
- let kind = [
421
- ( "a" , AlgebraicType :: String ) ,
422
- ( "b" , AlgebraicType :: U256 ) ,
423
- ( "o" , Identity :: get_type ( ) ) ,
424
- ( "p" , ConnectionId :: get_type ( ) ) ,
455
+ let kind: ProductType = [
456
+ AlgebraicType :: String ,
457
+ AlgebraicType :: U256 ,
458
+ Identity :: get_type ( ) ,
459
+ ConnectionId :: get_type ( ) ,
460
+ Timestamp :: get_type ( ) ,
461
+ TimeDuration :: get_type ( ) ,
425
462
]
426
463
. into ( ) ;
464
+ let value = product ! [
465
+ "a" ,
466
+ Identity :: ZERO . to_u256( ) ,
467
+ Identity :: ZERO ,
468
+ ConnectionId :: ZERO ,
469
+ Timestamp :: UNIX_EPOCH ,
470
+ TimeDuration :: ZERO
471
+ ] ;
427
472
428
- let value = AlgebraicValue :: product ( [
429
- AlgebraicValue :: String ( "a" . into ( ) ) ,
430
- Identity :: ZERO . to_u256 ( ) . into ( ) ,
431
- Identity :: ZERO . to_u256 ( ) . into ( ) ,
432
- ConnectionId :: ZERO . to_u128 ( ) . into ( ) ,
433
- ] ) ;
434
-
435
- assert_eq ! (
436
- satn:: PsqlWrapper { ty: & kind, value } . to_string( ) . as_str( ) ,
437
- "(0 = \" a\" , 1 = 0, 2 = 0, 3 = 0)"
473
+ expect_psql_table (
474
+ & kind,
475
+ vec ! [ value] ,
476
+ r#"
477
+ column 0 | column 1 | column 2 | column 3 | column 4 | column 5
478
+ ----------+----------+--------------------------------------------------------------------+------------------------------------+---------------------------+-----------
479
+ "a" | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | 0x00000000000000000000000000000000 | 1970-01-01T00:00:00+00:00 | +0.000000"# ,
438
480
) ;
439
481
440
- let ty = Typespace :: EMPTY . with_type ( & kind) ;
441
-
442
482
// Check struct
483
+ let kind: ProductType = [
484
+ ( "bool" , AlgebraicType :: Bool ) ,
485
+ ( "str" , AlgebraicType :: String ) ,
486
+ ( "bytes" , AlgebraicType :: bytes ( ) ) ,
487
+ ( "identity" , Identity :: get_type ( ) ) ,
488
+ ( "connection_id" , ConnectionId :: get_type ( ) ) ,
489
+ ( "timestamp" , Timestamp :: get_type ( ) ) ,
490
+ ( "duration" , TimeDuration :: get_type ( ) ) ,
491
+ ]
492
+ . into ( ) ;
493
+
443
494
let value = product ! [
444
- "a" ,
445
- Identity :: ZERO . to_u256( ) ,
446
- AlgebraicValue :: product( [ Identity :: ZERO . to_u256( ) . into( ) ] ) ,
447
- AlgebraicValue :: product( [ ConnectionId :: ZERO . to_u128( ) . into( ) ] ) ,
495
+ true ,
496
+ "This is spacetimedb" . to_string( ) ,
497
+ AlgebraicValue :: Bytes ( [ 1 , 2 , 3 , 4 , 5 , 6 , 7 ] . into( ) ) ,
498
+ Identity :: ZERO ,
499
+ ConnectionId :: ZERO ,
500
+ Timestamp :: UNIX_EPOCH ,
501
+ TimeDuration :: ZERO
448
502
] ;
449
503
450
- let value = ValueWithType :: new ( ty, & value) ;
451
- assert_eq ! (
452
- satn:: PsqlWrapper { ty: ty. ty( ) , value } . to_string( ) . as_str( ) ,
453
- "(a = \" a\" , b = 0, o = 0, p = 0)"
504
+ expect_psql_table (
505
+ & kind,
506
+ vec ! [ value. clone( ) ] ,
507
+ r#"
508
+ bool | str | bytes | identity | connection_id | timestamp | duration
509
+ ------+-----------------------+------------------+--------------------------------------------------------------------+------------------------------------+---------------------------+-----------
510
+ true | "This is spacetimedb" | 0x01020304050607 | 0x0000000000000000000000000000000000000000000000000000000000000000 | 0x00000000000000000000000000000000 | 1970-01-01T00:00:00+00:00 | +0.000000"# ,
511
+ ) ;
512
+
513
+ // Check nested struct, tuple...
514
+ let kind: ProductType = [ ( None , AlgebraicType :: product ( kind) ) ] . into ( ) ;
515
+
516
+ let value = product ! [ value. clone( ) ] ;
517
+
518
+ expect_psql_table (
519
+ & kind,
520
+ vec ! [ value. clone( ) ] ,
521
+ r#"
522
+ column 0
523
+ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
524
+ (bool = true, str = "This is spacetimedb", bytes = 0x01020304050607, identity = 0x0000000000000000000000000000000000000000000000000000000000000000, connection_id = 0x00000000000000000000000000000000, timestamp = 1970-01-01T00:00:00+00:00, duration = +0.000000)"# ,
525
+ ) ;
526
+
527
+ let kind: ProductType = [ ( "tuple" , AlgebraicType :: product ( kind) ) ] . into ( ) ;
528
+
529
+ let value = product ! [ value] ;
530
+
531
+ expect_psql_table (
532
+ & kind,
533
+ vec ! [ value] ,
534
+ r#"
535
+ tuple
536
+ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
537
+ (0 = (bool = true, str = "This is spacetimedb", bytes = 0x01020304050607, identity = 0x0000000000000000000000000000000000000000000000000000000000000000, connection_id = 0x00000000000000000000000000000000, timestamp = 1970-01-01T00:00:00+00:00, duration = +0.000000))"# ,
454
538
) ;
455
539
456
540
Ok ( ( ) )
0 commit comments