| <!--- | |
| 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. | |
| --> | |
| # SQL Parser Derive Macro | |
| ## Visit | |
| This crate contains a procedural macro that can automatically derive | |
| implementations of the `Visit` trait in the [sqlparser](https://crates.io/crates/sqlparser) crate | |
| ```rust | |
| #[derive(Visit, VisitMut)] | |
| struct Foo { | |
| boolean: bool, | |
| bar: Bar, | |
| } | |
| #[derive(Visit, VisitMut)] | |
| enum Bar { | |
| A(), | |
| B(String, bool), | |
| C { named: i32 }, | |
| } | |
| ``` | |
| Will generate code akin to | |
| ```rust | |
| impl Visit for Foo { | |
| fn visit<V: Visitor>(&self, visitor: &mut V) -> ControlFlow<V::Break> { | |
| self.boolean.visit(visitor)?; | |
| self.bar.visit(visitor)?; | |
| ControlFlow::Continue(()) | |
| } | |
| } | |
| impl Visit for Bar { | |
| fn visit<V: Visitor>(&self, visitor: &mut V) -> ControlFlow<V::Break> { | |
| match self { | |
| Self::A() => {} | |
| Self::B(_1, _2) => { | |
| _1.visit(visitor)?; | |
| _2.visit(visitor)?; | |
| } | |
| Self::C { named } => { | |
| named.visit(visitor)?; | |
| } | |
| } | |
| ControlFlow::Continue(()) | |
| } | |
| } | |
| ``` | |
| Some types may wish to call a corresponding method on the visitor: | |
| ```rust | |
| #[derive(Visit, VisitMut)] | |
| #[visit(with = "visit_expr")] | |
| enum Expr { | |
| IsNull(Box<Expr>), | |
| .. | |
| } | |
| ``` | |
| This will result in the following sequence of visitor calls when an `IsNull` | |
| expression is visited | |
| ``` | |
| visitor.pre_visit_expr(<is null expr>) | |
| visitor.pre_visit_expr(<is null operand>) | |
| visitor.post_visit_expr(<is null operand>) | |
| visitor.post_visit_expr(<is null expr>) | |
| ``` | |
| For some types it is only appropriate to call a particular visitor method in | |
| some contexts. For example, not every `ObjectName` refers to a relation. | |
| In these cases, the `visit` attribute can be used on the field for which we'd | |
| like to call the method: | |
| ```rust | |
| #[derive(Visit, VisitMut)] | |
| #[visit(with = "visit_table_factor")] | |
| pub enum TableFactor { | |
| Table { | |
| #[visit(with = "visit_relation")] | |
| name: ObjectName, | |
| alias: Option<TableAlias>, | |
| }, | |
| .. | |
| } | |
| ``` | |
| This will generate | |
| ```rust | |
| impl Visit for TableFactor { | |
| fn visit<V: Visitor>(&self, visitor: &mut V) -> ControlFlow<V::Break> { | |
| visitor.pre_visit_table_factor(self)?; | |
| match self { | |
| Self::Table { name, alias } => { | |
| visitor.pre_visit_relation(name)?; | |
| name.visit(visitor)?; | |
| visitor.post_visit_relation(name)?; | |
| alias.visit(visitor)?; | |
| } | |
| } | |
| visitor.post_visit_table_factor(self)?; | |
| ControlFlow::Continue(()) | |
| } | |
| } | |
| ``` | |
| Note that annotating both the type and the field is incorrect as it will result | |
| in redundant calls to the method. For example | |
| ```rust | |
| #[derive(Visit, VisitMut)] | |
| #[visit(with = "visit_expr")] | |
| enum Expr { | |
| IsNull(#[visit(with = "visit_expr")] Box<Expr>), | |
| .. | |
| } | |
| ``` | |
| will result in these calls to the visitor | |
| ``` | |
| visitor.pre_visit_expr(<is null expr>) | |
| visitor.pre_visit_expr(<is null operand>) | |
| visitor.pre_visit_expr(<is null operand>) | |
| visitor.post_visit_expr(<is null operand>) | |
| visitor.post_visit_expr(<is null operand>) | |
| visitor.post_visit_expr(<is null expr>) | |
| ``` | |
| If the field is a `Option` and add `#[with = "visit_xxx"]` to the field, the generated code | |
| will try to access the field only if it is `Some`: | |
| ```rust | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub struct ShowStatementIn { | |
| pub clause: ShowStatementInClause, | |
| pub parent_type: Option<ShowStatementInParentType>, | |
| #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))] | |
| pub parent_name: Option<ObjectName>, | |
| } | |
| ``` | |
| This will generate | |
| ```rust | |
| impl sqlparser::ast::Visit for ShowStatementIn { | |
| fn visit<V: sqlparser::ast::Visitor>( | |
| &self, | |
| visitor: &mut V, | |
| ) -> ::std::ops::ControlFlow<V::Break> { | |
| sqlparser::ast::Visit::visit(&self.clause, visitor)?; | |
| sqlparser::ast::Visit::visit(&self.parent_type, visitor)?; | |
| if let Some(value) = &self.parent_name { | |
| visitor.pre_visit_relation(value)?; | |
| sqlparser::ast::Visit::visit(value, visitor)?; | |
| visitor.post_visit_relation(value)?; | |
| } | |
| ::std::ops::ControlFlow::Continue(()) | |
| } | |
| } | |
| impl sqlparser::ast::VisitMut for ShowStatementIn { | |
| fn visit<V: sqlparser::ast::VisitorMut>( | |
| &mut self, | |
| visitor: &mut V, | |
| ) -> ::std::ops::ControlFlow<V::Break> { | |
| sqlparser::ast::VisitMut::visit(&mut self.clause, visitor)?; | |
| sqlparser::ast::VisitMut::visit(&mut self.parent_type, visitor)?; | |
| if let Some(value) = &mut self.parent_name { | |
| visitor.pre_visit_relation(value)?; | |
| sqlparser::ast::VisitMut::visit(value, visitor)?; | |
| visitor.post_visit_relation(value)?; | |
| } | |
| ::std::ops::ControlFlow::Continue(()) | |
| } | |
| } | |
| ``` | |
| ## Releasing | |
| This crate's release is not automated. Instead it is released manually as needed | |
| Steps: | |
| 1. Update the version in `Cargo.toml` | |
| 2. Update the corresponding version in `../Cargo.toml` | |
| 3. Commit via PR | |
| 4. Publish to crates.io: | |
| ```shell | |
| # update to latest checked in main branch and publish via | |
| cargo publish | |
| ``` | |