Skip to content

Commit 7e72c0c

Browse files
adriangbclaude
andcommitted
fix: address review feedback — proto format roundtrip, use-after-move, metrics null, preserve_order
- statement.rs: match on `&format` to avoid use-after-move before storing the value in Analyze { format, .. } - display.rs: omit metrics fields entirely when plan.metrics() returns None instead of emitting "Metrics": null - physical-plan/Cargo.toml: promote serde_json preserve_order to main dep so key insertion order is guaranteed in production builds too - proto: add ExplainFormat field to AnalyzeNode (logical) so the format survives logical plan proto roundtrips (complements the AnalyzeExecNode fix already in the previous commit for the physical plan) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent d57d082 commit 7e72c0c

7 files changed

Lines changed: 49 additions & 7 deletions

File tree

datafusion/physical-plan/Cargo.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ log = { workspace = true }
8585
num-traits = { workspace = true }
8686
parking_lot = { workspace = true }
8787
pin-project-lite = "^0.2.7"
88-
serde_json = { workspace = true }
88+
serde_json = { workspace = true, features = ["preserve_order"] }
8989
tokio = { workspace = true }
9090

9191
[dev-dependencies]
@@ -97,9 +97,6 @@ insta = { workspace = true }
9797
rand = { workspace = true }
9898
rstest = { workspace = true }
9999
rstest_reuse = "0.7.0"
100-
# Ensure `pgjson_snapshot_of_sample_plan` sees insertion-order JSON output
101-
# regardless of feature unification with upstream consumers.
102-
serde_json = { workspace = true, features = ["preserve_order"] }
103100
tokio = { workspace = true, features = [
104101
"rt-multi-thread",
105102
"fs",

datafusion/physical-plan/src/display.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,6 @@ impl PgJsonExecutionPlanVisitor<'_> {
786786
return;
787787
}
788788
let Some(metrics) = plan.metrics() else {
789-
object["Metrics"] = serde_json::json!(null);
790789
return;
791790
};
792791

datafusion/proto-models/proto/datafusion.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ message AnalyzeNode {
230230
// Statement-level override for `datafusion.explain.analyze_categories`.
231231
// Absent means "fall back to session config".
232232
optional datafusion_common.ExplainAnalyzeCategoriesNode analyze_categories = 4;
233+
datafusion_common.ExplainFormat format = 5;
233234
}
234235

235236
message ExplainNode {

datafusion/proto-models/src/generated/pbjson.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,9 @@ impl serde::Serialize for AnalyzeNode {
11911191
if self.analyze_categories.is_some() {
11921192
len += 1;
11931193
}
1194+
if self.format != 0 {
1195+
len += 1;
1196+
}
11941197
let mut struct_ser = serializer.serialize_struct("datafusion.AnalyzeNode", len)?;
11951198
if let Some(v) = self.input.as_ref() {
11961199
struct_ser.serialize_field("input", v)?;
@@ -1206,6 +1209,11 @@ impl serde::Serialize for AnalyzeNode {
12061209
if let Some(v) = self.analyze_categories.as_ref() {
12071210
struct_ser.serialize_field("analyzeCategories", v)?;
12081211
}
1212+
if self.format != 0 {
1213+
let v = super::datafusion_common::ExplainFormat::try_from(self.format)
1214+
.map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.format)))?;
1215+
struct_ser.serialize_field("format", &v)?;
1216+
}
12091217
struct_ser.end()
12101218
}
12111219
}
@@ -1222,6 +1230,7 @@ impl<'de> serde::Deserialize<'de> for AnalyzeNode {
12221230
"analyzeLevel",
12231231
"analyze_categories",
12241232
"analyzeCategories",
1233+
"format",
12251234
];
12261235

