7 unstable releases

new 0.4.0 Dec 7, 2025
0.3.0 Dec 18, 2024
0.2.2 Jan 3, 2024
0.2.1 Nov 27, 2023
0.1.0 Dec 28, 2022

#17 in #ansi-sql

Download history 327760/week @ 2025-08-16 303738/week @ 2025-08-23 302248/week @ 2025-08-30 317527/week @ 2025-09-06 307697/week @ 2025-09-13 304618/week @ 2025-09-20 322413/week @ 2025-09-27 339188/week @ 2025-10-04 357673/week @ 2025-10-11 329892/week @ 2025-10-18 375743/week @ 2025-10-25 359598/week @ 2025-11-01 375529/week @ 2025-11-08 414629/week @ 2025-11-15 297751/week @ 2025-11-22 251944/week @ 2025-11-29

1,411,700 downloads per month
Used in 328 crates (5 directly)

Apache-2.0

20KB
241 lines

SQL Parser Derive Macro

Visit

This crate contains a procedural macro that can automatically derive implementations of the Visit trait in the sqlparser crate

#[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

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:

#[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:

#[derive(Visit, VisitMut)]
#[visit(with = "visit_table_factor")]
pub enum TableFactor {
    Table {
        #[visit(with = "visit_relation")]
        name: ObjectName,
        alias: Option<TableAlias>,
    },
    ..
}

This will generate

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

#[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:

#[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

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:
# update to latest checked in main branch and publish via
cargo publish 

Dependencies

~160–570KB
~14K SLoC