feat(oracle): support explain plans

This commit is contained in:
zipg authored and GitHub committed 2026-07-10 17:55:03 +08:00
1 parent 0f4abce2b8
commit e1d6d1224e
13 files changed
+605 -117

No files matched your search

+42 -2
View File
@@ -37,7 +37,7 @@ pub fn build_explain_sql(options: ExplainSqlOptions) -> ExplainSqlBuildResult {
if source.is_empty() {
return explain_err("empty");
}
if !is_safe_explain_source(&source) {
if !is_safe_explain_sql(&source) {
return explain_err("unsafe");
}
@@ -48,6 +48,7 @@ pub fn build_explain_sql(options: ExplainSqlOptions) -> ExplainSqlBuildResult {
Some(DatabaseType::Dameng | DatabaseType::Questdb) => {
format!("EXPLAIN {source}")
}
Some(DatabaseType::Oracle) => format!("EXPLAIN PLAN FOR {source}"),
_ => format!("EXPLAIN FORMAT=JSON {source}"),
};
ExplainSqlBuildResult { ok: true, sql: Some(sql), reason: None }
@@ -75,10 +76,24 @@ pub fn build_dropped_file_preview_sql(options: DroppedFilePreviewSqlOptions) ->
pub fn supports_explain_plan(database_type: Option<DatabaseType>) -> bool {
matches!(
database_type,
Some(DatabaseType::Mysql | DatabaseType::Postgres | DatabaseType::Questdb | DatabaseType::Dameng)
Some(
DatabaseType::Mysql
| DatabaseType::Postgres
| DatabaseType::Questdb
| DatabaseType::Dameng
| DatabaseType::Oracle
)
)
}
pub fn is_safe_explain_sql(sql: &str) -> bool {
let source = strip_trailing_semicolons(sql.trim());
!source.is_empty()
&& !has_extra_statement_after_semicolon(&source)
&& is_safe_explain_source(&source)
&& !contains_dangerous_sql_keyword(&source)
}
/// Returns true for databases that support SQL query execution (execute_query / get_sample_data).
/// Non-SQL databases (Redis, MongoDB, Elasticsearch, InfluxDB, Neo4j, etcd) are excluded.
pub fn supports_sql_query(database_type: DatabaseType) -> bool {
@@ -420,6 +435,23 @@ mod tests {
);
}
#[test]
fn builds_oracle_explain_plan_sql() {
let result = build_explain_sql(ExplainSqlOptions {
database_type: Some(DatabaseType::Oracle),
sql: "WITH rows AS (SELECT 1 AS id FROM dual) SELECT * FROM rows;".to_string(),
});
assert_eq!(
result,
ExplainSqlBuildResult {
ok: true,
sql: Some("EXPLAIN PLAN FOR WITH rows AS (SELECT 1 AS id FROM dual) SELECT * FROM rows".to_string()),
reason: None,
}
);
}
#[test]
fn validates_dameng_autotrace_sql_safety() {
assert!(is_safe_dameng_autotrace_sql("SELECT * FROM t WHERE name = 'delete';"));
@@ -451,6 +483,14 @@ mod tests {
}),
ExplainSqlBuildResult { ok: false, sql: None, reason: Some("unsafe".to_string()) }
);
assert_eq!(
build_explain_sql(ExplainSqlOptions {
database_type: Some(DatabaseType::Mysql),
sql: "SELECT * FROM users; DELETE FROM users".to_string(),
}),
ExplainSqlBuildResult { ok: false, sql: None, reason: Some("unsafe".to_string()) }
);
}
#[test]