├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── HEADER ├── LICENSE.TXT ├── README.md ├── docs ├── benchmarking.md ├── custom_sql_parser.md ├── fuzzing.md └── releasing.md ├── examples ├── cli.rs └── parse_select.rs ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ └── fuzz_parse_sql.rs ├── rust-toolchain ├── rustfmt.toml ├── sqlparser_bench ├── Cargo.toml └── benches │ └── sqlparser_bench.rs ├── src ├── ast │ ├── data_type.rs │ ├── ddl.rs │ ├── mod.rs │ ├── operator.rs │ ├── query.rs │ └── value.rs ├── dialect │ ├── ansi.rs │ ├── clickhouse.rs │ ├── generic.rs │ ├── hive.rs │ ├── mod.rs │ ├── mssql.rs │ ├── mysql.rs │ ├── postgresql.rs │ ├── snowflake.rs │ └── sqlite.rs ├── keywords.rs ├── lib.rs ├── parser.rs ├── test_utils.rs └── tokenizer.rs └── tests ├── queries └── tpch │ ├── 1.sql │ ├── 10.sql │ ├── 11.sql │ ├── 12.sql │ ├── 13.sql │ ├── 14.sql │ ├── 15.sql │ ├── 16.sql │ ├── 17.sql │ ├── 18.sql │ ├── 19.sql │ ├── 2.sql │ ├── 20.sql │ ├── 21.sql │ ├── 22.sql │ ├── 3.sql │ ├── 4.sql │ ├── 5.sql │ ├── 6.sql │ ├── 7.sql │ ├── 8.sql │ └── 9.sql ├── sqlparser_common.rs ├── sqlparser_hive.rs ├── sqlparser_mssql.rs ├── sqlparser_mysql.rs ├── sqlparser_postgres.rs ├── sqlparser_regression.rs ├── sqlparser_snowflake.rs ├── sqlparser_sqlite.rs ├── sqpparser_clickhouse.rs └── test_utils └── mod.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: cargo 9 | directory: "/sqlparser_bench" 10 | schedule: 11 | interval: daily 12 | open-pull-requests-limit: 10 13 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | codestyle: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Set up Rust 11 | uses: hecrj/setup-rust-action@v1 12 | with: 13 | components: rustfmt 14 | # Note that `nightly` is required for `license_template_path`, as 15 | # it's an unstable feature. 16 | rust-version: nightly 17 | - uses: actions/checkout@v2 18 | - run: cargo +nightly fmt -- --check --config-path <(echo 'license_template_path = "HEADER"') 19 | 20 | lint: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Set up Rust 24 | uses: hecrj/setup-rust-action@v1 25 | with: 26 | components: clippy 27 | - uses: actions/checkout@v2 28 | - run: cargo clippy --all-targets --all-features -- -D warnings 29 | 30 | compile: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Set up Rust 34 | uses: hecrj/setup-rust-action@v1 35 | - uses: actions/checkout@master 36 | - run: cargo check --all-targets --all-features 37 | 38 | compile-no-std: 39 | runs-on: ubuntu-latest 40 | steps: 41 | - name: Set up Rust 42 | uses: hecrj/setup-rust-action@v1 43 | with: 44 | targets: 'thumbv6m-none-eabi' 45 | - uses: actions/checkout@master 46 | - run: cargo check --no-default-features --target thumbv6m-none-eabi 47 | 48 | test: 49 | strategy: 50 | matrix: 51 | rust: [stable, beta, nightly] 52 | runs-on: ubuntu-latest 53 | steps: 54 | - name: Setup Rust 55 | uses: hecrj/setup-rust-action@v1 56 | with: 57 | rust-version: ${{ matrix.rust }} 58 | - name: Install Tarpaulin 59 | uses: actions-rs/install@v0.1 60 | with: 61 | crate: cargo-tarpaulin 62 | version: 0.14.2 63 | use-tool-cache: true 64 | - name: Checkout 65 | uses: actions/checkout@v2 66 | - name: Test 67 | run: cargo test --all-features 68 | - name: Coverage 69 | if: matrix.rust == 'stable' 70 | run: cargo tarpaulin -o Lcov --output-dir ./coverage 71 | - name: Coveralls 72 | if: matrix.rust == 'stable' 73 | uses: coverallsapp/github-action@master 74 | with: 75 | github-token: ${{ secrets.GITHUB_TOKEN }} 76 | 77 | publish-crate: 78 | if: startsWith(github.ref, 'refs/tags/v0') 79 | runs-on: ubuntu-latest 80 | needs: [test] 81 | steps: 82 | - name: Set up Rust 83 | uses: hecrj/setup-rust-action@v1 84 | - uses: actions/checkout@v2 85 | - name: Publish 86 | shell: bash 87 | run: | 88 | cargo publish --token ${{ secrets.CRATES_TOKEN }} 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | /sqlparser_bench/target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # IDEs 14 | .idea 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | sudo: required 3 | cache: cargo 4 | language: rust 5 | addons: 6 | apt: 7 | packages: 8 | - kcov 9 | - libcurl4-openssl-dev 10 | - libelf-dev 11 | - libdw-dev 12 | - binutils-dev 13 | - cmake 14 | sources: 15 | - kalakris-cmake 16 | # The version of kcov shipped with Xenial (v25) doesn't support the 17 | # --verify option that `cargo coveralls` passes. This PPA has a more 18 | # up-to-date version. It can be removed if Ubuntu ever ships a newer 19 | # version, or replaced with another PPA if this one falls out of date. 20 | - sourceline: ppa:sivakov512/kcov 21 | 22 | rust: 23 | - stable 24 | 25 | before_script: 26 | # Travis installs rust with a non-default "minimal" profile, and advises us 27 | # to add clippy manually: 28 | - rustup component add clippy 29 | # Unfortunately, this method often breaks on the nightly channel, where the 30 | # most recent build might not have all the optional components. 31 | # We explicitly specify `--profile default` to obtain the most recent nightly 32 | # that has rustfmt (we don't care if it's a week old, as we need it only for 33 | # an experimental flag): 34 | - rustup toolchain install nightly --profile default 35 | 36 | - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH 37 | - export PATH=$HOME/.cargo/bin:$PATH 38 | # `cargo install` fails if the specified binary is already installed, and 39 | # doesn't yet support a `--if-not-installed` option [0], so for now assume 40 | # failures mean the package is already installed. If installation truly 41 | # failed, the build will fail later anyway, when we try to use the installed 42 | # binary. Note that `cargo install --force` is not a solution, as it always 43 | # rebuilds from scratch, ignoring the cache entirely. 44 | # 45 | # [0]: https://github.com/rust-lang/cargo/issues/2082 46 | - cargo install cargo-update || echo "cargo-update already installed" # for `cargo install-update` 47 | - cargo install cargo-travis || echo "cargo-travis already installed" # for `cargo coveralls` 48 | - cargo install-update -a # updates cargo-travis, if the cached version is outdated 49 | 50 | script: 51 | # Clippy must be run first, as its lints are only triggered during 52 | # compilation. Put another way: after a successful `cargo build`, `cargo 53 | # clippy` is guaranteed to produce no results. This bug is known upstream: 54 | # https://github.com/rust-lang/rust-clippy/issues/2604. 55 | - travis-cargo clippy -- --all-targets --all-features -- -D warnings 56 | - travis-cargo build 57 | - travis-cargo test 58 | - travis-cargo test -- all-features 59 | # The license_template_path setting we use to verify copyright headers is 60 | # only available on the nightly rustfmt. 61 | - cargo +nightly fmt -- --check --config-path <(echo 'license_template_path = "HEADER"') 62 | 63 | after_success: 64 | - cargo coveralls --verbose 65 | 66 | env: 67 | global: 68 | - TRAVIS_CARGO_NIGHTLY_FEATURE="" 69 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project aims to adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 5 | 6 | Given that the parser produces a typed AST, any changes to the AST will technically be breaking and thus will result in a `0.(N+1)` version. We document changes that break via addition as "Added". 7 | 8 | ## [Unreleased] 9 | Check https://github.com/sqlparser-rs/sqlparser-rs/commits/main for undocumented changes. 10 | 11 | 12 | ## [0.13.0] 2020-12-10 13 | 14 | ### Added 15 | * Add ALTER TABLE CHANGE COLUMN, extend the UPDATE statement with ON clause (#375) - Thanks @0xA537FD! 16 | * Add support for GROUPIING SETS, ROLLUP and CUBE - Thanks @Jimexist! 17 | * Add basic support for GRANT and REVOKE (#365) - Thanks @blx! 18 | 19 | ### Changed 20 | * Use Rust 2021 edition (#368) - Thanks @Jimexist! 21 | 22 | ### Fixed 23 | * Fix clippy errors (#367, #374) - Thanks @Jimexist! 24 | 25 | 26 | ## [0.12.0] 2020-10-14 27 | 28 | ### Added 29 | * Add support for [NOT] IS DISTINCT FROM (#306) - @Dandandan 30 | 31 | ### Changed 32 | * Move the keywords module - Thanks @koushiro! 33 | 34 | 35 | ## [0.11.0] 2020-09-24 36 | 37 | ### Added 38 | * Support minimum display width for integer data types (#337) Thanks @vasilev-alex! 39 | * Add logical XOR operator (#357) - Thanks @xzmrdltl! 40 | * Support DESCRIBE table_name (#340) - Thanks @ovr! 41 | * Support SHOW CREATE TABLE|EVENT|FUNCTION (#338) - Thanks @ovr! 42 | * Add referential actions to TableConstraint foreign key (#306) - Thanks @joshwd36! 43 | 44 | ### Changed 45 | * Enable map access for numbers, multiple nesting levels (#356) - Thanks @Igosuki! 46 | * Rename Token::Mult to Token::Mul (#353) - Thanks @koushiro! 47 | * Use derive(Default) for HiveFormat (#348) - Thanks @koushiro! 48 | * Improve tokenizer error (#347) - Thanks @koushiro! 49 | * Eliminate redundant string copy in Tokenizer (#343) - Thanks @koushiro! 50 | * Update bigdecimal requirement from 0.2 to 0.3 dependencies (#341) 51 | * Support parsing hexadecimal literals that start with `0x` (#324) - Thanks @TheSchemm! 52 | 53 | 54 | ## [0.10.0] 2020-08-23 55 | 56 | ### Added 57 | * Support for `no_std` (#332) - Thanks @koushiro! 58 | * Postgres regular expression operators (`~`, `~*`, `!~`, `!~*`) (#328) - Thanks @b41sh! 59 | * tinyint (#320) - Thanks @sundy-li 60 | * ILIKE (#300) - Thanks @maxcountryman! 61 | * TRIM syntax (#331, #334) - Thanks ever0de 62 | 63 | 64 | ### Fixed 65 | * Return error instead of panic (#316) - Thanks @BohuTANG! 66 | 67 | ### Changed 68 | - Rename `Modulus` to `Modulo` (#335) - Thanks @RGRAVITY817! 69 | - Update links to reflect repository move to `sqlparser-rs` GitHub org (#333) - Thanks @andygrove 70 | - Add default value for `WindowFrame` (#313) - Thanks @Jimexist! 71 | 72 | ## [0.9.0] 2020-03-21 73 | 74 | ### Added 75 | * Add support for `TRY_CAST` syntax (#299) - Thanks @seddonm1! 76 | 77 | ## [0.8.0] 2020-02-20 78 | 79 | ### Added 80 | * Introduce Hive QL dialect `HiveDialect` and syntax (#235) - Thanks @hntd187! 81 | * Add `SUBSTRING(col [FROM ] [FOR ])` syntax (#293) 82 | * Support parsing floats without leading digits `.01` (#294) 83 | * Support parsing multiple show variables (#290) - Thanks @francis-du! 84 | * Support SQLite `INSERT OR [..]` syntax (#281) - Thanks @zhangli-pear! 85 | 86 | ## [0.7.0] 2020-12-28 87 | 88 | ### Changed 89 | - Change the MySQL dialect to support `` `identifiers` `` quoted with backticks instead of the standard `"double-quoted"` identifiers (#247) - thanks @mashuai! 90 | - Update bigdecimal requirement from 0.1 to 0.2 (#268) 91 | 92 | ### Added 93 | - Enable dialect-specific behaviours in the parser (`dialect_of!()`) (#254) - thanks @eyalleshem! 94 | - Support named arguments in function invocations (`ARG_NAME => val`) (#250) - thanks @eyalleshem! 95 | - Support `TABLE()` functions in `FROM` (#253) - thanks @eyalleshem! 96 | - Support Snowflake's single-line comments starting with '#' or '//' (#264) - thanks @eyalleshem! 97 | - Support PostgreSQL `PREPARE`, `EXECUTE`, and `DEALLOCATE` (#243) - thanks @silathdiir! 98 | - Support PostgreSQL math operators (#267) - thanks @alex-dukhno! 99 | - Add SQLite dialect (#248) - thanks @mashuai! 100 | - Add Snowflake dialect (#259) - thanks @eyalleshem! 101 | - Support for Recursive CTEs - thanks @rhanqtl! 102 | - Support `FROM (table_name) alias` syntax - thanks @eyalleshem! 103 | - Support for `EXPLAIN [ANALYZE] VERBOSE` - thanks @ovr! 104 | - Support `ANALYZE TABLE` 105 | - DDL: 106 | - Support `OR REPLACE` in `CREATE VIEW`/`TABLE` (#239) - thanks @Dandandan! 107 | - Support specifying `ASC`/`DESC` in index columns (#249) - thanks @mashuai! 108 | - Support SQLite `AUTOINCREMENT` and MySQL `AUTO_INCREMENT` column option in `CREATE TABLE` (#234) - thanks @mashuai! 109 | - Support PostgreSQL `IF NOT EXISTS` for `CREATE SCHEMA` (#276) - thanks @alex-dukhno! 110 | 111 | ### Fixed 112 | - Fix a typo in `JSONFILE` serialization, introduced in 0.3.1 (#237) 113 | - Change `CREATE INDEX` serialization to not end with a semicolon, introduced in 0.5.1 (#245) 114 | - Don't fail parsing `ALTER TABLE ADD COLUMN` ending with a semicolon, introduced in 0.5.1 (#246) - thanks @mashuai 115 | 116 | ## [0.6.1] - 2020-07-20 117 | 118 | ### Added 119 | - Support BigQuery `ASSERT` statement (#226) 120 | 121 | ## [0.6.0] - 2020-07-20 122 | 123 | ### Added 124 | - Support SQLite's `CREATE TABLE (...) WITHOUT ROWID` (#208) - thanks @mashuai! 125 | - Support SQLite's `CREATE VIRTUAL TABLE` (#209) - thanks @mashuai! 126 | 127 | ## [0.5.1] - 2020-06-26 128 | This release should have been called `0.6`, as it introduces multiple incompatible changes to the API. If you don't want to upgrade yet, you can revert to the previous version by changing your `Cargo.toml` to: 129 | 130 | sqlparser = "= 0.5.0" 131 | 132 | 133 | ### Changed 134 | - **`Parser::parse_sql` now accepts a `&str` instead of `String` (#182)** - thanks @Dandandan! 135 | - Change `Ident` (previously a simple `String`) to store the parsed (unquoted) `value` of the identifier and the `quote_style` separately (#143) - thanks @apparebit! 136 | - Support Snowflake's `FROM (table_name)` (#155) - thanks @eyalleshem! 137 | - Add line and column number to TokenizerError (#194) - thanks @Dandandan! 138 | - Use Token::EOF instead of Option (#195) 139 | - Make the units keyword following `INTERVAL '...'` optional (#184) - thanks @maxcountryman! 140 | - Generalize `DATE`/`TIME`/`TIMESTAMP` literals representation in the AST (`TypedString { data_type, value }`) and allow `DATE` and other keywords to be used as identifiers when not followed by a string (#187) - thanks @maxcountryman! 141 | - Output DataType capitalized (`fmt::Display`) (#202) - thanks @Dandandan! 142 | 143 | ### Added 144 | - Support MSSQL `TOP () [ PERCENT ] [ WITH TIES ]` (#150) - thanks @alexkyllo! 145 | - Support MySQL `LIMIT row_count OFFSET offset` (not followed by `ROW` or `ROWS`) and remember which variant was parsed (#158) - thanks @mjibson! 146 | - Support PostgreSQL `CREATE TABLE IF NOT EXISTS table_name` (#163) - thanks @alex-dukhno! 147 | - Support basic forms of `CREATE INDEX` and `DROP INDEX` (#167) - thanks @mashuai! 148 | - Support `ON { UPDATE | DELETE } { RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT }` in `FOREIGN KEY` constraints (#170) - thanks @c7hm4r! 149 | - Support basic forms of `CREATE SCHEMA` and `DROP SCHEMA` (#173) - thanks @alex-dukhno! 150 | - Support `NULLS FIRST`/`LAST` in `ORDER BY` expressions (#176) - thanks @houqp! 151 | - Support `LISTAGG()` (#174) - thanks @maxcountryman! 152 | - Support the string concatentation operator `||` (#178) - thanks @Dandandan! 153 | - Support bitwise AND (`&`), OR (`|`), XOR (`^`) (#181) - thanks @Dandandan! 154 | - Add serde support to AST structs and enums (#196) - thanks @panarch! 155 | - Support `ALTER TABLE ADD COLUMN`, `RENAME COLUMN`, and `RENAME TO` (#203) - thanks @mashuai! 156 | - Support `ALTER TABLE DROP COLUMN` (#148) - thanks @ivanceras! 157 | - Support `CREATE TABLE ... AS ...` (#206) - thanks @Dandandan! 158 | 159 | ### Fixed 160 | - Report an error for unterminated string literals (#165) 161 | - Make file format (`STORED AS`) case insensitive (#200) and don't allow quoting it (#201) - thanks @Dandandan! 162 | 163 | ## [0.5.0] - 2019-10-10 164 | 165 | ### Changed 166 | - Replace the `Value::Long(u64)` and `Value::Double(f64)` variants with `Value::Number(String)` to avoid losing precision when parsing decimal literals (#130) - thanks @benesch! 167 | - `--features bigdecimal` can be enabled to work with `Value::Number(BigDecimal)` instead, at the cost of an additional dependency. 168 | 169 | ### Added 170 | - Support MySQL `SHOW COLUMNS`, `SET =`, and `SHOW ` statements (#135) - thanks @quodlibetor and @benesch! 171 | 172 | ### Fixed 173 | - Don't fail to parse `START TRANSACTION` followed by a semicolon (#139) - thanks @gaffneyk! 174 | 175 | 176 | ## [0.4.0] - 2019-07-02 177 | This release brings us closer to SQL-92 support, mainly thanks to the improvements contributed back from @MaterializeInc's fork and other work by @benesch. 178 | 179 | ### Changed 180 | - Remove "SQL" from type and enum variant names, `SQLType` -> `DataType`, remove "sql" prefix from module names (#105, #122) 181 | - Rename `ASTNode` -> `Expr` (#119) 182 | - Improve consistency of binary/unary op nodes (#112): 183 | - `ASTNode::SQLBinaryExpr` is now `Expr::BinaryOp` and `ASTNode::SQLUnary` is `Expr::UnaryOp`; 184 | - The `op: SQLOperator` field is now either a `BinaryOperator` or an `UnaryOperator`. 185 | - Change the representation of JOINs to match the standard (#109): `SQLSelect`'s `relation` and `joins` are replaced with `from: Vec`. Before this change `FROM foo NATURAL JOIN bar, baz` was represented as "foo" as the `relation` followed by two joins (`Inner(Natural)` and `Implicit`); now it's two `TableWithJoins` (`foo NATURAL JOIN bar` and `baz`). 186 | - Extract a `SQLFunction` struct (#89) 187 | - Replace `Option>` with `Vec` in the AST structs (#73) 188 | - Change `Value::Long()` to be unsigned, use u64 consistently (#65) 189 | 190 | ### Added 191 | - Infra: 192 | - Implement `fmt::Display` on AST nodes (#124) - thanks @vemoo! 193 | - Implement `Hash` (#88) and `Eq` (#123) on all AST nodes 194 | - Implement `std::error::Error` for `ParserError` (#72) 195 | - Handle Windows line-breaks (#54) 196 | - Expressions: 197 | - Support `INTERVAL` literals (#103) 198 | - Support `DATE` / `TIME` / `TIMESTAMP` literals (#99) 199 | - Support `EXTRACT` (#96) 200 | - Support `X'hex value'` literals (#95) 201 | - Support `EXISTS` subqueries (#90) 202 | - Support nested expressions in `BETWEEN` (#80) 203 | - Support `COUNT(DISTINCT x)` and similar (#77) 204 | - Support `CASE operand WHEN expected_value THEN ..` and table-valued functions (#59) 205 | - Support analytic (window) functions (`OVER` clause) (#50) 206 | - Queries / DML: 207 | - Support nested joins (#100) and derived tables with set operations (#111) 208 | - Support `UPDATE` statements (#97) 209 | - Support `INSERT INTO foo SELECT * FROM bar` and `FROM VALUES (...)` (#91) 210 | - Support `SELECT ALL` (#76) 211 | - Add `FETCH` and `OFFSET` support, and `LATERAL` (#69) - thanks @thomas-jeepe! 212 | - Support `COLLATE`, optional column list in CTEs (#64) 213 | - DDL/TCL: 214 | - Support `START/SET/COMMIT/ROLLBACK TRANSACTION` (#106) - thanks @SamuelMarks! 215 | - Parse column constraints in any order (#93) 216 | - Parse `DECIMAL` and `DEC` aliases for `NUMERIC` type (#92) 217 | - Support `DROP [TABLE|VIEW]` (#75) 218 | - Support arbitrary `WITH` options for `CREATE [TABLE|VIEW]` (#74) 219 | - Support constraints in `CREATE TABLE` (#65) 220 | - Add basic MSSQL dialect (#61) and some MSSQL-specific features: 221 | - `CROSS`/`OUTER APPLY` (#120) 222 | - MSSQL identifier and alias parsing rules (#66) 223 | - `WITH` hints (#59) 224 | 225 | ### Fixed 226 | - Report an error for `SELECT * FROM a OUTER JOIN b` instead of parsing `OUTER` as an alias (#118) 227 | - Fix the precedence of `NOT LIKE` (#82) and unary `NOT` (#107) 228 | - Do not panic when `NOT` is not followed by an expected keyword (#71) 229 | successfully instead of returning a parse error - thanks @ivanceras! (#67) - and similar fixes for queries with no `FROM` (#116) 230 | - Fix issues with `ALTER TABLE ADD CONSTRAINT` parsing (#65) 231 | - Serialize the "not equals" operator as `<>` instead of `!=` (#64) 232 | - Remove dependencies on `uuid` (#59) and `chrono` (#61) 233 | - Make `SELECT` query with `LIMIT` clause but no `WHERE` parse - Fix incorrect behavior of `ASTNode::SQLQualifiedWildcard::to_string()` (returned `foo*` instead of `foo.*`) - thanks @thomas-jeepe! (#52) 234 | 235 | ## [0.3.1] - 2019-04-20 236 | ### Added 237 | - Extended `SQLStatement::SQLCreateTable` to support Hive's EXTERNAL TABLES (`CREATE EXTERNAL TABLE .. STORED AS .. LOCATION '..'`) - thanks @zhzy0077! (#46) 238 | - Parse `SELECT DISTINCT` to `SQLSelect::distinct` (#49) 239 | 240 | ## [0.3.0] - 2019-04-03 241 | ### Changed 242 | This release includes major changes to the AST structs to add a number of features, as described in #37 and #43. In particular: 243 | - `ASTNode` variants that represent statements were extracted from `ASTNode` into a separate `SQLStatement` enum; 244 | - `Parser::parse_sql` now returns a `Vec` of parsed statements. 245 | - `ASTNode` now represents an expression (renamed to `Expr` in 0.4.0) 246 | - The query representation (formerly `ASTNode::SQLSelect`) became more complicated to support: 247 | - `WITH` and `UNION`/`EXCEPT`/`INTERSECT` (via `SQLQuery`, `Cte`, and `SQLSetExpr`), 248 | - aliases and qualified wildcards in `SELECT` (via `SQLSelectItem`), 249 | - and aliases in `FROM`/`JOIN` (via `TableFactor`). 250 | - A new `SQLObjectName` struct is used instead of `String` or `ASTNode::SQLCompoundIdentifier` - for objects like tables, custom types, etc. 251 | - Added support for "delimited identifiers" and made keywords context-specific (thus accepting them as valid identifiers in most contexts) - **this caused a regression in parsing `SELECT .. FROM .. LIMIT ..` (#67), fixed in 0.4.0** 252 | 253 | ### Added 254 | Other than the changes listed above, some less intrusive additions include: 255 | - Support `CREATE [MATERIALIZED] VIEW` statement 256 | - Support `IN`, `BETWEEN`, unary +/- in epressions 257 | - Support `CHAR` data type and `NUMERIC` not followed by `(p,s)`. 258 | - Support national string literals (`N'...'`) 259 | 260 | ## [0.2.4] - 2019-03-08 261 | Same as 0.2.2. 262 | 263 | ## [0.2.3] - 2019-03-08 [YANKED] 264 | 265 | ## [0.2.2] - 2019-03-08 266 | ### Changed 267 | - Removed `Value::String`, `Value::DoubleQuotedString`, and `Token::String`, making 268 | - `'...'` parse as a string literal (`Value::SingleQuotedString`), and 269 | - `"..."` fail to parse until version 0.3.0 (#36) 270 | 271 | ## [0.2.1] - 2019-01-13 272 | We don't have a changelog for the changes made in 2018, but thanks to @crw5996, @cswinter, @fredrikroos, @ivanceras, @nickolay, @virattara for their contributions in the early stages of the project! 273 | 274 | ## [0.1.0] - 2018-09-03 275 | Initial release 276 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlparser" 3 | description = "Extensible SQL Lexer and Parser with support for ANSI SQL:2011" 4 | version = "0.13.1-alpha.0" 5 | authors = ["Andy Grove "] 6 | homepage = "https://github.com/sqlparser-rs/sqlparser-rs" 7 | documentation = "https://docs.rs/sqlparser/" 8 | keywords = [ "ansi", "sql", "lexer", "parser" ] 9 | repository = "https://github.com/sqlparser-rs/sqlparser-rs" 10 | license = "Apache-2.0" 11 | include = [ 12 | "src/**/*.rs", 13 | "Cargo.toml", 14 | ] 15 | edition = "2021" 16 | 17 | [lib] 18 | name = "sqlparser" 19 | path = "src/lib.rs" 20 | 21 | [features] 22 | default = ["std"] 23 | std = [] 24 | # Enable JSON output in the `cli` example: 25 | json_example = ["serde_json", "serde"] 26 | 27 | [dependencies] 28 | bigdecimal = { version = "0.3", features = ["serde"], optional = true } 29 | log = "0.4" 30 | serde = { version = "1.0", features = ["derive"], optional = true } 31 | # serde_json is only used in examples/cli, but we have to put it outside 32 | # of dev-dependencies because of 33 | # https://github.com/rust-lang/cargo/issues/1596 34 | serde_json = { version = "1.0", optional = true } 35 | 36 | [dev-dependencies] 37 | simple_logger = "1.9" 38 | matches = "0.1" 39 | 40 | [package.metadata.release] 41 | # Instruct `cargo release` to not run `cargo publish` locally: 42 | # https://github.com/sunng87/cargo-release/blob/master/docs/reference.md#config-fields 43 | # See docs/releasing.md for details. 44 | disable-publish = true 45 | -------------------------------------------------------------------------------- /HEADER: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Extensible SQL Lexer and Parser for Rust 2 | 3 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 4 | [![Version](https://img.shields.io/crates/v/sqlparser.svg)](https://crates.io/crates/sqlparser) 5 | [![Build Status](https://github.com/sqlparser-rs/sqlparser-rs/workflows/Rust/badge.svg?branch=main)](https://github.com/sqlparser-rs/sqlparser-rs/actions?query=workflow%3ARust+branch%3Amain) 6 | [![Coverage Status](https://coveralls.io/repos/github/sqlparser-rs/sqlparser-rs/badge.svg?branch=main)](https://coveralls.io/github/sqlparser-rs/sqlparser-rs?branch=main) 7 | [![Gitter Chat](https://badges.gitter.im/sqlparser-rs/community.svg)](https://gitter.im/sqlparser-rs/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 8 | 9 | The goal of this project is to build a SQL lexer and parser capable of parsing 10 | SQL that conforms with the [ANSI/ISO SQL standard][sql-standard] while also 11 | making it easy to support custom dialects so that this crate can be used as a 12 | foundation for vendor-specific parsers. 13 | 14 | This parser is currently being used by the [DataFusion] query engine, 15 | [LocustDB], [Ballista] and [GlueSQL]. 16 | 17 | ## Example 18 | 19 | To parse a simple `SELECT` statement: 20 | 21 | ```rust 22 | use sqlparser::dialect::GenericDialect; 23 | use sqlparser::parser::Parser; 24 | 25 | let sql = "SELECT a, b, 123, myfunc(b) \ 26 | FROM table_1 \ 27 | WHERE a > b AND b < 100 \ 28 | ORDER BY a DESC, b"; 29 | 30 | let dialect = GenericDialect {}; // or AnsiDialect, or your own dialect ... 31 | 32 | let ast = Parser::parse_sql(&dialect, sql).unwrap(); 33 | 34 | println!("AST: {:?}", ast); 35 | ``` 36 | 37 | This outputs 38 | 39 | ```rust 40 | AST: [Query(Query { ctes: [], body: Select(Select { distinct: false, projection: [UnnamedExpr(Identifier("a")), UnnamedExpr(Identifier("b")), UnnamedExpr(Value(Long(123))), UnnamedExpr(Function(Function { name: ObjectName(["myfunc"]), args: [Identifier("b")], over: None, distinct: false }))], from: [TableWithJoins { relation: Table { name: ObjectName(["table_1"]), alias: None, args: [], with_hints: [] }, joins: [] }], selection: Some(BinaryOp { left: BinaryOp { left: Identifier("a"), op: Gt, right: Identifier("b") }, op: And, right: BinaryOp { left: Identifier("b"), op: Lt, right: Value(Long(100)) } }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier("a"), asc: Some(false) }, OrderByExpr { expr: Identifier("b"), asc: None }], limit: None, offset: None, fetch: None })] 41 | ``` 42 | 43 | ## Command line 44 | To parse a file and dump the results as JSON: 45 | ``` 46 | $ cargo run --features json_example --example cli FILENAME.sql [--dialectname] 47 | ``` 48 | 49 | ## SQL compliance 50 | 51 | SQL was first standardized in 1987, and revisions of the standard have been 52 | published regularly since. Most revisions have added significant new features to 53 | the language, and as a result no database claims to support the full breadth of 54 | features. This parser currently supports most of the SQL-92 syntax, plus some 55 | syntax from newer versions that have been explicitly requested, plus some MSSQL, 56 | PostgreSQL, and other dialect-specific syntax. Whenever possible, the [online 57 | SQL:2016 grammar][sql-2016-grammar] is used to guide what syntax to accept. 58 | 59 | Unfortunately, stating anything more specific about compliance is difficult. 60 | There is no publicly available test suite that can assess compliance 61 | automatically, and doing so manually would strain the project's limited 62 | resources. Still, we are interested in eventually supporting the full SQL 63 | dialect, and we are slowly building out our own test suite. 64 | 65 | If you are assessing whether this project will be suitable for your needs, 66 | you'll likely need to experimentally verify whether it supports the subset of 67 | SQL that you need. Please file issues about any unsupported queries that you 68 | discover. Doing so helps us prioritize support for the portions of the standard 69 | that are actually used. Note that if you urgently need support for a feature, 70 | you will likely need to write the implementation yourself. See the 71 | [Contributing](#Contributing) section for details. 72 | 73 | ### Supporting custom SQL dialects 74 | 75 | This is a work in progress, but we have some notes on [writing a custom SQL 76 | parser](docs/custom_sql_parser.md). 77 | 78 | ## Design 79 | 80 | The core expression parser uses the [Pratt Parser] design, which is a top-down 81 | operator-precedence (TDOP) parser, while the surrounding SQL statement parser is 82 | a traditional, hand-written recursive descent parser. Eli Bendersky has a good 83 | [tutorial on TDOP parsers][tdop-tutorial], if you are interested in learning 84 | more about the technique. 85 | 86 | We are a fan of this design pattern over parser generators for the following 87 | reasons: 88 | 89 | - Code is simple to write and can be concise and elegant 90 | - Performance is generally better than code generated by parser generators 91 | - Debugging is much easier with hand-written code 92 | - It is far easier to extend and make dialect-specific extensions 93 | compared to using a parser generator 94 | 95 | ## Contributing 96 | 97 | Contributions are highly encouraged! 98 | 99 | Pull requests that add support for or fix a bug in a feature in the SQL 100 | standard, or a feature in a popular RDBMS, like Microsoft SQL Server or 101 | PostgreSQL, will almost certainly be accepted after a brief review. For 102 | particularly large or invasive changes, consider opening an issue first, 103 | especially if you are a first time contributor, so that you can coordinate with 104 | the maintainers. CI will ensure that your code passes `cargo test`, 105 | `cargo fmt`, and `cargo clippy`, so you will likely want to run all three 106 | commands locally before submitting your PR. 107 | 108 | If you are unable to submit a patch, feel free to file an issue instead. Please 109 | try to include: 110 | 111 | * some representative examples of the syntax you wish to support or fix; 112 | * the relevant bits of the [SQL grammar][sql-2016-grammar], if the syntax is 113 | part of SQL:2016; and 114 | * links to documentation for the feature for a few of the most popular 115 | databases that support it. 116 | 117 | Please be aware that, while we strive to address bugs and review PRs quickly, we 118 | make no such guarantees for feature requests. If you need support for a feature, 119 | you will likely need to implement it yourself. Our goal as maintainers is to 120 | facilitate the integration of various features from various contributors, but 121 | not to provide the implementations ourselves, as we simply don't have the 122 | resources. 123 | 124 | [tdop-tutorial]: https://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing 125 | [`cargo fmt`]: https://github.com/rust-lang/rustfmt#on-the-stable-toolchain 126 | [current issues]: https://github.com/sqlparser-rs/sqlparser-rs/issues 127 | [DataFusion]: https://github.com/apache/arrow-datafusion 128 | [LocustDB]: https://github.com/cswinter/LocustDB 129 | [Ballista]: https://github.com/apache/arrow-datafusion/tree/master/ballista 130 | [GlueSQL]: https://github.com/gluesql/gluesql 131 | [Pratt Parser]: https://tdop.github.io/ 132 | [sql-2016-grammar]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html 133 | [sql-standard]: https://en.wikipedia.org/wiki/ISO/IEC_9075 134 | -------------------------------------------------------------------------------- /docs/benchmarking.md: -------------------------------------------------------------------------------- 1 | # Benchmarking 2 | 3 | Run `cargo bench` in the project `sqlparser_bench` execute the queries. 4 | It will report results using the `criterion` library to perform the benchmarking. 5 | 6 | The bench project lives in another crate, to avoid the negative impact on building the `sqlparser` crate. 7 | -------------------------------------------------------------------------------- /docs/custom_sql_parser.md: -------------------------------------------------------------------------------- 1 | # Writing a Custom SQL Parser 2 | 3 | I have explored many different ways of building this library to make it easy to extend it for custom SQL dialects. Most of my attempts ended in failure but I have now found a workable solution. It is not without downsides but this seems to be the most pragmatic solution. 4 | 5 | The concept is simply to write a new parser that delegates to the ANSI parser so that as much as possible of the core functionality can be re-used. 6 | 7 | I also plan on building in specific support for custom data types, where a lambda function can be parsed to the parser to parse data types. 8 | 9 | For an example of this, see the [DataFusion](https://github.com/datafusion-rs/datafusion) project. 10 | 11 | -------------------------------------------------------------------------------- /docs/fuzzing.md: -------------------------------------------------------------------------------- 1 | # Fuzzing 2 | 3 | ## Installing `honggfuzz` 4 | 5 | ``` 6 | cargo install honggfuzz 7 | ``` 8 | 9 | Install [dependencies](https://github.com/rust-fuzz/honggfuzz-rs#dependencies) for your system. 10 | 11 | ## Running the fuzzer 12 | 13 | Running the fuzzer is as easy as running in the `fuzz` directory. 14 | 15 | Choose a target: 16 | 17 | These are `[[bin]]` entries in `Cargo.toml`. 18 | List them with `cargo read-manifest | jq '.targets[].name'` from the `fuzz` directory. 19 | 20 | Run the fuzzer: 21 | 22 | ```shell 23 | cd fuzz 24 | cargo hfuzz run 25 | ``` 26 | 27 | After a panic is found, get a stack trace with: 28 | 29 | ```shell 30 | cargo hfuzz run-debug hfuzz_workspace//*.fuzz 31 | ``` 32 | 33 | For example, with the `fuzz_parse_sql` target: 34 | 35 | ```shell 36 | cargo hfuzz run fuzz_parse_sql 37 | cargo hfuzz run-debug fuzz_parse_sql hfuzz_workspace/fuzz_parse_sql/*.fuzz 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/releasing.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | ## Prerequisites 4 | Publishing to crates.io has been automated via GitHub Actions, so you will only 5 | need push access to the [sqlparser-rs GitHub repository](https://github.com/sqlparser-rs/sqlparser-rs) 6 | in order to publish a release. 7 | 8 | We use the [`cargo release`](https://github.com/sunng87/cargo-release) 9 | subcommand to ensure correct versioning. Install via: 10 | 11 | ``` 12 | $ cargo install cargo-release 13 | ``` 14 | 15 | ## Process 16 | 17 | 1. **Before releasing** ensure `CHANGELOG.md` is updated appropriately and that 18 | you have a clean checkout of the `main` branch of the sqlparser repository: 19 | ``` 20 | $ git fetch && git status 21 | On branch main 22 | Your branch is up to date with 'upstream/main'. 23 | 24 | nothing to commit, working tree clean 25 | ``` 26 | * If you have the time, check that the examples in the README are up to date. 27 | 28 | 2. Using `cargo-release` we can publish a new release like so: 29 | 30 | ``` 31 | $ cargo release minor --push-remote upstream 32 | ``` 33 | 34 | After verifying, you can rerun with `--execute` if all looks good. 35 | You can add `--no-push` to stop before actually publishing the release. 36 | 37 | `cargo release` will then: 38 | 39 | * Bump the minor part of the version in `Cargo.toml` (e.g. `0.7.1-alpha.0` 40 | -> `0.8.0`. You can use `patch` instead of `minor`, as appropriate). 41 | * Create a new tag (e.g. `v0.8.0`) locally 42 | * Push the new tag to the specified remote (`upstream` in the above 43 | example), which will trigger a publishing process to crates.io as part of 44 | the [corresponding GitHub Action](https://github.com/sqlparser-rs/sqlparser-rs/blob/main/.github/workflows/rust.yml). 45 | 46 | Note that credentials for authoring in this way are securely stored in 47 | the (GitHub) repo secrets as `CRATE_TOKEN`. 48 | * Bump the crate version again (to something like `0.8.1-alpha.0`) to 49 | indicate the start of new development cycle. 50 | 51 | 3. Push the updates to the `main` branch upstream: 52 | ``` 53 | $ git push upstream 54 | ``` 55 | 56 | 4. Check that the new version of the crate is available on crates.io: 57 | https://crates.io/crates/sqlparser 58 | 59 | -------------------------------------------------------------------------------- /examples/cli.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #![warn(clippy::all)] 14 | 15 | /// A small command-line app to run the parser. 16 | /// Run with `cargo run --example cli` 17 | use std::fs; 18 | 19 | use simple_logger::SimpleLogger; 20 | use sqlparser::dialect::*; 21 | use sqlparser::parser::Parser; 22 | 23 | fn main() { 24 | SimpleLogger::new().init().unwrap(); 25 | 26 | let filename = std::env::args().nth(1).expect( 27 | r#" 28 | No arguments provided! 29 | 30 | Usage: 31 | $ cargo run --example cli FILENAME.sql [--dialectname] 32 | 33 | To print the parse results as JSON: 34 | $ cargo run --feature json_example --example cli FILENAME.sql [--dialectname] 35 | 36 | "#, 37 | ); 38 | 39 | let dialect: Box = match std::env::args().nth(2).unwrap_or_default().as_ref() { 40 | "--ansi" => Box::new(AnsiDialect {}), 41 | "--postgres" => Box::new(PostgreSqlDialect {}), 42 | "--ms" => Box::new(MsSqlDialect {}), 43 | "--mysql" => Box::new(MySqlDialect {}), 44 | "--snowflake" => Box::new(SnowflakeDialect {}), 45 | "--hive" => Box::new(HiveDialect {}), 46 | "--generic" | "" => Box::new(GenericDialect {}), 47 | s => panic!("Unexpected parameter: {}", s), 48 | }; 49 | 50 | println!("Parsing from file '{}' using {:?}", &filename, dialect); 51 | let contents = fs::read_to_string(&filename) 52 | .unwrap_or_else(|_| panic!("Unable to read the file {}", &filename)); 53 | let without_bom = if contents.chars().next().unwrap() as u64 != 0xfeff { 54 | contents.as_str() 55 | } else { 56 | let mut chars = contents.chars(); 57 | chars.next(); 58 | chars.as_str() 59 | }; 60 | let parse_result = Parser::parse_sql(&*dialect, without_bom); 61 | match parse_result { 62 | Ok(statements) => { 63 | println!( 64 | "Round-trip:\n'{}'", 65 | statements 66 | .iter() 67 | .map(std::string::ToString::to_string) 68 | .collect::>() 69 | .join("\n") 70 | ); 71 | 72 | if cfg!(feature = "json_example") { 73 | #[cfg(feature = "json_example")] 74 | { 75 | let serialized = serde_json::to_string_pretty(&statements).unwrap(); 76 | println!("Serialized as JSON:\n{}", serialized); 77 | } 78 | } else { 79 | println!("Parse results:\n{:#?}", statements); 80 | } 81 | 82 | std::process::exit(0); 83 | } 84 | Err(e) => { 85 | println!("Error during parsing: {:?}", e); 86 | std::process::exit(1); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/parse_select.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #![warn(clippy::all)] 14 | 15 | use sqlparser::dialect::GenericDialect; 16 | use sqlparser::parser::*; 17 | 18 | fn main() { 19 | let sql = "SELECT a, b, 123, myfunc(b) \ 20 | FROM table_1 \ 21 | WHERE a > b AND b < 100 \ 22 | ORDER BY a DESC, b"; 23 | 24 | let dialect = GenericDialect {}; 25 | 26 | let ast = Parser::parse_sql(&dialect, sql).unwrap(); 27 | 28 | println!("AST: {:?}", ast); 29 | } 30 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | corpus 2 | hfuzz_target 3 | hfuzz_workspace 4 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzz" 3 | version = "0.1.0" 4 | edition = "2018" 5 | publish = false 6 | 7 | [dependencies] 8 | honggfuzz = "0.5.54" 9 | sqlparser = { path = ".." } 10 | 11 | # Prevent this from interfering with workspaces 12 | [workspace] 13 | members = ["."] 14 | 15 | [[bin]] 16 | name = "fuzz_parse_sql" 17 | path = "fuzz_targets/fuzz_parse_sql.rs" 18 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz_parse_sql.rs: -------------------------------------------------------------------------------- 1 | use honggfuzz::fuzz; 2 | use sqlparser::dialect::GenericDialect; 3 | use sqlparser::parser::Parser; 4 | 5 | fn main() { 6 | loop { 7 | fuzz!(|data: String| { 8 | let dialect = GenericDialect {}; 9 | let _ = Parser::parse_sql(&dialect, &data); 10 | }); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | stable -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # We use rustfmt's default settings to format the source code -------------------------------------------------------------------------------- /sqlparser_bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlparser_bench" 3 | version = "0.1.0" 4 | authors = ["Dandandan "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | sqlparser = { path = "../" } 9 | 10 | [dev-dependencies] 11 | criterion = "0.3" 12 | 13 | [[bench]] 14 | name = "sqlparser_bench" 15 | harness = false 16 | -------------------------------------------------------------------------------- /sqlparser_bench/benches/sqlparser_bench.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | use criterion::{criterion_group, criterion_main, Criterion}; 14 | use sqlparser::dialect::GenericDialect; 15 | use sqlparser::parser::Parser; 16 | 17 | fn basic_queries(c: &mut Criterion) { 18 | let mut group = c.benchmark_group("sqlparser-rs parsing benchmark"); 19 | let dialect = GenericDialect {}; 20 | 21 | let string = "SELECT * FROM table WHERE 1 = 1"; 22 | group.bench_function("sqlparser::select", |b| { 23 | b.iter(|| Parser::parse_sql(&dialect, string)); 24 | }); 25 | 26 | let with_query = " 27 | WITH derived AS ( 28 | SELECT MAX(a) AS max_a, 29 | COUNT(b) AS b_num, 30 | user_id 31 | FROM TABLE 32 | GROUP BY user_id 33 | ) 34 | SELECT * FROM table 35 | LEFT JOIN derived USING (user_id) 36 | "; 37 | group.bench_function("sqlparser::with_select", |b| { 38 | b.iter(|| Parser::parse_sql(&dialect, with_query)); 39 | }); 40 | } 41 | 42 | criterion_group!(benches, basic_queries); 43 | criterion_main!(benches); 44 | -------------------------------------------------------------------------------- /src/ast/data_type.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #[cfg(not(feature = "std"))] 14 | use alloc::boxed::Box; 15 | use core::fmt; 16 | 17 | #[cfg(feature = "serde")] 18 | use serde::{Deserialize, Serialize}; 19 | 20 | use crate::ast::ObjectName; 21 | 22 | /// SQL data types 23 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 24 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 25 | pub enum DataType { 26 | /// Fixed-length character type e.g. CHAR(10) 27 | Char(Option), 28 | /// Variable-length character type e.g. VARCHAR(10) 29 | Varchar(Option), 30 | /// Uuid type 31 | Uuid, 32 | /// Large character object e.g. CLOB(1000) 33 | Clob(u64), 34 | /// Fixed-length binary type e.g. BINARY(10) 35 | Binary(u64), 36 | /// Variable-length binary type e.g. VARBINARY(10) 37 | Varbinary(u64), 38 | /// Large binary object e.g. BLOB(1000) 39 | Blob(u64), 40 | /// Decimal type with optional precision and scale e.g. DECIMAL(10,2) 41 | Decimal(Option, Option), 42 | /// Floating point with optional precision e.g. FLOAT(8) 43 | Float(Option), 44 | /// Tiny integer with optional display width e.g. TINYINT or TINYINT(3) 45 | TinyInt(Option), 46 | /// Small integer with optional display width e.g. SMALLINT or SMALLINT(5) 47 | SmallInt(Option), 48 | /// Integer with optional display width e.g. INT or INT(11) 49 | Int(Option), 50 | /// Big integer with optional display width e.g. BIGINT or BIGINT(20) 51 | BigInt(Option), 52 | /// Floating point e.g. REAL 53 | Real, 54 | /// Double e.g. DOUBLE PRECISION 55 | Double, 56 | /// Boolean 57 | Boolean, 58 | /// Date 59 | Date, 60 | /// Time 61 | Time, 62 | /// Timestamp 63 | Timestamp, 64 | /// Interval 65 | Interval, 66 | /// Regclass used in postgresql serial 67 | Regclass, 68 | /// Text 69 | Text, 70 | /// String 71 | String, 72 | /// Bytea 73 | Bytea, 74 | /// Custom type such as enums 75 | Custom(ObjectName), 76 | /// Arrays 77 | Array(Box), 78 | } 79 | 80 | impl fmt::Display for DataType { 81 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 82 | match self { 83 | DataType::Char(size) => format_type_with_optional_length(f, "CHAR", size), 84 | DataType::Varchar(size) => { 85 | format_type_with_optional_length(f, "CHARACTER VARYING", size) 86 | } 87 | DataType::Uuid => write!(f, "UUID"), 88 | DataType::Clob(size) => write!(f, "CLOB({})", size), 89 | DataType::Binary(size) => write!(f, "BINARY({})", size), 90 | DataType::Varbinary(size) => write!(f, "VARBINARY({})", size), 91 | DataType::Blob(size) => write!(f, "BLOB({})", size), 92 | DataType::Decimal(precision, scale) => { 93 | if let Some(scale) = scale { 94 | write!(f, "NUMERIC({},{})", precision.unwrap(), scale) 95 | } else { 96 | format_type_with_optional_length(f, "NUMERIC", precision) 97 | } 98 | } 99 | DataType::Float(size) => format_type_with_optional_length(f, "FLOAT", size), 100 | DataType::TinyInt(zerofill) => format_type_with_optional_length(f, "TINYINT", zerofill), 101 | DataType::SmallInt(zerofill) => { 102 | format_type_with_optional_length(f, "SMALLINT", zerofill) 103 | } 104 | DataType::Int(zerofill) => format_type_with_optional_length(f, "INT", zerofill), 105 | DataType::BigInt(zerofill) => format_type_with_optional_length(f, "BIGINT", zerofill), 106 | DataType::Real => write!(f, "REAL"), 107 | DataType::Double => write!(f, "DOUBLE"), 108 | DataType::Boolean => write!(f, "BOOLEAN"), 109 | DataType::Date => write!(f, "DATE"), 110 | DataType::Time => write!(f, "TIME"), 111 | DataType::Timestamp => write!(f, "TIMESTAMP"), 112 | DataType::Interval => write!(f, "INTERVAL"), 113 | DataType::Regclass => write!(f, "REGCLASS"), 114 | DataType::Text => write!(f, "TEXT"), 115 | DataType::String => write!(f, "STRING"), 116 | DataType::Bytea => write!(f, "BYTEA"), 117 | DataType::Array(ty) => write!(f, "{}[]", ty), 118 | DataType::Custom(ty) => write!(f, "{}", ty), 119 | } 120 | } 121 | } 122 | 123 | fn format_type_with_optional_length( 124 | f: &mut fmt::Formatter, 125 | sql_type: &'static str, 126 | len: &Option, 127 | ) -> fmt::Result { 128 | write!(f, "{}", sql_type)?; 129 | if let Some(len) = len { 130 | write!(f, "({})", len)?; 131 | } 132 | Ok(()) 133 | } 134 | -------------------------------------------------------------------------------- /src/ast/ddl.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | //! AST types specific to CREATE/ALTER variants of [Statement] 14 | //! (commonly referred to as Data Definition Language, or DDL) 15 | 16 | #[cfg(not(feature = "std"))] 17 | use alloc::{boxed::Box, string::ToString, vec::Vec}; 18 | use core::fmt; 19 | 20 | #[cfg(feature = "serde")] 21 | use serde::{Deserialize, Serialize}; 22 | 23 | use crate::ast::{display_comma_separated, display_separated, DataType, Expr, Ident, ObjectName}; 24 | use crate::tokenizer::Token; 25 | 26 | /// An `ALTER TABLE` (`Statement::AlterTable`) operation 27 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 28 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 29 | pub enum AlterTableOperation { 30 | /// `ADD ` 31 | AddConstraint(TableConstraint), 32 | /// `ADD [ COLUMN ] ` 33 | AddColumn { column_def: ColumnDef }, 34 | /// TODO: implement `DROP CONSTRAINT ` 35 | DropConstraint { name: Ident }, 36 | /// `DROP [ COLUMN ] [ IF EXISTS ] [ CASCADE ]` 37 | DropColumn { 38 | column_name: Ident, 39 | if_exists: bool, 40 | cascade: bool, 41 | }, 42 | /// `RENAME TO PARTITION (partition=val)` 43 | RenamePartitions { 44 | old_partitions: Vec, 45 | new_partitions: Vec, 46 | }, 47 | /// Add Partitions 48 | AddPartitions { 49 | if_not_exists: bool, 50 | new_partitions: Vec, 51 | }, 52 | DropPartitions { 53 | partitions: Vec, 54 | if_exists: bool, 55 | }, 56 | /// `RENAME [ COLUMN ] TO ` 57 | RenameColumn { 58 | old_column_name: Ident, 59 | new_column_name: Ident, 60 | }, 61 | /// `RENAME TO ` 62 | RenameTable { table_name: ObjectName }, 63 | // CHANGE [ COLUMN ] [ ] 64 | ChangeColumn { 65 | old_name: Ident, 66 | new_name: Ident, 67 | data_type: DataType, 68 | options: Vec, 69 | }, 70 | /// `RENAME CONSTRAINT TO ` 71 | /// 72 | /// Note: this is a PostgreSQL-specific operation. 73 | RenameConstraint { old_name: Ident, new_name: Ident }, 74 | /// `ALTER [ COLUMN ]` 75 | AlterColumn { 76 | column_name: Ident, 77 | op: AlterColumnOperation, 78 | }, 79 | } 80 | 81 | impl fmt::Display for AlterTableOperation { 82 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 83 | match self { 84 | AlterTableOperation::AddPartitions { 85 | if_not_exists, 86 | new_partitions, 87 | } => write!( 88 | f, 89 | "ADD{ine} PARTITION ({})", 90 | display_comma_separated(new_partitions), 91 | ine = if *if_not_exists { " IF NOT EXISTS" } else { "" } 92 | ), 93 | AlterTableOperation::AddConstraint(c) => write!(f, "ADD {}", c), 94 | AlterTableOperation::AddColumn { column_def } => { 95 | write!(f, "ADD COLUMN {}", column_def.to_string()) 96 | } 97 | AlterTableOperation::AlterColumn { column_name, op } => { 98 | write!(f, "ALTER COLUMN {} {}", column_name, op) 99 | } 100 | AlterTableOperation::DropPartitions { 101 | partitions, 102 | if_exists, 103 | } => write!( 104 | f, 105 | "DROP{ie} PARTITION ({})", 106 | display_comma_separated(partitions), 107 | ie = if *if_exists { " IF EXISTS" } else { "" } 108 | ), 109 | AlterTableOperation::DropConstraint { name } => write!(f, "DROP CONSTRAINT {}", name), 110 | AlterTableOperation::DropColumn { 111 | column_name, 112 | if_exists, 113 | cascade, 114 | } => write!( 115 | f, 116 | "DROP COLUMN {}{}{}", 117 | if *if_exists { "IF EXISTS " } else { "" }, 118 | column_name, 119 | if *cascade { " CASCADE" } else { "" } 120 | ), 121 | AlterTableOperation::RenamePartitions { 122 | old_partitions, 123 | new_partitions, 124 | } => write!( 125 | f, 126 | "PARTITION ({}) RENAME TO PARTITION ({})", 127 | display_comma_separated(old_partitions), 128 | display_comma_separated(new_partitions) 129 | ), 130 | AlterTableOperation::RenameColumn { 131 | old_column_name, 132 | new_column_name, 133 | } => write!( 134 | f, 135 | "RENAME COLUMN {} TO {}", 136 | old_column_name, new_column_name 137 | ), 138 | AlterTableOperation::RenameTable { table_name } => { 139 | write!(f, "RENAME TO {}", table_name) 140 | } 141 | AlterTableOperation::ChangeColumn { 142 | old_name, 143 | new_name, 144 | data_type, 145 | options, 146 | } => { 147 | write!(f, "CHANGE COLUMN {} {} {}", old_name, new_name, data_type)?; 148 | if options.is_empty() { 149 | Ok(()) 150 | } else { 151 | write!(f, " {}", display_separated(options, " ")) 152 | } 153 | } 154 | AlterTableOperation::RenameConstraint { old_name, new_name } => { 155 | write!(f, "RENAME CONSTRAINT {} TO {}", old_name, new_name) 156 | } 157 | } 158 | } 159 | } 160 | 161 | /// An `ALTER COLUMN` (`Statement::AlterTable`) operation 162 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 163 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 164 | pub enum AlterColumnOperation { 165 | /// `SET NOT NULL` 166 | SetNotNull, 167 | /// `DROP NOT NULL` 168 | DropNotNull, 169 | /// `SET DEFAULT ` 170 | SetDefault { value: Expr }, 171 | /// `DROP DEFAULT` 172 | DropDefault, 173 | /// `[SET DATA] TYPE [USING ]` 174 | SetDataType { 175 | data_type: DataType, 176 | /// PostgreSQL specific 177 | using: Option, 178 | }, 179 | } 180 | 181 | impl fmt::Display for AlterColumnOperation { 182 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 183 | match self { 184 | AlterColumnOperation::SetNotNull => write!(f, "SET NOT NULL",), 185 | AlterColumnOperation::DropNotNull => write!(f, "DROP NOT NULL",), 186 | AlterColumnOperation::SetDefault { value } => { 187 | write!(f, "SET DEFAULT {}", value) 188 | } 189 | AlterColumnOperation::DropDefault {} => { 190 | write!(f, "DROP DEFAULT") 191 | } 192 | AlterColumnOperation::SetDataType { data_type, using } => { 193 | if let Some(expr) = using { 194 | write!(f, "SET DATA TYPE {} USING {}", data_type, expr) 195 | } else { 196 | write!(f, "SET DATA TYPE {}", data_type) 197 | } 198 | } 199 | } 200 | } 201 | } 202 | 203 | /// A table-level constraint, specified in a `CREATE TABLE` or an 204 | /// `ALTER TABLE ADD ` statement. 205 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 206 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 207 | pub enum TableConstraint { 208 | /// `[ CONSTRAINT ] { PRIMARY KEY | UNIQUE } ()` 209 | Unique { 210 | name: Option, 211 | columns: Vec, 212 | /// Whether this is a `PRIMARY KEY` or just a `UNIQUE` constraint 213 | is_primary: bool, 214 | }, 215 | /// A referential integrity constraint (`[ CONSTRAINT ] FOREIGN KEY () 216 | /// REFERENCES () 217 | /// { [ON DELETE ] [ON UPDATE ] | 218 | /// [ON UPDATE ] [ON DELETE ] 219 | /// }`). 220 | ForeignKey { 221 | name: Option, 222 | columns: Vec, 223 | foreign_table: ObjectName, 224 | referred_columns: Vec, 225 | on_delete: Option, 226 | on_update: Option, 227 | }, 228 | /// `[ CONSTRAINT ] CHECK ()` 229 | Check { 230 | name: Option, 231 | expr: Box, 232 | }, 233 | } 234 | 235 | impl fmt::Display for TableConstraint { 236 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 237 | match self { 238 | TableConstraint::Unique { 239 | name, 240 | columns, 241 | is_primary, 242 | } => write!( 243 | f, 244 | "{}{} ({})", 245 | display_constraint_name(name), 246 | if *is_primary { "PRIMARY KEY" } else { "UNIQUE" }, 247 | display_comma_separated(columns) 248 | ), 249 | TableConstraint::ForeignKey { 250 | name, 251 | columns, 252 | foreign_table, 253 | referred_columns, 254 | on_delete, 255 | on_update, 256 | } => { 257 | write!( 258 | f, 259 | "{}FOREIGN KEY ({}) REFERENCES {}({})", 260 | display_constraint_name(name), 261 | display_comma_separated(columns), 262 | foreign_table, 263 | display_comma_separated(referred_columns), 264 | )?; 265 | if let Some(action) = on_delete { 266 | write!(f, " ON DELETE {}", action)?; 267 | } 268 | if let Some(action) = on_update { 269 | write!(f, " ON UPDATE {}", action)?; 270 | } 271 | Ok(()) 272 | } 273 | TableConstraint::Check { name, expr } => { 274 | write!(f, "{}CHECK ({})", display_constraint_name(name), expr) 275 | } 276 | } 277 | } 278 | } 279 | 280 | /// SQL column definition 281 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 282 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 283 | pub struct ColumnDef { 284 | pub name: Ident, 285 | pub data_type: DataType, 286 | pub collation: Option, 287 | pub options: Vec, 288 | } 289 | 290 | impl fmt::Display for ColumnDef { 291 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 292 | write!(f, "{} {}", self.name, self.data_type)?; 293 | for option in &self.options { 294 | write!(f, " {}", option)?; 295 | } 296 | Ok(()) 297 | } 298 | } 299 | 300 | /// An optionally-named `ColumnOption`: `[ CONSTRAINT ] `. 301 | /// 302 | /// Note that implementations are substantially more permissive than the ANSI 303 | /// specification on what order column options can be presented in, and whether 304 | /// they are allowed to be named. The specification distinguishes between 305 | /// constraints (NOT NULL, UNIQUE, PRIMARY KEY, and CHECK), which can be named 306 | /// and can appear in any order, and other options (DEFAULT, GENERATED), which 307 | /// cannot be named and must appear in a fixed order. PostgreSQL, however, 308 | /// allows preceding any option with `CONSTRAINT `, even those that are 309 | /// not really constraints, like NULL and DEFAULT. MSSQL is less permissive, 310 | /// allowing DEFAULT, UNIQUE, PRIMARY KEY and CHECK to be named, but not NULL or 311 | /// NOT NULL constraints (the last of which is in violation of the spec). 312 | /// 313 | /// For maximum flexibility, we don't distinguish between constraint and 314 | /// non-constraint options, lumping them all together under the umbrella of 315 | /// "column options," and we allow any column option to be named. 316 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 317 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 318 | pub struct ColumnOptionDef { 319 | pub name: Option, 320 | pub option: ColumnOption, 321 | } 322 | 323 | impl fmt::Display for ColumnOptionDef { 324 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 325 | write!(f, "{}{}", display_constraint_name(&self.name), self.option) 326 | } 327 | } 328 | 329 | /// `ColumnOption`s are modifiers that follow a column definition in a `CREATE 330 | /// TABLE` statement. 331 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 332 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 333 | pub enum ColumnOption { 334 | /// `NULL` 335 | Null, 336 | /// `NOT NULL` 337 | NotNull, 338 | /// `DEFAULT ` 339 | Default(Expr), 340 | /// `{ PRIMARY KEY | UNIQUE }` 341 | Unique { is_primary: bool }, 342 | /// A referential integrity constraint (`[FOREIGN KEY REFERENCES 343 | /// () 344 | /// { [ON DELETE ] [ON UPDATE ] | 345 | /// [ON UPDATE ] [ON DELETE ] 346 | /// }`). 347 | ForeignKey { 348 | foreign_table: ObjectName, 349 | referred_columns: Vec, 350 | on_delete: Option, 351 | on_update: Option, 352 | }, 353 | /// `CHECK ()` 354 | Check(Expr), 355 | /// Dialect-specific options, such as: 356 | /// - MySQL's `AUTO_INCREMENT` or SQLite's `AUTOINCREMENT` 357 | /// - ... 358 | DialectSpecific(Vec), 359 | } 360 | 361 | impl fmt::Display for ColumnOption { 362 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 363 | use ColumnOption::*; 364 | match self { 365 | Null => write!(f, "NULL"), 366 | NotNull => write!(f, "NOT NULL"), 367 | Default(expr) => write!(f, "DEFAULT {}", expr), 368 | Unique { is_primary } => { 369 | write!(f, "{}", if *is_primary { "PRIMARY KEY" } else { "UNIQUE" }) 370 | } 371 | ForeignKey { 372 | foreign_table, 373 | referred_columns, 374 | on_delete, 375 | on_update, 376 | } => { 377 | write!(f, "REFERENCES {}", foreign_table)?; 378 | if !referred_columns.is_empty() { 379 | write!(f, " ({})", display_comma_separated(referred_columns))?; 380 | } 381 | if let Some(action) = on_delete { 382 | write!(f, " ON DELETE {}", action)?; 383 | } 384 | if let Some(action) = on_update { 385 | write!(f, " ON UPDATE {}", action)?; 386 | } 387 | Ok(()) 388 | } 389 | Check(expr) => write!(f, "CHECK ({})", expr), 390 | DialectSpecific(val) => write!(f, "{}", display_separated(val, " ")), 391 | } 392 | } 393 | } 394 | 395 | fn display_constraint_name(name: &'_ Option) -> impl fmt::Display + '_ { 396 | struct ConstraintName<'a>(&'a Option); 397 | impl<'a> fmt::Display for ConstraintName<'a> { 398 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 399 | if let Some(name) = self.0 { 400 | write!(f, "CONSTRAINT {} ", name)?; 401 | } 402 | Ok(()) 403 | } 404 | } 405 | ConstraintName(name) 406 | } 407 | 408 | /// ` = 409 | /// { RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT }` 410 | /// 411 | /// Used in foreign key constraints in `ON UPDATE` and `ON DELETE` options. 412 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 413 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 414 | pub enum ReferentialAction { 415 | Restrict, 416 | Cascade, 417 | SetNull, 418 | NoAction, 419 | SetDefault, 420 | } 421 | 422 | impl fmt::Display for ReferentialAction { 423 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 424 | f.write_str(match self { 425 | ReferentialAction::Restrict => "RESTRICT", 426 | ReferentialAction::Cascade => "CASCADE", 427 | ReferentialAction::SetNull => "SET NULL", 428 | ReferentialAction::NoAction => "NO ACTION", 429 | ReferentialAction::SetDefault => "SET DEFAULT", 430 | }) 431 | } 432 | } 433 | -------------------------------------------------------------------------------- /src/ast/operator.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | use core::fmt; 14 | 15 | #[cfg(feature = "serde")] 16 | use serde::{Deserialize, Serialize}; 17 | 18 | /// Unary operators 19 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 20 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 21 | pub enum UnaryOperator { 22 | Plus, 23 | Minus, 24 | Not, 25 | /// Bitwise Not, e.g. `~9` (PostgreSQL-specific) 26 | PGBitwiseNot, 27 | /// Square root, e.g. `|/9` (PostgreSQL-specific) 28 | PGSquareRoot, 29 | /// Cube root, e.g. `||/27` (PostgreSQL-specific) 30 | PGCubeRoot, 31 | /// Factorial, e.g. `9!` (PostgreSQL-specific) 32 | PGPostfixFactorial, 33 | /// Factorial, e.g. `!!9` (PostgreSQL-specific) 34 | PGPrefixFactorial, 35 | /// Absolute value, e.g. `@ -9` (PostgreSQL-specific) 36 | PGAbs, 37 | } 38 | 39 | impl fmt::Display for UnaryOperator { 40 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 41 | f.write_str(match self { 42 | UnaryOperator::Plus => "+", 43 | UnaryOperator::Minus => "-", 44 | UnaryOperator::Not => "NOT", 45 | UnaryOperator::PGBitwiseNot => "~", 46 | UnaryOperator::PGSquareRoot => "|/", 47 | UnaryOperator::PGCubeRoot => "||/", 48 | UnaryOperator::PGPostfixFactorial => "!", 49 | UnaryOperator::PGPrefixFactorial => "!!", 50 | UnaryOperator::PGAbs => "@", 51 | }) 52 | } 53 | } 54 | 55 | /// Binary operators 56 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 57 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 58 | pub enum BinaryOperator { 59 | Plus, 60 | Minus, 61 | Multiply, 62 | Divide, 63 | Modulo, 64 | StringConcat, 65 | Gt, 66 | Lt, 67 | GtEq, 68 | LtEq, 69 | Spaceship, 70 | Eq, 71 | NotEq, 72 | And, 73 | Or, 74 | Xor, 75 | Like, 76 | NotLike, 77 | ILike, 78 | NotILike, 79 | BitwiseOr, 80 | BitwiseAnd, 81 | BitwiseXor, 82 | PGBitwiseXor, 83 | PGBitwiseShiftLeft, 84 | PGBitwiseShiftRight, 85 | PGRegexMatch, 86 | PGRegexIMatch, 87 | PGRegexNotMatch, 88 | PGRegexNotIMatch, 89 | } 90 | 91 | impl fmt::Display for BinaryOperator { 92 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 93 | f.write_str(match self { 94 | BinaryOperator::Plus => "+", 95 | BinaryOperator::Minus => "-", 96 | BinaryOperator::Multiply => "*", 97 | BinaryOperator::Divide => "/", 98 | BinaryOperator::Modulo => "%", 99 | BinaryOperator::StringConcat => "||", 100 | BinaryOperator::Gt => ">", 101 | BinaryOperator::Lt => "<", 102 | BinaryOperator::GtEq => ">=", 103 | BinaryOperator::LtEq => "<=", 104 | BinaryOperator::Spaceship => "<=>", 105 | BinaryOperator::Eq => "=", 106 | BinaryOperator::NotEq => "<>", 107 | BinaryOperator::And => "AND", 108 | BinaryOperator::Or => "OR", 109 | BinaryOperator::Xor => "XOR", 110 | BinaryOperator::Like => "LIKE", 111 | BinaryOperator::NotLike => "NOT LIKE", 112 | BinaryOperator::ILike => "ILIKE", 113 | BinaryOperator::NotILike => "NOT ILIKE", 114 | BinaryOperator::BitwiseOr => "|", 115 | BinaryOperator::BitwiseAnd => "&", 116 | BinaryOperator::BitwiseXor => "^", 117 | BinaryOperator::PGBitwiseXor => "#", 118 | BinaryOperator::PGBitwiseShiftLeft => "<<", 119 | BinaryOperator::PGBitwiseShiftRight => ">>", 120 | BinaryOperator::PGRegexMatch => "~", 121 | BinaryOperator::PGRegexIMatch => "~*", 122 | BinaryOperator::PGRegexNotMatch => "!~", 123 | BinaryOperator::PGRegexNotIMatch => "!~*", 124 | }) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/ast/query.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | #[cfg(not(feature = "std"))] 14 | use alloc::{boxed::Box, vec::Vec}; 15 | 16 | #[cfg(feature = "serde")] 17 | use serde::{Deserialize, Serialize}; 18 | 19 | use crate::ast::*; 20 | 21 | /// The most complete variant of a `SELECT` query expression, optionally 22 | /// including `WITH`, `UNION` / other set operations, and `ORDER BY`. 23 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 24 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 25 | pub struct Query { 26 | /// WITH (common table expressions, or CTEs) 27 | pub with: Option, 28 | /// SELECT or UNION / EXCEPT / INTERSECT 29 | pub body: SetExpr, 30 | /// ORDER BY 31 | pub order_by: Vec, 32 | /// `LIMIT { | ALL }` 33 | pub limit: Option, 34 | /// `OFFSET [ { ROW | ROWS } ]` 35 | pub offset: Option, 36 | /// `FETCH { FIRST | NEXT } [ PERCENT ] { ROW | ROWS } | { ONLY | WITH TIES }` 37 | pub fetch: Option, 38 | } 39 | 40 | impl fmt::Display for Query { 41 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 42 | if let Some(ref with) = self.with { 43 | write!(f, "{} ", with)?; 44 | } 45 | write!(f, "{}", self.body)?; 46 | if !self.order_by.is_empty() { 47 | write!(f, " ORDER BY {}", display_comma_separated(&self.order_by))?; 48 | } 49 | if let Some(ref limit) = self.limit { 50 | write!(f, " LIMIT {}", limit)?; 51 | } 52 | if let Some(ref offset) = self.offset { 53 | write!(f, " {}", offset)?; 54 | } 55 | if let Some(ref fetch) = self.fetch { 56 | write!(f, " {}", fetch)?; 57 | } 58 | Ok(()) 59 | } 60 | } 61 | 62 | /// A node in a tree, representing a "query body" expression, roughly: 63 | /// `SELECT ... [ {UNION|EXCEPT|INTERSECT} SELECT ...]` 64 | #[allow(clippy::large_enum_variant)] 65 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 66 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 67 | pub enum SetExpr { 68 | /// Restricted SELECT .. FROM .. HAVING (no ORDER BY or set operations) 69 | Select(Box