Skip to content

Commit 2399c0d

Browse files
committed
test(nodedb): add integration tests for expression evaluation in UPDATE and WHERE
sql_update_expressions.rs — covers UPDATE statements where the RHS is a non-literal expression (column arithmetic, scalar functions, NOW()). These catch the silent corruption bug where expression RHS was serialized as a debug string instead of being evaluated against the current row. sql_where_expressions.rs — covers WHERE clauses containing scalar function calls (LOWER, UPPER, LENGTH, arithmetic). These must be evaluated per-row by the scan filter, not silently dropped through a match-all fall-through. sql_transactions.rs — covers basic transaction semantics: COMMIT persists buffered writes and ROLLBACK discards them. Existing executor tests updated to pass the new `on_conflict_updates` and `expr` fields added to the physical plan types.
1 parent ed8d87f commit 2399c0d

14 files changed

Lines changed: 785 additions & 11 deletions

nodedb/tests/executor_tests/test_aggregate_aliases.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ fn aggregate_output_uses_user_alias_but_having_reads_canonical_key() {
3636
op: FilterOp::Gt,
3737
value: nodedb_types::Value::Integer(1),
3838
clauses: Vec::new(),
39+
expr: None,
3940
}])
4041
.unwrap();
4142

nodedb/tests/executor_tests/test_array_ops.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ fn filter(field: &str, op: &str, value: nodedb_types::Value) -> ScanFilter {
3131
op: op.into(),
3232
value,
3333
clauses: Vec::new(),
34+
expr: None,
3435
}
3536
}
3637

nodedb/tests/executor_tests/test_columnar_aggregate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ fn columnar_having_uses_canonical_key_but_output_keeps_user_alias() {
8989
op: FilterOp::Gt,
9090
value: nodedb_types::Value::Integer(1),
9191
clauses: Vec::new(),
92+
expr: None,
9293
}])
9394
.unwrap();
9495

nodedb/tests/executor_tests/test_conditional_update.rs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ fn filter(field: &str, op: &str, value: nodedb_types::Value) -> ScanFilter {
1919
op: op.into(),
2020
value,
2121
clauses: Vec::new(),
22+
expr: None,
2223
}
2324
}
2425

