| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you under the Apache License, Version 2.0 (the | |
| // "License"); you may not use this file except in compliance | |
| // with the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, | |
| // software distributed under the License is distributed on an | |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| // KIND, either express or implied. See the License for the | |
| // specific language governing permissions and limitations | |
| // under the License. | |
| //! See `main.rs` for how to run it. | |
| use arrow::datatypes::{DataType, Field, Schema}; | |
| use datafusion::common::DFSchema; | |
| use datafusion::common::ScalarValue; | |
| use datafusion::logical_expr::{col, lit}; | |
| use datafusion::sql::unparser::Unparser; | |
| use datafusion::{ | |
| assert_batches_eq, | |
| error::Result, | |
| prelude::{ParquetReadOptions, SessionContext}, | |
| }; | |
| use datafusion_examples::utils::{datasets::ExampleDataset, write_csv_to_parquet}; | |
| /// This example demonstrates the programmatic parsing of SQL expressions using | |
| /// the DataFusion [`SessionContext::parse_sql_expr`] API or the [`DataFrame::parse_sql_expr`] API. | |
| /// | |
| /// | |
| /// The code in this example shows how to: | |
| /// | |
| /// 1. [`simple_session_context_parse_sql_expr_demo`]: Parse a simple SQL text into a logical | |
| /// expression using a schema at [`SessionContext`]. | |
| /// | |
| /// 2. [`simple_dataframe_parse_sql_expr_demo`]: Parse a simple SQL text into a logical expression | |
| /// using a schema at [`DataFrame`]. | |
| /// | |
| /// 3. [`query_parquet_demo`]: Query a parquet file using the parsed_sql_expr from a DataFrame. | |
| /// | |
| /// 4. [`round_trip_parse_sql_expr_demo`]: Parse a SQL text and convert it back to SQL using [`Unparser`]. | |
| pub async fn parse_sql_expr() -> Result<()> { | |
| // See how to evaluate expressions | |
| simple_session_context_parse_sql_expr_demo()?; | |
| simple_dataframe_parse_sql_expr_demo().await?; | |
| query_parquet_demo().await?; | |
| round_trip_parse_sql_expr_demo().await?; | |
| Ok(()) | |
| } | |
| /// DataFusion can parse a SQL text to a logical expression against a schema at [`SessionContext`]. | |
| fn simple_session_context_parse_sql_expr_demo() -> Result<()> { | |
| let sql = "a < 5 OR a = 8"; | |
| let expr = col("a").lt(lit(5_i64)).or(col("a").eq(lit(8_i64))); | |
| // provide type information that `a` is an Int32 | |
| let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]); | |
| let df_schema = DFSchema::try_from(schema).unwrap(); | |
| let ctx = SessionContext::new(); | |
| let parsed_expr = ctx.parse_sql_expr(sql, &df_schema)?; | |
| assert_eq!(parsed_expr, expr); | |
| Ok(()) | |
| } | |
| /// DataFusion can parse a SQL text to an logical expression using schema at [`DataFrame`]. | |
| async fn simple_dataframe_parse_sql_expr_demo() -> Result<()> { | |
| let sql = "car = 'red' OR speed > 1.0"; | |
| let expr = col("car") | |
| .eq(lit(ScalarValue::Utf8(Some("red".to_string())))) | |
| .or(col("speed").gt(lit(1.0_f64))); | |
| let ctx = SessionContext::new(); | |
| // Convert the CSV input into a temporary Parquet directory for querying | |
| let dataset = ExampleDataset::Cars; | |
| let parquet_temp = write_csv_to_parquet(&ctx, &dataset.path()).await?; | |
| let df = ctx | |
| .read_parquet(parquet_temp.path_str()?, ParquetReadOptions::default()) | |
| .await?; | |
| let parsed_expr = df.parse_sql_expr(sql)?; | |
| assert_eq!(parsed_expr, expr); | |
| Ok(()) | |
| } | |
| async fn query_parquet_demo() -> Result<()> { | |
| let ctx = SessionContext::new(); | |
| // Convert the CSV input into a temporary Parquet directory for querying | |
| let dataset = ExampleDataset::Cars; | |
| let parquet_temp = write_csv_to_parquet(&ctx, &dataset.path()).await?; | |
| let df = ctx | |
| .read_parquet(parquet_temp.path_str()?, ParquetReadOptions::default()) | |
| .await?; | |
| let df = df | |
| .clone() | |
| .select(vec![df.parse_sql_expr("car")?, df.parse_sql_expr("speed")?])? | |
| .filter(df.parse_sql_expr("car = 'red' OR speed > 1.0")?)? | |
| .aggregate( | |
| vec![df.parse_sql_expr("car")?], | |
| vec![df.parse_sql_expr("SUM(speed) as sum_speed")?], | |
| )? | |
| // Directly parsing the SQL text into a sort expression is not supported yet, so | |
| // construct it programmatically | |
| .sort(vec![col("car").sort(false, false)])? | |
| .limit(0, Some(1))?; | |
| let result = df.collect().await?; | |
| assert_batches_eq!( | |
| &[ | |
| "+-----+--------------------+", | |
| "| car | sum_speed |", | |
| "+-----+--------------------+", | |
| "| red | 162.49999999999997 |", | |
| "+-----+--------------------+" | |
| ], | |
| &result | |
| ); | |
| Ok(()) | |
| } | |
| /// DataFusion can parse a SQL text and convert it back to SQL using [`Unparser`]. | |
| async fn round_trip_parse_sql_expr_demo() -> Result<()> { | |
| let sql = "((car = 'red') OR (speed > 1.0))"; | |
| let ctx = SessionContext::new(); | |
| // Convert the CSV input into a temporary Parquet directory for querying | |
| let dataset = ExampleDataset::Cars; | |
| let parquet_temp = write_csv_to_parquet(&ctx, &dataset.path()).await?; | |
| let df = ctx | |
| .read_parquet(parquet_temp.path_str()?, ParquetReadOptions::default()) | |
| .await?; | |
| let parsed_expr = df.parse_sql_expr(sql)?; | |
| let unparser = Unparser::default(); | |
| let round_trip_sql = unparser.expr_to_sql(&parsed_expr)?.to_string(); | |
| assert_eq!(sql, round_trip_sql); | |
| // enable pretty-unparsing. This make the output more human-readable | |
| // but can be problematic when passed to other SQL engines due to | |
| // difference in precedence rules between DataFusion and target engines. | |
| let unparser = Unparser::default().with_pretty(true); | |
| let pretty = "car = 'red' OR speed > 1.0"; | |
| let pretty_round_trip_sql = unparser.expr_to_sql(&parsed_expr)?.to_string(); | |
| assert_eq!(pretty, pretty_round_trip_sql); | |
| Ok(()) | |
| } |