Skip to content

Commit f047e41

Browse files
zzylolclaude
andcommitted
feat(frontend-sql): recognize the SQL frequency L2 and entropy idioms
Port of the parked #562 and #564 for #509 Example 2. The SQL frontend names `SQRT(SUM(c*c))` over Float64 grouped unit counts as FrequencyL2, and `-SUM(p*LN(p))` with `p = COUNT(*)*1.0 / SUM(COUNT(*)) OVER ()` as FrequencyEntropy converted to nats. An exact population count guards SQL's empty-input NULL. Integer products, nullable keys, filters, HAVING, other log bases and partial windows are refused. The rules are the old Pass 1 SemanticEquivalentRewriteStrategy rules, run by lower_sql at the query root so the Stage 1 pipeline sees the intents; the exact candidate is the native exact reducer. The executor gains SQL sqrt (from #562). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 502e276 commit f047e41

10 files changed

Lines changed: 798 additions & 2 deletions

File tree

‎Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/executor/src/expressions/planner.rs‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,16 @@ pub(super) fn evaluate(
127127
.collect::<Result<Vec<_>, _>>()?;
128128
return Ok(Value::Float64(promql_function(name, &values)?));
129129
}
130+
if name.eq_ignore_ascii_case("sqrt") {
131+
return match evaluate(&args[0], row, schema)? {
132+
Value::Null => Ok(Value::Null),
133+
Value::Float64(value) => Ok(Value::Float64(value.sqrt())),
134+
Value::Int64(value) => Ok(Value::Float64((value as f64).sqrt())),
135+
_ => Err(Error::Invalid(
136+
"SQL sqrt requires a numeric argument".into(),
137+
)),
138+
};
139+
}
130140
if name == "promql_drop_metric_name" {
131141
let Value::Utf8(encoded) = evaluate(&args[0], row, schema)? else {
132142
return Err(Error::Invalid("series identity must be Utf8".into()));
@@ -646,7 +656,19 @@ fn validate(expr: &ScalarExpr, schema: &planner_types::ir::schema::Schema) -> Re
646656
Ok(())
647657
}
648658
ScalarExpr::FunctionCall { name, args } => {
649-
if name != "promql_drop_metric_name"
659+
if name.eq_ignore_ascii_case("sqrt") {
660+
if args.len() != 1
661+
|| !matches!(
662+
args[0]
663+
.scalar_type(schema)
664+
.map_err(|e| Error::Invalid(e.to_string()))?
665+
.0,
666+
DataType::Int64 | DataType::Float64 | DataType::Null
667+
)
668+
{
669+
return Err(invalid());
670+
}
671+
} else if name != "promql_drop_metric_name"
650672
&& planner_types::ir::scalar::scalar_type_rules::promql_function_arity(name)
651673
.is_none()
652674
&& name != "asap_struct_field"

‎crates/executor/tests/physical_semantics.rs‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,3 +915,33 @@ fn exact_cardinality_grouping_normalizes_float_identities() {
915915
[Value::Int64(2), Value::Int64(0)]
916916
));
917917
}
918+
919+
// SQL SQRT propagates NULL and accepts numeric inputs with a floating result.
920+
#[test]
921+
fn sql_sqrt_executes_numeric_and_null_arguments() {
922+
for (dtype, value, expected) in [
923+
(DataType::Int64, Value::Int64(9), 3.0),
924+
(DataType::Float64, Value::Float64(2.25), 1.5),
925+
] {
926+
let input = schema(&[("v", dtype, true)]);
927+
let expression = ScalarExpr::FunctionCall {
928+
name: "sqrt".into(),
929+
args: vec![ScalarExpr::Column(0)],
930+
};
931+
let compiled = CompiledExpression::compile(&expression, &input).unwrap();
932+
assert!(matches!(compiled.evaluate(&[value]).unwrap(), Value::Float64(v) if v == expected));
933+
assert!(matches!(
934+
compiled.evaluate(&[Value::Null]).unwrap(),
935+
Value::Null
936+
));
937+
}
938+
let input = schema(&[("v", DataType::Float64, false)]);
939+
let expression = ScalarExpr::FunctionCall {
940+
name: "sqrt".into(),
941+
args: vec![ScalarExpr::Column(0)],
942+
};
943+
let compiled = CompiledExpression::compile(&expression, &input).unwrap();
944+
assert!(
945+
matches!(compiled.evaluate(&[Value::Float64(-1.0)]).unwrap(), Value::Float64(v) if v.is_nan())
946+
);
947+
}

0 commit comments

Comments
 (0)