datafusion_index_provider/lib.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18#![warn(missing_docs)]
19
20//! # DataFusion Index Provider
21//!
22//! This crate provides a comprehensive framework for adding index-based scanning capabilities
23//! to DataFusion [`datafusion::datasource::TableProvider`]s. It enables efficient query execution
24//! by leveraging secondary indexes to reduce I/O and improve query performance through a
25//! sophisticated two-phase execution model.
26//!
27//! ## Architecture Overview
28//!
29//! The crate implements a two-phase execution model:
30//!
31//! 1. **Index Phase**: Scan one or more indexes to identify primary key values matching the query filters
32//! 2. **Fetch Phase**: Use the primary key values to fetch complete records from the underlying storage
33//!
34//! This approach is particularly effective for selective queries where indexes can significantly
35//! reduce the number of rows that need to be fetched from primary storage.
36//!
37//! ## Core Components
38//!
39//! ### Index Management
40//! - [`physical_plan::Index`]: Trait representing a physical index that can be scanned to retrieve primary key values
41//! - [`provider::IndexedTableProvider`]: Extension of DataFusion's `TableProvider` with index discovery
42//! - [`types::IndexFilter`]: Enum representing filter operations that can be pushed down to indexes
43//!
44//! ### Execution Engine
45//! - [`physical_plan::exec::fetch::RecordFetchExec`]: Top-level execution plan orchestrating the two phases
46//! - [`physical_plan::exec::index::IndexScanExec`]: Execution plan for scanning a single index
47//! - [`physical_plan::fetcher::RecordFetcher`]: Trait for fetching complete records using primary key values
48//!
49//! ## Query Capabilities
50//!
51//! The system's query capabilities are defined by the trait implementations:
52//!
53//! ### Index Trait Capabilities
54//! Each [`physical_plan::Index`] implementation defines its query capabilities through:
55//!
56//! - **`supports_predicate(&self, predicate: &Expr) -> Result<bool>`**: Determines if the index can handle a specific predicate
57//! - **Default implementation**: Supports any predicate that references the index's column name
58//! - **Custom implementations**: Can implement sophisticated predicate analysis for complex index types
59//!
60//! ### IndexedTableProvider Filter Analysis
61//! The [`provider::IndexedTableProvider`] trait provides comprehensive filter analysis through
62//! `build_index_filter()`:
63//!
64//! #### Supported Expression Types
65//! - **Simple predicates**: Any expression that an index's `supports_predicate()` method accepts
66//! - **AND operations**: `Expr::BinaryExpr` with `Operator::And` - creates `IndexFilter::And`
67//! - **OR operations**: `Expr::BinaryExpr` with `Operator::Or` - creates `IndexFilter::Or`
68//! - **Nested expressions**: Recursive traversal supports arbitrarily nested AND/OR combinations
69//!
70//! #### Filter Processing Rules
71//! 1. **Recursive traversal**: Expressions are recursively analyzed to build [`types::IndexFilter`] trees
72//! 2. **All-or-nothing**: If any part of an AND/OR expression cannot be indexed, the entire expression is rejected
73//! 3. **Index matching**: Each leaf predicate must match exactly one index via `supports_predicate()`
74//! 4. **Automatic grouping**: Multiple root-level indexable filters are automatically wrapped in `IndexFilter::And`
75//!
76//! ### Supported Query Patterns
77//!
78//! Based on the trait design, the system supports:
79//!
80//! #### Basic Index Predicates
81//! - Any predicate supported by the index implementation
82//! - Equality conditions: `indexed_column = 'value'`
83//! - Inequality conditions: `indexed_column > 100`
84//! - Range queries: `indexed_column BETWEEN 10 AND 50`
85//!
86//! #### Logical Combinations
87//! - AND across different indexes: `col_a = 1 AND col_b = 2`
88//! - OR across different indexes: `col_a = 1 OR col_b = 2`
89//! - Complex nested combinations: `(col_a = 1 AND col_b = 2) OR (col_a = 3 AND col_b = 4)`
90//!
91//! #### Multi-level Nesting
92//! - Arbitrarily deep nesting supported: `((col_a = 1 OR col_a = 2) AND col_b = 3) OR (col_c = 4 AND col_d = 5)`
93//!
94//! ## Execution Plan Generation
95//!
96//! The [`physical_plan::exec::fetch::RecordFetchExec`] generates
97//! optimized execution plans based on the [`types::IndexFilter`] structure:
98//!
99//! ### IndexFilter::Single - Direct Index Scan
100//!
101//! For simple conditions on a single indexed column:
102//! ```text
103//! RecordFetchExec
104//! └── IndexScanExec (target_index)
105//! ```
106//!
107//! ### IndexFilter::And - Index Intersection
108//!
109//! For conjunctive conditions across multiple indexes, the system builds a left-deep tree
110//! of joins to intersect primary key values:
111//! ```text
112//! RecordFetchExec
113//! └── Projection(PK columns)
114//! └── HashJoin/SortMergeJoin (INNER on PK columns)
115//! ├── Projection(PK columns)
116//! │ └── HashJoin/SortMergeJoin (INNER on PK columns)
117//! │ ├── IndexScanExec (col_a_index)
118//! │ └── IndexScanExec (col_b_index)
119//! └── IndexScanExec (col_c_index)
120//! ```
121//!
122//! ### IndexFilter::Or - Union with Deduplication
123//!
124//! For disjunctive conditions, the system uses `UnionExec` followed by `AggregateExec`
125//! for automatic primary key deduplication:
126//! ```text
127//! RecordFetchExec
128//! └── AggregateExec (GROUP BY PK columns)
129//! └── UnionExec
130//! ├── IndexScanExec (col_a)
131//! ├── IndexScanExec (col_b)
132//! └── IndexScanExec (col_c)
133//! ```
134//!
135//! This approach ensures that overlapping results from different indexes are automatically
136//! deduplicated before record fetching.
137//!
138//! ## Implementation Guide
139//!
140//! - **Implement the Index Trait**: Create indexes that can scan and return primary key values
141//! - **Implement the RecordFetcher Trait**: Define how to fetch complete records using primary key values
142//! - **Implement IndexedTableProvider**: Expose available indexes and filter analysis capabilities
143//! - **Update TableProvider Implementation**: Integrate index-based execution into your scan method
144//!
145//! ## Performance Characteristics
146//!
147//! ### Execution Plan Efficiency
148//! - **Single index**: Direct scan with minimal overhead
149//! - **AND operations**: Intersection cost scales with number of indexes and intermediate result sizes
150//! - **OR operations**: Union cost includes deduplication overhead but prevents duplicate fetches
151//!
152//! ### Memory Usage
153//! - **Index results**: Streamed through execution pipeline to minimize memory footprint
154//! - **Join operations**: Hash joins require memory proportional to smaller index result set
155//! - **Deduplication**: OR operations require memory to store unique primary key values during aggregation
156//!
157//! ### Bounded Execution
158//! - **Single partition requirement**: [`physical_plan::exec::fetch::RecordFetchExec`] requires single partition input for correct result merging
159//! - **Incremental emission**: Results are emitted incrementally as they become available
160//! - **Bounded memory**: Execution is bounded with predictable memory usage patterns
161
162pub mod physical_plan;
163/// Index-aware [`datafusion::catalog::TableProvider`] extension trait.
164pub mod provider;
165/// Core types for representing index filter structures.
166pub mod types;
167
168pub use physical_plan::fetcher::RecordFetcher;
169pub use physical_plan::Index;
170pub use provider::IndexedTableProvider;
171pub use types::{IndexFilter, IndexFilters, UnionMode};