Skip to content

Add dialect_from_str and improve Dialect documentation #848

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 81 additions & 2 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,44 @@ pub use self::sqlite::SQLiteDialect;
pub use crate::keywords;
use crate::parser::{Parser, ParserError};

/// `dialect_of!(parser is SQLiteDialect | GenericDialect)` evaluates
/// to `true` if `parser.dialect` is one of the `Dialect`s specified.
/// `dialect_of!(parser Is SQLiteDialect | GenericDialect)` evaluates
/// to `true` if `parser.dialect` is one of the [`Dialect`]s specified.
///
///
macro_rules! dialect_of {
( $parsed_dialect: ident is $($dialect_type: ty)|+ ) => {
($($parsed_dialect.dialect.is::<$dialect_type>())||+)
};
}

/// Encapsulates the differences between SQL implementations.
///
/// # SQL Dialects
/// SQL implementations deviatiate from one another, either due to
/// custom extensions or various historical reasons. This trait
/// encapsulates the parsing differences between dialects.
///
/// # Examples
/// Most users create a [`Dialect`] directly, as shown on the [module
/// level documentation]:
///
/// ```
/// # use sqlparser::dialect::AnsiDialect;
/// let dialect = AnsiDialect {};
/// ```
///
/// It is also possible to dynamically create a [`Dialect`] from its
/// name. For example:
///
/// ```
/// # use sqlparser::dialect::{AnsiDialect, dialect_from_str};
/// let dialect = dialect_from_str("ansi").unwrap();
///
/// // Parsed dialect is an instance of `AnsiDialect`:
/// assert!(dialect.is::<AnsiDialect>());
/// ```
///
/// [module level documentation]: crate
pub trait Dialect: Debug + Any {
/// Determine if a character starts a quoted identifier. The default
/// implementation, accepting "double quoted" ids is both ANSI-compliant
Expand Down Expand Up @@ -113,6 +143,27 @@ impl dyn Dialect {
}
}

/// Returns the built in [`Dialect`] corresponding to `dialect_name`.
///
/// See [`Dialect`] documentation for an example.
pub fn dialect_from_str(dialect_name: impl AsRef<str>) -> Option<Box<dyn Dialect>> {
let dialect_name = dialect_name.as_ref();
match dialect_name.to_lowercase().as_str() {
"generic" => Some(Box::new(GenericDialect)),
"mysql" => Some(Box::new(MySqlDialect {})),
"postgresql" | "postgres" => Some(Box::new(PostgreSqlDialect {})),
"hive" => Some(Box::new(HiveDialect {})),
"sqlite" => Some(Box::new(SQLiteDialect {})),
"snowflake" => Some(Box::new(SnowflakeDialect)),
"redshift" => Some(Box::new(RedshiftSqlDialect {})),
"mssql" => Some(Box::new(MsSqlDialect {})),
"clickhouse" => Some(Box::new(ClickHouseDialect {})),
"bigquery" => Some(Box::new(BigQueryDialect)),
"ansi" => Some(Box::new(AnsiDialect {})),
_ => None,
}
}

#[cfg(test)]
mod tests {
use super::ansi::AnsiDialect;
Expand Down Expand Up @@ -141,4 +192,32 @@ mod tests {
assert!(dialect_of!(ansi_holder is GenericDialect | AnsiDialect));
assert!(!dialect_of!(ansi_holder is GenericDialect | MsSqlDialect));
}

#[test]
fn test_dialect_from_str() {
assert!(parse_dialect("generic").is::<GenericDialect>());
assert!(parse_dialect("mysql").is::<MySqlDialect>());
assert!(parse_dialect("MySql").is::<MySqlDialect>());
assert!(parse_dialect("postgresql").is::<PostgreSqlDialect>());
assert!(parse_dialect("postgres").is::<PostgreSqlDialect>());
assert!(parse_dialect("hive").is::<HiveDialect>());
assert!(parse_dialect("sqlite").is::<SQLiteDialect>());
assert!(parse_dialect("snowflake").is::<SnowflakeDialect>());
assert!(parse_dialect("SnowFlake").is::<SnowflakeDialect>());
assert!(parse_dialect("MsSql").is::<MsSqlDialect>());
assert!(parse_dialect("clickhouse").is::<ClickHouseDialect>());
assert!(parse_dialect("ClickHouse").is::<ClickHouseDialect>());
assert!(parse_dialect("bigquery").is::<BigQueryDialect>());
assert!(parse_dialect("BigQuery").is::<BigQueryDialect>());
assert!(parse_dialect("ansi").is::<AnsiDialect>());
assert!(parse_dialect("ANSI").is::<AnsiDialect>());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish there were a way for us to throw a compile error if one of the dialects isn't tested here (or alternatively, loop through a list of dialects and roundtrip them or something to make sure each one is represented).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that would be great.

The only thing I can think of is to make an enum or something with all the dialects 🤔


// error cases
assert!(dialect_from_str("Unknown").is_none());
assert!(dialect_from_str("").is_none());
}

fn parse_dialect(v: &str) -> Box<dyn Dialect> {
dialect_from_str(v).unwrap()
}
}
1 change: 1 addition & 0 deletions src/dialect/mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

use crate::dialect::Dialect;

// [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server/) dialect
#[derive(Debug)]
pub struct MsSqlDialect {}

Expand Down
1 change: 1 addition & 0 deletions src/dialect/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

use crate::dialect::Dialect;

/// [MySQL](https://www.mysql.com/)
#[derive(Debug)]
pub struct MySqlDialect {}

Expand Down
19 changes: 13 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//! SQL Parser for Rust
//! # SQL Parser for Rust
//!
//! This crate provides an ANSI:SQL 2011 lexer and parser that can parse SQL
//! into an Abstract Syntax Tree (AST). See the [sqlparser crates.io page]
//! into an Abstract Syntax Tree ([`AST`]). See the [sqlparser crates.io page]
//! for more information.
//!
//! See [`Parser::parse_sql`](crate::parser::Parser::parse_sql) and
//! [`Parser::new`](crate::parser::Parser::new) for the Parsing API
//! and the [`ast`](crate::ast) crate for the AST structure.
//! For more information:
//! 1. [`Parser::parse_sql`] and [`Parser::new`] for the Parsing API
//! 2. [`ast`] for the AST structure
//! 3. [`Dialect`] for supported SQL dialects
//!
//! Example:
//! # Example
//!
//! ```
//! use sqlparser::dialect::GenericDialect;
Expand All @@ -37,7 +38,13 @@
//!
//! println!("AST: {:?}", ast);
//! ```
//!
//! [sqlparser crates.io page]: https://crates.io/crates/sqlparser
//! [`Parser::parse_sql`]: crate::parser::Parser::parse_sql
//! [`Parser::new`]: crate::parser::Parser::new
//! [`AST`]: crate::ast
//! [`ast`]: crate::ast
//! [`Dialect`]: crate::dialect::Dialect

#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::upper_case_acronyms)]
Expand Down