diff --git a/Cargo.lock b/Cargo.lock index 76616c373..5b68bc67f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -373,7 +373,7 @@ dependencies = [ [[package]] name = "asap-aware-mapping" version = "0.1.0" -source = "git+https://github.com/ProjectASAP/ASAPPlanner?rev=b2b05628dd9a58db555ba309bf7201e10513f4d2#b2b05628dd9a58db555ba309bf7201e10513f4d2" +source = "git+https://github.com/ProjectASAP/ASAPPlanner?rev=0402384e589df6e087d6d2d22b463ddc2eea0774#0402384e589df6e087d6d2d22b463ddc2eea0774" dependencies = [ "asap-types", "serde", @@ -384,7 +384,7 @@ dependencies = [ [[package]] name = "asap-frontend-promql" version = "0.1.0" -source = "git+https://github.com/ProjectASAP/ASAPPlanner?rev=b2b05628dd9a58db555ba309bf7201e10513f4d2#b2b05628dd9a58db555ba309bf7201e10513f4d2" +source = "git+https://github.com/ProjectASAP/ASAPPlanner?rev=0402384e589df6e087d6d2d22b463ddc2eea0774#0402384e589df6e087d6d2d22b463ddc2eea0774" dependencies = [ "asap-types", "promql-parser", @@ -393,7 +393,7 @@ dependencies = [ [[package]] name = "asap-frontend-sql" version = "0.1.0" -source = "git+https://github.com/ProjectASAP/ASAPPlanner?rev=b2b05628dd9a58db555ba309bf7201e10513f4d2#b2b05628dd9a58db555ba309bf7201e10513f4d2" +source = "git+https://github.com/ProjectASAP/ASAPPlanner?rev=0402384e589df6e087d6d2d22b463ddc2eea0774#0402384e589df6e087d6d2d22b463ddc2eea0774" dependencies = [ "asap-sql-function-catalog", "asap-types", @@ -415,12 +415,12 @@ dependencies = [ [[package]] name = "asap-sql-function-catalog" version = "0.1.0" -source = "git+https://github.com/ProjectASAP/ASAPPlanner?rev=b2b05628dd9a58db555ba309bf7201e10513f4d2#b2b05628dd9a58db555ba309bf7201e10513f4d2" +source = "git+https://github.com/ProjectASAP/ASAPPlanner?rev=0402384e589df6e087d6d2d22b463ddc2eea0774#0402384e589df6e087d6d2d22b463ddc2eea0774" [[package]] name = "asap-types" version = "0.1.0" -source = "git+https://github.com/ProjectASAP/ASAPPlanner?rev=b2b05628dd9a58db555ba309bf7201e10513f4d2#b2b05628dd9a58db555ba309bf7201e10513f4d2" +source = "git+https://github.com/ProjectASAP/ASAPPlanner?rev=0402384e589df6e087d6d2d22b463ddc2eea0774#0402384e589df6e087d6d2d22b463ddc2eea0774" dependencies = [ "serde", "serde_json", diff --git a/control_plane/Cargo.toml b/control_plane/Cargo.toml index 2aec46833..7cb29606b 100644 --- a/control_plane/Cargo.toml +++ b/control_plane/Cargo.toml @@ -76,8 +76,8 @@ asap_types.workspace = true # scaffolding, unaware that `data_plane`'s `summary_executor.rs` in *this* # repo is a real one. Vendored locally instead of chased upstream -- see # `data_plane/src/query_engines/asap_query_engine/summary_exec.rs`. -planner-types = { package = "asap-types", git = "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/ProjectASAP/ASAPPlanner", rev = "b2b05628dd9a58db555ba309bf7201e10513f4d2" } -asap-aware-mapping = { git = "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/ProjectASAP/ASAPPlanner", rev = "b2b05628dd9a58db555ba309bf7201e10513f4d2" } +planner-types = { package = "asap-types", git = "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/ProjectASAP/ASAPPlanner", rev = "0402384e589df6e087d6d2d22b463ddc2eea0774" } +asap-aware-mapping = { git = "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/ProjectASAP/ASAPPlanner", rev = "0402384e589df6e087d6d2d22b463ddc2eea0774" } # L1 adoption (design-target-architecture.md Part B): the PromQL front # end itself, replacing control_plane's own query_parser/promql.rs. @@ -85,8 +85,8 @@ asap-aware-mapping = { git = "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/ProjectASAP/ASAPPlanner", rev = # `planner-types`/`asap-aware-mapping` above -- these three MUST move # together (two revs of the same upstream repo's types in one workspace # resolve to distinct Rust types that won't unify). -asap-frontend-promql = { git = "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/ProjectASAP/ASAPPlanner", rev = "b2b05628dd9a58db555ba309bf7201e10513f4d2" } -asap-frontend-sql = { git = "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/ProjectASAP/ASAPPlanner", rev = "b2b05628dd9a58db555ba309bf7201e10513f4d2" } +asap-frontend-promql = { git = "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/ProjectASAP/ASAPPlanner", rev = "0402384e589df6e087d6d2d22b463ddc2eea0774" } +asap-frontend-sql = { git = "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/ProjectASAP/ASAPPlanner", rev = "0402384e589df6e087d6d2d22b463ddc2eea0774" } [dev-dependencies] tokio = { version = "1", features = ["full", "test-util"] } diff --git a/control_plane/src/query_plan.rs b/control_plane/src/query_plan.rs index af3078f03..17943b88e 100644 --- a/control_plane/src/query_plan.rs +++ b/control_plane/src/query_plan.rs @@ -5,6 +5,7 @@ //! node IDs. Serving executes this graph without reconstructing Planner IR or //! searching for compatible materializations. +mod clickhouse_exact; pub mod logical; use std::collections::{BTreeMap, BTreeSet}; @@ -990,6 +991,9 @@ where | planner_types::post_asap::ValueOperation::Filter { .. } | planner_types::post_asap::ValueOperation::Sort { .. } | planner_types::post_asap::ValueOperation::Limit { .. } + | planner_types::post_asap::ValueOperation::Exact( + planner_types::post_asap::ExactOperation::Aggregate { .. } + ) ) => { QueryPlanNode::Relational { @@ -1324,48 +1328,28 @@ where } } SummaryExpr::KeepPreAsap(expr) if self.preserve_relational => { - let planner_types::pre_asap::QueryExpr::Scan { - source: planner_types::pre_asap::Source::Table { table_ref }, - predicates, - schema, + let mut expression = + clickhouse_exact::render(expr).map_err(QueryPlanError::UnsupportedNode)?; + let mut bounded = false; + if let planner_types::pre_asap::QueryExpr::Scan { + predicates, schema, .. } = expr.as_ref() - else { - return Err(QueryPlanError::UnsupportedNode( - "SQL exact cut is not a direct table scan".into(), - )); - }; - if !predicates.is_empty() { - return Err(QueryPlanError::UnsupportedNode( - "SQL exact table cut contains unrendered predicates".into(), - )); - } - fn quoted(identifier: &str) -> String { - identifier - .split('.') - .map(|part| format!("`{}`", part.replace('`', "``"))) - .collect::>() - .join(".") + { + if predicates.is_empty() { + if let Some(column) = schema.time_index.and_then(|i| schema.columns.get(i)) + { + let name = format!("`{}`", column.name.replace('`', "``")); + expression.push_str(&format!( + " WHERE {name} >= {{from:UInt64}} AND {name} <= {{to:UInt64}}" + )); + bounded = true; + } + } } - let columns = schema - .columns - .iter() - .map(|column| quoted(&column.name)) - .collect::>() - .join(", "); - let time_filter = schema.time_index.and_then(|index| { - schema.columns.get(index).map(|source_column| { - let column = quoted(&source_column.name); - format!(" WHERE {column} >= {{from:UInt64}} AND {column} <= {{to:UInt64}}") - }) - }); QueryPlanNode::ExternalExact { request: ExternalExactRequest { language: QueryLanguage::ClickHouseSql, - expression: format!( - "SELECT {columns} FROM {}{}", - quoted(table_ref), - time_filter.unwrap_or_default() - ), + expression, output: ExternalExactOutput::Relation { schema: serde_json::to_value(&node.schema).map_err(|error| { QueryPlanError::Invalid(format!( @@ -1374,8 +1358,8 @@ where })?, }, parameters: BTreeMap::new(), - start_parameter: Some("from".into()), - end_parameter: Some("to".into()), + start_parameter: bounded.then(|| "from".into()), + end_parameter: bounded.then(|| "to".into()), input_contracts: Vec::new(), }, inputs: Vec::new(), diff --git a/control_plane/src/query_plan/clickhouse_exact.rs b/control_plane/src/query_plan/clickhouse_exact.rs new file mode 100644 index 000000000..9c8455429 --- /dev/null +++ b/control_plane/src/query_plan/clickhouse_exact.rs @@ -0,0 +1,423 @@ +//! Render a supported canonical relational cut without changing its row population. +//! Unsupported operators remain admission errors, never guessed SQL semantics. +use planner_types::pre_asap::{ + AggIntent, ArithmeticOpKind, CompareOpKind, QueryExpr, Reduction, ScalarValue, Schema, Source, +}; + +fn quoted(name: &str) -> String { + format!("`{}`", name.replace('`', "``")) +} +fn column(index: usize, schema: &Schema) -> Result { + schema + .columns + .get(index) + .map(|c| quoted(&c.name)) + .ok_or_else(|| format!("unresolved exact column {index}")) +} +fn scalar(expr: &QueryExpr, schema: &Schema) -> Result { + Ok(match expr { + QueryExpr::Column(id) => column(*id, schema)?, + QueryExpr::Literal(value) => match value { + ScalarValue::Int64(v) => v.to_string(), + ScalarValue::Float64(v) if v.is_finite() => format!("toFloat64('{}')", v), + ScalarValue::Utf8(v) => format!("'{}'", v.replace('\\', "\\\\").replace('\'', "\\'")), + ScalarValue::Boolean(v) => if *v { "true" } else { "false" }.into(), + ScalarValue::Null => "NULL".into(), + _ => return Err("nonfinite exact literal".into()), + }, + QueryExpr::Arithmetic { op, left, right } => { + let op = match op { + ArithmeticOpKind::Add => "+", + ArithmeticOpKind::Sub => "-", + ArithmeticOpKind::Mul => "*", + ArithmeticOpKind::Div => "/", + ArithmeticOpKind::Mod => "%", + _ => return Err("unsupported exact arithmetic".into()), + }; + format!( + "({} {op} {})", + scalar(left, schema)?, + scalar(right, schema)? + ) + } + QueryExpr::Compare { op, left, right } => { + let op = match op { + CompareOpKind::Eq => "=", + CompareOpKind::Ne => "!=", + CompareOpKind::Lt => "<", + CompareOpKind::Le => "<=", + CompareOpKind::Gt => ">", + CompareOpKind::Ge => ">=", + _ => return Err("unsupported exact comparison".into()), + }; + format!( + "({} {op} {})", + scalar(left, schema)?, + scalar(right, schema)? + ) + } + QueryExpr::BoolAnd(args) | QueryExpr::BoolOr(args) => { + let and = matches!(expr, QueryExpr::BoolAnd(_)); + if args.is_empty() { + if and { "true" } else { "false" }.into() + } else { + format!( + "({})", + args.iter() + .map(|e| scalar(e, schema)) + .collect::, _>>()? + .join(if and { " AND " } else { " OR " }) + ) + } + } + QueryExpr::Not(arg) => format!("NOT ({})", scalar(arg, schema)?), + QueryExpr::IsNull(arg) => format!("({} IS NULL)", scalar(arg, schema)?), + QueryExpr::IsNotNull(arg) => format!("({} IS NOT NULL)", scalar(arg, schema)?), + QueryExpr::FunctionCall { name, args } => { + let function = match name.as_str() { + "map" => "map", + "mapconcat" => "mapConcat", + "asap_map_access" => "arrayElement", + _ => return Err(format!("unsupported exact scalar function {name}")), + }; + expr.scalar_type(schema).map_err(|e| e.to_string())?; + format!( + "{function}({})", + args.iter() + .map(|e| scalar(e, schema)) + .collect::, _>>()? + .join(", ") + ) + } + _ => return Err("unsupported exact scalar expression".into()), + }) +} +fn aggregate(intent: &AggIntent, schema: &Schema) -> Result { + if let Some((arg, order)) = intent.arg_selector_columns(schema)? { + let AggIntent::Extension { ext_kind, .. } = intent else { + unreachable!() + }; + let function = if ext_kind == "arg_max" { + "argMax" + } else { + "argMin" + }; + return Ok(format!( + "{function}({}, {})", + column(arg, schema)?, + column(order, schema)? + )); + } + let (function, col) = match intent { + AggIntent::Count { .. } => return Ok("count()".into()), + AggIntent::Sum { col } => ("sum", col), + AggIntent::Min { col } => ("min", col), + AggIntent::Max { col } => ("max", col), + AggIntent::Avg { col } => ("avg", col), + _ => return Err("unsupported exact aggregate contract".into()), + }; + Ok(format!( + "{function}({})", + column( + col.ok_or("SQL aggregate requires explicit input column")?, + schema + )? + )) +} +/// Composite cuts retain their own canonical predicates; caller bounds are not +/// injected into descendant scans (which may belong to independent windows). +pub(super) fn render(expr: &QueryExpr) -> Result { + let output = expr.output_schema().map_err(|e| e.to_string())?; + // SQL names must identify a unique positional field at each nested boundary. + let mut names = std::collections::BTreeSet::new(); + if output.columns.iter().any(|c| !names.insert(&c.name)) { + return Err("ambiguous exact output column names".into()); + } + match expr { + QueryExpr::Scan { + source: Source::Table { table_ref }, + predicates, + schema, + } => { + let table = table_ref + .split('.') + .map(quoted) + .collect::>() + .join("."); + let columns = schema + .columns + .iter() + .map(|c| quoted(&c.name)) + .collect::>() + .join(", "); + let filters = predicates + .iter() + .map(|p| scalar(&p.0, schema)) + .collect::, _>>()?; + Ok(format!( + "SELECT {columns} FROM {table}{}", + if filters.is_empty() { + String::new() + } else { + format!(" WHERE {}", filters.join(" AND ")) + } + )) + } + QueryExpr::Filter { pred, child } => { + let schema = child.output_schema().map_err(|e| e.to_string())?; + Ok(format!( + "SELECT * FROM ({}) WHERE {}", + render(child)?, + scalar(&pred.0, &schema)? + )) + } + QueryExpr::Project { cols, child, .. } => { + let schema = child.output_schema().map_err(|e| e.to_string())?; + if cols.len() != output.columns.len() { + return Err("exact projection width mismatch".into()); + } + let columns = cols + .iter() + .zip(&output.columns) + .map(|(item, col)| { + Ok(format!( + "{} AS {}", + scalar(&item.expr, &schema)?, + quoted(&col.name) + )) + }) + .collect::, String>>()?; + Ok(format!( + "SELECT {} FROM ({})", + columns.join(", "), + render(child)? + )) + } + QueryExpr::Aggregate { + reduction: Reduction::Reduce(keys), + measures, + having: None, + child, + .. + } if !keys.is_without() => { + let schema = child.output_schema().map_err(|e| e.to_string())?; + let groups = keys + .keys() + .iter() + .map(|id| column(*id, &schema)) + .collect::, _>>()?; + let mut values = groups.clone(); + values.extend( + measures + .iter() + .map(|m| aggregate(m, &schema)) + .collect::, _>>()?, + ); + if values.len() != output.columns.len() { + return Err("exact aggregate width mismatch".into()); + } + let values = values + .iter() + .zip(&output.columns) + .map(|(v, c)| format!("{v} AS {}", quoted(&c.name))) + .collect::>(); + Ok(format!( + "SELECT {} FROM ({}){}", + values.join(", "), + render(child)?, + if groups.is_empty() { + String::new() + } else { + format!(" GROUP BY {}", groups.join(", ")) + } + )) + } + QueryExpr::Sort { + keys, + partition_by, + child, + } if partition_by.keys().is_empty() && !partition_by.is_without() => { + let schema = child.output_schema().map_err(|e| e.to_string())?; + let keys = keys + .iter() + .map(|key| { + Ok(format!( + "{} {} NULLS {}", + scalar(&key.expr, &schema)?, + if key.ascending { "ASC" } else { "DESC" }, + if key.nulls_first { "FIRST" } else { "LAST" } + )) + }) + .collect::, String>>()?; + if keys.is_empty() { + return Err("empty exact sort".into()); + } + Ok(format!( + "SELECT * FROM ({}) ORDER BY {}", + render(child)?, + keys.join(", ") + )) + } + QueryExpr::Limit { n, offset, child } => Ok(format!( + "SELECT * FROM ({}) LIMIT {n} OFFSET {offset}", + render(child)? + )), + _ => Err("unsupported canonical ClickHouse exact subtree".into()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use planner_types::pre_asap::{Column, DataType, Predicate, ProjectItem}; + use std::rc::Rc; + #[test] + fn composite_cut_preserves_branch_time_and_positional_projection() { + let scan = Rc::new(QueryExpr::Scan { + source: Source::Table { + table_ref: "db.samples".into(), + }, + schema: Schema::new(vec![ + Column::new("ts", DataType::Int64, false), + Column::new("v", DataType::Float64, false), + ]), + predicates: vec![Predicate(Rc::new(QueryExpr::Compare { + left: Rc::new(QueryExpr::Column(0)), + op: CompareOpKind::Lt, + right: Rc::new(QueryExpr::Literal(ScalarValue::Int64(-100))), + }))], + }); + let project = QueryExpr::Project { + cols: vec![ProjectItem { + alias: Some("result".into()), + expr: QueryExpr::Column(1), + }], + qualifier: None, + child: scan, + }; + let sql = render(&project).unwrap(); + assert!(sql.contains("`ts` < -100")); + assert!(sql.starts_with("SELECT `v` AS `result`")); + assert!(!sql.contains("{from:")); + assert!(!sql.contains("{to:")); + } + #[test] + fn unsupported_scalar_is_not_forwarded_as_arbitrary_native_code() { + let schema = Schema::new(vec![]); + let expr = QueryExpr::FunctionCall { + name: "unreviewedFunction".into(), + args: vec![], + }; + assert!(scalar(&expr, &schema).is_err()); + } +} + +#[cfg(test)] +mod original_tests { + use super::*; + use asap_frontend_sql::{lower_sql_dialect, SqlCatalog}; + use planner_types::{ + pre_asap::{Column, DataType}, + types::AccuracyTarget, + workload::SqlDialect, + }; + #[tokio::test] + async fn original_exact_shapes_retain_native_aggregates_and_bounds() { + let catalog = SqlCatalog::new().with_table( + "raw_samples", + Schema::new(vec![ + Column::new("metric", DataType::Utf8, false), + Column::new("ts_ms", DataType::Int64, false), + Column::new("value", DataType::Float64, false), + Column::new( + "labels", + DataType::Map { + key: Box::new(DataType::Utf8), + value: Box::new(DataType::Utf8), + value_nullable: false, + }, + false, + ), + ]), + ); + for sql in [ + include_str!("../../tests/fixtures/sql_exact_cuts/q07.sql"), + include_str!("../../tests/fixtures/sql_exact_cuts/q09.sql"), + include_str!("../../tests/fixtures/sql_exact_cuts/q12.sql"), + include_str!("../../tests/fixtures/sql_exact_cuts/q27.sql"), + ] { + let canonical = lower_sql_dialect( + sql, + &catalog, + SqlDialect::ClickhouseSQL, + AccuracyTarget::Exact, + ) + .await + .unwrap(); + let rendered = render(&canonical).unwrap(); + assert!(rendered.contains("1788891296000")); + assert!(rendered.contains("`ts_ms`")); + assert!(!rendered.contains("{from:")); + if sql.contains("sum(value)") && sql.contains("argMax") { + use crate::physical::post_asap::{PhysicalExpr, PostAsapPlan}; + use crate::query_plan::{ + FallbackPolicy, FixedEvaluationRange, InstantExecution, QueryPlanEntry, + QueryPlanError, QueryPlanNode, + }; + let planned = + crate::clickhouse::plan_clickhouse_sql(sql, &catalog, AccuracyTarget::Exact) + .await + .unwrap(); + let PhysicalExpr::Committed(PostAsapPlan::Summary(root)) = planned.physical else { + panic!("missing selected SQL DAG") + }; + let entry = QueryPlanEntry::compile_bound_relational( + "test".into(), + planned.canonical_sql, + &root, + FixedEvaluationRange { + start_ms: 1788890996000, + end_ms: 1788891296000, + cumulative: false, + }, + InstantExecution { + lookback_ms: 300000, + full_history: false, + cumulative_readout: false, + }, + FallbackPolicy::ExactBackend, + |_, _| Err(QueryPlanError::Invalid("unexpected summary binding".into())), + ) + .unwrap(); + assert!( + !entry + .nodes + .values() + .any(|node| matches!(node, QueryPlanNode::Logical { .. })), + "SQL must not acquire PromQL operators" + ); + assert!(entry.nodes.values().any(|node| match node { + QueryPlanNode::Relational { operation, .. } => matches!( + serde_json::from_value::( + operation.clone() + ) + .unwrap(), + planner_types::post_asap::ValueOperation::Exact( + planner_types::post_asap::ExactOperation::Aggregate { .. } + ) + ), + _ => false, + })); + } + } + } + #[test] + fn literal_quotes_and_backslashes_are_escaped_independently() { + let rendered = scalar( + &QueryExpr::Literal(ScalarValue::Utf8("a\\'b\n".into())), + &Schema::new(vec![]), + ) + .unwrap(); + assert_eq!(rendered, "'a\\\\\\'b\n'"); + } +} diff --git a/control_plane/tests/fixtures/sql_exact_cuts/q07.sql b/control_plane/tests/fixtures/sql_exact_cuts/q07.sql new file mode 100644 index 000000000..fd214356e --- /dev/null +++ b/control_plane/tests/fixtures/sql_exact_cuts/q07.sql @@ -0,0 +1 @@ +SELECT mapConcat(labels,map('__name__','user_service_cache_refresh_lag_seconds')) AS labels, argMax(value,ts_ms) AS value FROM raw_samples WHERE metric='user_service_cache_refresh_lag_seconds' AND ts_ms>1788891296000-300000 AND ts_ms<=1788891296000 GROUP BY labels ORDER BY labels diff --git a/control_plane/tests/fixtures/sql_exact_cuts/q09.sql b/control_plane/tests/fixtures/sql_exact_cuts/q09.sql new file mode 100644 index 000000000..d134dc552 --- /dev/null +++ b/control_plane/tests/fixtures/sql_exact_cuts/q09.sql @@ -0,0 +1 @@ +SELECT map() AS labels, sum(value) AS value FROM (SELECT labels,argMax(value,ts_ms) AS value FROM raw_samples WHERE metric='backend_process_resident_memory_bytes' AND ts_ms>1788891296000-300000 AND ts_ms<=1788891296000 GROUP BY labels) diff --git a/control_plane/tests/fixtures/sql_exact_cuts/q12.sql b/control_plane/tests/fixtures/sql_exact_cuts/q12.sql new file mode 100644 index 000000000..3d860f5eb --- /dev/null +++ b/control_plane/tests/fixtures/sql_exact_cuts/q12.sql @@ -0,0 +1 @@ +SELECT map('job',job) AS labels,sum(value) AS value FROM (SELECT labels['job'] AS job,labels,argMax(value,ts_ms) AS value FROM raw_samples WHERE metric='backend_process_resident_memory_bytes' AND ts_ms>1788891296000-300000 AND ts_ms<=1788891296000 GROUP BY job,labels) GROUP BY job ORDER BY value DESC,job LIMIT 2 diff --git a/control_plane/tests/fixtures/sql_exact_cuts/q27.sql b/control_plane/tests/fixtures/sql_exact_cuts/q27.sql new file mode 100644 index 000000000..f3fbb3d0a --- /dev/null +++ b/control_plane/tests/fixtures/sql_exact_cuts/q27.sql @@ -0,0 +1 @@ +SELECT map('job',job) labels,avg(value) value FROM (SELECT labels['job'] job,ts_ms,sum(value) value FROM raw_samples WHERE metric='backend_process_resident_memory_bytes' AND ts_ms+4000>=1788891296000-21600000 AND ts_ms+4000<=1788891296000 AND modulo(ts_ms+4000,60000)=0 GROUP BY job,ts_ms) GROUP BY job ORDER BY value DESC,job LIMIT 3 diff --git a/crates/asap_types/Cargo.toml b/crates/asap_types/Cargo.toml index a50afa72f..a83ea156c 100644 --- a/crates/asap_types/Cargo.toml +++ b/crates/asap_types/Cargo.toml @@ -33,4 +33,4 @@ sha2 = "0.10" # exactly (`control_plane/Cargo.toml`) -- two different revs of the same # git dependency in one workspace resolve to two distinct Rust types that # won't unify. -planner-types = { package = "asap-types", git = "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/ProjectASAP/ASAPPlanner", rev = "b2b05628dd9a58db555ba309bf7201e10513f4d2" } +planner-types = { package = "asap-types", git = "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/ProjectASAP/ASAPPlanner", rev = "0402384e589df6e087d6d2d22b463ddc2eea0774" } diff --git a/data_plane/Cargo.toml b/data_plane/Cargo.toml index 6bf883259..60ae2ef00 100644 --- a/data_plane/Cargo.toml +++ b/data_plane/Cargo.toml @@ -39,8 +39,8 @@ sha2 = "0.10" # reduction: Reduction, .. }`) are `pre_asap` types, in the same crate now # (not a separate `asap-ir` import). Query serving consumes the compiled # QueryPlan; these types are used at physical-plan compilation boundaries. -planner-types = { package = "asap-types", git = "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/ProjectASAP/ASAPPlanner", rev = "b2b05628dd9a58db555ba309bf7201e10513f4d2" } -asap-frontend-promql = { git = "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/ProjectASAP/ASAPPlanner", rev = "b2b05628dd9a58db555ba309bf7201e10513f4d2" } +planner-types = { package = "asap-types", git = "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/ProjectASAP/ASAPPlanner", rev = "0402384e589df6e087d6d2d22b463ddc2eea0774" } +asap-frontend-promql = { git = "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/ProjectASAP/ASAPPlanner", rev = "0402384e589df6e087d6d2d22b463ddc2eea0774" } # Shared external (workspace) serde.workspace = true @@ -133,7 +133,7 @@ fs2 = "0.4" # none of them. [dev-dependencies] -asap-aware-mapping = { git = "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/ProjectASAP/ASAPPlanner", rev = "b2b05628dd9a58db555ba309bf7201e10513f4d2" } +asap-aware-mapping = { git = "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/ProjectASAP/ASAPPlanner", rev = "0402384e589df6e087d6d2d22b463ddc2eea0774" } tempfile = "3.20.0" criterion = { version = "0.5", features = ["html_reports"] } tokio-tungstenite = "0.21"