@@ -2,7 +2,8 @@ use crate::db::relational_db::RelationalDB;
2
2
use crate :: error:: { DBError , SubscriptionError } ;
3
3
use crate :: host:: module_host:: DatabaseTableUpdate ;
4
4
use crate :: sql:: execute:: { compile_sql, execute_single_sql} ;
5
- use spacetimedb_sats:: relation:: MemTable ;
5
+ use spacetimedb_sats:: relation:: { Column , FieldName , MemTable } ;
6
+ use spacetimedb_sats:: AlgebraicType ;
6
7
use spacetimedb_vm:: expr:: { Crud , CrudExpr , DbType , QueryExpr , SourceExpr } ;
7
8
8
9
pub enum QueryDef {
@@ -28,16 +29,25 @@ impl Query {
28
29
}
29
30
}
30
31
32
+ pub const OP_TYPE_FIELD_NAME : & str = "__op_type" ;
33
+
34
+ //HACK: To recover the `op_type` of this particular row I add a "hidden" column `OP_TYPE_FIELD_NAME`
31
35
pub fn to_mem_table ( of : QueryExpr , data : & DatabaseTableUpdate ) -> QueryExpr {
32
36
let mut q = of;
33
37
34
38
let mut t = match & q. source {
35
39
SourceExpr :: MemTable ( x) => MemTable :: new ( & x. head , & [ ] ) ,
36
40
SourceExpr :: DbTable ( table) => MemTable :: new ( & table. head , & [ ] ) ,
37
41
} ;
42
+ t. head . fields . push ( Column :: new (
43
+ FieldName :: named ( & t. head . table_name , OP_TYPE_FIELD_NAME ) ,
44
+ AlgebraicType :: U8 ,
45
+ ) ) ;
38
46
39
47
for row in & data. ops {
40
- t. data . push ( row. row . clone ( ) ) ;
48
+ let mut new = row. row . clone ( ) ;
49
+ new. elements . push ( row. op_type . into ( ) ) ;
50
+ t. data . push ( new) ;
41
51
}
42
52
43
53
q. source = SourceExpr :: MemTable ( t) ;
@@ -87,10 +97,11 @@ mod tests {
87
97
use crate :: subscription:: subscription:: QuerySet ;
88
98
use crate :: vm:: tests:: create_table_from_program;
89
99
use crate :: vm:: DbProgram ;
100
+ use itertools:: Itertools ;
90
101
use spacetimedb_lib:: data_key:: ToDataKey ;
91
102
use spacetimedb_lib:: error:: ResultTest ;
92
103
use spacetimedb_sats:: relation:: FieldName ;
93
- use spacetimedb_sats:: { product, BuiltinType , ProductType , ProductValue } ;
104
+ use spacetimedb_sats:: { product, BuiltinType , ProductType } ;
94
105
use spacetimedb_vm:: dsl:: { db_table, mem_table, scalar} ;
95
106
use spacetimedb_vm:: operator:: OpCmp ;
96
107
@@ -102,6 +113,7 @@ mod tests {
102
113
let p = & mut DbProgram :: new ( & db, & mut tx) ;
103
114
104
115
let head = ProductType :: from_iter ( [ ( "inventory_id" , BuiltinType :: U64 ) , ( "name" , BuiltinType :: String ) ] ) ;
116
+
105
117
let row = product ! ( 1u64 , "health" ) ;
106
118
let table = mem_table ( head. clone ( ) , [ row. clone ( ) ] ) ;
107
119
let table_id = create_table_from_program ( p, "inventory" , head. clone ( ) , & [ row. clone ( ) ] ) ?;
@@ -110,7 +122,7 @@ mod tests {
110
122
db. commit_tx ( tx) ?;
111
123
112
124
let op = TableOp {
113
- op_type : 0 ,
125
+ op_type : 1 ,
114
126
row_pk : vec ! [ ] ,
115
127
row,
116
128
} ;
@@ -120,7 +132,13 @@ mod tests {
120
132
table_name : "inventory" . to_string ( ) ,
121
133
ops : vec ! [ op. clone( ) ] ,
122
134
} ;
123
- let q = QueryExpr :: new ( db_table ( ( & schema) . into ( ) , "inventory" , table_id) ) ;
135
+ // For filtering out the hidden field `OP_TYPE_FIELD_NAME`
136
+ let fields = & [
137
+ FieldName :: named ( "inventory" , "inventory_id" ) . into ( ) ,
138
+ FieldName :: named ( "inventory" , "name" ) . into ( ) ,
139
+ ] ;
140
+
141
+ let q = QueryExpr :: new ( db_table ( ( & schema) . into ( ) , "inventory" , table_id) ) . with_project ( fields) ;
124
142
125
143
let q = to_mem_table ( q, & data) ;
126
144
let result = run_query ( & db, & q) ?;
@@ -136,16 +154,14 @@ mod tests {
136
154
ops : vec ! [ op] ,
137
155
} ;
138
156
139
- let q = QueryExpr :: new ( db_table ( ( & schema) . into ( ) , "inventory" , table_id) ) . with_select_cmp (
140
- OpCmp :: Eq ,
141
- FieldName :: named ( "inventory" , "inventory_id" ) ,
142
- scalar ( 0 ) ,
143
- ) ;
157
+ let q = QueryExpr :: new ( db_table ( ( & schema) . into ( ) , "inventory" , table_id) )
158
+ . with_select_cmp ( OpCmp :: Eq , FieldName :: named ( "inventory" , "inventory_id" ) , scalar ( 1u64 ) )
159
+ . with_project ( fields) ;
144
160
145
161
let q = to_mem_table ( q, & data) ;
146
162
let result = run_query ( & db, & q) ?;
147
163
148
- let table = mem_table ( head, Vec :: < ProductValue > :: new ( ) ) ;
164
+ let table = mem_table ( head, vec ! [ product! ( 1u64 , "health" ) ] ) ;
149
165
assert_eq ! (
150
166
Some ( table. as_without_table_name( ) ) ,
151
167
result. first( ) . map( |x| x. as_without_table_name( ) )
@@ -192,8 +208,12 @@ mod tests {
192
208
] ) ;
193
209
194
210
let result = s. eval ( & db) ?;
195
- assert_eq ! ( result. tables. len( ) , 1 , "Must return 1 table" ) ;
196
- assert_eq ! ( result. tables[ 0 ] . ops. len( ) , 1 , "Must return 1 row" ) ;
211
+ assert_eq ! ( result. tables. len( ) , 3 , "Must return 3 tables" ) ;
212
+ assert_eq ! (
213
+ result. tables. iter( ) . map( |x| x. ops. len( ) ) . sum:: <usize >( ) ,
214
+ 1 ,
215
+ "Must return 1 row"
216
+ ) ;
197
217
assert_eq ! ( result. tables[ 0 ] . ops[ 0 ] . row, row, "Must return the correct row" ) ;
198
218
199
219
Ok ( ( ) )
@@ -251,10 +271,77 @@ mod tests {
251
271
let update = DatabaseUpdate { tables : vec ! [ data] } ;
252
272
253
273
let result = s. eval_incr ( & db, & update) ?;
254
- assert_eq ! ( result. tables. len( ) , 1 , "Must return 1 table" ) ;
255
- assert_eq ! ( result. tables[ 0 ] . ops. len( ) , 1 , "Must return 1 row" ) ;
274
+ assert_eq ! ( result. tables. len( ) , 3 , "Must return 3 tables" ) ;
275
+ assert_eq ! (
276
+ result. tables. iter( ) . map( |x| x. ops. len( ) ) . sum:: <usize >( ) ,
277
+ 1 ,
278
+ "Must return 1 row"
279
+ ) ;
256
280
assert_eq ! ( result. tables[ 0 ] . ops[ 0 ] . row, row, "Must return the correct row" ) ;
257
281
258
282
Ok ( ( ) )
259
283
}
284
+
285
+ //Check that
286
+ //```
287
+ //SELECT * FROM table1
288
+ //SELECT * FROM table2
289
+ // =
290
+ //SELECT * FROM table2
291
+ //SELECT * FROM table1
292
+ //```
293
+ // return just one row irrespective of the order of the queries
294
+ #[ test]
295
+ fn test_subscribe_commutative ( ) -> ResultTest < ( ) > {
296
+ let ( db, _tmp_dir) = make_test_db ( ) ?;
297
+ let mut tx = db. begin_tx ( ) ;
298
+ let p = & mut DbProgram :: new ( & db, & mut tx) ;
299
+
300
+ let head_1 = ProductType :: from_iter ( [ ( "inventory_id" , BuiltinType :: U64 ) , ( "name" , BuiltinType :: String ) ] ) ;
301
+ let row_1 = product ! ( 1u64 , "health" ) ;
302
+ let table_id_1 = create_table_from_program ( p, "inventory" , head_1. clone ( ) , & [ row_1. clone ( ) ] ) ?;
303
+
304
+ let head_2 = ProductType :: from_iter ( [ ( "player_id" , BuiltinType :: U64 ) , ( "name" , BuiltinType :: String ) ] ) ;
305
+ let row_2 = product ! ( 2u64 , "jhon doe" ) ;
306
+ let table_id_2 = create_table_from_program ( p, "player" , head_2, & [ row_2. clone ( ) ] ) ?;
307
+
308
+ let schema_1 = db. schema_for_table ( & tx, table_id_1) . unwrap ( ) ;
309
+ let schema_2 = db. schema_for_table ( & tx, table_id_2) . unwrap ( ) ;
310
+ db. commit_tx ( tx) ?;
311
+
312
+ let q_1 = QueryExpr :: new ( db_table ( ( & schema_1) . into ( ) , "inventory" , table_id_1) ) ;
313
+ let q_2 = QueryExpr :: new ( db_table ( ( & schema_2) . into ( ) , "player" , table_id_2) ) ;
314
+
315
+ let s = QuerySet ( vec ! [
316
+ Query {
317
+ queries: vec![ q_1. clone( ) ] ,
318
+ } ,
319
+ Query {
320
+ queries: vec![ q_2. clone( ) ] ,
321
+ } ,
322
+ ] ) ;
323
+
324
+ let result_1 = s. eval ( & db) ?;
325
+
326
+ let s = QuerySet ( vec ! [
327
+ Query {
328
+ queries: vec![ q_2. clone( ) ] ,
329
+ } ,
330
+ Query { queries: vec![ q_1] } ,
331
+ ] ) ;
332
+
333
+ let result_2 = s. eval ( & db) ?;
334
+ let to_row = |of : DatabaseUpdate | {
335
+ of. tables
336
+ . iter ( )
337
+ . map ( |x| x. ops . iter ( ) . map ( |x| x. row . clone ( ) ) )
338
+ . flatten ( )
339
+ . sorted ( )
340
+ . collect :: < Vec < _ > > ( )
341
+ } ;
342
+
343
+ assert_eq ! ( to_row( result_1) , to_row( result_2) ) ;
344
+
345
+ Ok ( ( ) )
346
+ }
260
347
}
0 commit comments