12271236
#[allow(clippy::enum_variant_names)]
@@ -1230,6 +1239,7 @@ impl<'de> serde::Deserialize<'de> for AnalyzeNode {
12301239
Verbose,
12311240
AnalyzeLevel,
12321241
AnalyzeCategories,
1242+
Format,
12331243
}
12341244
impl<'de> serde::Deserialize<'de> for GeneratedField {
12351245
fn deserialize<D>(deserializer: D) -> std::result::Result<GeneratedField, D::Error>
@@ -1255,6 +1265,7 @@ impl<'de> serde::Deserialize<'de> for AnalyzeNode {
12551265
"verbose" => Ok(GeneratedField::Verbose),
12561266
"analyzeLevel" | "analyze_level" => Ok(GeneratedField::AnalyzeLevel),
12571267
"analyzeCategories" | "analyze_categories" => Ok(GeneratedField::AnalyzeCategories),
1268+
"format" => Ok(GeneratedField::Format),
12581269
_ => Err(serde::de::Error::unknown_field(value, FIELDS)),
12591270
}
12601271
}
@@ -1278,6 +1289,7 @@ impl<'de> serde::Deserialize<'de> for AnalyzeNode {
12781289
let mut verbose__ = None;
12791290
let mut analyze_level__ = None;
12801291
let mut analyze_categories__ = None;
1292+
let mut format__ = None;
12811293
while let Some(k) = map_.next_key()? {
12821294
match k {
12831295
GeneratedField::Input => {
@@ -1304,13 +1316,20 @@ impl<'de> serde::Deserialize<'de> for AnalyzeNode {
13041316
}
13051317
analyze_categories__ = map_.next_value()?;
13061318
}
1319+
GeneratedField::Format => {
1320+
if format__.is_some() {
1321+
return Err(serde::de::Error::duplicate_field("format"));
1322+
}
1323+
format__ = Some(map_.next_value::<super::datafusion_common::ExplainFormat>()? as i32);
1324+
}
13071325
}
13081326
}
13091327
Ok(AnalyzeNode {
13101328
input: input__,
13111329
verbose: verbose__.unwrap_or_default(),
13121330
analyze_level: analyze_level__,
13131331
analyze_categories: analyze_categories__,
1332+
format: format__.unwrap_or_default(),
13141333
})
13151334
}
13161335
}

datafusion/proto-models/src/generated/prost.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,8 @@ pub struct AnalyzeNode {
354354
pub analyze_categories: ::core::option::Option<
355355
super::datafusion_common::ExplainAnalyzeCategoriesNode,
356356
>,
357+
#[prost(enumeration = "super::datafusion_common::ExplainFormat", tag = "5")]
358+
pub format: i32,
357359
}
358360
#[derive(Clone, PartialEq, ::prost::Message)]
359361
pub struct ExplainNode {

datafusion/proto/src/logical_plan/mod.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -874,12 +874,26 @@ impl AsLogicalPlan for LogicalPlanNode {
874874
.as_ref()
875875
.map(explain_analyze_categories_from_proto)
876876
.transpose()?;
877+
let pb_format = protobuf::ExplainFormat::try_from(analyze.format)
878+
.map_err(|_| {
879+
proto_error(format!(
880+
"Received an AnalyzeNode message with unknown ExplainFormat {}",
881+
analyze.format
882+
))
883+
})?;
884+
let analyze_format = match pb_format {
885+
protobuf::ExplainFormat::Indent => ExplainFormat::Indent,
886+
protobuf::ExplainFormat::Tree => ExplainFormat::Tree,
887+
protobuf::ExplainFormat::Pgjson => ExplainFormat::PostgresJSON,
888+
protobuf::ExplainFormat::Graphviz => ExplainFormat::Graphviz,
889+
};
877890
let explain_option =
878891
datafusion_expr::logical_plan::ExplainOption::default()
879892
.with_verbose(analyze.verbose)
880893
.with_analyze(true)
881894
.with_analyze_level(analyze_level)
882-
.with_analyze_categories(analyze_categories);
895+
.with_analyze_categories(analyze_categories)
896+
.with_format(analyze_format);
883897
LogicalPlanBuilder::from(input)
884898
.explain_option_format(explain_option)?
885899
.build()
@@ -1878,6 +1892,16 @@ impl AsLogicalPlan for LogicalPlanNode {
18781892
.analyze_categories
18791893
.as_ref()
18801894
.map(explain_analyze_categories_to_proto),
1895+
format: match &a.format {
1896+
ExplainFormat::Indent => protobuf::ExplainFormat::Indent,
1897+
ExplainFormat::Tree => protobuf::ExplainFormat::Tree,
1898+
ExplainFormat::PostgresJSON => {
1899+
protobuf::ExplainFormat::Pgjson
1900+
}
1901+
ExplainFormat::Graphviz => {
1902+
protobuf::ExplainFormat::Graphviz
1903+
}
1904+
} as i32,
18811905
},
18821906
))),
18831907
})

datafusion/sql/src/statement.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2066,7 +2066,7 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
20662066
};
20672067

20682068
if analyze {
2069-
match format {
2069+
match &format {
20702070
ExplainFormat::Indent | ExplainFormat::PostgresJSON => {}
20712071
ExplainFormat::Tree | ExplainFormat::Graphviz => {
20722072
return plan_err!(

0 commit comments

Comments
 (0)