Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved unparser documentation #11395

Merged
merged 2 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion datafusion/sql/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,22 @@
// Make cheap clones clear: https://github.com/apache/datafusion/issues/11143
#![deny(clippy::clone_on_ref_ptr)]

//! This module provides:
//! This crate provides:
//!
//! 1. A SQL parser, [`DFParser`], that translates SQL query text into
//! an abstract syntax tree (AST), [`Statement`].
//!
//! 2. A SQL query planner [`SqlToRel`] that creates [`LogicalPlan`]s
//! from [`Statement`]s.
//!
//! 3. A SQL [`unparser`] that converts [`Expr`]s and [`LogicalPlan`]s
//! into SQL query text.
//!
//! [`DFParser`]: parser::DFParser
//! [`Statement`]: parser::Statement
//! [`SqlToRel`]: planner::SqlToRel
//! [`LogicalPlan`]: datafusion_expr::logical_plan::LogicalPlan
//! [`Expr`]: datafusion_expr::expr::Expr

mod cte;
mod expr;
Expand Down
29 changes: 21 additions & 8 deletions datafusion/sql/src/unparser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,21 +72,34 @@ impl Display for Unparsed {
}
}

/// Convert a DataFusion [`Expr`] to `sqlparser::ast::Expr`
/// Convert a DataFusion [`Expr`] to [`ast::Expr`]
///
/// This function is the opposite of `SqlToRel::sql_to_expr` and can
/// be used to, among other things, convert [`Expr`]s to strings.
/// Throws an error if [`Expr`] can not be represented by an `sqlparser::ast::Expr`
/// This function is the opposite of [`SqlToRel::sql_to_expr`] and can be used
/// to, among other things, convert [`Expr`]s to SQL strings. Such strings could
/// be used to pass filters or other expressions to another SQL engine.
///
/// # Errors
///
/// Throws an error if [`Expr`] can not be represented by an [`ast::Expr`]
///
/// # See Also
///
/// * [`Unparser`] for more control over the conversion to SQL
/// * [`plan_to_sql`] for converting a [`LogicalPlan`] to SQL
///
/// # Example
/// ```
/// use datafusion_expr::{col, lit};
/// use datafusion_sql::unparser::expr_to_sql;
/// let expr = col("a").gt(lit(4));
/// let sql = expr_to_sql(&expr).unwrap();
///
/// assert_eq!(format!("{}", sql), "(a > 4)")
/// let expr = col("a").gt(lit(4)); // form an expression `a > 4`
/// let sql = expr_to_sql(&expr).unwrap(); // convert to ast::Expr
/// // use the Display impl to convert to SQL text
/// assert_eq!(sql.to_string(), "(a > 4)")
/// ```
///
/// [`SqlToRel::sql_to_expr`]: crate::planner::SqlToRel::sql_to_expr
/// [`plan_to_sql`]: crate::unparser::plan_to_sql
/// [`LogicalPlan`]: datafusion_expr::logical_plan::LogicalPlan
pub fn expr_to_sql(expr: &Expr) -> Result<ast::Expr> {
let unparser = Unparser::default();
unparser.expr_to_sql(expr)
Expand Down
64 changes: 61 additions & 3 deletions datafusion/sql/src/unparser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
// specific language governing permissions and limitations
// under the License.

//! [`Unparser`] for converting `Expr` to SQL text

mod ast;
mod expr;
mod plan;
Expand All @@ -27,6 +29,29 @@ pub use plan::plan_to_sql;
use self::dialect::{DefaultDialect, Dialect};
pub mod dialect;

/// Convert a DataFusion [`Expr`] to [`sqlparser::ast::Expr`]
///
/// See [`expr_to_sql`] for background. `Unparser` allows greater control of
/// the conversion, but with a more complicated API.
///
/// To get more human-readable output, see [`Self::with_pretty`]
///
/// # Example
/// ```
/// use datafusion_expr::{col, lit};
/// use datafusion_sql::unparser::Unparser;
/// let expr = col("a").gt(lit(4)); // form an expression `a > 4`
/// let unparser = Unparser::default();
/// let sql = unparser.expr_to_sql(&expr).unwrap();// convert to AST
/// // use the Display impl to convert to SQL text
/// assert_eq!(sql.to_string(), "(a > 4)");
/// // now convert to pretty sql
/// let unparser = unparser.with_pretty(true);
/// let sql = unparser.expr_to_sql(&expr).unwrap();
/// assert_eq!(sql.to_string(), "a > 4"); // note lack of parenthesis
/// ```
///
/// [`Expr`]: datafusion_expr::Expr
pub struct Unparser<'a> {
dialect: &'a dyn Dialect,
pretty: bool,
Expand All @@ -40,9 +65,42 @@ impl<'a> Unparser<'a> {
}
}

