#[cfg(feature = "postgres")]
use sqlx::postgres::PgArguments;
#[cfg(feature = "postgres")]
use sqlx::query::Query;
#[cfg(feature = "postgres")]
use super::bind_query;
#[cfg(feature = "mysql")]
use super::bind_query_my;
#[cfg(feature = "sqlite")]
use super::bind_query_sqlite;
use super::ExecError;
use crate::core::SelectQuery;
use crate::sql::Pool;
#[derive(Debug, Clone, Default)]
pub struct ExplainOptions {
pub analyze: bool,
pub buffers: bool,
pub verbose: bool,
pub format: ExplainFormat,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum ExplainFormat {
#[default]
Text,
Json,
Yaml,
Xml,
}
#[cfg(feature = "postgres")]
impl ExplainOptions {
pub(super) fn to_clause(&self) -> String {
let mut bits: Vec<&'static str> = Vec::new();
if self.analyze {
bits.push("ANALYZE");
}
if self.buffers {
bits.push("BUFFERS");
}
if self.verbose {
bits.push("VERBOSE");
}
let format_bit = match self.format {
ExplainFormat::Text => None,
ExplainFormat::Json => Some("FORMAT JSON"),
ExplainFormat::Yaml => Some("FORMAT YAML"),
ExplainFormat::Xml => Some("FORMAT XML"),
};
if let Some(f) = format_bit {
bits.push(f);
}
if bits.is_empty() {
String::new()
} else {
format!("({})", bits.join(", "))
}
}
}
pub async fn explain_pool(
pool: &Pool,
query: &SelectQuery,
options: ExplainOptions,
) -> Result<String, ExecError> {
let stmt = pool.dialect().compile_select(query)?;
match pool {
#[cfg(feature = "postgres")]
Pool::Postgres(pg) => {
let mut sql = String::with_capacity(stmt.sql.len() + 32);
sql.push_str("EXPLAIN ");
let clause = options.to_clause();
if !clause.is_empty() {
sql.push_str(&clause);
sql.push(' ');
}
sql.push_str(&stmt.sql);
let mut q: Query<'_, sqlx::Postgres, PgArguments> = sqlx::query(&sql);
for v in stmt.params {
q = bind_query(q, v);
}
let rows = q.fetch_all(pg).await?;
let mut parts = Vec::with_capacity(rows.len());
for row in &rows {
let line: String = match options.format {
ExplainFormat::Json => {
let v: serde_json::Value = sqlx::Row::try_get(row, 0)?;
v.to_string()
}
ExplainFormat::Text | ExplainFormat::Yaml | ExplainFormat::Xml => {
sqlx::Row::try_get(row, 0)?
}
};
parts.push(line);
}
Ok(parts.join("\n"))
}
#[cfg(feature = "mysql")]
Pool::Mysql(my) => {
let prefix = if options.analyze {
"EXPLAIN ANALYZE "
} else {
match options.format {
ExplainFormat::Json => "EXPLAIN FORMAT=JSON ",
_ => "EXPLAIN FORMAT=TREE ",
}
};
let mut sql = String::with_capacity(stmt.sql.len() + prefix.len());
sql.push_str(prefix);
sql.push_str(&stmt.sql);
let mut q = sqlx::query(&sql);
for v in stmt.params {
q = bind_query_my(q, v);
}
let rows = q.fetch_all(my).await?;
let mut parts = Vec::with_capacity(rows.len());
for row in &rows {
let line: String = match options.format {
ExplainFormat::Json if !options.analyze => {
let v: serde_json::Value = sqlx::Row::try_get(row, 0)?;
v.to_string()
}
_ => sqlx::Row::try_get(row, 0)?,
};
parts.push(line);
}
Ok(parts.join("\n"))
}
#[cfg(feature = "sqlite")]
Pool::Sqlite(sq) => {
let sql = format!("EXPLAIN QUERY PLAN {}", stmt.sql);
let mut q = sqlx::query(&sql);
for v in stmt.params {
q = bind_query_sqlite(q, v);
}
let rows = q.fetch_all(sq).await?;
match options.format {
ExplainFormat::Json => {
let mut arr = Vec::with_capacity(rows.len());
for row in &rows {
let id: i64 = sqlx::Row::try_get(row, 0)?;
let parent: i64 = sqlx::Row::try_get(row, 1)?;
let detail: String = sqlx::Row::try_get(row, 3)?;
arr.push(serde_json::json!({
"id": id,
"parent": parent,
"detail": detail,
}));
}
Ok(serde_json::Value::Array(arr).to_string())
}
ExplainFormat::Text | ExplainFormat::Yaml | ExplainFormat::Xml => {
let mut parts = Vec::with_capacity(rows.len());
for row in &rows {
let detail: String = sqlx::Row::try_get(row, 3)?;
parts.push(detail);
}
Ok(parts.join("\n"))
}
}
}
}
}