Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions asap-query-engine/tests/sql_query_support.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//! SQL query support runner: plans each suite query in isolation and builds the
//! engine's execution context for it, without ingesting data or executing.
//!
//! A query is `supported` when both the planner accepts it and the engine
//! resolves an execution context against that plan. Each suite case declares
//! the expected outcome, so a query that starts or stops being supported
//! fails the run until its suite entry is updated.

use asap_planner::{SQLController, SQLRuntimeOptions, StreamingEngine};
use asap_types::enums::{CleanupPolicy, QueryLanguage};
use query_engine_rust::{SimpleEngine, SimpleMapStore};
use serde::Deserialize;
use serde_json::json;
use std::path::{Path, PathBuf};
use std::sync::Arc;

const SUITES_DIR: &str = "tests/sql_query_support";
const SUITE_EXTENSION: &str = "yaml";
/// Fixed evaluation time (seconds) so NOW()-relative queries plan and match
/// deterministically.
const QUERY_EVALUATION_TIME_SECS: f64 = 1_000_000.0;
const ACCURACY_SLA: f64 = 0.95;

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct Suite {
/// Planner table definitions, passed through verbatim.
tables: serde_json::Value,
data_ingestion_interval_ms: u64,
repetition_delay_ms: u64,
cases: Vec<Case>,
}

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct Case {
name: String,
query: String,
expected: Outcome,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
enum Outcome {
Supported,
Unsupported,
}

/// Where a query stopped, with the reason. `None` means it is supported.
type Failure = Option<String>;

fn run_case(suite: &Suite, query: &str) -> Failure {
let planner_config = json!({
"tables": suite.tables,
"query_groups": [{
"queries": [query],
"repetition_delay_ms": suite.repetition_delay_ms,
"controller_options": { "accuracy_sla": ACCURACY_SLA },
}],
});
let planner_yaml = serde_yaml::to_string(&planner_config).expect("planner config serializes");
let opts = SQLRuntimeOptions {
streaming_engine: StreamingEngine::Precompute,
query_evaluation_time: Some(QUERY_EVALUATION_TIME_SECS),
data_ingestion_interval_ms: suite.data_ingestion_interval_ms,
};
let output = match SQLController::from_yaml(&planner_yaml, opts).and_then(|c| c.generate()) {
Ok(output) => output,
Err(error) => return Some(format!("plan: {error}")),
};

let inference_config = output
.to_inference_config(QueryLanguage::sql)
.expect("planner output converts to inference config");
let streaming_config = Arc::new(
output
.to_streaming_config(QueryLanguage::sql)
.expect("planner output converts to streaming config"),
);
let store = Arc::new(SimpleMapStore::new(
streaming_config.clone(),
CleanupPolicy::NoCleanup,
));
let engine = SimpleEngine::new(
store,
inference_config,
streaming_config,
suite.data_ingestion_interval_ms,
QueryLanguage::sql,
);
match engine.build_query_execution_context_sql(query.to_string(), QUERY_EVALUATION_TIME_SECS) {
Some(_) => None,
None => Some("engine: no execution context for planned query".to_string()),
}
}

fn suite_paths() -> Vec<PathBuf> {
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join(SUITES_DIR);
let mut paths: Vec<PathBuf> = std::fs::read_dir(&dir)
.unwrap_or_else(|e| panic!("read {}: {e}", dir.display()))
.map(|entry| entry.expect("suite dir entry").path())
.filter(|path| path.extension().is_some_and(|ext| ext == SUITE_EXTENSION))
.collect();
paths.sort();
assert!(!paths.is_empty(), "no suites in {}", dir.display());
paths
}

#[test]
fn sql_queries_match_expected_support() {
let mut mismatches = Vec::new();
for path in suite_paths() {
let suite_name = path.file_stem().unwrap().to_string_lossy().into_owned();
let contents = std::fs::read_to_string(&path).expect("read suite");
let suite: Suite = serde_yaml::from_str(&contents)
.unwrap_or_else(|e| panic!("parse {}: {e}", path.display()));

for case in &suite.cases {
let failure = run_case(&suite, &case.query);
let actual = match failure {
None => Outcome::Supported,
Some(_) => Outcome::Unsupported,
};
let status = if actual == case.expected {
"ok"
} else {
"MISMATCH"
};
let reason = failure.as_deref().unwrap_or("");
println!("[{status}] {suite_name}/{}: {actual:?} {reason}", case.name);
if actual != case.expected {
mismatches.push(format!(
"{suite_name}/{}: expected {:?}, got {actual:?} {reason}",
case.name, case.expected
));
}
}
}
assert!(
mismatches.is_empty(),
"SQL query support mismatches:\n{}",
mismatches.join("\n")
);
}
194 changes: 194 additions & 0 deletions asap-query-engine/tests/sql_query_support/aggregations.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# SQL equivalents of promql-compliance/suites/aggregations.yaml; case names match.
#
# Translation: `[5m]` range functions aggregate over a 300s window; instant
# vectors aggregate over one ingestion interval (1s). A per-series result
# groups by every label (job, instance). Per-group top-k uses `LIMIT k BY`.
#
# Skipped, no SQL equivalent for counter rate/increase: increase, rate,
# topk-3-rate, topk-3-rate-by-job, topk-3-rate-by-job-instance,
# sum-by-job-rate, sum-by-job-instance-rate.
tables:
- name: data
time_column: time
value_columns: [value]
metadata_columns: [job, instance]
data_ingestion_interval_ms: 1000
repetition_delay_ms: 60000

cases:
- name: sum-over-time
query: SELECT job, instance, SUM(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance
expected: supported
- name: count-over-time
query: SELECT job, instance, COUNT(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance
expected: supported
- name: avg-over-time
query: SELECT job, instance, AVG(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance
expected: unsupported
- name: min-over-time
query: SELECT job, instance, MIN(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance
expected: supported
- name: max-over-time
query: SELECT job, instance, MAX(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance
expected: supported

- name: topk-1-bare
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -1, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 1
expected: supported
- name: topk-3-bare
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -1, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 3
expected: supported
- name: topk-5-bare
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -1, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 5
expected: supported
- name: topk-10-bare
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -1, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 10
expected: supported
- name: topk-3-bare-by-job
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -1, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 3 BY job
expected: unsupported
- name: topk-3-bare-by-job-instance
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -1, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 3 BY job, instance
expected: unsupported

- name: topk-1-sum-over-time
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 1
expected: supported
- name: topk-3-sum-over-time
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 3
expected: supported
- name: topk-5-sum-over-time
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 5
expected: supported
- name: topk-10-sum-over-time
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 10
expected: supported
- name: topk-1-sum-over-time-by-job
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 1 BY job
expected: unsupported
- name: topk-3-sum-over-time-by-job
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 3 BY job
expected: unsupported
- name: topk-5-sum-over-time-by-job
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 5 BY job
expected: unsupported
- name: topk-10-sum-over-time-by-job
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 10 BY job
expected: unsupported
- name: topk-1-sum-over-time-by-job-instance
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 1 BY job, instance
expected: unsupported
- name: topk-3-sum-over-time-by-job-instance
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 3 BY job, instance
expected: unsupported
- name: topk-5-sum-over-time-by-job-instance
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 5 BY job, instance
expected: unsupported
- name: topk-10-sum-over-time-by-job-instance
query: SELECT job, instance, SUM(value) AS v FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY v DESC LIMIT 10 BY job, instance
expected: unsupported

- name: topk-1-count-over-time
query: SELECT job, instance, COUNT(value) AS c FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY c DESC LIMIT 1
expected: supported
- name: topk-3-count-over-time
query: SELECT job, instance, COUNT(value) AS c FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY c DESC LIMIT 3
expected: supported
- name: topk-5-count-over-time
query: SELECT job, instance, COUNT(value) AS c FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY c DESC LIMIT 5
expected: supported
- name: topk-10-count-over-time
query: SELECT job, instance, COUNT(value) AS c FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY c DESC LIMIT 10
expected: supported
- name: topk-1-count-over-time-by-job
query: SELECT job, instance, COUNT(value) AS c FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY c DESC LIMIT 1 BY job
expected: unsupported
- name: topk-3-count-over-time-by-job
query: SELECT job, instance, COUNT(value) AS c FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY c DESC LIMIT 3 BY job
expected: unsupported
- name: topk-5-count-over-time-by-job
query: SELECT job, instance, COUNT(value) AS c FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY c DESC LIMIT 5 BY job
expected: unsupported
- name: topk-10-count-over-time-by-job
query: SELECT job, instance, COUNT(value) AS c FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY c DESC LIMIT 10 BY job
expected: unsupported
- name: topk-1-count-over-time-by-job-instance
query: SELECT job, instance, COUNT(value) AS c FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY c DESC LIMIT 1 BY job, instance
expected: unsupported
- name: topk-3-count-over-time-by-job-instance
query: SELECT job, instance, COUNT(value) AS c FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY c DESC LIMIT 3 BY job, instance
expected: unsupported
- name: topk-5-count-over-time-by-job-instance
query: SELECT job, instance, COUNT(value) AS c FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY c DESC LIMIT 5 BY job, instance
expected: unsupported
- name: topk-10-count-over-time-by-job-instance
query: SELECT job, instance, COUNT(value) AS c FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance ORDER BY c DESC LIMIT 10 BY job, instance
expected: unsupported

- name: sum
query: SELECT SUM(value) FROM data WHERE time BETWEEN DATEADD(s, -1, NOW()) AND NOW()
expected: unsupported
- name: sum-by-job
query: SELECT job, SUM(value) FROM data WHERE time BETWEEN DATEADD(s, -1, NOW()) AND NOW() GROUP BY job
expected: supported
- name: sum-by-job-instance
query: SELECT job, instance, SUM(value) FROM data WHERE time BETWEEN DATEADD(s, -1, NOW()) AND NOW() GROUP BY job, instance
expected: supported
- name: count
query: SELECT COUNT(value) FROM data WHERE time BETWEEN DATEADD(s, -1, NOW()) AND NOW()
expected: unsupported
- name: count-by-job
query: SELECT job, COUNT(value) FROM data WHERE time BETWEEN DATEADD(s, -1, NOW()) AND NOW() GROUP BY job
expected: supported
- name: count-by-job-instance
query: SELECT job, instance, COUNT(value) FROM data WHERE time BETWEEN DATEADD(s, -1, NOW()) AND NOW() GROUP BY job, instance
expected: supported

- name: sum-by-job-sum-over-time
query: SELECT job, SUM(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job
expected: supported
- name: sum-by-job-instance-sum-over-time
query: SELECT job, instance, SUM(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance
expected: supported
- name: sum-sum-over-time
query: SELECT SUM(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW()
expected: unsupported
- name: sum-without-job-sum-over-time
query: SELECT instance, SUM(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY instance
expected: supported
- name: sum-count-over-time
query: SELECT COUNT(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW()
expected: unsupported
- name: sum-by-job-count-over-time
query: SELECT job, COUNT(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job
expected: supported
- name: sum-by-job-instance-count-over-time
query: SELECT job, instance, COUNT(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance
expected: supported
- name: sum-without-job-count-over-time
query: SELECT instance, COUNT(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY instance
expected: supported
- name: min-min-over-time
query: SELECT MIN(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW()
expected: unsupported
- name: min-by-job-min-over-time
query: SELECT job, MIN(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job
expected: supported
- name: min-by-job-instance-min-over-time
query: SELECT job, instance, MIN(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance
expected: supported
- name: min-without-job-min-over-time
query: SELECT instance, MIN(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY instance
expected: supported
- name: max-max-over-time
query: SELECT MAX(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW()
expected: unsupported
- name: max-by-job-max-over-time
query: SELECT job, MAX(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job
expected: supported
- name: max-by-job-instance-max-over-time
query: SELECT job, instance, MAX(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY job, instance
expected: supported
- name: max-without-job-max-over-time
query: SELECT instance, MAX(value) FROM data WHERE time BETWEEN DATEADD(s, -300, NOW()) AND NOW() GROUP BY instance
expected: supported
Loading
Loading