1
1
use anyhow:: Result ;
2
+ use hashbrown:: HashMap ;
2
3
use spacetimedb_execution:: { Datastore , DeltaStore } ;
3
4
use spacetimedb_lib:: metrics:: ExecutionMetrics ;
4
5
use spacetimedb_subscription:: SubscriptionPlan ;
6
+ use spacetimedb_vm:: relation:: RelValue ;
5
7
6
8
use crate :: host:: module_host:: UpdatesRelValue ;
7
9
@@ -21,24 +23,92 @@ pub fn eval_delta<'a, Tx: Datastore + DeltaStore>(
21
23
plan : & SubscriptionPlan ,
22
24
) -> Result < Option < UpdatesRelValue < ' a > > > {
23
25
metrics. delta_queries_evaluated += 1 ;
26
+
24
27
let mut inserts = vec ! [ ] ;
25
28
let mut deletes = vec ! [ ] ;
26
29
27
- plan. for_each_insert ( tx, metrics, & mut |row| {
28
- inserts. push ( row. into ( ) ) ;
29
- Ok ( ( ) )
30
- } ) ?;
30
+ let mut duplicate_rows_evaluated = 0 ;
31
+ let mut duplicate_rows_sent = 0 ;
32
+
33
+ if !plan. is_join ( ) {
34
+ // Single table plans will never return redundant rows,
35
+ // so there's no need to track row counts.
36
+ plan. for_each_insert ( tx, metrics, & mut |row| {
37
+ inserts. push ( row. into ( ) ) ;
38
+ Ok ( ( ) )
39
+ } ) ?;
40
+
41
+ plan. for_each_delete ( tx, metrics, & mut |row| {
42
+ deletes. push ( row. into ( ) ) ;
43
+ Ok ( ( ) )
44
+ } ) ?;
45
+ } else {
46
+ // Query plans for joins may return redundant rows.
47
+ // We track row counts to avoid sending them to clients.
48
+ let mut insert_counts = HashMap :: new ( ) ;
49
+ let mut delete_counts = HashMap :: new ( ) ;
50
+
51
+ plan. for_each_insert ( tx, metrics, & mut |row| {
52
+ let n = insert_counts. entry ( row) . or_default ( ) ;
53
+ if * n > 0 {
54
+ duplicate_rows_evaluated += 1 ;
55
+ }
56
+ * n += 1 ;
57
+ Ok ( ( ) )
58
+ } ) ?;
31
59
32
- plan. for_each_delete ( tx, metrics, & mut |row| {
33
- deletes. push ( row. into ( ) ) ;
34
- Ok ( ( ) )
35
- } ) ?;
60
+ plan. for_each_delete ( tx, metrics, & mut |row| {
61
+ match insert_counts. get_mut ( & row) {
62
+ // We have not seen an insert for this row.
63
+ // If we have seen a delete, increment the metric.
64
+ // Always increment the delete_count.
65
+ None => {
66
+ let n = delete_counts. entry ( row) . or_default ( ) ;
67
+ if * n > 0 {
68
+ duplicate_rows_evaluated += 1 ;
69
+ }
70
+ * n += 1 ;
71
+ }
72
+ // We have already seen an insert for this row.
73
+ // This is a duplicate, so increment the metric.
74
+ //
75
+ // There are no more inserts for this row,
76
+ // so increment the delete_count as well.
77
+ Some ( 0 ) => {
78
+ duplicate_rows_evaluated += 1 ;
79
+ * delete_counts. entry ( row) . or_default ( ) += 1 ;
80
+ }
81
+ // We have already seen an insert for this row.
82
+ // This is a duplicate, so increment the metric.
83
+ //
84
+ // There are still more inserts for this row,
85
+ // so don't increment the delete_count.
86
+ Some ( n) => {
87
+ duplicate_rows_evaluated += 1 ;
88
+ * n -= 1 ;
89
+ }
90
+ }
91
+ Ok ( ( ) )
92
+ } ) ?;
93
+
94
+ for ( row, n) in insert_counts. into_iter ( ) . filter ( |( _, n) | * n > 0 ) {
95
+ duplicate_rows_sent += n as u64 - 1 ;
96
+ inserts. extend ( std:: iter:: repeat_n ( row, n) . map ( RelValue :: from) ) ;
97
+ }
98
+ for ( row, n) in delete_counts. into_iter ( ) . filter ( |( _, n) | * n > 0 ) {
99
+ duplicate_rows_sent += n as u64 - 1 ;
100
+ deletes. extend ( std:: iter:: repeat_n ( row, n) . map ( RelValue :: from) ) ;
101
+ }
102
+ }
36
103
37
104
// Return `None` for empty updates
38
105
if inserts. is_empty ( ) && deletes. is_empty ( ) {
39
106
return Ok ( None ) ;
40
107
}
41
108
42
109
metrics. delta_queries_matched += 1 ;
110
+ metrics. duplicate_rows_evaluated += duplicate_rows_evaluated;
111
+ metrics. duplicate_rows_sent += duplicate_rows_sent;
112
+
43
113
Ok ( Some ( UpdatesRelValue { inserts, deletes } ) )
44
114
}
0 commit comments