mirror of
https://github.com/langchain-ai/datafusion.git
synced 2026-07-21 03:05:28 -04:00
59dcc362ca
## Which issue does this PR close? Closes https://github.com/apache/datafusion/issues/15804 ## Rationale for this change Now that we are on MSRV 1.88 we can use rust edition 2024, which brings let chains and other nice features. It also improves `unsafe` checking. In order to introduce these changes in slower way instead of one massive PR that is too difficult to manage we are updating a few crates at a time. ## What changes are included in this PR? Updates these crates to 2024. - datafusion-benchmarks - datafusion-ffi - datafusion-sqllogictest - datafusion-examples ## Are these changes tested? Existing unit tests. There are no functional code changes. ## Are there any user-facing changes? None. ## Note It is recommended to review with the ignore whitespace setting: https://github.com/apache/datafusion/pull/19361/files?w=1
210 lines
7.7 KiB
Rust
210 lines
7.7 KiB
Rust
// 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, SchemaRef};
|
|
use datafusion::common::{TableReference, plan_err};
|
|
use datafusion::config::ConfigOptions;
|
|
use datafusion::error::Result;
|
|
use datafusion::logical_expr::{
|
|
AggregateUDF, Expr, LogicalPlan, ScalarUDF, TableProviderFilterPushDown, TableSource,
|
|
WindowUDF,
|
|
};
|
|
use datafusion::optimizer::{
|
|
Analyzer, AnalyzerRule, Optimizer, OptimizerConfig, OptimizerContext, OptimizerRule,
|
|
};
|
|
use datafusion::sql::planner::{ContextProvider, SqlToRel};
|
|
use datafusion::sql::sqlparser::dialect::PostgreSqlDialect;
|
|
use datafusion::sql::sqlparser::parser::Parser;
|
|
use std::any::Any;
|
|
use std::sync::Arc;
|
|
|
|
/// This example shows how to use DataFusion's SQL planner to parse SQL text and
|
|
/// build `LogicalPlan`s without executing them.
|
|
///
|
|
/// For example, if you need a SQL planner and optimizer like Apache Calcite,
|
|
/// but do not want a Java runtime dependency for some reason, you could use
|
|
/// DataFusion as a SQL frontend.
|
|
///
|
|
/// Normally, users interact with DataFusion via SessionContext. However, using
|
|
/// SessionContext requires depending on the full `datafusion` core crate.
|
|
///
|
|
/// In this example, we demonstrate how to use the lower level APIs directly,
|
|
/// which only requires the `datafusion-sql` dependency.
|
|
pub fn frontend() -> Result<()> {
|
|
// First, we parse the SQL string. Note that we use the DataFusion
|
|
// Parser, which wraps the `sqlparser-rs` SQL parser and adds DataFusion
|
|
// specific syntax such as `CREATE EXTERNAL TABLE`
|
|
let dialect = PostgreSqlDialect {};
|
|
let sql = "SELECT name FROM person WHERE age BETWEEN 21 AND 32";
|
|
let statements = Parser::parse_sql(&dialect, sql)?;
|
|
|
|
// Now, use DataFusion's SQL planner, called `SqlToRel` to create a
|
|
// `LogicalPlan` from the parsed statement
|
|
//
|
|
// To invoke SqlToRel we must provide it schema and function information
|
|
// via an object that implements the `ContextProvider` trait
|
|
let context_provider = MyContextProvider::default();
|
|
let sql_to_rel = SqlToRel::new(&context_provider);
|
|
let logical_plan = sql_to_rel.sql_statement_to_plan(statements[0].clone())?;
|
|
|
|
// Here is the logical plan that was generated:
|
|
assert_eq!(
|
|
logical_plan.display_indent().to_string(),
|
|
"Projection: person.name\
|
|
\n Filter: person.age BETWEEN Int64(21) AND Int64(32)\
|
|
\n TableScan: person"
|
|
);
|
|
|
|
// The initial LogicalPlan is a mechanical translation from the parsed SQL
|
|
// and often can not run without the Analyzer passes.
|
|
//
|
|
// In this example, `person.age` is actually a different data type (Int8)
|
|
// than the values to which it is compared to which are Int64. Most
|
|
// execution engines, including DataFusion's, will fail if you provide such
|
|
// a plan.
|
|
//
|
|
// To prepare it to run, we must apply type coercion to align types, and
|
|
// check for other semantic errors. In DataFusion this is done by a
|
|
// component called the Analyzer.
|
|
let config = OptimizerContext::default().with_skip_failing_rules(false);
|
|
let analyzed_plan = Analyzer::new().execute_and_check(
|
|
logical_plan,
|
|
&config.options(),
|
|
observe_analyzer,
|
|
)?;
|
|
// Note that the Analyzer has added a CAST to the plan to align the types
|
|
assert_eq!(
|
|
analyzed_plan.display_indent().to_string(),
|
|
"Projection: person.name\
|
|
\n Filter: CAST(person.age AS Int64) BETWEEN Int64(21) AND Int64(32)\
|
|
\n TableScan: person",
|
|
);
|
|
|
|
// As we can see, the Analyzer added a CAST so the types are the same
|
|
// (Int64). However, this plan is not as efficient as it could be, as it
|
|
// will require casting *each row* of the input to UInt64 before comparison
|
|
// to 21 and 32. To optimize this query's performance, it is better to cast
|
|
// the constants once at plan time to UInt8.
|
|
//
|
|
// Query optimization is handled in DataFusion by a component called the
|
|
// Optimizer, which we now invoke
|
|
//
|
|
let optimized_plan =
|
|
Optimizer::new().optimize(analyzed_plan, &config, observe_optimizer)?;
|
|
|
|
// Show the fully optimized plan. Note that the optimizer did several things
|
|
// to prepare this plan for execution:
|
|
//
|
|
// 1. Removed casts from person.age as we described above
|
|
// 2. Converted BETWEEN to two single column inequalities (which are typically faster to execute)
|
|
// 3. Pushed the projection of `name` down to the scan (so the scan only returns that column)
|
|
// 4. Pushed the filter into the scan
|
|
// 5. Removed the projection as it was only serving to pass through the name column
|
|
assert_eq!(
|
|
optimized_plan.display_indent().to_string(),
|
|
"TableScan: person projection=[name], full_filters=[person.age >= UInt8(21), person.age <= UInt8(32)]"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Note that both the optimizer and the analyzer take a callback, called an
|
|
// "observer" that is invoked after each pass. We do not do anything with these
|
|
// callbacks in this example
|
|
|
|
fn observe_analyzer(_plan: &LogicalPlan, _rule: &dyn AnalyzerRule) {}
|
|
fn observe_optimizer(_plan: &LogicalPlan, _rule: &dyn OptimizerRule) {}
|
|
|
|
/// Implements the `ContextProvider` trait required to plan SQL
|
|
#[derive(Default)]
|
|
struct MyContextProvider {
|
|
options: ConfigOptions,
|
|
}
|
|
|
|
impl ContextProvider for MyContextProvider {
|
|
fn get_table_source(&self, name: TableReference) -> Result<Arc<dyn TableSource>> {
|
|
if name.table() == "person" {
|
|
Ok(Arc::new(MyTableSource {
|
|
schema: Arc::new(Schema::new(vec![
|
|
Field::new("name", DataType::Utf8, false),
|
|
Field::new("age", DataType::UInt8, false),
|
|
])),
|
|
}))
|
|
} else {
|
|
plan_err!("Table {} not found", name.table())
|
|
}
|
|
}
|
|
|
|
fn get_function_meta(&self, _name: &str) -> Option<Arc<ScalarUDF>> {
|
|
None
|
|
}
|
|
|
|
fn get_aggregate_meta(&self, _name: &str) -> Option<Arc<AggregateUDF>> {
|
|
None
|
|
}
|
|
|
|
fn get_variable_type(&self, _variable_names: &[String]) -> Option<DataType> {
|
|
None
|
|
}
|
|
|
|
fn get_window_meta(&self, _name: &str) -> Option<Arc<WindowUDF>> {
|
|
None
|
|
}
|
|
|
|
fn options(&self) -> &ConfigOptions {
|
|
&self.options
|
|
}
|
|
|
|
fn udf_names(&self) -> Vec<String> {
|
|
Vec::new()
|
|
}
|
|
|
|
fn udaf_names(&self) -> Vec<String> {
|
|
Vec::new()
|
|
}
|
|
|
|
fn udwf_names(&self) -> Vec<String> {
|
|
Vec::new()
|
|
}
|
|
}
|
|
|
|
/// TableSource is the part of TableProvider needed for creating a LogicalPlan.
|
|
struct MyTableSource {
|
|
schema: SchemaRef,
|
|
}
|
|
|
|
impl TableSource for MyTableSource {
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
|
|
fn schema(&self) -> SchemaRef {
|
|
self.schema.clone()
|
|
}
|
|
|
|
// For this example, we report to the DataFusion optimizer that
|
|
// this provider can apply filters during the scan
|
|
fn supports_filters_pushdown(
|
|
&self,
|
|
filters: &[&Expr],
|
|
) -> Result<Vec<TableProviderFilterPushDown>> {
|
|
Ok(vec![TableProviderFilterPushDown::Exact; filters.len()])
|
|
}
|
|
}
|