@@ -77,7 +78,9 @@ fn bulk_update_returns_affected_count() {
7778
let filter_bytes = zerompk::to_msgpack_vec(&filters).unwrap();
7879
let updates = vec![(
7980
"stock".to_string(),
80-
nodedb_types::json_to_msgpack(&serde_json::json!(99)).unwrap(),
81+
nodedb::bridge::physical_plan::UpdateValue::Literal(
82+
nodedb_types::json_to_msgpack(&serde_json::json!(99)).unwrap(),
83+
),
8184
)];
8285

8386
let payload = send_ok(
@@ -116,7 +119,9 @@ fn conditional_decrement_stops_at_zero() {
116119
let new_stock = current_stock.saturating_sub(1);
117120
let updates = vec![(
118121
"stock".to_string(),
119-
nodedb_types::json_to_msgpack(&serde_json::json!(new_stock)).unwrap(),
122+
nodedb::bridge::physical_plan::UpdateValue::Literal(
123+
nodedb_types::json_to_msgpack(&serde_json::json!(new_stock)).unwrap(),
124+
),
120125
)];
121126

122127
let payload = send_ok(
@@ -159,7 +164,9 @@ fn bulk_update_zero_match_returns_zero_affected() {
159164
let filter_bytes = zerompk::to_msgpack_vec(&filters).unwrap();
160165
let updates = vec![(
161166
"stock".to_string(),
162-
nodedb_types::json_to_msgpack(&serde_json::json!(999)).unwrap(),
167+
nodedb::bridge::physical_plan::UpdateValue::Literal(
168+
nodedb_types::json_to_msgpack(&serde_json::json!(999)).unwrap(),
169+
),
163170
)];
164171

165172
let payload = send_ok(
@@ -189,7 +196,9 @@ fn bulk_update_returning_returns_updated_documents() {
189196
let filter_bytes = zerompk::to_msgpack_vec(&filters).unwrap();
190197
let updates = vec![(
191198
"stock".to_string(),
192-
nodedb_types::json_to_msgpack(&serde_json::json!(0)).unwrap(),
199+
nodedb::bridge::physical_plan::UpdateValue::Literal(
200+
nodedb_types::json_to_msgpack(&serde_json::json!(0)).unwrap(),
201+
),
193202
)];
194203

195204
let payload = send_ok(
@@ -224,7 +233,9 @@ fn bulk_update_returning_zero_match_returns_affected_zero() {
224233
let filter_bytes = zerompk::to_msgpack_vec(&filters).unwrap();
225234
let updates = vec![(
226235
"stock".to_string(),
227-
nodedb_types::json_to_msgpack(&serde_json::json!(999)).unwrap(),
236+
nodedb::bridge::physical_plan::UpdateValue::Literal(
237+
nodedb_types::json_to_msgpack(&serde_json::json!(999)).unwrap(),
238+
),
228239
)];
229240

230241
let payload = send_ok(
@@ -251,7 +262,9 @@ fn point_update_returns_affected_count() {
251262

252263
let updates = vec![(
253264
"stock".to_string(),
254-
nodedb_types::json_to_msgpack(&serde_json::json!(5)).unwrap(),
265+
nodedb::bridge::physical_plan::UpdateValue::Literal(
266+
nodedb_types::json_to_msgpack(&serde_json::json!(5)).unwrap(),
267+
),
255268
)];
256269

257270
let payload = send_ok(
@@ -279,7 +292,9 @@ fn point_update_returning_returns_updated_document() {
279292

280293
let updates = vec![(
281294
"stock".to_string(),
282-
nodedb_types::json_to_msgpack(&serde_json::json!(7)).unwrap(),
295+
nodedb::bridge::physical_plan::UpdateValue::Literal(
296+
nodedb_types::json_to_msgpack(&serde_json::json!(7)).unwrap(),
297+
),
283298
)];
284299

285300
let payload = send_ok(
@@ -335,7 +350,9 @@ fn transaction_batch_does_not_abort_on_zero_row_update() {
335350
filters: filters_match,
336351
updates: vec![(
337352
"stock".to_string(),
338-
nodedb_types::json_to_msgpack(&serde_json::json!(0)).unwrap(),
353+
nodedb::bridge::physical_plan::UpdateValue::Literal(
354+
nodedb_types::json_to_msgpack(&serde_json::json!(0)).unwrap(),
355+
),
339356
)],
340357
returning: false,
341358
}),
@@ -344,7 +361,9 @@ fn transaction_batch_does_not_abort_on_zero_row_update() {
344361
filters: filters_nomatch,
345362
updates: vec![(
346363
"stock".to_string(),
347-
nodedb_types::json_to_msgpack(&serde_json::json!(999)).unwrap(),
364+
nodedb::bridge::physical_plan::UpdateValue::Literal(
365+
nodedb_types::json_to_msgpack(&serde_json::json!(999)).unwrap(),
366+
),
348367
)],
349368
returning: false,
350369
}),

nodedb/tests/executor_tests/test_cross_engine_validation.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ fn cross_model_query_vector_graph_relational() {
123123
op: "gte".into(),
124124
value: nodedb_types::Value::Integer(2023),
125125
clauses: Vec::new(),
126+
expr: None,
126127
}];
127128
let filter_bytes = zerompk::to_msgpack_vec(&filter).unwrap();
128129
let scan_payload = send_ok(

nodedb/tests/executor_tests/test_cross_type_join.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ fn single_core_self_join_respects_aliases_in_filter_and_projection() {
428428
op: FilterOp::LtColumn,
429429
value: nodedb_types::Value::String("b.id".into()),
430430
clauses: Vec::new(),
431+
expr: None,
431432
}])
432433
.unwrap();
433434

@@ -571,6 +572,7 @@ fn schemaless_self_join_matches_on_canonicalized_object_fields() {
571572
op: FilterOp::LtColumn,
572573
value: nodedb_types::Value::String("b.id".into()),
573574
clauses: Vec::new(),
575+
expr: None,
574576
}])
575577
.unwrap();
576578

@@ -651,6 +653,7 @@ fn cross_join_uses_inline_right_scalar_aggregate_for_post_filter() {
651653
op: FilterOp::GtColumn,
652654
value: nodedb_types::Value::String("avg_score".into()),
653655
clauses: Vec::new(),
656+
expr: None,
654657
}])
655658
.unwrap();
656659

@@ -738,6 +741,7 @@ fn cross_join_uses_unaliased_scalar_aggregate_key_for_post_filter() {
738741
op: FilterOp::GtColumn,
739742
value: nodedb_types::Value::String("avg(amount)".into()),
740743
clauses: Vec::new(),
744+
expr: None,
741745
}])
742746
.unwrap();
743747

@@ -843,6 +847,7 @@ fn semi_join_uses_nested_scalar_subquery_result_as_inline_right() {
843847
op: FilterOp::GtColumn,
844848
value: nodedb_types::Value::String("avg(amount)".into()),
845849
clauses: Vec::new(),
850+
expr: None,
846851
}])
847852
.unwrap();
848853

nodedb/tests/executor_tests/test_facet.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ fn filter(field: &str, op: &str, value: nodedb_types::Value) -> ScanFilter {
6969
op: op.into(),
7070
value,
7171
clauses: Vec::new(),
72+
expr: None,
7273
}
7374
}
7475

nodedb/tests/executor_tests/test_generated_columns.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,9 @@ fn update_recomputes_generated_column() {
186186
document_id: "p1".into(),
187187
updates: vec![(
188188
"price".to_string(),
189-
nodedb_types::json_to_msgpack(&serde_json::json!(200.0)).unwrap(),
189+
nodedb::bridge::physical_plan::UpdateValue::Literal(
190+
nodedb_types::json_to_msgpack(&serde_json::json!(200.0)).unwrap(),
191+
),
190192
)],
191193
returning: false,
192194
}),
@@ -236,7 +238,9 @@ fn update_generated_column_directly_rejected() {
236238
document_id: "p1".into(),
237239
updates: vec![(
238240
"price_with_tax".to_string(),
239-
nodedb_types::json_to_msgpack(&serde_json::json!(999.0)).unwrap(),
241+
nodedb::bridge::physical_plan::UpdateValue::Literal(
242+
nodedb_types::json_to_msgpack(&serde_json::json!(999.0)).unwrap(),
243+
),
240244
)],
241245
returning: false,
242246
}),

nodedb/tests/executor_tests/test_security_and_isolation.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ fn security_rls_policy_enforcement() {
6262
op: "eq".into(),
6363
value: nodedb_types::Value::String("approved".into()),
6464
clauses: Vec::new(),
65+
expr: None,
6566
};
6667
let predicate = zerompk::to_msgpack_vec(&vec![filter]).unwrap();
6768

nodedb/tests/executor_tests/test_tenant_isolation_rls.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ fn rls_policies_isolated_between_tenants() {
1818
op: "eq".into(),
1919
value: nodedb_types::Value::String("approved".into()),
2020
clauses: Vec::new(),
21+
expr: None,
2122
};
2223
let predicate = zerompk::to_msgpack_vec(&vec![filter]).unwrap();
2324

@@ -72,6 +73,7 @@ fn rls_policy_listing_scoped() {
7273
op: "eq".into(),
7374
value: nodedb_types::Value::String("admin".into()),
7475
clauses: Vec::new(),
76+
expr: None,
7577
};
7678
store
7779
.create_policy(RlsPolicy {

0 commit comments

Comments
 (0)