Skip to content

Commit

Permalink
Minor: improve documentation for sql unparsing (apache#11395)
Browse files Browse the repository at this point in the history
  • Loading branch information
alamb authored and xinlifoobar committed Jul 17, 2024
1 parent d04743e commit 9461647
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 18 deletions.
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

0 comments on commit 9461647

Please sign in to comment.