use super::*;
use uqa_core::{memory::MemoryBudget, ArrayValue, CancellationToken};
fn array(elements: Vec<Value>) -> Value {
Value::Array(ArrayValue::try_new(elements).unwrap())
}
#[test]
fn boolean_tests_match_postgresql_without_allocating_result_memory() {
let expected: Vec<Vec<Option<String>>> =
serde_json::from_str(include_str!("tests/boolean_tests.json")).unwrap();
let tests = [
BooleanTest::IsTrue,
BooleanTest::IsNotTrue,
BooleanTest::IsFalse,
BooleanTest::IsNotFalse,
BooleanTest::IsUnknown,
BooleanTest::IsNotUnknown,
];
let budget = MemoryBudget::new(0);
let token = CancellationToken::new();
let control = ProductionControl::new(&budget, &token, &token);
for row in expected {
let input = row[0]
.as_ref()
.map_or(Value::Null, |s| Value::Bool(s == "t"));
for (test, expected) in tests.into_iter().zip(&row[1..]) {
let result = evaluate(
FunctionDispatch::BooleanTest(test),
std::slice::from_ref(&input),
&control,
)
.unwrap()
.unwrap();
assert_eq!(*result, Value::Bool(expected.as_deref() == Some("t")));
assert_eq!(budget.used(), 0);
}
}
token.cancel();
assert!(evaluate(
FunctionDispatch::BooleanTest(BooleanTest::IsUnknown),
&[Value::Null],
&control
)
.unwrap()
.is_err());
assert_eq!(budget.used(), 0);
}
#[test]
fn outer_null_test_keeps_null_fields_distinct_from_a_null_value() {
let budget = MemoryBudget::new(0);
let token = CancellationToken::new();
let control = ProductionControl::new(&budget, &token, &token);
for (value, is_null) in [
(Value::Null, true),
(Value::Row(vec![].into()), false),
(Value::Row(vec![Value::Null, Value::Null].into()), false),
] {
for negated in [false, true] {
let result = evaluate(
FunctionDispatch::ValueNullTest { negated },
std::slice::from_ref(&value),
&control,
)
.unwrap()
.unwrap();
assert_eq!(*result, Value::Bool(is_null != negated));
assert_eq!(budget.used(), 0);
}
}
}
#[test]
fn controlled_quantified_comparison_preserves_nested_null_and_empty_semantics() {
let budget = MemoryBudget::new(4096);
let token = CancellationToken::new();
let control = ProductionControl::new(&budget, &token, &token);
let nested = array(vec![
Value::List(vec![Value::Int(1), Value::Null]),
Value::List(vec![Value::Int(2), Value::Int(3)]),
]);
for (dispatch, needle, values, expected) in [
(
FunctionDispatch::AnyOperator,
Value::Int(2),
nested.clone(),
Value::Bool(true),
),
(
FunctionDispatch::AnyOperator,
Value::Int(4),
nested.clone(),
Value::Null,
),
(
FunctionDispatch::AllOperator,
Value::Int(2),
nested,
Value::Bool(false),
),
(
FunctionDispatch::AnyOperator,
Value::Null,
array(vec![]),
Value::Bool(false),
),
(
FunctionDispatch::AllOperator,
Value::Null,
array(vec![]),
Value::Bool(true),
),
(
FunctionDispatch::AllOperator,
Value::Int(1),
Value::Null,
Value::Null,
),
] {
let output = evaluate(
dispatch,
&[needle, values, Value::Str("=".into())],
&control,
)
.unwrap()
.unwrap();
assert_eq!(*output, expected);
assert_eq!(
budget.used(),
0,
"comparison scratch ends before result handoff"
);
}
}
#[test]
fn controlled_comparison_keeps_early_decisions_and_first_typed_errors() {
let budget = MemoryBudget::new(4096);
let token = CancellationToken::new();
let control = ProductionControl::new(&budget, &token, &token);
let values = array(vec![Value::Int(2), Value::Bool(true)]);
for (dispatch, needle, expected) in [
(
FunctionDispatch::AnyOperator,
Value::Int(1),
Value::Bool(true),
),
(
FunctionDispatch::AllOperator,
Value::Int(3),
Value::Bool(false),
),
] {
assert_eq!(
*evaluate(
dispatch,
&[needle, values.clone(), Value::Str("<".into())],
&control
)
.unwrap()
.unwrap(),
expected
);
}
for (dispatch, args, expected) in [
(
FunctionDispatch::BetweenSymmetric,
vec![Value::Int(2), Value::Int(3), Value::Int(1)],
Value::Bool(true),
),
(
FunctionDispatch::BetweenSymmetric,
vec![Value::Int(2), Value::Null, Value::Int(1)],
Value::Null,
),
(
FunctionDispatch::IsDistinct,
vec![Value::Null, Value::Null],
Value::Bool(false),
),
(
FunctionDispatch::IsDistinct,
vec![Value::Null, Value::Int(1)],
Value::Bool(true),
),
] {
assert_eq!(
*evaluate(dispatch, &args, &control).unwrap().unwrap(),
expected
);
}
assert!(matches!(
evaluate(
FunctionDispatch::BetweenSymmetric,
&[Value::Int(2), Value::Int(3), Value::Bool(true)],
&control
)
.unwrap(),
Err(SQLError::TypeMismatch(_))
));
assert_eq!(budget.used(), 0);
}
#[test]
fn quantified_comparison_quota_and_both_cancellation_scopes_release_scratch() {
let args = [
Value::Int(1),
array(vec![Value::Int(2)]),
Value::Str("=".into()),
];
let token = CancellationToken::new();
let budget = MemoryBudget::new(4);
let control = ProductionControl::new(&budget, &token, &token);
assert_eq!(
evaluate(FunctionDispatch::AnyOperator, &args, &control)
.unwrap()
.unwrap_err()
.sqlstate(),
Some("53200")
);
assert_eq!(budget.used(), 0);
for original_cancelled in [true, false] {
let budget = MemoryBudget::new(4096);
let original = CancellationToken::new();
let invoking = CancellationToken::new();
let control = ProductionControl::new(&budget, &original, &invoking);
let held = control.copy_text("earlier result").unwrap();
let used = budget.used();
if original_cancelled {
original.cancel();
} else {
invoking.cancel();
}
assert_eq!(
evaluate(FunctionDispatch::AllOperator, &args, &control)
.unwrap()
.unwrap_err()
.sqlstate(),
Some("57014")
);
assert_eq!(budget.used(), used);
assert_eq!(&*held, "earlier result");
drop(held);
assert_eq!(budget.used(), 0);
}
}
#[test]
fn quantified_values_and_distinct_predicates_select_record_null_equality() {
let budget = MemoryBudget::new(4096);
let token = CancellationToken::new();
let control = ProductionControl::new(&budget, &token, &token);
let record = Value::Row(vec![Value::Int(1), Value::Null].into());
for (dispatch, args, expected) in [
(
FunctionDispatch::IsDistinct,
vec![record.clone(), record.clone()],
Value::Bool(false),
),
(
FunctionDispatch::AnyOperator,
vec![
record.clone(),
array(vec![record.clone()]),
Value::Str("=".into()),
],
Value::Bool(true),
),
(
FunctionDispatch::AllOperator,
vec![record.clone(), array(vec![record]), Value::Str("=".into())],
Value::Bool(true),
),
] {
let result = evaluate(dispatch, &args, &control).unwrap().unwrap();
assert_eq!(*result, expected);
drop(result);
assert_eq!(budget.used(), 0);
}
}