feat(plugins): expose read-only estimated plan host api

This commit is contained in:
jearhe authored and GitHub committed 2026-09-20 17:15:52 +08:00
1 parent ea16af7f28
commit f909f85075
23 files changed
+2059 -17

No files matched your search

+82
View File
@@ -37,6 +37,33 @@ pub struct ExplainSqlBuildResult {
pub reason: Option<String>,
}
/// Payload encoding of an estimated plan produced by [`build_explain_sql`].
///
/// Callers that hand a raw plan to a consumer which cannot inspect the SQL
/// (the plugin Host API) need to know how to decode it. Keep this in sync with
/// the generated statements in [`build_explain_sql`];
/// `estimated_plan_format_matches_generated_sql` guards the pairing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum EstimatedPlanFormat {
/// A JSON document returned as a single cell (PostgreSQL, MySQL, OceanBase Oracle).
Json,
/// A `ShowPlanXML` document returned as text (SQL Server `SET SHOWPLAN_XML`).
Xml,
/// A human-readable plan listing (Dameng, Doris, QuestDB, Oracle).
Text,
}
impl EstimatedPlanFormat {
pub const fn as_str(self) -> &'static str {
match self {
Self::Json => "json",
Self::Xml => "xml",
Self::Text => "text",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DroppedFilePreviewSqlOptions {
@@ -119,6 +146,20 @@ pub fn build_dropped_file_preview_sql(options: DroppedFilePreviewSqlOptions) ->
None
}
/// How a consumer should decode the estimated plan `build_explain_sql` asks
/// the server for. Dameng and Oracle plans reach DBX through the driver's
/// native plan text rather than a generated `EXPLAIN` statement, and every
/// agent plan is text.
pub fn estimated_plan_format(database_type: Option<DatabaseType>) -> EstimatedPlanFormat {
match database_type {
Some(DatabaseType::SqlServer) => EstimatedPlanFormat::Xml,
Some(DatabaseType::Dameng | DatabaseType::Questdb | DatabaseType::Doris | DatabaseType::Oracle) => {
EstimatedPlanFormat::Text
}
_ => EstimatedPlanFormat::Json,
}
}
pub fn supports_explain_plan(database_type: Option<DatabaseType>) -> bool {
matches!(
database_type,
@@ -1282,6 +1323,47 @@ mod tests {
}
}
#[test]
fn estimated_plan_format_matches_generated_sql() {
// Every dialect `supports_explain_plan` advertises must declare a payload
// format that the statement `build_explain_sql` generates really produces.
for database_type in
DatabaseType::ALL.iter().copied().filter(|database_type| supports_explain_plan(Some(*database_type)))
{
let explain_sql = build_explain_sql(ExplainSqlOptions {
database_type: Some(database_type),
format: None,
analyze: None,
sql: "SELECT 1".to_string(),
})
.sql
.expect("a supported dialect always builds EXPLAIN SQL");
match estimated_plan_format(Some(database_type)) {
EstimatedPlanFormat::Json => assert!(
explain_sql.contains("FORMAT JSON") || explain_sql.contains("FORMAT=JSON"),
"{database_type:?} declares a JSON plan but generated '{explain_sql}'"
),
EstimatedPlanFormat::Xml => assert!(
explain_sql.contains("SHOWPLAN_XML"),
"{database_type:?} declares an XML plan but generated '{explain_sql}'"
),
EstimatedPlanFormat::Text => assert!(
explain_sql.starts_with("EXPLAIN ") && !explain_sql.contains("FORMAT"),
"{database_type:?} declares a text plan but generated '{explain_sql}'"
),
}
}
assert_eq!(estimated_plan_format(None), EstimatedPlanFormat::Json);
assert_eq!(estimated_plan_format(Some(DatabaseType::Dameng)), EstimatedPlanFormat::Text);
assert_eq!(estimated_plan_format(Some(DatabaseType::Questdb)), EstimatedPlanFormat::Text);
assert_eq!(estimated_plan_format(Some(DatabaseType::Doris)), EstimatedPlanFormat::Text);
assert_eq!(estimated_plan_format(Some(DatabaseType::Oracle)), EstimatedPlanFormat::Text);
assert_eq!(estimated_plan_format(Some(DatabaseType::SqlServer)), EstimatedPlanFormat::Xml);
assert_eq!(estimated_plan_format(Some(DatabaseType::Mysql)), EstimatedPlanFormat::Json);
}
#[test]
fn builds_sqlserver_showplan_xml_batches() {
let result = build_explain_sql(ExplainSqlOptions {