use serde::Deserialize;
use super::plan::{AggregateKind, DistinctType, LocalQueryInfo, SortOrder};
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct GatewayQueryPlan {
#[serde(default)]
pub(crate) partitioned_query_execution_info_version: i32,
pub(crate) query_info: GatewayQueryInfo,
#[serde(default)]
pub(crate) query_ranges: Vec<GatewayQueryRange>,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct GatewayQueryInfo {
#[serde(default)]
pub(crate) distinct_type: DistinctType,
#[serde(default)]
pub(crate) top: Option<i64>,
#[serde(default)]
pub(crate) offset: Option<i64>,
#[serde(default)]
pub(crate) limit: Option<i64>,
#[serde(default)]
pub(crate) order_by: Vec<SortOrder>,
#[serde(default)]
pub(crate) order_by_expressions: Vec<String>,
#[serde(default)]
pub(crate) group_by_expressions: Vec<String>,
#[serde(default)]
pub(crate) group_by_aliases: Vec<String>,
#[serde(default)]
pub(crate) aggregates: Vec<AggregateKind>,
#[serde(default)]
pub(crate) group_by_alias_to_aggregate_type: Option<serde_json::Value>,
#[serde(default)]
pub(crate) rewritten_query: Option<String>,
#[serde(default)]
pub(crate) has_select_value: bool,
#[serde(default)]
pub(crate) has_non_streaming_order_by: bool,
#[serde(default)]
pub(crate) d_count_info: Option<serde_json::Value>,
}
impl GatewayQueryInfo {
pub(crate) fn shared_fields_match(
&self,
local: &LocalQueryInfo,
) -> Result<(), Vec<&'static str>> {
let mut mismatches: Vec<&'static str> = Vec::new();
if self.distinct_type != local.distinct_type {
mismatches.push("distinct_type");
}
if self.top != local.top {
mismatches.push("top");
}
if self.offset != local.offset {
mismatches.push("offset");
}
if self.limit != local.limit {
mismatches.push("limit");
}
if self.order_by != local.order_by {
mismatches.push("order_by");
}
if self.order_by_expressions != local.order_by_expressions {
mismatches.push("order_by_expressions");
}
if self.group_by_expressions != local.group_by_expressions {
mismatches.push("group_by_expressions");
}
if self.aggregates != local.aggregates {
mismatches.push("aggregates");
}
if self.has_select_value != local.has_select_value {
mismatches.push("has_select_value");
}
if mismatches.is_empty() {
Ok(())
} else {
Err(mismatches)
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct GatewayQueryRange {
#[serde(default)]
pub(crate) min: String,
#[serde(default)]
pub(crate) max: String,
#[serde(default = "default_true")]
pub(crate) is_min_inclusive: bool,
#[serde(default)]
pub(crate) is_max_inclusive: bool,
}
fn default_true() -> bool {
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::query::plan::{AggregateKind, DistinctType, SortOrder};
#[test]
fn deserializes_gateway_query_plan_into_gateway_query_info() {
let plan: GatewayQueryPlan = serde_json::from_value(serde_json::json!({
"partitionedQueryExecutionInfoVersion": 2,
"queryInfo": {
"distinctType": "Ordered",
"top": 5,
"offset": 3,
"limit": 10,
"orderBy": ["Ascending", "Descending"],
"orderByExpressions": ["c.city", "c.score"],
"groupByExpressions": ["c.city"],
"aggregates": ["Count"],
"rewrittenQuery": "SELECT VALUE 1",
"hasSelectValue": true,
"hasNonStreamingOrderBy": true
},
"queryRanges": [
{
"min": "05C1C9CD673398",
"max": "FF",
"isMinInclusive": false,
"isMaxInclusive": true
}
]
}))
.unwrap();
assert_eq!(plan.partitioned_query_execution_info_version, 2);
assert_eq!(plan.query_info.distinct_type, DistinctType::Ordered);
assert_eq!(plan.query_info.top, Some(5));
assert_eq!(plan.query_info.offset, Some(3));
assert_eq!(plan.query_info.limit, Some(10));
assert_eq!(
plan.query_info.order_by,
vec![SortOrder::Ascending, SortOrder::Descending]
);
assert_eq!(
plan.query_info.order_by_expressions,
vec!["c.city", "c.score"]
);
assert_eq!(plan.query_info.group_by_expressions, vec!["c.city"]);
assert_eq!(plan.query_info.aggregates, vec![AggregateKind::Count]);
assert_eq!(
plan.query_info.rewritten_query.as_deref(),
Some("SELECT VALUE 1")
);
assert!(plan.query_info.has_select_value);
assert!(plan.query_info.has_non_streaming_order_by);
assert_eq!(plan.query_ranges.len(), 1);
assert_eq!(plan.query_ranges[0].min, "05C1C9CD673398");
assert_eq!(plan.query_ranges[0].max, "FF");
assert!(!plan.query_ranges[0].is_min_inclusive);
assert!(plan.query_ranges[0].is_max_inclusive);
}
#[test]
fn gateway_query_range_defaults_match_gateway_contract() {
let plan: GatewayQueryPlan = serde_json::from_value(serde_json::json!({
"queryInfo": {},
"queryRanges": [
{
"min": "A",
"max": "B"
}
]
}))
.unwrap();
assert_eq!(plan.partitioned_query_execution_info_version, 0);
assert_eq!(plan.query_ranges.len(), 1);
assert_eq!(plan.query_ranges[0].min, "A");
assert_eq!(plan.query_ranges[0].max, "B");
assert!(plan.query_ranges[0].is_min_inclusive);
assert!(!plan.query_ranges[0].is_max_inclusive);
assert_eq!(plan.query_info, GatewayQueryInfo::default());
}
#[test]
fn shared_fields_match_ignores_disjoint_extras() {
let gw = GatewayQueryInfo {
distinct_type: DistinctType::Ordered,
top: Some(5),
offset: Some(3),
limit: Some(10),
order_by: vec![SortOrder::Ascending],
order_by_expressions: vec!["c.city".into()],
group_by_expressions: vec!["c.city".into()],
group_by_aliases: vec!["alias_0".into()],
aggregates: vec![AggregateKind::Count],
group_by_alias_to_aggregate_type: Some(serde_json::json!({"alias_0": "Count"})),
rewritten_query: Some("SELECT VALUE 1".into()),
has_select_value: true,
has_non_streaming_order_by: true,
d_count_info: Some(serde_json::json!({"dCountAlias": null})),
};
let local = crate::query::plan::LocalQueryInfo {
distinct_type: DistinctType::Ordered,
top: Some(5),
offset: Some(3),
limit: Some(10),
order_by: vec![SortOrder::Ascending],
order_by_expressions: vec!["c.city".into()],
group_by_expressions: vec!["c.city".into()],
aggregates: vec![AggregateKind::Count],
has_select_value: true,
has_join: true,
has_subquery: true,
has_where: true,
has_udf: true,
};
gw.shared_fields_match(&local)
.expect("shared fields must match");
}
#[test]
fn shared_fields_match_detects_shared_field_divergence() {
let gw = GatewayQueryInfo {
top: Some(5),
..Default::default()
};
let local_diff = crate::query::plan::LocalQueryInfo {
top: Some(6),
..Default::default()
};
let mismatches = gw
.shared_fields_match(&local_diff)
.expect_err("differing top must surface");
assert_eq!(mismatches, vec!["top"]);
let local_same = crate::query::plan::LocalQueryInfo {
top: Some(5),
..Default::default()
};
gw.shared_fields_match(&local_same)
.expect("matching shared fields must return Ok");
}
}