blob: ee971c2e586a8d2b1221e15ebe7c6d32dd2ad82c [file]
// 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.
//! A `Session` that borrows another one but owns its optimizer rules.
//!
//! Physical planning applies `session.physical_optimizers()`. When the session
//! arrived over FFI those rules are the *host's*, so each one runs back across
//! the boundary and hands this library a `ForeignExecutionPlan`. A stock
//! `CooperativeExec` produced that way has no reachable `try_to_proto`, so a
//! planner that must serialize its result -- and `FFI_QueryPlanner` always
//! must, it returns proto bytes rather than a handle -- fails on a node that
//! is perfectly serializable in the process that made it. The guide spells
//! this out, with the error it produces, under [Plan against your own
//! optimizer rules](https://datafusion.apache.org/python/extension-guide/query-planners.html#plan-against-your-own-optimizer-rules)
//! -- which names this type as the worked example, so the two are meant to be
//! read together.
//!
//! Wrapping the session with a locally-owned copy of the same rule set keeps
//! every rewrite inside this library, where the nodes stay concrete. That is
//! also what lets this engine split the plan: it cannot rewrite a subtree it
//! is only holding an opaque handle to.
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
use datafusion::catalog::CatalogProviderList;
use datafusion::common::config::{ConfigOptions, TableOptions};
use datafusion::common::{DFSchema, Result};
use datafusion::execution::TaskContext;
use datafusion::execution::config::SessionConfig;
use datafusion::execution::runtime_env::RuntimeEnv;
use datafusion::logical_expr::execution_props::ExecutionProps;
use datafusion::logical_expr::registry::ExtensionTypeRegistryRef;
use datafusion::logical_expr::{
AggregateUDF, Expr, HigherOrderUDF, LogicalPlan, ScalarUDF, WindowUDF,
};
use datafusion::physical_expr::PhysicalExpr;
use datafusion::physical_optimizer::PhysicalOptimizerRule;
use datafusion::physical_plan::ExecutionPlan;
use datafusion_session::Session;
/// Borrows `inner` for everything except the physical optimizer rules.
pub(crate) struct LocalOptimizerSession<'a> {
inner: &'a dyn Session,
rules: Vec<Arc<dyn PhysicalOptimizerRule + Send + Sync>>,
}
impl<'a> LocalOptimizerSession<'a> {
/// Wrap `inner` with the stock DataFusion rule set, owned here.
pub(crate) fn new(inner: &'a dyn Session) -> Self {
Self {
inner,
rules: datafusion::physical_optimizer::optimizer::PhysicalOptimizer::default().rules,
}
}
}
#[async_trait::async_trait]
impl Session for LocalOptimizerSession<'_> {
/// The one override. Everything below delegates.
fn physical_optimizers(&self) -> &[Arc<dyn PhysicalOptimizerRule + Send + Sync>] {
&self.rules
}
fn session_id(&self) -> &str {
self.inner.session_id()
}
fn config(&self) -> &SessionConfig {
self.inner.config()
}
fn catalog_list(&self) -> Arc<dyn CatalogProviderList> {
self.inner.catalog_list()
}
fn config_options(&self) -> &ConfigOptions {
self.inner.config_options()
}
fn optimize(&self, plan: &LogicalPlan) -> Result<LogicalPlan> {
self.inner.optimize(plan)
}
async fn create_physical_plan(
&self,
logical_plan: &LogicalPlan,
) -> Result<Arc<dyn ExecutionPlan>> {
self.inner.create_physical_plan(logical_plan).await
}
fn create_physical_expr(
&self,
expr: Expr,
df_schema: &DFSchema,
) -> Result<Arc<dyn PhysicalExpr>> {
self.inner.create_physical_expr(expr, df_schema)
}
fn scalar_functions(&self) -> &HashMap<String, Arc<ScalarUDF>> {
self.inner.scalar_functions()
}
fn higher_order_functions(&self) -> &HashMap<String, Arc<HigherOrderUDF>> {
self.inner.higher_order_functions()
}
fn aggregate_functions(&self) -> &HashMap<String, Arc<AggregateUDF>> {
self.inner.aggregate_functions()
}
fn window_functions(&self) -> &HashMap<String, Arc<WindowUDF>> {
self.inner.window_functions()
}
fn extension_type_registry(&self) -> &ExtensionTypeRegistryRef {
self.inner.extension_type_registry()
}
fn runtime_env(&self) -> &Arc<RuntimeEnv> {
self.inner.runtime_env()
}
fn execution_props(&self) -> &ExecutionProps {
self.inner.execution_props()
}
fn as_any(&self) -> &dyn Any {
// Delegated, not `self`: the return type is implicitly `&dyn Any +
// 'static` and this wrapper only lives as long as its borrow. It also
// keeps `as_any().is::<ForeignSession>()` answering about the real
// session rather than the wrapper.
self.inner.as_any()
}
fn table_options(&self) -> &TableOptions {
self.inner.table_options()
}
fn table_options_mut(&mut self) -> &mut TableOptions {
// Written because the trait requires it -- there is no default -- and
// unreachable by type rather than by luck: this takes `&mut self`,
// `create_physical_plan` takes `&dyn Session`, nothing in DataFusion
// asks for a `&mut dyn Session`, and `planner.rs` binds the wrapper
// immutably. A call here does not compile, so the guarantee is the
// borrow checker's rather than a comment's.
//
// Panicking is the answer on purpose, not an unfinished one. This type
// borrows the session it wraps and has no table options of its own to
// lend. Keeping a clone and returning `&mut` to that would compile and
// would be worse: a caller could set a Parquet option, watch it apply
// to nothing, and have no way to notice. Quietly dropping a mutation
// is not an improvement on refusing one.
unimplemented!(
"LocalOptimizerSession borrows its session and cannot lend out mutable table options"
)
}
fn task_ctx(&self) -> Arc<TaskContext> {
self.inner.task_ctx()
}
}