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
27 changes: 22 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ members = [
"crates/ir",
"crates/sketch",
"crates/plan",
"crates/frontend-promql",
"crates/frontend-sql",
"crates/lower",
"crates/e2e",
]
Expand Down
2 changes: 1 addition & 1 deletion crates/e2e/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ edition = "2021"

[dependencies]
asap-ir = { path = "../ir" }
asap-control-lower = { path = "../lower" }
asap-frontend-promql = { path = "../frontend-promql" }
2 changes: 1 addition & 1 deletion crates/e2e/tests/aggregate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

use asap_ir::intent_algebra::{AggIntent, QueryExpr, Source};
use asap_ir::types::AccuracyTarget;
use asap_control_lower::lower_promql;
use asap_frontend_promql::lower_promql;
use asap_e2e::fixtures::metric_schema;

fn lower(q: &str) -> QueryExpr {
Expand Down
2 changes: 1 addition & 1 deletion crates/e2e/tests/binary_op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use asap_ir::intent_algebra::{
VectorMatch, VectorMatchKind,
};
use asap_ir::types::AccuracyTarget;
use asap_control_lower::lower_promql;
use asap_frontend_promql::lower_promql;
use asap_e2e::fixtures::metric_schema;

fn lower(q: &str) -> QueryExpr {
Expand Down
2 changes: 1 addition & 1 deletion crates/e2e/tests/nested.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use asap_ir::intent_algebra::{
AggIntent, ArithOp, BinaryOpKind, CompareOp, L3Expr, L3Scalar, Predicate, QueryExpr, Source,
};
use asap_ir::types::AccuracyTarget;
use asap_control_lower::lower_promql;
use asap_frontend_promql::lower_promql;
use asap_e2e::fixtures::metric_schema;

fn lower(q: &str) -> QueryExpr {
Expand Down
2 changes: 1 addition & 1 deletion crates/e2e/tests/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use asap_ir::intent_algebra::{
CompareOp, L3Expr, L3Scalar, Predicate, QueryExpr, Source,
};
use asap_ir::types::AccuracyTarget;
use asap_control_lower::lower_promql;
use asap_frontend_promql::lower_promql;
use asap_e2e::fixtures::metric_schema;

fn lower(q: &str) -> QueryExpr {
Expand Down
2 changes: 1 addition & 1 deletion crates/e2e/tests/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! are label-preserving and keep the schema open.

use asap_ir::types::AccuracyTarget;
use asap_control_lower::lower_promql;
use asap_frontend_promql::lower_promql;

fn lower(q: &str) -> asap_ir::intent_algebra::QueryExpr {
lower_promql(q, AccuracyTarget::Exact).unwrap_or_else(|e| panic!("lower failed for {q:?}: {e}"))
Expand Down
2 changes: 1 addition & 1 deletion crates/e2e/tests/time_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::time::Duration;

use asap_ir::intent_algebra::{AggIntent, QueryExpr, Source};
use asap_ir::types::AccuracyTarget;
use asap_control_lower::lower_promql;
use asap_frontend_promql::lower_promql;
use asap_e2e::fixtures::metric_schema;

fn lower(q: &str) -> QueryExpr {
Expand Down
13 changes: 13 additions & 0 deletions crates/frontend-promql/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "asap-frontend-promql"
version = "0.1.0"
edition = "2021"

# PromQL front end: L1 (parse) → L2 relational, then the shared L2→L3 converter
# in asap-ir. Pulls the PromQL parser only — never DataFusion.
[dependencies]
asap-ir = { path = "../ir" }

# Private mirror of GreptimeTeam/promql-parser (Apache-2.0). `main` tracks
# upstream; our `asap` branch carries the local patches.
promql-parser = { git = "https://github.com/ProjectASAP/promql-parser", branch = "asap" }
67 changes: 67 additions & 0 deletions crates/frontend-promql/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use std::fmt;

use asap_ir::intent_algebra::ConvertError;

/// Errors from lowering a PromQL query (L1 parse → L2 → shared L2→L3 convert).
///
/// Carries no DataFusion type — the PromQL front end never depends on the SQL
/// stack. The language-neutral variants (`UnsupportedFeature` / `WrongLanguage`
/// / `Convert`) are mirrored by [`asap_frontend_sql::SqlError`] rather than
/// shared, so neither front end pulls the other's parser.
#[derive(Debug)]
pub enum PromqlError {
/// The `promql-parser` crate rejected the query string (L1 parse failure).
Parse(String),
/// A PromQL function (`rate`, `*_over_time`, …) not supported in this version.
UnsupportedFunction(String),
/// A PromQL aggregation operator (`sum`, `topk`, …) not supported.
UnsupportedAggregateOp(String),
/// A structural feature (offset / `@` / `without` / unary negation) not
/// supported in this version.
UnsupportedFeature(String),
/// A required function / aggregator argument was missing.
MissingArgument(String),
/// An argument had the wrong shape (e.g. a non-numeric `topk` parameter).
InvalidParameter(String),
/// The workload's query language is not PromQL.
WrongLanguage(String),
/// The L2→L3 converter failed (name resolution against the bound schema).
Convert(ConvertError),
}

impl fmt::Display for PromqlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Parse(e) => write!(f, "PromQL parse error: {e}"),
Self::UnsupportedFunction(n) => write!(f, "unsupported PromQL function: {n}"),
Self::UnsupportedAggregateOp(n) => write!(f, "unsupported PromQL aggregate op: {n}"),
Self::UnsupportedFeature(m) => write!(f, "unsupported feature: {m}"),
Self::MissingArgument(m) => write!(f, "missing argument: {m}"),
Self::InvalidParameter(m) => write!(f, "invalid parameter: {m}"),
Self::WrongLanguage(l) => write!(f, "unsupported query language: {l}"),
Self::Convert(e) => write!(f, "L2→L3 conversion failed: {e}"),
}
}
}

impl std::error::Error for PromqlError {}

impl From<ConvertError> for PromqlError {
fn from(e: ConvertError) -> Self {
Self::Convert(e)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn unsupported_feature_label_is_language_neutral() {
// `UnsupportedFeature` shares a Display label with the SQL side, so it
// must not hardcode "PromQL".
let msg = PromqlError::UnsupportedFeature("subquery".into()).to_string();
assert_eq!(msg, "unsupported feature: subquery");
assert!(!msg.contains("PromQL"), "got: {msg}");
}
}
61 changes: 61 additions & 0 deletions crates/frontend-promql/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! PromQL front end: L1 (parse via `promql-parser`) → L2 relational, then the
//! shared L2→L3 [`convert_root`](asap_ir::intent_algebra::convert_root).
//!
//! Emits the per-language
//! [`relational::QueryExpr`](asap_ir::intent_algebra::relational); the shared
//! converter runs the [`Binder`](asap_ir::intent_algebra::Binder) for
//! positional name resolution. Depends on the PromQL parser only — never on the
//! SQL / DataFusion stack.

pub mod error;
pub mod promql;

use asap_ir::intent_algebra::{convert_root, QueryExpr};
use asap_ir::types::AccuracyTarget;
use asap_ir::workload::{QueryLanguage, QueryWorkload};

pub use error::PromqlError;
pub use promql::PromqlLowerer;

/// Lower a single PromQL query string to the canonical L3 `QueryExpr`.
///
/// `accuracy` is threaded onto every approximate intent (`Count`, `Quantile`,
/// `Cardinality`, `TopK`). The returned tree carries a self-contained `Schema`
/// on its `Scan`; call [`QueryExpr::output_schema`] for any node's schema.
pub fn lower_promql(query: &str, accuracy: AccuracyTarget) -> Result<QueryExpr, PromqlError> {
let l2 = PromqlLowerer::lower(query)?;
let l3 = convert_root(&l2, &accuracy)?;
Ok(l3)
}

/// Lower every PromQL batch entry in `workload` to a `QueryExpr`.
///
/// One `Result` per entry — errors are per-query, not fatal for the batch.
/// Returns an empty `Vec` if `workload.query_batch` is absent or empty, and a
/// `WrongLanguage` error for every entry if the workload language is not PromQL.
pub fn lower_promql_batch(workload: &QueryWorkload) -> Vec<Result<QueryExpr, PromqlError>> {
let entries = match &workload.query_batch {
Some(e) if !e.is_empty() => e,
_ => return vec![],
};

if !matches!(workload.language, QueryLanguage::PromQL) {
let lang = format!("{:?}", workload.language);
return entries
.iter()
.map(|_| Err(PromqlError::WrongLanguage(lang.clone())))
.collect();
}

entries
.iter()
.map(|entry| {
let accuracy = entry
.requirements
.as_ref()
.and_then(|r| r.accuracy.clone())
.unwrap_or(AccuracyTarget::Exact);
lower_promql(&entry.query.0, accuracy)
})
.collect()
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ use asap_ir::intent_algebra::relational::{
};
use asap_ir::intent_algebra::{ArithOp, ColumnRef, CompareOp, L2Expr, L3Scalar};

use crate::error::LoweringError;
use crate::error::PromqlError as LoweringError;

type Result<T> = std::result::Result<T, LoweringError>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

use asap_ir::intent_algebra::{AggIntent, BinaryOpKind, CompareOp, QueryExpr};
use asap_ir::types::AccuracyTarget;
use asap_control_lower::{lower_promql, LoweringError};
use asap_frontend_promql::{lower_promql, PromqlError as LoweringError};

const CORPUS: &str = include_str!("data/awesome_prometheus_alerts.txt");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use asap_ir::intent_algebra::{
AggIntent, ArithOp, BinaryOpKind, CompareOp, QueryExpr, Source,
};
use asap_ir::types::AccuracyTarget;
use asap_control_lower::{lower_promql, LoweringError};
use asap_frontend_promql::{lower_promql, PromqlError as LoweringError};

// ── harness helpers ─────────────────────────────────────────────────────────────

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
//! tanking how much of the corpus we can lower.

use asap_ir::types::AccuracyTarget;
use asap_control_lower::{lower_promql, LoweringError};
use asap_frontend_promql::{lower_promql, PromqlError as LoweringError};

const DOCS: &str = include_str!("data/promql_corpus_docs.txt");
const TESTDATA: &str = include_str!("data/promql_corpus_testdata.txt");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

use asap_ir::intent_algebra::QueryExpr;
use asap_ir::types::AccuracyTarget;
use asap_control_lower::lower_promql;
use asap_frontend_promql::lower_promql;

fn lo(q: &str) -> QueryExpr {
lower_promql(q, AccuracyTarget::Exact).unwrap_or_else(|e| panic!("{q:?} should lower: {e}"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use asap_ir::workload::{
BatchEntry, Query, QueryLanguage, QueryRequirements, QueryWorkload,
};

use asap_control_lower::{lower_promql, lower_promql_batch, LoweringError};
use asap_frontend_promql::{lower_promql, lower_promql_batch, PromqlError as LoweringError};

fn lower(q: &str) -> QueryExpr {
lower_promql(q, AccuracyTarget::Exact).unwrap_or_else(|e| panic!("lower failed for {q:?}: {e}"))
Expand Down
13 changes: 13 additions & 0 deletions crates/frontend-sql/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "asap-frontend-sql"
version = "0.1.0"
edition = "2021"

# SQL front end: L1 (parse + plan via DataFusion) → L2 relational, then the
# shared L2→L3 converter in asap-ir. Pulls DataFusion only — never promql-parser.
[dependencies]
asap-ir = { path = "../ir" }
datafusion = "43"

[dev-dependencies]
tokio = { version = "1", features = ["rt", "macros", "rt-multi-thread"] }
60 changes: 60 additions & 0 deletions crates/frontend-sql/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use std::fmt;

use asap_ir::intent_algebra::ConvertError;

/// Errors from lowering a SQL query (L1 parse + plan via DataFusion → L2 →
/// shared L2→L3 convert).
///
/// Carries no PromQL type — the SQL front end never depends on the PromQL
/// parser. The language-neutral variants (`UnsupportedFeature` / `WrongLanguage`
/// / `Convert`) are mirrored by [`asap_frontend_promql::PromqlError`] rather
/// than shared, so neither front end pulls the other's parser.
#[derive(Debug)]
pub enum SqlError {
/// DataFusion failed to parse / plan the SQL query.
DataFusion(datafusion::error::DataFusionError),
/// A table referenced by the query is absent from the catalog.
TableNotFound(String),
/// A SQL aggregate function not supported in this version.
UnsupportedAggregate(String),
/// A SQL scalar expression that could not be lowered.
InvalidExpression(String),
/// The SQL dialect is not supported (only DataFusionSQL is implemented).
UnsupportedDialect(String),
/// A structural feature (JOIN type / subquery / derived table) not
/// supported in this version.
UnsupportedFeature(String),
/// The workload's query language is not SQL.
WrongLanguage(String),
/// The L2→L3 converter failed (name resolution against the bound schema).
Convert(ConvertError),
}

impl fmt::Display for SqlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DataFusion(e) => write!(f, "DataFusion error: {e}"),
Self::TableNotFound(t) => write!(f, "table not found in catalog: {t}"),
Self::UnsupportedAggregate(n) => write!(f, "unsupported aggregate: {n}"),
Self::InvalidExpression(m) => write!(f, "invalid expression: {m}"),
Self::UnsupportedDialect(d) => write!(f, "unsupported SQL dialect: {d}"),
Self::UnsupportedFeature(m) => write!(f, "unsupported feature: {m}"),
Self::WrongLanguage(l) => write!(f, "unsupported query language: {l}"),
Self::Convert(e) => write!(f, "L2→L3 conversion failed: {e}"),
}
}
}

impl std::error::Error for SqlError {}

impl From<ConvertError> for SqlError {
fn from(e: ConvertError) -> Self {
Self::Convert(e)
}
}

impl From<datafusion::error::DataFusionError> for SqlError {
fn from(e: datafusion::error::DataFusionError) -> Self {
Self::DataFusion(e)
}
}
Loading
Loading