/// Allow unparser to remove parenthesis according to the precedence rules of DataFusion.
/// This might make it invalid SQL for other SQL query engines with different precedence
/// rules, even if its valid for DataFusion.
/// Create pretty SQL output, better suited for human consumption
///
/// See example on the struct level documentation
///
/// # Pretty Output
///
/// By default, `Unparser` generates SQL text that will parse back to the
/// same parsed [`Expr`], which is useful for creating machine readable
/// expressions to send to other systems. However, the resulting expressions are
/// not always nice to read for humans.
///
/// For example
///
/// ```sql
/// ((a + 4) > 5)
/// ```
///
/// This method removes parenthesis using to the precedence rules of
/// DataFusion. If the output is reparsed, the resulting [`Expr`] produces
/// same value as the original in DataFusion, but with a potentially
/// different order of operations.
///
/// Note that this setting may create invalid SQL for other SQL query
/// engines with different precedence rules
///
/// # Example
/// ```
/// use datafusion_expr::{col, lit};
/// use datafusion_sql::unparser::Unparser;
/// let expr = col("a").gt(lit(4)).and(col("b").lt(lit(5))); // form an expression `a > 4 AND b < 5`
/// let unparser = Unparser::default().with_pretty(true);
/// let sql = unparser.expr_to_sql(&expr).unwrap();
/// assert_eq!(sql.to_string(), "a > 4 AND b < 5"); // note lack of parenthesis
/// ```
///
/// [`Expr`]: datafusion_expr::Expr
pub fn with_pretty(mut self, pretty: bool) -> Self {
self.pretty = pretty;
self
Expand Down
24 changes: 18 additions & 6 deletions datafusion/sql/src/unparser/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,18 @@ use super::{
Unparser,
};

/// Convert a DataFusion [`LogicalPlan`] to `sqlparser::ast::Statement`
/// Convert a DataFusion [`LogicalPlan`] to [`ast::Statement`]
///
/// This function is the opposite of `SqlToRel::sql_statement_to_plan` and can
/// be used to, among other things, convert `LogicalPlan`s to strings.
/// This function is the opposite of [`SqlToRel::sql_statement_to_plan`] and can
/// be used to, among other things, to convert `LogicalPlan`s to SQL strings.
///
/// # Errors
///
/// This function returns an error if the plan cannot be converted to SQL.
///
/// # See Also
///
/// * [`expr_to_sql`] for converting [`Expr`], a single expression to SQL
///
/// # Example
/// ```
Expand All @@ -47,16 +55,20 @@ use super::{
/// Field::new("id", DataType::Utf8, false),
/// Field::new("value", DataType::Utf8, false),
/// ]);
/// // Scan 'table' and select columns 'id' and 'value'
/// let plan = table_scan(Some("table"), &schema, None)
/// .unwrap()
/// .project(vec![col("id"), col("value")])
/// .unwrap()
/// .build()
/// .unwrap();
/// let sql = plan_to_sql(&plan).unwrap();
///
/// assert_eq!(format!("{}", sql), "SELECT \"table\".id, \"table\".\"value\" FROM \"table\"")
/// let sql = plan_to_sql(&plan).unwrap(); // convert to AST
/// // use the Display impl to convert to SQL text
/// assert_eq!(sql.to_string(), "SELECT \"table\".id, \"table\".\"value\" FROM \"table\"")
/// ```
///
/// [`SqlToRel::sql_statement_to_plan`]: crate::planner::SqlToRel::sql_statement_to_plan
/// [`expr_to_sql`]: crate::unparser::expr_to_sql
pub fn plan_to_sql(plan: &LogicalPlan) -> Result<ast::Statement> {
let unparser = Unparser::default();
unparser.plan_to_sql(plan)
Expand Down