Skip to content

Commit e6d8afd

Browse files
Added SQL support to asap-planner-rs (#227)
* Added SQL support to asap-planner-rs * Fixed tests and changed cmd line args * Added some tests for Elastic syntax * Removed unnecessary enum * Refactored code
1 parent c4dfa45 commit e6d8afd

11 files changed

Lines changed: 1465 additions & 132 deletions

File tree

‎Cargo.lock‎

Lines changed: 98 additions & 119 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎asap-planner-rs/Cargo.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ path = "src/main.rs"
1414
[dependencies]
1515
sketch_db_common.workspace = true
1616
promql_utilities.workspace = true
17+
sql_utilities.workspace = true
18+
sqlparser = "0.59.0"
1719
serde.workspace = true
1820
serde_json.workspace = true
1921
serde_yaml.workspace = true

‎asap-planner-rs/src/config/input.rs‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,27 @@ pub struct HydraParams {
7070
pub col_num: u64,
7171
pub k: u64,
7272
}
73+
74+
#[derive(Debug, Clone, Deserialize)]
75+
pub struct SQLControllerConfig {
76+
pub query_groups: Vec<SQLQueryGroup>,
77+
pub tables: Vec<TableDefinition>,
78+
pub sketch_parameters: Option<SketchParameterOverrides>,
79+
pub aggregate_cleanup: Option<AggregateCleanupConfig>,
80+
}
81+
82+
#[derive(Debug, Clone, Deserialize)]
83+
pub struct SQLQueryGroup {
84+
pub id: Option<u32>,
85+
pub queries: Vec<String>,
86+
pub repetition_delay: u64,
87+
pub controller_options: ControllerOptions,
88+
}
89+
90+
#[derive(Debug, Clone, Deserialize)]
91+
pub struct TableDefinition {
92+
pub name: String,
93+
pub time_column: String,
94+
pub value_columns: Vec<String>,
95+
pub metadata_columns: Vec<String>,
96+
}

‎asap-planner-rs/src/error.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,8 @@ pub enum ControllerError {
1414
PlannerError(String),
1515
#[error("Unknown metric: {0}")]
1616
UnknownMetric(String),
17+
#[error("SQL parse error: {0}")]
18+
SqlParse(String),
19+
#[error("Unknown table: {0}")]
20+
UnknownTable(String),
1721
}

‎asap-planner-rs/src/lib.rs‎

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ use serde_yaml::Value as YamlValue;
77
use std::path::Path;
88

99
pub use config::input::ControllerConfig;
10+
pub use config::input::SQLControllerConfig;
1011
pub use error::ControllerError;
1112
pub use output::generator::{GeneratorOutput, PuntedQuery};
13+
pub use output::sql_generator::SQLRuntimeOptions;
1214

1315
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1416
pub enum StreamingEngine {
@@ -162,6 +164,117 @@ impl PlannerOutput {
162164
pub fn to_inference_yaml_string(&self) -> Result<String, anyhow::Error> {
163165
Ok(serde_yaml::to_string(&self.inference_yaml)?)
164166
}
167+
168+
/// Returns the table_name field of the first aggregation matching agg_type.
169+
pub fn aggregation_table_name(&self, agg_type: &str) -> Option<String> {
170+
if let YamlValue::Mapping(root) = &self.streaming_yaml {
171+
if let Some(YamlValue::Sequence(aggs)) = root.get("aggregations") {
172+
for agg in aggs {
173+
if let YamlValue::Mapping(m) = agg {
174+
if let Some(YamlValue::String(t)) = m.get("aggregationType") {
175+
if t == agg_type {
176+
if let Some(YamlValue::String(name)) = m.get("table_name") {
177+
return Some(name.clone());
178+
}
179+
}
180+
}
181+
}
182+
}
183+
}
184+
}
185+
None
186+
}
187+
188+
/// Returns the value_column field of the first aggregation matching agg_type.
189+
pub fn aggregation_value_column(&self, agg_type: &str) -> Option<String> {
190+
if let YamlValue::Mapping(root) = &self.streaming_yaml {
191+
if let Some(YamlValue::Sequence(aggs)) = root.get("aggregations") {
192+
for agg in aggs {
193+
if let YamlValue::Mapping(m) = agg {
194+
if let Some(YamlValue::String(t)) = m.get("aggregationType") {
195+
if t == agg_type {
196+
if let Some(YamlValue::String(col)) = m.get("value_column") {
197+
return Some(col.clone());
198+
}
199+
}
200+
}
201+
}
202+
}
203+
}
204+
}
205+
None
206+
}
207+
208+
/// Returns true if any aggregation has the matching type AND sub_type.
209+
pub fn has_aggregation_type_and_sub_type(&self, agg_type: &str, sub_type: &str) -> bool {
210+
if let YamlValue::Mapping(root) = &self.streaming_yaml {
211+
if let Some(YamlValue::Sequence(aggs)) = root.get("aggregations") {
212+
return aggs.iter().any(|agg| {
213+
if let YamlValue::Mapping(m) = agg {
214+
let type_matches = m.get("aggregationType").and_then(|v| {
215+
if let YamlValue::String(s) = v {
216+
Some(s.as_str())
217+
} else {
218+
None
219+
}
220+
}) == Some(agg_type);
221+
let sub_matches = m.get("aggregationSubType").and_then(|v| {
222+
if let YamlValue::String(s) = v {
223+
Some(s.as_str())
224+
} else {
225+
None
226+
}
227+
}) == Some(sub_type);
228+
type_matches && sub_matches
229+
} else {
230+
false
231+
}
232+
});
233+
}
234+
}
235+
false
236+
}
237+
}
238+
239+
pub struct SQLController {
240+
config: SQLControllerConfig,
241+
options: SQLRuntimeOptions,
242+
}
243+
244+
impl SQLController {
245+
pub fn from_file(path: &Path, opts: SQLRuntimeOptions) -> Result<Self, ControllerError> {
246+
let yaml_str = std::fs::read_to_string(path)?;
247+
Self::from_yaml(&yaml_str, opts)
248+
}
249+
250+
pub fn from_yaml(yaml: &str, opts: SQLRuntimeOptions) -> Result<Self, ControllerError> {
251+
let config: SQLControllerConfig = serde_yaml::from_str(yaml)?;
252+
Ok(Self {
253+
config,
254+
options: opts,
255+
})
256+
}
257+
258+
pub fn generate(&self) -> Result<PlannerOutput, ControllerError> {
259+
let output = output::sql_generator::generate_sql_plan(&self.config, &self.options)?;
260+
Ok(PlannerOutput {
261+
punted_queries: output.punted_queries,
262+
streaming_yaml: output.streaming_yaml,
263+
inference_yaml: output.inference_yaml,
264+
aggregation_count: output.aggregation_count,
265+
query_count: output.query_count,
266+
})
267+
}
268+
269+
pub fn generate_to_dir(&self, dir: &Path) -> Result<PlannerOutput, ControllerError> {
270+
let output = self.generate()?;
271+
std::fs::create_dir_all(dir)?;
272+
let streaming_str = serde_yaml::to_string(&output.streaming_yaml)?;
273+
let inference_str = serde_yaml::to_string(&output.inference_yaml)?;
274+
std::fs::write(dir.join("streaming_config.yaml"), streaming_str)?;
275+
std::fs::write(dir.join("inference_config.yaml"), inference_str)?;
276+
Ok(output)
277+
}
165278
}
166279

167280
impl Controller {

‎asap-planner-rs/src/main.rs‎

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use asap_planner::{Controller, RuntimeOptions, StreamingEngine};
1+
use asap_planner::{Controller, RuntimeOptions, SQLController, SQLRuntimeOptions, StreamingEngine};
22
use clap::Parser;
3+
use sketch_db_common::enums::QueryLanguage;
34
use std::path::PathBuf;
45

56
#[derive(Parser, Debug)]
@@ -11,8 +12,8 @@ struct Args {
1112
#[arg(long = "output_dir")]
1213
output_dir: PathBuf,
1314

14-
#[arg(long = "prometheus_scrape_interval")]
15-
prometheus_scrape_interval: u64,
15+
#[arg(long = "prometheus_scrape_interval", required = false)]
16+
prometheus_scrape_interval: Option<u64>,
1617

1718
#[arg(long = "streaming_engine", value_enum)]
1819
streaming_engine: EngineArg,
@@ -26,6 +27,12 @@ struct Args {
2627
#[arg(long = "step", default_value = "0")]
2728
step: u64,
2829

30+
#[arg(long = "query-language", value_enum, default_value = "promql")]
31+
query_language: QueryLanguage,
32+
33+
#[arg(long = "data-ingestion-interval", required = false)]
34+
data_ingestion_interval: Option<u64>,
35+
2936
#[arg(short, long, action = clap::ArgAction::Count)]
3037
verbose: u8,
3138
}
@@ -52,16 +59,37 @@ fn main() -> anyhow::Result<()> {
5259
EngineArg::Flink => StreamingEngine::Flink,
5360
};
5461

55-
let opts = RuntimeOptions {
56-
prometheus_scrape_interval: args.prometheus_scrape_interval,
57-
streaming_engine: engine,
58-
enable_punting: args.enable_punting,
59-
range_duration: args.range_duration,
60-
step: args.step,
61-
};
62-
63-
let controller = Controller::from_file(&args.input_config, opts)?;
64-
controller.generate_to_dir(&args.output_dir)?;
62+
match args.query_language {
63+
QueryLanguage::promql => {
64+
let scrape_interval = args.prometheus_scrape_interval.ok_or_else(|| {
65+
anyhow::anyhow!("--prometheus_scrape_interval is required for PromQL mode")
66+
})?;
67+
let opts = RuntimeOptions {
68+
prometheus_scrape_interval: scrape_interval,
69+
streaming_engine: engine,
70+
enable_punting: args.enable_punting,
71+
range_duration: args.range_duration,
72+
step: args.step,
73+
};
74+
let controller = Controller::from_file(&args.input_config, opts)?;
75+
controller.generate_to_dir(&args.output_dir)?;
76+
}
77+
QueryLanguage::sql | QueryLanguage::elastic_sql => {
78+
let interval = args.data_ingestion_interval.ok_or_else(|| {
79+
anyhow::anyhow!("--data-ingestion-interval is required for SQL mode")
80+
})?;
81+
let opts = SQLRuntimeOptions {
82+
streaming_engine: engine,
83+
query_evaluation_time: None,
84+
data_ingestion_interval: interval,
85+
};
86+
SQLController::from_file(&args.input_config, opts)?
87+
.generate_to_dir(&args.output_dir)?;
88+
}
89+
QueryLanguage::elastic_querydsl => {
90+
anyhow::bail!("ElasticQueryDSL is not yet supported");
91+
}
92+
}
6593

6694
println!("Generated configs in {}", args.output_dir.display());
6795
Ok(())

‎asap-planner-rs/src/output/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod generator;
2+
pub mod sql_generator;
23
pub use generator::*;

0 commit comments

Comments
 (0)