Skip to main content

proof_of_sql_planner/
error.rs

1use arrow::datatypes::DataType;
2use datafusion::{
3    common::{DataFusionError, JoinConstraint, JoinType},
4    logical_expr::{
5        expr::{AggregateFunction, Placeholder},
6        Expr, Operator,
7    },
8    physical_plan,
9};
10use derive_more::Display;
11use proof_of_sql::{
12    base::math::decimal::DecimalError,
13    sql::{proof_plans::AggregateExecError, AnalyzeError},
14};
15use snafu::Snafu;
16use sqlparser::parser::ParserError;
17
18/// Errors encountered while converting an aggregate logical plan.
19#[non_exhaustive]
20#[derive(Clone, Debug, Eq, PartialEq, Snafu)]
21pub enum AggregatePlanError {
22    /// A grouping expression was absent from `DataFusion`'s output alias map.
23    #[snafu(display("group expression {expression} has no output alias"))]
24    MissingGroupExpressionAlias {
25        /// Display name used to look up the expression.
26        expression: String,
27    },
28    /// An aggregate expression was absent from `DataFusion`'s output alias map.
29    #[snafu(display("aggregate expression {expression} has no output alias"))]
30    MissingAggregateExpressionAlias {
31        /// Display name used to look up the expression.
32        expression: String,
33    },
34    /// `DataFusion` placed a non-aggregate expression in the aggregate expression list.
35    #[snafu(display("aggregate expression list contains non-aggregate expression {expression}"))]
36    UnexpectedAggregateExpression {
37        /// Unexpected expression.
38        expression: String,
39    },
40    /// The aggregate proof-plan constructor rejected the requested shape.
41    #[snafu(transparent)]
42    AggregateExec {
43        /// Authoritative aggregate construction error.
44        source: AggregateExecError,
45    },
46}
47
48/// Errors encountered while converting a join logical plan.
49#[non_exhaustive]
50#[derive(Clone, Debug, Eq, PartialEq, Snafu)]
51pub enum JoinPlanError {
52    /// Only inner joins can be converted to the current proof plan.
53    #[snafu(display("join type {join_type} is not supported"))]
54    UnsupportedJoinType {
55        /// Unsupported join type.
56        join_type: JoinType,
57    },
58    /// Only `ON` join constraints can be converted to the current proof plan.
59    #[snafu(display("join constraint {constraint:?} is not supported"))]
60    UnsupportedJoinConstraint {
61        /// Unsupported join constraint.
62        constraint: JoinConstraint,
63    },
64    /// The join predicate is not a supported pair of matching column references.
65    #[snafu(display(
66        "join predicate must pair supported columns with the same unqualified name, found {left} and {right}"
67    ))]
68    UnsupportedPredicate {
69        /// Left-hand join expression.
70        left: String,
71        /// Right-hand join expression.
72        right: String,
73    },
74}
75
76/// Kind of `DataFusion` logical plan node presented to the Proof of SQL converter.
77#[non_exhaustive]
78#[derive(Clone, Copy, Debug, Display, Eq, PartialEq)]
79pub enum LogicalPlanNodeKind {
80    /// Window node.
81    #[display(fmt = "window")]
82    Window,
83    /// Sort node.
84    #[display(fmt = "sort")]
85    Sort,
86    /// Cross-join node.
87    #[display(fmt = "cross join")]
88    CrossJoin,
89    /// Repartition node.
90    #[display(fmt = "repartition")]
91    Repartition,
92    /// Unsupported table-scan shape.
93    #[display(fmt = "table scan")]
94    TableScan,
95    /// Subquery node.
96    #[display(fmt = "subquery")]
97    Subquery,
98    /// Statement node.
99    #[display(fmt = "statement")]
100    Statement,
101    /// Values node.
102    #[display(fmt = "values")]
103    Values,
104    /// Explain node.
105    #[display(fmt = "explain")]
106    Explain,
107    /// Analyze node.
108    #[display(fmt = "analyze")]
109    Analyze,
110    /// Extension node.
111    #[display(fmt = "extension")]
112    Extension,
113    /// Distinct node.
114    #[display(fmt = "distinct")]
115    Distinct,
116    /// Prepare node.
117    #[display(fmt = "prepare")]
118    Prepare,
119    /// Data-manipulation node.
120    #[display(fmt = "data manipulation")]
121    Dml,
122    /// Data-definition node.
123    #[display(fmt = "data definition")]
124    Ddl,
125    /// Copy node.
126    #[display(fmt = "copy")]
127    Copy,
128    /// Describe-table node.
129    #[display(fmt = "describe table")]
130    DescribeTable,
131    /// Unnest node.
132    #[display(fmt = "unnest")]
133    Unnest,
134    /// Recursive-query node.
135    #[display(fmt = "recursive query")]
136    RecursiveQuery,
137}
138
139/// Proof of SQL Planner error
140#[non_exhaustive]
141#[derive(Debug, Snafu)]
142pub enum PlannerError {
143    /// Returned when the internal analyze process fails
144    #[snafu(transparent)]
145    AnalyzeError {
146        /// Underlying analyze error
147        source: AnalyzeError,
148    },
149    /// Returned when a decimal error occurs
150    #[snafu(transparent)]
151    DecimalError {
152        /// Underlying decimal error
153        source: DecimalError,
154    },
155    /// Returned when sqlparser fails to parse a query
156    #[snafu(transparent)]
157    SqlParserError {
158        /// Underlying sqlparser error
159        source: ParserError,
160    },
161    /// Returned when datafusion fails to plan a query
162    #[snafu(transparent)]
163    DataFusionError {
164        /// Underlying datafusion error
165        source: DataFusionError,
166    },
167    /// Returned if a column is not found
168    #[snafu(display("Column not found"))]
169    ColumnNotFound,
170    /// Returned if a table is not found
171    #[snafu(display("Table not found: {}", table_name))]
172    TableNotFound {
173        /// Table name
174        table_name: String,
175    },
176    /// Returned when a placeholder id is invalid
177    #[snafu(display("Placeholder id {id:?} is invalid"))]
178    InvalidPlaceholderId {
179        /// Unsupported placeholder id
180        id: String,
181    },
182    /// Returned when a placeholder is untyped
183    #[snafu(display("Placeholder {placeholder:?} is untyped"))]
184    UntypedPlaceholder {
185        /// Untyped placeholder
186        placeholder: Placeholder,
187    },
188    /// Returned when a datatype is not supported
189    #[snafu(display("Unsupported datatype: {}", data_type))]
190    UnsupportedDataType {
191        /// Unsupported datatype
192        data_type: DataType,
193    },
194    /// Returned when a binary operator is not supported
195    #[snafu(display("Binary operator {} is not supported", op))]
196    UnsupportedBinaryOperator {
197        /// Unsupported binary operation
198        op: Operator,
199    },
200    /// Returned when the aggregate opetation is not supported
201    #[snafu(display("Aggregate operation {op:?} is not supported"))]
202    UnsupportedAggregateOperation {
203        /// Unsupported aggregate operation
204        op: physical_plan::aggregates::AggregateFunction,
205    },
206    /// Returned when the `AggregateFunction` is not supported
207    #[snafu(display("AggregateFunction {function:?} is not supported"))]
208    UnsupportedAggregateFunction {
209        /// Unsupported `AggregateFunction`
210        function: AggregateFunction,
211    },
212    /// Returned when a logical expression is not resolved
213    #[snafu(display("Logical expression {:?} is not supported", expr))]
214    UnsupportedLogicalExpression {
215        /// Unsupported logical expression
216        expr: Box<Expr>,
217    },
218    /// Returned when an aggregate logical plan cannot be converted.
219    #[snafu(
220        context(false),
221        display("Aggregate logical plan is not supported: {source}")
222    )]
223    UnsupportedAggregatePlan {
224        /// Specific aggregate conversion error.
225        source: AggregatePlanError,
226    },
227    /// Returned when a join logical plan cannot be converted.
228    #[snafu(
229        context(false),
230        display("Join logical plan is not supported: {source}")
231    )]
232    UnsupportedJoinPlan {
233        /// Specific join conversion error.
234        source: JoinPlanError,
235    },
236    /// Returned when a `LogicalPlan` node or shape is not supported.
237    #[snafu(display("Logical plan node or shape {node} is not supported"))]
238    UnsupportedLogicalPlan {
239        /// Kind of node whose particular shape was unsupported.
240        node: LogicalPlanNodeKind,
241    },
242    /// Returned when the `LogicalPlan` is not resolved
243    #[snafu(display("LogicalPlan is not resolved"))]
244    UnresolvedLogicalPlan,
245    /// Returned when catalog is provided since it is not supported
246    #[snafu(display("Catalog is not supported"))]
247    CatalogNotSupported,
248}
249
250/// Proof of SQL Planner result
251pub type PlannerResult<T> = Result<T, PlannerError>;
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256    use proof_of_sql::base::database::ColumnType;
257
258    #[test]
259    fn planner_sub_errors_include_actionable_context() {
260        assert_eq!(
261            AggregatePlanError::MissingGroupExpressionAlias {
262                expression: "table.id".to_string(),
263            }
264            .to_string(),
265            "group expression table.id has no output alias"
266        );
267        assert_eq!(
268            AggregatePlanError::AggregateExec {
269                source: AggregateExecError::UnsupportedGroupByExpressionType {
270                    data_type: ColumnType::VarChar,
271                },
272            }
273            .to_string(),
274            "grouping and deduplication do not support expressions of type VARCHAR"
275        );
276        assert_eq!(
277            JoinPlanError::UnsupportedPredicate {
278                left: "left.a".to_string(),
279                right: "right.b".to_string(),
280            }
281            .to_string(),
282            "join predicate must pair supported columns with the same unqualified name, found left.a and right.b"
283        );
284    }
285
286    #[test]
287    fn logical_plan_node_kinds_have_human_readable_labels() {
288        let expected_labels = [
289            (LogicalPlanNodeKind::Window, "window"),
290            (LogicalPlanNodeKind::Sort, "sort"),
291            (LogicalPlanNodeKind::CrossJoin, "cross join"),
292            (LogicalPlanNodeKind::Repartition, "repartition"),
293            (LogicalPlanNodeKind::TableScan, "table scan"),
294            (LogicalPlanNodeKind::Subquery, "subquery"),
295            (LogicalPlanNodeKind::Statement, "statement"),
296            (LogicalPlanNodeKind::Values, "values"),
297            (LogicalPlanNodeKind::Explain, "explain"),
298            (LogicalPlanNodeKind::Analyze, "analyze"),
299            (LogicalPlanNodeKind::Extension, "extension"),
300            (LogicalPlanNodeKind::Distinct, "distinct"),
301            (LogicalPlanNodeKind::Prepare, "prepare"),
302            (LogicalPlanNodeKind::Dml, "data manipulation"),
303            (LogicalPlanNodeKind::Ddl, "data definition"),
304            (LogicalPlanNodeKind::Copy, "copy"),
305            (LogicalPlanNodeKind::DescribeTable, "describe table"),
306            (LogicalPlanNodeKind::Unnest, "unnest"),
307            (LogicalPlanNodeKind::RecursiveQuery, "recursive query"),
308        ];
309
310        for (node, expected_label) in expected_labels {
311            assert_eq!(node.to_string(), expected_label);
312        }
313    }
314}