mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-04 14:28:22 +00:00
Compare commits
No commits in common. "main" and "v0.56.0" have entirely different histories.
55 changed files with 1994 additions and 11534 deletions
39
.github/workflows/license.yml
vendored
39
.github/workflows/license.yml
vendored
|
@ -1,39 +0,0 @@
|
|||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
name: license
|
||||
|
||||
# trigger for all PRs and changes to main
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
||||
rat:
|
||||
name: Release Audit Tool (RAT)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Audit licenses
|
||||
run: ./dev/release/run-rat.sh .
|
10
.github/workflows/rust.yml
vendored
10
.github/workflows/rust.yml
vendored
|
@ -19,9 +19,6 @@ name: Rust
|
|||
|
||||
on: [push, pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
|
||||
codestyle:
|
||||
|
@ -88,8 +85,11 @@ jobs:
|
|||
uses: ./.github/actions/setup-builder
|
||||
with:
|
||||
rust-version: ${{ matrix.rust }}
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: Install Tarpaulin
|
||||
run: cargo install cargo-tarpaulin
|
||||
uses: actions-rs/install@v0.1
|
||||
with:
|
||||
crate: cargo-tarpaulin
|
||||
version: 0.14.2
|
||||
use-tool-cache: true
|
||||
- name: Test
|
||||
run: cargo test --all-features
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
[package]
|
||||
name = "sqlparser"
|
||||
description = "Extensible SQL Lexer and Parser with support for ANSI SQL:2011"
|
||||
version = "0.58.0"
|
||||
version = "0.56.0"
|
||||
authors = ["Apache DataFusion <dev@datafusion.apache.org>"]
|
||||
homepage = "https://github.com/apache/datafusion-sqlparser-rs"
|
||||
documentation = "https://docs.rs/sqlparser/"
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://crates.io/crates/sqlparser)
|
||||
[](https://github.com/sqlparser-rs/sqlparser-rs/actions?query=workflow%3ARust+branch%3Amain)
|
||||
[](https://github.com/sqlparser-rs/sqlparser-rs/actions?query=workflow%3ARust+branch%3Amain)
|
||||
[](https://coveralls.io/github/sqlparser-rs/sqlparser-rs?branch=main)
|
||||
[](https://gitter.im/sqlparser-rs/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
|
@ -89,14 +89,10 @@ keywords, the following should hold true for all SQL:
|
|||
|
||||
```rust
|
||||
// Parse SQL
|
||||
let sql = "SELECT 'hello'";
|
||||
let ast = Parser::parse_sql(&GenericDialect, sql).unwrap();
|
||||
|
||||
// The original SQL text can be generated from the AST
|
||||
assert_eq!(ast[0].to_string(), sql);
|
||||
|
||||
// The SQL can also be pretty-printed with newlines and indentation
|
||||
assert_eq!(format!("{:#}", ast[0]), "SELECT\n 'hello'");
|
||||
```
|
||||
|
||||
There are still some cases in this crate where different SQL with seemingly
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
# sqlparser-rs 0.57.0 Changelog
|
||||
|
||||
This release consists of 39 commits from 19 contributors. See credits at the end of this changelog for more information.
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- feat: Hive: support `SORT BY` direction [#1873](https://github.com/apache/datafusion-sqlparser-rs/pull/1873) (chenkovsky)
|
||||
|
||||
**Other:**
|
||||
|
||||
- Support some of pipe operators [#1759](https://github.com/apache/datafusion-sqlparser-rs/pull/1759) (simonvandel)
|
||||
- Added support for `DROP DOMAIN` [#1828](https://github.com/apache/datafusion-sqlparser-rs/pull/1828) (LucaCappelletti94)
|
||||
- Improve support for cursors for SQL Server [#1831](https://github.com/apache/datafusion-sqlparser-rs/pull/1831) (aharpervc)
|
||||
- Add all missing table options to be handled in any order [#1747](https://github.com/apache/datafusion-sqlparser-rs/pull/1747) (benrsatori)
|
||||
- Add `CREATE TRIGGER` support for SQL Server [#1810](https://github.com/apache/datafusion-sqlparser-rs/pull/1810) (aharpervc)
|
||||
- Added support for `CREATE DOMAIN` [#1830](https://github.com/apache/datafusion-sqlparser-rs/pull/1830) (LucaCappelletti94)
|
||||
- Allow stored procedures to be defined without `BEGIN`/`END` [#1834](https://github.com/apache/datafusion-sqlparser-rs/pull/1834) (aharpervc)
|
||||
- Add support for the MATCH and REGEXP binary operators [#1840](https://github.com/apache/datafusion-sqlparser-rs/pull/1840) (lovasoa)
|
||||
- Fix: parsing ident starting with underscore in certain dialects [#1835](https://github.com/apache/datafusion-sqlparser-rs/pull/1835) (MohamedAbdeen21)
|
||||
- implement pretty-printing with `{:#}` [#1847](https://github.com/apache/datafusion-sqlparser-rs/pull/1847) (lovasoa)
|
||||
- Fix big performance issue in string serialization [#1848](https://github.com/apache/datafusion-sqlparser-rs/pull/1848) (lovasoa)
|
||||
- Add support for `DENY` statements [#1836](https://github.com/apache/datafusion-sqlparser-rs/pull/1836) (aharpervc)
|
||||
- Postgresql: Add `REPLICA IDENTITY` operation for `ALTER TABLE` [#1844](https://github.com/apache/datafusion-sqlparser-rs/pull/1844) (MohamedAbdeen21)
|
||||
- Add support for INCLUDE/EXCLUDE NULLS for UNPIVOT [#1849](https://github.com/apache/datafusion-sqlparser-rs/pull/1849) (Vedin)
|
||||
- pretty print improvements [#1851](https://github.com/apache/datafusion-sqlparser-rs/pull/1851) (lovasoa)
|
||||
- fix new rust 1.87 cargo clippy warnings [#1856](https://github.com/apache/datafusion-sqlparser-rs/pull/1856) (lovasoa)
|
||||
- Update criterion requirement from 0.5 to 0.6 in /sqlparser_bench [#1857](https://github.com/apache/datafusion-sqlparser-rs/pull/1857) (dependabot[bot])
|
||||
- pretty-print CREATE TABLE statements [#1854](https://github.com/apache/datafusion-sqlparser-rs/pull/1854) (lovasoa)
|
||||
- pretty-print CREATE VIEW statements [#1855](https://github.com/apache/datafusion-sqlparser-rs/pull/1855) (lovasoa)
|
||||
- Handle optional datatypes properly in `CREATE FUNCTION` statements [#1826](https://github.com/apache/datafusion-sqlparser-rs/pull/1826) (LucaCappelletti94)
|
||||
- Mysql: Add `SRID` column option [#1852](https://github.com/apache/datafusion-sqlparser-rs/pull/1852) (MohamedAbdeen21)
|
||||
- Add support for table valued functions for SQL Server [#1839](https://github.com/apache/datafusion-sqlparser-rs/pull/1839) (aharpervc)
|
||||
- Keep the COLUMN keyword only if it exists when dropping the column [#1862](https://github.com/apache/datafusion-sqlparser-rs/pull/1862) (git-hulk)
|
||||
- Add support for parameter default values in SQL Server [#1866](https://github.com/apache/datafusion-sqlparser-rs/pull/1866) (aharpervc)
|
||||
- Add support for `TABLESAMPLE` pipe operator [#1860](https://github.com/apache/datafusion-sqlparser-rs/pull/1860) (hendrikmakait)
|
||||
- Adds support for mysql's drop index [#1864](https://github.com/apache/datafusion-sqlparser-rs/pull/1864) (dmzmk)
|
||||
- Fix: GROUPING SETS accept values without parenthesis [#1867](https://github.com/apache/datafusion-sqlparser-rs/pull/1867) (Vedin)
|
||||
- Add ICEBERG keyword support to ALTER TABLE statement [#1869](https://github.com/apache/datafusion-sqlparser-rs/pull/1869) (osipovartem)
|
||||
- MySQL: Support `index_name` in FK constraints [#1871](https://github.com/apache/datafusion-sqlparser-rs/pull/1871) (MohamedAbdeen21)
|
||||
- Postgres: Apply `ONLY` keyword per table in TRUNCATE stmt [#1872](https://github.com/apache/datafusion-sqlparser-rs/pull/1872) (MohamedAbdeen21)
|
||||
- Fix `CASE` expression spans [#1874](https://github.com/apache/datafusion-sqlparser-rs/pull/1874) (eliaperantoni)
|
||||
- MySQL: `[[NOT] ENFORCED]` in CHECK constraint [#1870](https://github.com/apache/datafusion-sqlparser-rs/pull/1870) (MohamedAbdeen21)
|
||||
- Add support for `CREATE SCHEMA WITH ( <properties> )` [#1877](https://github.com/apache/datafusion-sqlparser-rs/pull/1877) (utay)
|
||||
- Add support for `ALTER TABLE DROP INDEX` [#1865](https://github.com/apache/datafusion-sqlparser-rs/pull/1865) (vimko)
|
||||
- chore: Replace archived actions-rs/install action [#1876](https://github.com/apache/datafusion-sqlparser-rs/pull/1876) (assignUser)
|
||||
- Allow `IF NOT EXISTS` after table name for Snowflake [#1881](https://github.com/apache/datafusion-sqlparser-rs/pull/1881) (bombsimon)
|
||||
- Support `DISTINCT AS { STRUCT | VALUE }` for BigQuery [#1880](https://github.com/apache/datafusion-sqlparser-rs/pull/1880) (bombsimon)
|
||||
|
||||
## Credits
|
||||
|
||||
Thank you to everyone who contributed to this release. Here is a breakdown of commits (PRs merged) per contributor.
|
||||
|
||||
```
|
||||
7 Ophir LOJKINE
|
||||
6 Andrew Harper
|
||||
6 Mohamed Abdeen
|
||||
3 Luca Cappelletti
|
||||
2 Denys Tsomenko
|
||||
2 Simon Sawert
|
||||
1 Andrew Lamb
|
||||
1 Artem Osipov
|
||||
1 Chen Chongchen
|
||||
1 Dmitriy Mazurin
|
||||
1 Elia Perantoni
|
||||
1 Hendrik Makait
|
||||
1 Jacob Wujciak-Jens
|
||||
1 Simon Vandel Sillesen
|
||||
1 Yannick Utard
|
||||
1 benrsatori
|
||||
1 dependabot[bot]
|
||||
1 hulk
|
||||
1 vimko
|
||||
```
|
||||
|
||||
Thank you also to everyone who contributed in other ways such as filing issues, reviewing PRs, and providing feedback on this release.
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
# sqlparser-rs 0.58.0 Changelog
|
||||
|
||||
This release consists of 47 commits from 18 contributors. See credits at the end of this changelog for more information.
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- fix: parse snowflake fetch clause [#1894](https://github.com/apache/datafusion-sqlparser-rs/pull/1894) (Vedin)
|
||||
|
||||
**Documentation updates:**
|
||||
|
||||
- docs: Update rust badge [#1943](https://github.com/apache/datafusion-sqlparser-rs/pull/1943) (Olexandr88)
|
||||
|
||||
**Other:**
|
||||
|
||||
- Add license header check to CI [#1888](https://github.com/apache/datafusion-sqlparser-rs/pull/1888) (alamb)
|
||||
- Add support of parsing struct field's options in BigQuery [#1890](https://github.com/apache/datafusion-sqlparser-rs/pull/1890) (git-hulk)
|
||||
- Fix parsing error when having fields after nested struct in BigQuery [#1897](https://github.com/apache/datafusion-sqlparser-rs/pull/1897) (git-hulk)
|
||||
- Extend exception handling [#1884](https://github.com/apache/datafusion-sqlparser-rs/pull/1884) (bombsimon)
|
||||
- Postgres: Add support for text search types [#1889](https://github.com/apache/datafusion-sqlparser-rs/pull/1889) (MohamedAbdeen21)
|
||||
- Fix `limit` in subqueries [#1899](https://github.com/apache/datafusion-sqlparser-rs/pull/1899) (Dimchikkk)
|
||||
- Use `IndexColumn` in all index definitions [#1900](https://github.com/apache/datafusion-sqlparser-rs/pull/1900) (mvzink)
|
||||
- Support procedure argmode [#1901](https://github.com/apache/datafusion-sqlparser-rs/pull/1901) (ZacJW)
|
||||
- Fix `impl Ord for Ident` [#1893](https://github.com/apache/datafusion-sqlparser-rs/pull/1893) (eliaperantoni)
|
||||
- Snowflake: support multiple column options in `CREATE VIEW` [#1891](https://github.com/apache/datafusion-sqlparser-rs/pull/1891) (eliaperantoni)
|
||||
- Add support for `LANGUAGE` clause in `CREATE PROCEDURE` [#1903](https://github.com/apache/datafusion-sqlparser-rs/pull/1903) (ZacJW)
|
||||
- Fix clippy lints on 1.88.0 [#1910](https://github.com/apache/datafusion-sqlparser-rs/pull/1910) (iffyio)
|
||||
- Snowflake: Add support for future grants [#1906](https://github.com/apache/datafusion-sqlparser-rs/pull/1906) (yoavcloud)
|
||||
- Support for Map values in ClickHouse settings [#1896](https://github.com/apache/datafusion-sqlparser-rs/pull/1896) (solontsev)
|
||||
- Fix join precedence for non-snowflake queries [#1905](https://github.com/apache/datafusion-sqlparser-rs/pull/1905) (Dimchikkk)
|
||||
- Support remaining pipe operators [#1879](https://github.com/apache/datafusion-sqlparser-rs/pull/1879) (simonvandel)
|
||||
- Make `GenericDialect` support from-first syntax [#1911](https://github.com/apache/datafusion-sqlparser-rs/pull/1911) (simonvandel)
|
||||
- Redshift utf8 idents [#1915](https://github.com/apache/datafusion-sqlparser-rs/pull/1915) (yoavcloud)
|
||||
- DuckDB: Add support for multiple `TRIM` arguments [#1916](https://github.com/apache/datafusion-sqlparser-rs/pull/1916) (ryanschneider)
|
||||
- Redshift alter column type no set [#1912](https://github.com/apache/datafusion-sqlparser-rs/pull/1912) (yoavcloud)
|
||||
- Postgres: support `ADD CONSTRAINT NOT VALID` and `VALIDATE CONSTRAINT` [#1908](https://github.com/apache/datafusion-sqlparser-rs/pull/1908) (achristmascarl)
|
||||
- Add support for MySQL MEMBER OF [#1917](https://github.com/apache/datafusion-sqlparser-rs/pull/1917) (yoavcloud)
|
||||
- Add span for `Expr::TypedString` [#1919](https://github.com/apache/datafusion-sqlparser-rs/pull/1919) (feral-dot-io)
|
||||
- Support for Postgres `CREATE SERVER` [#1914](https://github.com/apache/datafusion-sqlparser-rs/pull/1914) (solontsev)
|
||||
- Change tag and policy names to `ObjectName` [#1892](https://github.com/apache/datafusion-sqlparser-rs/pull/1892) (eliaperantoni)
|
||||
- Add support for NULL escape char in pattern match searches [#1913](https://github.com/apache/datafusion-sqlparser-rs/pull/1913) (yoavcloud)
|
||||
- Add support for dropping multiple columns in Snowflake [#1918](https://github.com/apache/datafusion-sqlparser-rs/pull/1918) (yoavcloud)
|
||||
- Align Snowflake dialect to new test of reserved keywords [#1924](https://github.com/apache/datafusion-sqlparser-rs/pull/1924) (yoavcloud)
|
||||
- Make `GenericDialect` support trailing commas in projections [#1921](https://github.com/apache/datafusion-sqlparser-rs/pull/1921) (simonvandel)
|
||||
- Add support for several Snowflake grant statements [#1922](https://github.com/apache/datafusion-sqlparser-rs/pull/1922) (yoavcloud)
|
||||
- Clickhouse: support empty parenthesized options [#1925](https://github.com/apache/datafusion-sqlparser-rs/pull/1925) (solontsev)
|
||||
- Add Snowflake `COPY/REVOKE CURRENT GRANTS` option [#1926](https://github.com/apache/datafusion-sqlparser-rs/pull/1926) (yoavcloud)
|
||||
- Add support for Snowflake identifier function [#1929](https://github.com/apache/datafusion-sqlparser-rs/pull/1929) (yoavcloud)
|
||||
- Add support for granting privileges to procedures and functions in Snowflake [#1930](https://github.com/apache/datafusion-sqlparser-rs/pull/1930) (yoavcloud)
|
||||
- Add support for `+` char in Snowflake stage names [#1935](https://github.com/apache/datafusion-sqlparser-rs/pull/1935) (yoavcloud)
|
||||
- Snowflake Reserved SQL Keywords as Implicit Table Alias [#1934](https://github.com/apache/datafusion-sqlparser-rs/pull/1934) (yoavcloud)
|
||||
- Add support for Redshift `SELECT * EXCLUDE` [#1936](https://github.com/apache/datafusion-sqlparser-rs/pull/1936) (yoavcloud)
|
||||
- Support optional semicolon between statements [#1937](https://github.com/apache/datafusion-sqlparser-rs/pull/1937) (yoavcloud)
|
||||
- Snowflake: support trailing options in `CREATE TABLE` [#1931](https://github.com/apache/datafusion-sqlparser-rs/pull/1931) (yoavcloud)
|
||||
- MSSQL: Add support for EXEC output and default keywords [#1940](https://github.com/apache/datafusion-sqlparser-rs/pull/1940) (yoavcloud)
|
||||
- Add identifier unicode support in Mysql, Postgres and Redshift [#1933](https://github.com/apache/datafusion-sqlparser-rs/pull/1933) (etgarperets)
|
||||
- Add identifier start unicode support for Postegres, MySql and Redshift [#1944](https://github.com/apache/datafusion-sqlparser-rs/pull/1944) (etgarperets)
|
||||
- Fix for Postgres regex and like binary operators [#1928](https://github.com/apache/datafusion-sqlparser-rs/pull/1928) (solontsev)
|
||||
- Snowflake: Improve accuracy of lookahead in implicit LIMIT alias [#1941](https://github.com/apache/datafusion-sqlparser-rs/pull/1941) (yoavcloud)
|
||||
- Add support for `DROP USER` statement [#1951](https://github.com/apache/datafusion-sqlparser-rs/pull/1951) (yoavcloud)
|
||||
|
||||
## Credits
|
||||
|
||||
Thank you to everyone who contributed to this release. Here is a breakdown of commits (PRs merged) per contributor.
|
||||
|
||||
```
|
||||
19 Yoav Cohen
|
||||
4 Sergey Olontsev
|
||||
3 Elia Perantoni
|
||||
3 Simon Vandel Sillesen
|
||||
2 Dima
|
||||
2 ZacJW
|
||||
2 etgarperets
|
||||
2 hulk
|
||||
1 Andrew Lamb
|
||||
1 Denys Tsomenko
|
||||
1 Ifeanyi Ubah
|
||||
1 Michael Victor Zink
|
||||
1 Mohamed Abdeen
|
||||
1 Olexandr88
|
||||
1 Ryan Schneider
|
||||
1 Simon Sawert
|
||||
1 carl
|
||||
1 feral-dot-io
|
||||
```
|
||||
|
||||
Thank you also to everyone who contributed in other ways such as filing issues, reviewing PRs, and providing feedback on this release.
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
# Files to exclude from the Apache Rat (license) check
|
||||
.gitignore
|
||||
.tool-versions
|
||||
target/*
|
||||
**.gitignore
|
||||
rat.txt
|
||||
dev/release/rat_exclude_files.txt
|
||||
fuzz/.gitignore
|
||||
sqlparser_bench/img/flamegraph.svg
|
||||
**Cargo.lock
|
||||
filtered_rat.txt
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ $ cargo run --example cli - [--dialectname]
|
|||
};
|
||||
|
||||
let contents = if filename == "-" {
|
||||
println!("Parsing from stdin using {dialect:?}");
|
||||
println!("Parsing from stdin using {:?}", dialect);
|
||||
let mut buf = Vec::new();
|
||||
stdin()
|
||||
.read_to_end(&mut buf)
|
||||
|
|
|
@ -26,7 +26,7 @@ edition = "2018"
|
|||
sqlparser = { path = "../" }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.7"
|
||||
criterion = "0.5"
|
||||
|
||||
[[bench]]
|
||||
name = "sqlparser_bench"
|
||||
|
|
|
@ -45,29 +45,30 @@ fn basic_queries(c: &mut Criterion) {
|
|||
|
||||
let large_statement = {
|
||||
let expressions = (0..1000)
|
||||
.map(|n| format!("FN_{n}(COL_{n})"))
|
||||
.map(|n| format!("FN_{}(COL_{})", n, n))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let tables = (0..1000)
|
||||
.map(|n| format!("TABLE_{n}"))
|
||||
.map(|n| format!("TABLE_{}", n))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" JOIN ");
|
||||
let where_condition = (0..1000)
|
||||
.map(|n| format!("COL_{n} = {n}"))
|
||||
.map(|n| format!("COL_{} = {}", n, n))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" OR ");
|
||||
let order_condition = (0..1000)
|
||||
.map(|n| format!("COL_{n} DESC"))
|
||||
.map(|n| format!("COL_{} DESC", n))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
format!(
|
||||
"SELECT {expressions} FROM {tables} WHERE {where_condition} ORDER BY {order_condition}"
|
||||
"SELECT {} FROM {} WHERE {} ORDER BY {}",
|
||||
expressions, tables, where_condition, order_condition
|
||||
)
|
||||
};
|
||||
|
||||
group.bench_function("parse_large_statement", |b| {
|
||||
b.iter(|| Parser::parse_sql(&dialect, std::hint::black_box(large_statement.as_str())));
|
||||
b.iter(|| Parser::parse_sql(&dialect, criterion::black_box(large_statement.as_str())));
|
||||
});
|
||||
|
||||
let large_statement = Parser::parse_sql(&dialect, large_statement.as_str())
|
||||
|
|
|
@ -48,17 +48,7 @@ pub enum DataType {
|
|||
/// Table type in [PostgreSQL], e.g. CREATE FUNCTION RETURNS TABLE(...).
|
||||
///
|
||||
/// [PostgreSQL]: https://www.postgresql.org/docs/15/sql-createfunction.html
|
||||
/// [MsSQL]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql?view=sql-server-ver16#c-create-a-multi-statement-table-valued-function
|
||||
Table(Option<Vec<ColumnDef>>),
|
||||
/// Table type with a name, e.g. CREATE FUNCTION RETURNS @result TABLE(...).
|
||||
///
|
||||
/// [MsSQl]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql?view=sql-server-ver16#table
|
||||
NamedTable {
|
||||
/// Table name.
|
||||
name: ObjectName,
|
||||
/// Table columns.
|
||||
columns: Vec<ColumnDef>,
|
||||
},
|
||||
Table(Vec<ColumnDef>),
|
||||
/// Fixed-length character type, e.g. CHARACTER(10).
|
||||
Character(Option<CharacterLength>),
|
||||
/// Fixed-length char type, e.g. CHAR(10).
|
||||
|
@ -346,16 +336,7 @@ pub enum DataType {
|
|||
/// [1]: https://docs.databricks.com/aws/en/sql/language-manual/data-types/timestamp-ntz-type
|
||||
TimestampNtz,
|
||||
/// Interval type.
|
||||
Interval {
|
||||
/// [PostgreSQL] fields specification like `INTERVAL YEAR TO MONTH`.
|
||||
///
|
||||
/// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-datetime.html
|
||||
fields: Option<IntervalFields>,
|
||||
/// [PostgreSQL] subsecond precision like `INTERVAL HOUR TO SECOND(3)`
|
||||
///
|
||||
/// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-datetime.html
|
||||
precision: Option<u64>,
|
||||
},
|
||||
Interval,
|
||||
/// JSON type.
|
||||
JSON,
|
||||
/// Binary JSON type.
|
||||
|
@ -455,14 +436,6 @@ pub enum DataType {
|
|||
///
|
||||
/// [PostgreSQL]: https://www.postgresql.org/docs/9.5/functions-geometry.html
|
||||
GeometricType(GeometricTypeKind),
|
||||
/// PostgreSQL text search vectors, see [PostgreSQL].
|
||||
///
|
||||
/// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-textsearch.html
|
||||
TsVector,
|
||||
/// PostgreSQL text search query, see [PostgreSQL].
|
||||
///
|
||||
/// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-textsearch.html
|
||||
TsQuery,
|
||||
}
|
||||
|
||||
impl fmt::Display for DataType {
|
||||
|
@ -644,16 +617,7 @@ impl fmt::Display for DataType {
|
|||
timezone,
|
||||
)
|
||||
}
|
||||
DataType::Interval { fields, precision } => {
|
||||
write!(f, "INTERVAL")?;
|
||||
if let Some(fields) = fields {
|
||||
write!(f, " {fields}")?;
|
||||
}
|
||||
if let Some(precision) = precision {
|
||||
write!(f, "({precision})")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
DataType::Interval => write!(f, "INTERVAL"),
|
||||
DataType::JSON => write!(f, "JSON"),
|
||||
DataType::JSONB => write!(f, "JSONB"),
|
||||
DataType::Regclass => write!(f, "REGCLASS"),
|
||||
|
@ -684,7 +648,7 @@ impl fmt::Display for DataType {
|
|||
}
|
||||
DataType::Enum(vals, bits) => {
|
||||
match bits {
|
||||
Some(bits) => write!(f, "ENUM{bits}"),
|
||||
Some(bits) => write!(f, "ENUM{}", bits),
|
||||
None => write!(f, "ENUM"),
|
||||
}?;
|
||||
write!(f, "(")?;
|
||||
|
@ -732,16 +696,16 @@ impl fmt::Display for DataType {
|
|||
}
|
||||
// ClickHouse
|
||||
DataType::Nullable(data_type) => {
|
||||
write!(f, "Nullable({data_type})")
|
||||
write!(f, "Nullable({})", data_type)
|
||||
}
|
||||
DataType::FixedString(character_length) => {
|
||||
write!(f, "FixedString({character_length})")
|
||||
write!(f, "FixedString({})", character_length)
|
||||
}
|
||||
DataType::LowCardinality(data_type) => {
|
||||
write!(f, "LowCardinality({data_type})")
|
||||
write!(f, "LowCardinality({})", data_type)
|
||||
}
|
||||
DataType::Map(key_data_type, value_data_type) => {
|
||||
write!(f, "Map({key_data_type}, {value_data_type})")
|
||||
write!(f, "Map({}, {})", key_data_type, value_data_type)
|
||||
}
|
||||
DataType::Tuple(fields) => {
|
||||
write!(f, "Tuple({})", display_comma_separated(fields))
|
||||
|
@ -752,20 +716,8 @@ impl fmt::Display for DataType {
|
|||
DataType::Unspecified => Ok(()),
|
||||
DataType::Trigger => write!(f, "TRIGGER"),
|
||||
DataType::AnyType => write!(f, "ANY TYPE"),
|
||||
DataType::Table(fields) => match fields {
|
||||
Some(fields) => {
|
||||
write!(f, "TABLE({})", display_comma_separated(fields))
|
||||
}
|
||||
None => {
|
||||
write!(f, "TABLE")
|
||||
}
|
||||
},
|
||||
DataType::NamedTable { name, columns } => {
|
||||
write!(f, "{} TABLE ({})", name, display_comma_separated(columns))
|
||||
}
|
||||
DataType::GeometricType(kind) => write!(f, "{kind}"),
|
||||
DataType::TsVector => write!(f, "TSVECTOR"),
|
||||
DataType::TsQuery => write!(f, "TSQUERY"),
|
||||
DataType::Table(fields) => write!(f, "TABLE({})", display_comma_separated(fields)),
|
||||
DataType::GeometricType(kind) => write!(f, "{}", kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -907,48 +859,6 @@ impl fmt::Display for TimezoneInfo {
|
|||
}
|
||||
}
|
||||
|
||||
/// Fields for [Postgres] `INTERVAL` type.
|
||||
///
|
||||
/// [Postgres]: https://www.postgresql.org/docs/17/datatype-datetime.html
|
||||
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum IntervalFields {
|
||||
Year,
|
||||
Month,
|
||||
Day,
|
||||
Hour,
|
||||
Minute,
|
||||
Second,
|
||||
YearToMonth,
|
||||
DayToHour,
|
||||
DayToMinute,
|
||||
DayToSecond,
|
||||
HourToMinute,
|
||||
HourToSecond,
|
||||
MinuteToSecond,
|
||||
}
|
||||
|
||||
impl fmt::Display for IntervalFields {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
IntervalFields::Year => write!(f, "YEAR"),
|
||||
IntervalFields::Month => write!(f, "MONTH"),
|
||||
IntervalFields::Day => write!(f, "DAY"),
|
||||
IntervalFields::Hour => write!(f, "HOUR"),
|
||||
IntervalFields::Minute => write!(f, "MINUTE"),
|
||||
IntervalFields::Second => write!(f, "SECOND"),
|
||||
IntervalFields::YearToMonth => write!(f, "YEAR TO MONTH"),
|
||||
IntervalFields::DayToHour => write!(f, "DAY TO HOUR"),
|
||||
IntervalFields::DayToMinute => write!(f, "DAY TO MINUTE"),
|
||||
IntervalFields::DayToSecond => write!(f, "DAY TO SECOND"),
|
||||
IntervalFields::HourToMinute => write!(f, "HOUR TO MINUTE"),
|
||||
IntervalFields::HourToSecond => write!(f, "HOUR TO SECOND"),
|
||||
IntervalFields::MinuteToSecond => write!(f, "MINUTE TO SECOND"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional information for `NUMERIC`, `DECIMAL`, and `DEC` data types
|
||||
/// following the 2016 [SQL Standard].
|
||||
///
|
||||
|
@ -1002,7 +912,7 @@ impl fmt::Display for CharacterLength {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
CharacterLength::IntegerLength { length, unit } => {
|
||||
write!(f, "{length}")?;
|
||||
write!(f, "{}", length)?;
|
||||
if let Some(unit) = unit {
|
||||
write!(f, " {unit}")?;
|
||||
}
|
||||
|
@ -1057,7 +967,7 @@ impl fmt::Display for BinaryLength {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
BinaryLength::IntegerLength { length } => {
|
||||
write!(f, "{length}")?;
|
||||
write!(f, "{}", length)?;
|
||||
}
|
||||
BinaryLength::Max => {
|
||||
write!(f, "MAX")?;
|
||||
|
|
|
@ -173,7 +173,7 @@ impl fmt::Display for AlterRoleOperation {
|
|||
in_database,
|
||||
} => {
|
||||
if let Some(database_name) = in_database {
|
||||
write!(f, "IN DATABASE {database_name} ")?;
|
||||
write!(f, "IN DATABASE {} ", database_name)?;
|
||||
}
|
||||
|
||||
match config_value {
|
||||
|
@ -187,7 +187,7 @@ impl fmt::Display for AlterRoleOperation {
|
|||
in_database,
|
||||
} => {
|
||||
if let Some(database_name) = in_database {
|
||||
write!(f, "IN DATABASE {database_name} ")?;
|
||||
write!(f, "IN DATABASE {} ", database_name)?;
|
||||
}
|
||||
|
||||
match config_name {
|
||||
|
@ -218,15 +218,15 @@ impl fmt::Display for Use {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("USE ")?;
|
||||
match self {
|
||||
Use::Catalog(name) => write!(f, "CATALOG {name}"),
|
||||
Use::Schema(name) => write!(f, "SCHEMA {name}"),
|
||||
Use::Database(name) => write!(f, "DATABASE {name}"),
|
||||
Use::Warehouse(name) => write!(f, "WAREHOUSE {name}"),
|
||||
Use::Role(name) => write!(f, "ROLE {name}"),
|
||||
Use::Catalog(name) => write!(f, "CATALOG {}", name),
|
||||
Use::Schema(name) => write!(f, "SCHEMA {}", name),
|
||||
Use::Database(name) => write!(f, "DATABASE {}", name),
|
||||
Use::Warehouse(name) => write!(f, "WAREHOUSE {}", name),
|
||||
Use::Role(name) => write!(f, "ROLE {}", name),
|
||||
Use::SecondaryRoles(secondary_roles) => {
|
||||
write!(f, "SECONDARY ROLES {secondary_roles}")
|
||||
write!(f, "SECONDARY ROLES {}", secondary_roles)
|
||||
}
|
||||
Use::Object(name) => write!(f, "{name}"),
|
||||
Use::Object(name) => write!(f, "{}", name),
|
||||
Use::Default => write!(f, "DEFAULT"),
|
||||
}
|
||||
}
|
||||
|
|
280
src/ast/ddl.rs
280
src/ast/ddl.rs
|
@ -30,48 +30,22 @@ use sqlparser_derive::{Visit, VisitMut};
|
|||
|
||||
use crate::ast::value::escape_single_quote_string;
|
||||
use crate::ast::{
|
||||
display_comma_separated, display_separated, ArgMode, CommentDef, CreateFunctionBody,
|
||||
display_comma_separated, display_separated, CommentDef, CreateFunctionBody,
|
||||
CreateFunctionUsing, DataType, Expr, FunctionBehavior, FunctionCalledOnNull,
|
||||
FunctionDeterminismSpecifier, FunctionParallel, Ident, IndexColumn, MySQLColumnPosition,
|
||||
ObjectName, OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag,
|
||||
Value, ValueWithSpan,
|
||||
FunctionDeterminismSpecifier, FunctionParallel, Ident, MySQLColumnPosition, ObjectName,
|
||||
OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, Value,
|
||||
ValueWithSpan,
|
||||
};
|
||||
use crate::keywords::Keyword;
|
||||
use crate::tokenizer::Token;
|
||||
|
||||
/// ALTER TABLE operation REPLICA IDENTITY values
|
||||
/// See [Postgres ALTER TABLE docs](https://www.postgresql.org/docs/current/sql-altertable.html)
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum ReplicaIdentity {
|
||||
None,
|
||||
Full,
|
||||
Default,
|
||||
Index(Ident),
|
||||
}
|
||||
|
||||
impl fmt::Display for ReplicaIdentity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ReplicaIdentity::None => f.write_str("NONE"),
|
||||
ReplicaIdentity::Full => f.write_str("FULL"),
|
||||
ReplicaIdentity::Default => f.write_str("DEFAULT"),
|
||||
ReplicaIdentity::Index(idx) => write!(f, "USING INDEX {idx}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An `ALTER TABLE` (`Statement::AlterTable`) operation
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum AlterTableOperation {
|
||||
/// `ADD <table_constraint> [NOT VALID]`
|
||||
AddConstraint {
|
||||
constraint: TableConstraint,
|
||||
not_valid: bool,
|
||||
},
|
||||
/// `ADD <table_constraint>`
|
||||
AddConstraint(TableConstraint),
|
||||
/// `ADD [COLUMN] [IF NOT EXISTS] <column_def>`
|
||||
AddColumn {
|
||||
/// `[COLUMN]`.
|
||||
|
@ -140,16 +114,15 @@ pub enum AlterTableOperation {
|
|||
name: Ident,
|
||||
drop_behavior: Option<DropBehavior>,
|
||||
},
|
||||
/// `DROP [ COLUMN ] [ IF EXISTS ] <column_name> [ , <column_name>, ... ] [ CASCADE ]`
|
||||
/// `DROP [ COLUMN ] [ IF EXISTS ] <column_name> [ CASCADE ]`
|
||||
DropColumn {
|
||||
has_column_keyword: bool,
|
||||
column_names: Vec<Ident>,
|
||||
column_name: Ident,
|
||||
if_exists: bool,
|
||||
drop_behavior: Option<DropBehavior>,
|
||||
},
|
||||
/// `ATTACH PART|PARTITION <partition_expr>`
|
||||
/// Note: this is a ClickHouse-specific operation, please refer to
|
||||
/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#attach-partitionpart)
|
||||
/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/pakrtition#attach-partitionpart)
|
||||
AttachPartition {
|
||||
// PART is not a short form of PARTITION, it's a separate keyword
|
||||
// which represents a physical file on disk and partition is a logical entity.
|
||||
|
@ -190,12 +163,6 @@ pub enum AlterTableOperation {
|
|||
DropForeignKey {
|
||||
name: Ident,
|
||||
},
|
||||
/// `DROP INDEX <index_name>`
|
||||
///
|
||||
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/alter-table.html
|
||||
DropIndex {
|
||||
name: Ident,
|
||||
},
|
||||
/// `ENABLE ALWAYS RULE rewrite_rule_name`
|
||||
///
|
||||
/// Note: this is a PostgreSQL-specific operation.
|
||||
|
@ -241,13 +208,6 @@ pub enum AlterTableOperation {
|
|||
old_partitions: Vec<Expr>,
|
||||
new_partitions: Vec<Expr>,
|
||||
},
|
||||
/// REPLICA IDENTITY { DEFAULT | USING INDEX index_name | FULL | NOTHING }
|
||||
///
|
||||
/// Note: this is a PostgreSQL-specific operation.
|
||||
/// Please refer to [PostgreSQL documentation](https://www.postgresql.org/docs/current/sql-altertable.html)
|
||||
ReplicaIdentity {
|
||||
identity: ReplicaIdentity,
|
||||
},
|
||||
/// Add Partitions
|
||||
AddPartitions {
|
||||
if_not_exists: bool,
|
||||
|
@ -347,20 +307,6 @@ pub enum AlterTableOperation {
|
|||
equals: bool,
|
||||
value: ValueWithSpan,
|
||||
},
|
||||
/// `VALIDATE CONSTRAINT <name>`
|
||||
ValidateConstraint {
|
||||
name: Ident,
|
||||
},
|
||||
/// Arbitrary parenthesized `SET` options.
|
||||
///
|
||||
/// Example:
|
||||
/// ```sql
|
||||
/// SET (scale_factor = 0.01, threshold = 500)`
|
||||
/// ```
|
||||
/// [PostgreSQL](https://www.postgresql.org/docs/current/sql-altertable.html)
|
||||
SetOptionsParens {
|
||||
options: Vec<SqlOption>,
|
||||
},
|
||||
}
|
||||
|
||||
/// An `ALTER Policy` (`Statement::AlterPolicy`) operation
|
||||
|
@ -467,7 +413,7 @@ pub enum Owner {
|
|||
impl fmt::Display for Owner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Owner::Ident(ident) => write!(f, "{ident}"),
|
||||
Owner::Ident(ident) => write!(f, "{}", ident),
|
||||
Owner::CurrentRole => write!(f, "CURRENT_ROLE"),
|
||||
Owner::CurrentUser => write!(f, "CURRENT_USER"),
|
||||
Owner::SessionUser => write!(f, "SESSION_USER"),
|
||||
|
@ -511,16 +457,7 @@ impl fmt::Display for AlterTableOperation {
|
|||
display_separated(new_partitions, " "),
|
||||
ine = if *if_not_exists { " IF NOT EXISTS" } else { "" }
|
||||
),
|
||||
AlterTableOperation::AddConstraint {
|
||||
not_valid,
|
||||
constraint,
|
||||
} => {
|
||||
write!(f, "ADD {constraint}")?;
|
||||
if *not_valid {
|
||||
write!(f, " NOT VALID")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
AlterTableOperation::AddConstraint(c) => write!(f, "ADD {c}"),
|
||||
AlterTableOperation::AddColumn {
|
||||
column_keyword,
|
||||
if_not_exists,
|
||||
|
@ -551,7 +488,7 @@ impl fmt::Display for AlterTableOperation {
|
|||
if *if_not_exists {
|
||||
write!(f, " IF NOT EXISTS")?;
|
||||
}
|
||||
write!(f, " {name} ({query})")
|
||||
write!(f, " {} ({})", name, query)
|
||||
}
|
||||
AlterTableOperation::Algorithm { equals, algorithm } => {
|
||||
write!(
|
||||
|
@ -566,7 +503,7 @@ impl fmt::Display for AlterTableOperation {
|
|||
if *if_exists {
|
||||
write!(f, " IF EXISTS")?;
|
||||
}
|
||||
write!(f, " {name}")
|
||||
write!(f, " {}", name)
|
||||
}
|
||||
AlterTableOperation::MaterializeProjection {
|
||||
if_exists,
|
||||
|
@ -577,9 +514,9 @@ impl fmt::Display for AlterTableOperation {
|
|||
if *if_exists {
|
||||
write!(f, " IF EXISTS")?;
|
||||
}
|
||||
write!(f, " {name}")?;
|
||||
write!(f, " {}", name)?;
|
||||
if let Some(partition) = partition {
|
||||
write!(f, " IN PARTITION {partition}")?;
|
||||
write!(f, " IN PARTITION {}", partition)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -592,9 +529,9 @@ impl fmt::Display for AlterTableOperation {
|
|||
if *if_exists {
|
||||
write!(f, " IF EXISTS")?;
|
||||
}
|
||||
write!(f, " {name}")?;
|
||||
write!(f, " {}", name)?;
|
||||
if let Some(partition) = partition {
|
||||
write!(f, " IN PARTITION {partition}")?;
|
||||
write!(f, " IN PARTITION {}", partition)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -638,18 +575,15 @@ impl fmt::Display for AlterTableOperation {
|
|||
}
|
||||
AlterTableOperation::DropPrimaryKey => write!(f, "DROP PRIMARY KEY"),
|
||||
AlterTableOperation::DropForeignKey { name } => write!(f, "DROP FOREIGN KEY {name}"),
|
||||
AlterTableOperation::DropIndex { name } => write!(f, "DROP INDEX {name}"),
|
||||
AlterTableOperation::DropColumn {
|
||||
has_column_keyword,
|
||||
column_names: column_name,
|
||||
column_name,
|
||||
if_exists,
|
||||
drop_behavior,
|
||||
} => write!(
|
||||
f,
|
||||
"DROP {}{}{}{}",
|
||||
if *has_column_keyword { "COLUMN " } else { "" },
|
||||
"DROP COLUMN {}{}{}",
|
||||
if *if_exists { "IF EXISTS " } else { "" },
|
||||
display_comma_separated(column_name),
|
||||
column_name,
|
||||
match drop_behavior {
|
||||
None => "",
|
||||
Some(DropBehavior::Restrict) => " RESTRICT",
|
||||
|
@ -795,15 +729,6 @@ impl fmt::Display for AlterTableOperation {
|
|||
AlterTableOperation::Lock { equals, lock } => {
|
||||
write!(f, "LOCK {}{}", if *equals { "= " } else { "" }, lock)
|
||||
}
|
||||
AlterTableOperation::ReplicaIdentity { identity } => {
|
||||
write!(f, "REPLICA IDENTITY {identity}")
|
||||
}
|
||||
AlterTableOperation::ValidateConstraint { name } => {
|
||||
write!(f, "VALIDATE CONSTRAINT {name}")
|
||||
}
|
||||
AlterTableOperation::SetOptionsParens { options } => {
|
||||
write!(f, "SET ({})", display_comma_separated(options))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -925,10 +850,7 @@ pub enum AlterColumnOperation {
|
|||
data_type: DataType,
|
||||
/// PostgreSQL specific
|
||||
using: Option<Expr>,
|
||||
/// Set to true if the statement includes the `SET DATA TYPE` keywords
|
||||
had_set: bool,
|
||||
},
|
||||
|
||||
/// `ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( sequence_options ) ]`
|
||||
///
|
||||
/// Note: this is a PostgreSQL-specific operation.
|
||||
|
@ -949,19 +871,12 @@ impl fmt::Display for AlterColumnOperation {
|
|||
AlterColumnOperation::DropDefault => {
|
||||
write!(f, "DROP DEFAULT")
|
||||
}
|
||||
AlterColumnOperation::SetDataType {
|
||||
data_type,
|
||||
using,
|
||||
had_set,
|
||||
} => {
|
||||
if *had_set {
|
||||
write!(f, "SET DATA ")?;
|
||||
}
|
||||
write!(f, "TYPE {data_type}")?;
|
||||
AlterColumnOperation::SetDataType { data_type, using } => {
|
||||
if let Some(expr) = using {
|
||||
write!(f, " USING {expr}")?;
|
||||
write!(f, "SET DATA TYPE {data_type} USING {expr}")
|
||||
} else {
|
||||
write!(f, "SET DATA TYPE {data_type}")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
AlterColumnOperation::AddGenerated {
|
||||
generated_as,
|
||||
|
@ -1021,7 +936,7 @@ pub enum TableConstraint {
|
|||
/// [1]: IndexType
|
||||
index_type: Option<IndexType>,
|
||||
/// Identifiers of the columns that are unique.
|
||||
columns: Vec<IndexColumn>,
|
||||
columns: Vec<Ident>,
|
||||
index_options: Vec<IndexOption>,
|
||||
characteristics: Option<ConstraintCharacteristics>,
|
||||
/// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]`
|
||||
|
@ -1057,7 +972,7 @@ pub enum TableConstraint {
|
|||
/// [1]: IndexType
|
||||
index_type: Option<IndexType>,
|
||||
/// Identifiers of the columns that form the primary key.
|
||||
columns: Vec<IndexColumn>,
|
||||
columns: Vec<Ident>,
|
||||
index_options: Vec<IndexOption>,
|
||||
characteristics: Option<ConstraintCharacteristics>,
|
||||
},
|
||||
|
@ -1068,9 +983,6 @@ pub enum TableConstraint {
|
|||
/// }`).
|
||||
ForeignKey {
|
||||
name: Option<Ident>,
|
||||
/// MySQL-specific field
|
||||
/// <https://dev.mysql.com/doc/refman/8.4/en/create-table-foreign-keys.html>
|
||||
index_name: Option<Ident>,
|
||||
columns: Vec<Ident>,
|
||||
foreign_table: ObjectName,
|
||||
referred_columns: Vec<Ident>,
|
||||
|
@ -1078,13 +990,10 @@ pub enum TableConstraint {
|
|||
on_update: Option<ReferentialAction>,
|
||||
characteristics: Option<ConstraintCharacteristics>,
|
||||
},
|
||||
/// `[ CONSTRAINT <name> ] CHECK (<expr>) [[NOT] ENFORCED]`
|
||||
/// `[ CONSTRAINT <name> ] CHECK (<expr>)`
|
||||
Check {
|
||||
name: Option<Ident>,
|
||||
expr: Box<Expr>,
|
||||
/// MySQL-specific syntax
|
||||
/// <https://dev.mysql.com/doc/refman/8.4/en/create-table.html>
|
||||
enforced: Option<bool>,
|
||||
},
|
||||
/// MySQLs [index definition][1] for index creation. Not present on ANSI so, for now, the usage
|
||||
/// is restricted to MySQL, as no other dialects that support this syntax were found.
|
||||
|
@ -1102,7 +1011,7 @@ pub enum TableConstraint {
|
|||
/// [1]: IndexType
|
||||
index_type: Option<IndexType>,
|
||||
/// Referred column identifier list.
|
||||
columns: Vec<IndexColumn>,
|
||||
columns: Vec<Ident>,
|
||||
},
|
||||
/// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same,
|
||||
/// and MySQL displays both the same way, it is part of this definition as well.
|
||||
|
@ -1125,7 +1034,7 @@ pub enum TableConstraint {
|
|||
/// Optional index name.
|
||||
opt_index_name: Option<Ident>,
|
||||
/// Referred column identifier list.
|
||||
columns: Vec<IndexColumn>,
|
||||
columns: Vec<Ident>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1184,7 +1093,6 @@ impl fmt::Display for TableConstraint {
|
|||
}
|
||||
TableConstraint::ForeignKey {
|
||||
name,
|
||||
index_name,
|
||||
columns,
|
||||
foreign_table,
|
||||
referred_columns,
|
||||
|
@ -1194,9 +1102,8 @@ impl fmt::Display for TableConstraint {
|
|||
} => {
|
||||
write!(
|
||||
f,
|
||||
"{}FOREIGN KEY{} ({}) REFERENCES {}",
|
||||
"{}FOREIGN KEY ({}) REFERENCES {}",
|
||||
display_constraint_name(name),
|
||||
display_option_spaced(index_name),
|
||||
display_comma_separated(columns),
|
||||
foreign_table,
|
||||
)?;
|
||||
|
@ -1210,21 +1117,12 @@ impl fmt::Display for TableConstraint {
|
|||
write!(f, " ON UPDATE {action}")?;
|
||||
}
|
||||
if let Some(characteristics) = characteristics {
|
||||
write!(f, " {characteristics}")?;
|
||||
write!(f, " {}", characteristics)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
TableConstraint::Check {
|
||||
name,
|
||||
expr,
|
||||
enforced,
|
||||
} => {
|
||||
write!(f, "{}CHECK ({})", display_constraint_name(name), expr)?;
|
||||
if let Some(b) = enforced {
|
||||
write!(f, " {}", if *b { "ENFORCED" } else { "NOT ENFORCED" })
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
TableConstraint::Check { name, expr } => {
|
||||
write!(f, "{}CHECK ({})", display_constraint_name(name), expr)
|
||||
}
|
||||
TableConstraint::Index {
|
||||
display_as_key,
|
||||
|
@ -1350,7 +1248,7 @@ impl fmt::Display for IndexType {
|
|||
Self::SPGiST => write!(f, "SPGIST"),
|
||||
Self::BRIN => write!(f, "BRIN"),
|
||||
Self::Bloom => write!(f, "BLOOM"),
|
||||
Self::Custom(name) => write!(f, "{name}"),
|
||||
Self::Custom(name) => write!(f, "{}", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1409,16 +1307,11 @@ impl fmt::Display for NullsDistinctOption {
|
|||
pub struct ProcedureParam {
|
||||
pub name: Ident,
|
||||
pub data_type: DataType,
|
||||
pub mode: Option<ArgMode>,
|
||||
}
|
||||
|
||||
impl fmt::Display for ProcedureParam {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let Some(mode) = &self.mode {
|
||||
write!(f, "{mode} {} {}", self.name, self.data_type)
|
||||
} else {
|
||||
write!(f, "{} {}", self.name, self.data_type)
|
||||
}
|
||||
write!(f, "{} {}", self.name, self.data_type)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1468,41 +1361,17 @@ impl fmt::Display for ColumnDef {
|
|||
pub struct ViewColumnDef {
|
||||
pub name: Ident,
|
||||
pub data_type: Option<DataType>,
|
||||
pub options: Option<ColumnOptions>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum ColumnOptions {
|
||||
CommaSeparated(Vec<ColumnOption>),
|
||||
SpaceSeparated(Vec<ColumnOption>),
|
||||
}
|
||||
|
||||
impl ColumnOptions {
|
||||
pub fn as_slice(&self) -> &[ColumnOption] {
|
||||
match self {
|
||||
ColumnOptions::CommaSeparated(options) => options.as_slice(),
|
||||
ColumnOptions::SpaceSeparated(options) => options.as_slice(),
|
||||
}
|
||||
}
|
||||
pub options: Option<Vec<ColumnOption>>,
|
||||
}
|
||||
|
||||
impl fmt::Display for ViewColumnDef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.name)?;
|
||||
if let Some(data_type) = self.data_type.as_ref() {
|
||||
write!(f, " {data_type}")?;
|
||||
write!(f, " {}", data_type)?;
|
||||
}
|
||||
if let Some(options) = self.options.as_ref() {
|
||||
match options {
|
||||
ColumnOptions::CommaSeparated(column_options) => {
|
||||
write!(f, " {}", display_comma_separated(column_options.as_slice()))?;
|
||||
}
|
||||
ColumnOptions::SpaceSeparated(column_options) => {
|
||||
write!(f, " {}", display_separated(column_options.as_slice(), " "))?
|
||||
}
|
||||
}
|
||||
write!(f, " {}", display_comma_separated(options.as_slice()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1722,7 +1591,7 @@ pub struct ColumnPolicyProperty {
|
|||
/// ```
|
||||
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
|
||||
pub with: bool,
|
||||
pub policy_name: ObjectName,
|
||||
pub policy_name: Ident,
|
||||
pub using_columns: Option<Vec<Ident>>,
|
||||
}
|
||||
|
||||
|
@ -1856,13 +1725,6 @@ pub enum ColumnOption {
|
|||
/// ```
|
||||
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
|
||||
Tags(TagsColumnOption),
|
||||
/// MySQL specific: Spatial reference identifier
|
||||
/// Syntax:
|
||||
/// ```sql
|
||||
/// CREATE TABLE geom (g GEOMETRY NOT NULL SRID 4326);
|
||||
/// ```
|
||||
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/creating-spatial-indexes.html
|
||||
Srid(Box<Expr>),
|
||||
}
|
||||
|
||||
impl fmt::Display for ColumnOption {
|
||||
|
@ -1887,7 +1749,7 @@ impl fmt::Display for ColumnOption {
|
|||
} => {
|
||||
write!(f, "{}", if *is_primary { "PRIMARY KEY" } else { "UNIQUE" })?;
|
||||
if let Some(characteristics) = characteristics {
|
||||
write!(f, " {characteristics}")?;
|
||||
write!(f, " {}", characteristics)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1909,7 +1771,7 @@ impl fmt::Display for ColumnOption {
|
|||
write!(f, " ON UPDATE {action}")?;
|
||||
}
|
||||
if let Some(characteristics) = characteristics {
|
||||
write!(f, " {characteristics}")?;
|
||||
write!(f, " {}", characteristics)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1969,7 +1831,7 @@ impl fmt::Display for ColumnOption {
|
|||
write!(f, "{parameters}")
|
||||
}
|
||||
OnConflict(keyword) => {
|
||||
write!(f, "ON CONFLICT {keyword:?}")?;
|
||||
write!(f, "ON CONFLICT {:?}", keyword)?;
|
||||
Ok(())
|
||||
}
|
||||
Policy(parameters) => {
|
||||
|
@ -1978,9 +1840,6 @@ impl fmt::Display for ColumnOption {
|
|||
Tags(tags) => {
|
||||
write!(f, "{tags}")
|
||||
}
|
||||
Srid(srid) => {
|
||||
write!(f, "SRID {srid}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2294,55 +2153,6 @@ impl fmt::Display for ClusteredBy {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
/// ```sql
|
||||
/// CREATE DOMAIN name [ AS ] data_type
|
||||
/// [ COLLATE collation ]
|
||||
/// [ DEFAULT expression ]
|
||||
/// [ domain_constraint [ ... ] ]
|
||||
///
|
||||
/// where domain_constraint is:
|
||||
///
|
||||
/// [ CONSTRAINT constraint_name ]
|
||||
/// { NOT NULL | NULL | CHECK (expression) }
|
||||
/// ```
|
||||
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createdomain.html)
|
||||
pub struct CreateDomain {
|
||||
/// The name of the domain to be created.
|
||||
pub name: ObjectName,
|
||||
/// The data type of the domain.
|
||||
pub data_type: DataType,
|
||||
/// The collation of the domain.
|
||||
pub collation: Option<Ident>,
|
||||
/// The default value of the domain.
|
||||
pub default: Option<Expr>,
|
||||
/// The constraints of the domain.
|
||||
pub constraints: Vec<TableConstraint>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CreateDomain {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"CREATE DOMAIN {name} AS {data_type}",
|
||||
name = self.name,
|
||||
data_type = self.data_type
|
||||
)?;
|
||||
if let Some(collation) = &self.collation {
|
||||
write!(f, " COLLATE {collation}")?;
|
||||
}
|
||||
if let Some(default) = &self.default {
|
||||
write!(f, " DEFAULT {default}")?;
|
||||
}
|
||||
if !self.constraints.is_empty() {
|
||||
write!(f, " {}", display_separated(&self.constraints, " "))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
|
@ -2454,12 +2264,6 @@ impl fmt::Display for CreateFunction {
|
|||
if let Some(CreateFunctionBody::Return(function_body)) = &self.function_body {
|
||||
write!(f, " RETURN {function_body}")?;
|
||||
}
|
||||
if let Some(CreateFunctionBody::AsReturnExpr(function_body)) = &self.function_body {
|
||||
write!(f, " AS RETURN {function_body}")?;
|
||||
}
|
||||
if let Some(CreateFunctionBody::AsReturnSelect(function_body)) = &self.function_body {
|
||||
write!(f, " AS RETURN {function_body}")?;
|
||||
}
|
||||
if let Some(using) = &self.using {
|
||||
write!(f, " {using}")?;
|
||||
}
|
||||
|
|
157
src/ast/dml.rs
157
src/ast/dml.rs
|
@ -29,17 +29,15 @@ use serde::{Deserialize, Serialize};
|
|||
#[cfg(feature = "visitor")]
|
||||
use sqlparser_derive::{Visit, VisitMut};
|
||||
|
||||
use crate::display_utils::{indented_list, DisplayCommaSeparated, Indent, NewLine, SpaceOrNewline};
|
||||
|
||||
pub use super::ddl::{ColumnDef, TableConstraint};
|
||||
|
||||
use super::{
|
||||
display_comma_separated, display_separated, query::InputFormatClause, Assignment, ClusteredBy,
|
||||
CommentDef, CreateTableOptions, Expr, FileFormat, FromTable, HiveDistributionStyle, HiveFormat,
|
||||
HiveIOFormat, HiveRowFormat, Ident, IndexType, InsertAliases, MysqlInsertPriority, ObjectName,
|
||||
OnCommit, OnInsert, OneOrManyWithParens, OrderByExpr, Query, RowAccessPolicy, SelectItem,
|
||||
Setting, SqliteOnConflict, StorageSerializationPolicy, TableObject, TableWithJoins, Tag,
|
||||
WrappedCollection,
|
||||
CommentDef, Expr, FileFormat, FromTable, HiveDistributionStyle, HiveFormat, HiveIOFormat,
|
||||
HiveRowFormat, Ident, IndexType, InsertAliases, MysqlInsertPriority, ObjectName, OnCommit,
|
||||
OnInsert, OneOrManyWithParens, OrderByExpr, Query, RowAccessPolicy, SelectItem, Setting,
|
||||
SqlOption, SqliteOnConflict, StorageSerializationPolicy, TableEngine, TableObject,
|
||||
TableWithJoins, Tag, WrappedCollection,
|
||||
};
|
||||
|
||||
/// Index column type.
|
||||
|
@ -55,7 +53,7 @@ impl Display for IndexColumn {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.column)?;
|
||||
if let Some(operator_class) = &self.operator_class {
|
||||
write!(f, " {operator_class}")?;
|
||||
write!(f, " {}", operator_class)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -148,17 +146,19 @@ pub struct CreateTable {
|
|||
pub constraints: Vec<TableConstraint>,
|
||||
pub hive_distribution: HiveDistributionStyle,
|
||||
pub hive_formats: Option<HiveFormat>,
|
||||
pub table_options: CreateTableOptions,
|
||||
pub table_properties: Vec<SqlOption>,
|
||||
pub with_options: Vec<SqlOption>,
|
||||
pub file_format: Option<FileFormat>,
|
||||
pub location: Option<String>,
|
||||
pub query: Option<Box<Query>>,
|
||||
pub without_rowid: bool,
|
||||
pub like: Option<ObjectName>,
|
||||
pub clone: Option<ObjectName>,
|
||||
// For Hive dialect, the table comment is after the column definitions without `=`,
|
||||
// so the `comment` field is optional and different than the comment field in the general options list.
|
||||
// [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateTable)
|
||||
pub engine: Option<TableEngine>,
|
||||
pub comment: Option<CommentDef>,
|
||||
pub auto_increment_offset: Option<u32>,
|
||||
pub default_charset: Option<String>,
|
||||
pub collation: Option<String>,
|
||||
pub on_commit: Option<OnCommit>,
|
||||
/// ClickHouse "ON CLUSTER" clause:
|
||||
/// <https://clickhouse.com/docs/en/sql-reference/distributed-ddl/>
|
||||
|
@ -175,12 +175,13 @@ pub struct CreateTable {
|
|||
pub partition_by: Option<Box<Expr>>,
|
||||
/// BigQuery: Table clustering column list.
|
||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
|
||||
/// Snowflake: Table clustering list which contains base column, expressions on base columns.
|
||||
/// <https://docs.snowflake.com/en/user-guide/tables-clustering-keys#defining-a-clustering-key-for-a-table>
|
||||
pub cluster_by: Option<WrappedCollection<Vec<Expr>>>,
|
||||
pub cluster_by: Option<WrappedCollection<Vec<Ident>>>,
|
||||
/// Hive: Table clustering column list.
|
||||
/// <https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateTable>
|
||||
pub clustered_by: Option<ClusteredBy>,
|
||||
/// BigQuery: Table options list.
|
||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
|
||||
pub options: Option<Vec<SqlOption>>,
|
||||
/// Postgres `INHERITs` clause, which contains the list of tables from which
|
||||
/// the new table inherits.
|
||||
/// <https://www.postgresql.org/docs/current/ddl-inherit.html>
|
||||
|
@ -266,27 +267,22 @@ impl Display for CreateTable {
|
|||
name = self.name,
|
||||
)?;
|
||||
if let Some(on_cluster) = &self.on_cluster {
|
||||
write!(f, " ON CLUSTER {on_cluster}")?;
|
||||
write!(f, " ON CLUSTER {}", on_cluster)?;
|
||||
}
|
||||
if !self.columns.is_empty() || !self.constraints.is_empty() {
|
||||
f.write_str(" (")?;
|
||||
NewLine.fmt(f)?;
|
||||
Indent(DisplayCommaSeparated(&self.columns)).fmt(f)?;
|
||||
write!(f, " ({}", display_comma_separated(&self.columns))?;
|
||||
if !self.columns.is_empty() && !self.constraints.is_empty() {
|
||||
f.write_str(",")?;
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
Indent(DisplayCommaSeparated(&self.constraints)).fmt(f)?;
|
||||
NewLine.fmt(f)?;
|
||||
f.write_str(")")?;
|
||||
write!(f, "{})", display_comma_separated(&self.constraints))?;
|
||||
} else if self.query.is_none() && self.like.is_none() && self.clone.is_none() {
|
||||
// PostgreSQL allows `CREATE TABLE t ();`, but requires empty parens
|
||||
f.write_str(" ()")?;
|
||||
write!(f, " ()")?;
|
||||
}
|
||||
|
||||
// Hive table comment should be after column definitions, please refer to:
|
||||
// [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateTable)
|
||||
if let Some(comment) = &self.comment {
|
||||
if let Some(CommentDef::AfterColumnDefsWithoutEq(comment)) = &self.comment {
|
||||
write!(f, " COMMENT '{comment}'")?;
|
||||
}
|
||||
|
||||
|
@ -379,19 +375,40 @@ impl Display for CreateTable {
|
|||
}
|
||||
write!(f, " LOCATION '{}'", self.location.as_ref().unwrap())?;
|
||||
}
|
||||
|
||||
match &self.table_options {
|
||||
options @ CreateTableOptions::With(_)
|
||||
| options @ CreateTableOptions::Plain(_)
|
||||
| options @ CreateTableOptions::TableProperties(_) => write!(f, " {options}")?,
|
||||
_ => (),
|
||||
if !self.table_properties.is_empty() {
|
||||
write!(
|
||||
f,
|
||||
" TBLPROPERTIES ({})",
|
||||
display_comma_separated(&self.table_properties)
|
||||
)?;
|
||||
}
|
||||
if !self.with_options.is_empty() {
|
||||
write!(f, " WITH ({})", display_comma_separated(&self.with_options))?;
|
||||
}
|
||||
if let Some(engine) = &self.engine {
|
||||
write!(f, " ENGINE={engine}")?;
|
||||
}
|
||||
if let Some(comment_def) = &self.comment {
|
||||
match comment_def {
|
||||
CommentDef::WithEq(comment) => {
|
||||
write!(f, " COMMENT = '{comment}'")?;
|
||||
}
|
||||
CommentDef::WithoutEq(comment) => {
|
||||
write!(f, " COMMENT '{comment}'")?;
|
||||
}
|
||||
// For CommentDef::AfterColumnDefsWithoutEq will be displayed after column definition
|
||||
CommentDef::AfterColumnDefsWithoutEq(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(auto_increment_offset) = self.auto_increment_offset {
|
||||
write!(f, " AUTO_INCREMENT {auto_increment_offset}")?;
|
||||
}
|
||||
if let Some(primary_key) = &self.primary_key {
|
||||
write!(f, " PRIMARY KEY {primary_key}")?;
|
||||
write!(f, " PRIMARY KEY {}", primary_key)?;
|
||||
}
|
||||
if let Some(order_by) = &self.order_by {
|
||||
write!(f, " ORDER BY {order_by}")?;
|
||||
write!(f, " ORDER BY {}", order_by)?;
|
||||
}
|
||||
if let Some(inherits) = &self.inherits {
|
||||
write!(f, " INHERITS ({})", display_comma_separated(inherits))?;
|
||||
|
@ -402,9 +419,15 @@ impl Display for CreateTable {
|
|||
if let Some(cluster_by) = self.cluster_by.as_ref() {
|
||||
write!(f, " CLUSTER BY {cluster_by}")?;
|
||||
}
|
||||
if let options @ CreateTableOptions::Options(_) = &self.table_options {
|
||||
write!(f, " {options}")?;
|
||||
|
||||
if let Some(options) = self.options.as_ref() {
|
||||
write!(
|
||||
f,
|
||||
" OPTIONS({})",
|
||||
display_comma_separated(options.as_slice())
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(external_volume) = self.external_volume.as_ref() {
|
||||
write!(f, " EXTERNAL_VOLUME = '{external_volume}'")?;
|
||||
}
|
||||
|
@ -480,6 +503,13 @@ impl Display for CreateTable {
|
|||
write!(f, " WITH TAG ({})", display_comma_separated(tag.as_slice()))?;
|
||||
}
|
||||
|
||||
if let Some(default_charset) = &self.default_charset {
|
||||
write!(f, " DEFAULT CHARSET={default_charset}")?;
|
||||
}
|
||||
if let Some(collation) = &self.collation {
|
||||
write!(f, " COLLATE={collation}")?;
|
||||
}
|
||||
|
||||
if self.on_commit.is_some() {
|
||||
let on_commit = match self.on_commit {
|
||||
Some(OnCommit::DeleteRows) => "ON COMMIT DELETE ROWS",
|
||||
|
@ -588,32 +618,28 @@ impl Display for Insert {
|
|||
)?;
|
||||
}
|
||||
if !self.columns.is_empty() {
|
||||
write!(f, "({})", display_comma_separated(&self.columns))?;
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
write!(f, "({}) ", display_comma_separated(&self.columns))?;
|
||||
}
|
||||
if let Some(ref parts) = self.partitioned {
|
||||
if !parts.is_empty() {
|
||||
write!(f, "PARTITION ({})", display_comma_separated(parts))?;
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
write!(f, "PARTITION ({}) ", display_comma_separated(parts))?;
|
||||
}
|
||||
}
|
||||
if !self.after_columns.is_empty() {
|
||||
write!(f, "({})", display_comma_separated(&self.after_columns))?;
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
write!(f, "({}) ", display_comma_separated(&self.after_columns))?;
|
||||
}
|
||||
|
||||
if let Some(settings) = &self.settings {
|
||||
write!(f, "SETTINGS {}", display_comma_separated(settings))?;
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
write!(f, "SETTINGS {} ", display_comma_separated(settings))?;
|
||||
}
|
||||
|
||||
if let Some(source) = &self.source {
|
||||
source.fmt(f)?;
|
||||
write!(f, "{source}")?;
|
||||
} else if !self.assignments.is_empty() {
|
||||
write!(f, "SET")?;
|
||||
indented_list(f, &self.assignments)?;
|
||||
write!(f, "SET ")?;
|
||||
write!(f, "{}", display_comma_separated(&self.assignments))?;
|
||||
} else if let Some(format_clause) = &self.format_clause {
|
||||
format_clause.fmt(f)?;
|
||||
write!(f, "{format_clause}")?;
|
||||
} else if self.columns.is_empty() {
|
||||
write!(f, "DEFAULT VALUES")?;
|
||||
}
|
||||
|
@ -633,9 +659,7 @@ impl Display for Insert {
|
|||
}
|
||||
|
||||
if let Some(returning) = &self.returning {
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
f.write_str("RETURNING")?;
|
||||
indented_list(f, returning)?;
|
||||
write!(f, " RETURNING {}", display_comma_separated(returning))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -664,45 +688,32 @@ pub struct Delete {
|
|||
|
||||
impl Display for Delete {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("DELETE")?;
|
||||
write!(f, "DELETE ")?;
|
||||
if !self.tables.is_empty() {
|
||||
indented_list(f, &self.tables)?;
|
||||
write!(f, "{} ", display_comma_separated(&self.tables))?;
|
||||
}
|
||||
match &self.from {
|
||||
FromTable::WithFromKeyword(from) => {
|
||||
f.write_str(" FROM")?;
|
||||
indented_list(f, from)?;
|
||||
write!(f, "FROM {}", display_comma_separated(from))?;
|
||||
}
|
||||
FromTable::WithoutKeyword(from) => {
|
||||
indented_list(f, from)?;
|
||||
write!(f, "{}", display_comma_separated(from))?;
|
||||
}
|
||||
}
|
||||
if let Some(using) = &self.using {
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
f.write_str("USING")?;
|
||||
indented_list(f, using)?;
|
||||
write!(f, " USING {}", display_comma_separated(using))?;
|
||||
}
|
||||
if let Some(selection) = &self.selection {
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
f.write_str("WHERE")?;
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
Indent(selection).fmt(f)?;
|
||||
write!(f, " WHERE {selection}")?;
|
||||
}
|
||||
if let Some(returning) = &self.returning {
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
f.write_str("RETURNING")?;
|
||||
indented_list(f, returning)?;
|
||||
write!(f, " RETURNING {}", display_comma_separated(returning))?;
|
||||
}
|
||||
if !self.order_by.is_empty() {
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
f.write_str("ORDER BY")?;
|
||||
indented_list(f, &self.order_by)?;
|
||||
write!(f, " ORDER BY {}", display_comma_separated(&self.order_by))?;
|
||||
}
|
||||
if let Some(limit) = &self.limit {
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
f.write_str("LIMIT")?;
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
Indent(limit).fmt(f)?;
|
||||
write!(f, " LIMIT {limit}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -31,22 +31,11 @@ use serde::{Deserialize, Serialize};
|
|||
#[cfg(feature = "visitor")]
|
||||
use sqlparser_derive::{Visit, VisitMut};
|
||||
|
||||
use crate::ast::display_separated;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct KeyValueOptions {
|
||||
pub options: Vec<KeyValueOption>,
|
||||
pub delimiter: KeyValueOptionsDelimiter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum KeyValueOptionsDelimiter {
|
||||
Space,
|
||||
Comma,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
@ -70,11 +59,18 @@ pub struct KeyValueOption {
|
|||
|
||||
impl fmt::Display for KeyValueOptions {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let sep = match self.delimiter {
|
||||
KeyValueOptionsDelimiter::Space => " ",
|
||||
KeyValueOptionsDelimiter::Comma => ", ",
|
||||
};
|
||||
write!(f, "{}", display_separated(&self.options, sep))
|
||||
if !self.options.is_empty() {
|
||||
let mut first = false;
|
||||
for option in &self.options {
|
||||
if !first {
|
||||
first = true;
|
||||
} else {
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
write!(f, "{}", option)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,5 @@
|
|||
// under the License.
|
||||
pub mod attached_token;
|
||||
pub mod key_value_options;
|
||||
pub mod stmt_create_database;
|
||||
pub mod stmt_create_table;
|
||||
pub mod stmt_data_loading;
|
||||
|
|
|
@ -1,324 +0,0 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{boxed::Box, format, string::String, vec, vec::Vec};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "visitor")]
|
||||
use sqlparser_derive::{Visit, VisitMut};
|
||||
|
||||
use crate::ast::{
|
||||
CatalogSyncNamespaceMode, ContactEntry, ObjectName, Statement, StorageSerializationPolicy, Tag,
|
||||
};
|
||||
use crate::parser::ParserError;
|
||||
|
||||
/// Builder for create database statement variant ([1]).
|
||||
///
|
||||
/// This structure helps building and accessing a create database with more ease, without needing to:
|
||||
/// - Match the enum itself a lot of times; or
|
||||
/// - Moving a lot of variables around the code.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use sqlparser::ast::helpers::stmt_create_database::CreateDatabaseBuilder;
|
||||
/// use sqlparser::ast::{ColumnDef, Ident, ObjectName};
|
||||
/// let builder = CreateDatabaseBuilder::new(ObjectName::from(vec![Ident::new("database_name")]))
|
||||
/// .if_not_exists(true);
|
||||
/// // You can access internal elements with ease
|
||||
/// assert!(builder.if_not_exists);
|
||||
/// // Convert to a statement
|
||||
/// assert_eq!(
|
||||
/// builder.build().to_string(),
|
||||
/// "CREATE DATABASE IF NOT EXISTS database_name"
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// [1]: Statement::CreateDatabase
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct CreateDatabaseBuilder {
|
||||
pub db_name: ObjectName,
|
||||
pub if_not_exists: bool,
|
||||
pub location: Option<String>,
|
||||
pub managed_location: Option<String>,
|
||||
pub or_replace: bool,
|
||||
pub transient: bool,
|
||||
pub clone: Option<ObjectName>,
|
||||
pub data_retention_time_in_days: Option<u64>,
|
||||
pub max_data_extension_time_in_days: Option<u64>,
|
||||
pub external_volume: Option<String>,
|
||||
pub catalog: Option<String>,
|
||||
pub replace_invalid_characters: Option<bool>,
|
||||
pub default_ddl_collation: Option<String>,
|
||||
pub storage_serialization_policy: Option<StorageSerializationPolicy>,
|
||||
pub comment: Option<String>,
|
||||
pub catalog_sync: Option<String>,
|
||||
pub catalog_sync_namespace_mode: Option<CatalogSyncNamespaceMode>,
|
||||
pub catalog_sync_namespace_flatten_delimiter: Option<String>,
|
||||
pub with_tags: Option<Vec<Tag>>,
|
||||
pub with_contacts: Option<Vec<ContactEntry>>,
|
||||
}
|
||||
|
||||
impl CreateDatabaseBuilder {
|
||||
pub fn new(name: ObjectName) -> Self {
|
||||
Self {
|
||||
db_name: name,
|
||||
if_not_exists: false,
|
||||
location: None,
|
||||
managed_location: None,
|
||||
or_replace: false,
|
||||
transient: false,
|
||||
clone: None,
|
||||
data_retention_time_in_days: None,
|
||||
max_data_extension_time_in_days: None,
|
||||
external_volume: None,
|
||||
catalog: None,
|
||||
replace_invalid_characters: None,
|
||||
default_ddl_collation: None,
|
||||
storage_serialization_policy: None,
|
||||
comment: None,
|
||||
catalog_sync: None,
|
||||
catalog_sync_namespace_mode: None,
|
||||
catalog_sync_namespace_flatten_delimiter: None,
|
||||
with_tags: None,
|
||||
with_contacts: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn location(mut self, location: Option<String>) -> Self {
|
||||
self.location = location;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn managed_location(mut self, managed_location: Option<String>) -> Self {
|
||||
self.managed_location = managed_location;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn or_replace(mut self, or_replace: bool) -> Self {
|
||||
self.or_replace = or_replace;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn transient(mut self, transient: bool) -> Self {
|
||||
self.transient = transient;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn if_not_exists(mut self, if_not_exists: bool) -> Self {
|
||||
self.if_not_exists = if_not_exists;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn clone_clause(mut self, clone: Option<ObjectName>) -> Self {
|
||||
self.clone = clone;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn data_retention_time_in_days(mut self, data_retention_time_in_days: Option<u64>) -> Self {
|
||||
self.data_retention_time_in_days = data_retention_time_in_days;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_data_extension_time_in_days(
|
||||
mut self,
|
||||
max_data_extension_time_in_days: Option<u64>,
|
||||
) -> Self {
|
||||
self.max_data_extension_time_in_days = max_data_extension_time_in_days;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn external_volume(mut self, external_volume: Option<String>) -> Self {
|
||||
self.external_volume = external_volume;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn catalog(mut self, catalog: Option<String>) -> Self {
|
||||
self.catalog = catalog;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn replace_invalid_characters(mut self, replace_invalid_characters: Option<bool>) -> Self {
|
||||
self.replace_invalid_characters = replace_invalid_characters;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn default_ddl_collation(mut self, default_ddl_collation: Option<String>) -> Self {
|
||||
self.default_ddl_collation = default_ddl_collation;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn storage_serialization_policy(
|
||||
mut self,
|
||||
storage_serialization_policy: Option<StorageSerializationPolicy>,
|
||||
) -> Self {
|
||||
self.storage_serialization_policy = storage_serialization_policy;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn comment(mut self, comment: Option<String>) -> Self {
|
||||
self.comment = comment;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn catalog_sync(mut self, catalog_sync: Option<String>) -> Self {
|
||||
self.catalog_sync = catalog_sync;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn catalog_sync_namespace_mode(
|
||||
mut self,
|
||||
catalog_sync_namespace_mode: Option<CatalogSyncNamespaceMode>,
|
||||
) -> Self {
|
||||
self.catalog_sync_namespace_mode = catalog_sync_namespace_mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn catalog_sync_namespace_flatten_delimiter(
|
||||
mut self,
|
||||
catalog_sync_namespace_flatten_delimiter: Option<String>,
|
||||
) -> Self {
|
||||
self.catalog_sync_namespace_flatten_delimiter = catalog_sync_namespace_flatten_delimiter;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_tags(mut self, with_tags: Option<Vec<Tag>>) -> Self {
|
||||
self.with_tags = with_tags;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_contacts(mut self, with_contacts: Option<Vec<ContactEntry>>) -> Self {
|
||||
self.with_contacts = with_contacts;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Statement {
|
||||
Statement::CreateDatabase {
|
||||
db_name: self.db_name,
|
||||
if_not_exists: self.if_not_exists,
|
||||
managed_location: self.managed_location,
|
||||
location: self.location,
|
||||
or_replace: self.or_replace,
|
||||
transient: self.transient,
|
||||
clone: self.clone,
|
||||
data_retention_time_in_days: self.data_retention_time_in_days,
|
||||
max_data_extension_time_in_days: self.max_data_extension_time_in_days,
|
||||
external_volume: self.external_volume,
|
||||
catalog: self.catalog,
|
||||
replace_invalid_characters: self.replace_invalid_characters,
|
||||
default_ddl_collation: self.default_ddl_collation,
|
||||
storage_serialization_policy: self.storage_serialization_policy,
|
||||
comment: self.comment,
|
||||
catalog_sync: self.catalog_sync,
|
||||
catalog_sync_namespace_mode: self.catalog_sync_namespace_mode,
|
||||
catalog_sync_namespace_flatten_delimiter: self.catalog_sync_namespace_flatten_delimiter,
|
||||
with_tags: self.with_tags,
|
||||
with_contacts: self.with_contacts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Statement> for CreateDatabaseBuilder {
|
||||
type Error = ParserError;
|
||||
|
||||
fn try_from(stmt: Statement) -> Result<Self, Self::Error> {
|
||||
match stmt {
|
||||
Statement::CreateDatabase {
|
||||
db_name,
|
||||
if_not_exists,
|
||||
location,
|
||||
managed_location,
|
||||
or_replace,
|
||||
transient,
|
||||
clone,
|
||||
data_retention_time_in_days,
|
||||
max_data_extension_time_in_days,
|
||||
external_volume,
|
||||
catalog,
|
||||
replace_invalid_characters,
|
||||
default_ddl_collation,
|
||||
storage_serialization_policy,
|
||||
comment,
|
||||
catalog_sync,
|
||||
catalog_sync_namespace_mode,
|
||||
catalog_sync_namespace_flatten_delimiter,
|
||||
with_tags,
|
||||
with_contacts,
|
||||
} => Ok(Self {
|
||||
db_name,
|
||||
if_not_exists,
|
||||
location,
|
||||
managed_location,
|
||||
or_replace,
|
||||
transient,
|
||||
clone,
|
||||
data_retention_time_in_days,
|
||||
max_data_extension_time_in_days,
|
||||
external_volume,
|
||||
catalog,
|
||||
replace_invalid_characters,
|
||||
default_ddl_collation,
|
||||
storage_serialization_policy,
|
||||
comment,
|
||||
catalog_sync,
|
||||
catalog_sync_namespace_mode,
|
||||
catalog_sync_namespace_flatten_delimiter,
|
||||
with_tags,
|
||||
with_contacts,
|
||||
}),
|
||||
_ => Err(ParserError::ParserError(format!(
|
||||
"Expected create database statement, but received: {stmt}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ast::helpers::stmt_create_database::CreateDatabaseBuilder;
|
||||
use crate::ast::{Ident, ObjectName, Statement};
|
||||
use crate::parser::ParserError;
|
||||
|
||||
#[test]
|
||||
pub fn test_from_valid_statement() {
|
||||
let builder = CreateDatabaseBuilder::new(ObjectName::from(vec![Ident::new("db_name")]));
|
||||
|
||||
let stmt = builder.clone().build();
|
||||
|
||||
assert_eq!(builder, CreateDatabaseBuilder::try_from(stmt).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_from_invalid_statement() {
|
||||
let stmt = Statement::Commit {
|
||||
chain: false,
|
||||
end: false,
|
||||
modifier: None,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
CreateDatabaseBuilder::try_from(stmt).unwrap_err(),
|
||||
ParserError::ParserError(
|
||||
"Expected create database statement, but received: COMMIT".to_owned()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -26,12 +26,10 @@ use sqlparser_derive::{Visit, VisitMut};
|
|||
|
||||
use super::super::dml::CreateTable;
|
||||
use crate::ast::{
|
||||
ClusteredBy, ColumnDef, CommentDef, CreateTableOptions, Expr, FileFormat,
|
||||
HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit, OneOrManyWithParens, Query,
|
||||
RowAccessPolicy, Statement, StorageSerializationPolicy, TableConstraint, Tag,
|
||||
WrappedCollection,
|
||||
ClusteredBy, ColumnDef, CommentDef, Expr, FileFormat, HiveDistributionStyle, HiveFormat, Ident,
|
||||
ObjectName, OnCommit, OneOrManyWithParens, Query, RowAccessPolicy, SqlOption, Statement,
|
||||
StorageSerializationPolicy, TableConstraint, TableEngine, Tag, WrappedCollection,
|
||||
};
|
||||
|
||||
use crate::parser::ParserError;
|
||||
|
||||
/// Builder for create table statement variant ([1]).
|
||||
|
@ -78,20 +76,27 @@ pub struct CreateTableBuilder {
|
|||
pub constraints: Vec<TableConstraint>,
|
||||
pub hive_distribution: HiveDistributionStyle,
|
||||
pub hive_formats: Option<HiveFormat>,
|
||||
pub table_properties: Vec<SqlOption>,
|
||||
pub with_options: Vec<SqlOption>,
|
||||
pub file_format: Option<FileFormat>,
|
||||
pub location: Option<String>,
|
||||
pub query: Option<Box<Query>>,
|
||||
pub without_rowid: bool,
|
||||
pub like: Option<ObjectName>,
|
||||
pub clone: Option<ObjectName>,
|
||||
pub engine: Option<TableEngine>,
|
||||
pub comment: Option<CommentDef>,
|
||||
pub auto_increment_offset: Option<u32>,
|
||||
pub default_charset: Option<String>,
|
||||
pub collation: Option<String>,
|
||||
pub on_commit: Option<OnCommit>,
|
||||
pub on_cluster: Option<Ident>,
|
||||
pub primary_key: Option<Box<Expr>>,
|
||||
pub order_by: Option<OneOrManyWithParens<Expr>>,
|
||||
pub partition_by: Option<Box<Expr>>,
|
||||
pub cluster_by: Option<WrappedCollection<Vec<Expr>>>,
|
||||
pub cluster_by: Option<WrappedCollection<Vec<Ident>>>,
|
||||
pub clustered_by: Option<ClusteredBy>,
|
||||
pub options: Option<Vec<SqlOption>>,
|
||||
pub inherits: Option<Vec<ObjectName>>,
|
||||
pub strict: bool,
|
||||
pub copy_grants: bool,
|
||||
|
@ -108,7 +113,6 @@ pub struct CreateTableBuilder {
|
|||
pub catalog: Option<String>,
|
||||
pub catalog_sync: Option<String>,
|
||||
pub storage_serialization_policy: Option<StorageSerializationPolicy>,
|
||||
pub table_options: CreateTableOptions,
|
||||
}
|
||||
|
||||
impl CreateTableBuilder {
|
||||
|
@ -127,13 +131,19 @@ impl CreateTableBuilder {
|
|||
constraints: vec![],
|
||||
hive_distribution: HiveDistributionStyle::NONE,
|
||||
hive_formats: None,
|
||||
table_properties: vec![],
|
||||
with_options: vec![],
|
||||
file_format: None,
|
||||
location: None,
|
||||
query: None,
|
||||
without_rowid: false,
|
||||
like: None,
|
||||
clone: None,
|
||||
engine: None,
|
||||
comment: None,
|
||||
auto_increment_offset: None,
|
||||
default_charset: None,
|
||||
collation: None,
|
||||
on_commit: None,
|
||||
on_cluster: None,
|
||||
primary_key: None,
|
||||
|
@ -141,6 +151,7 @@ impl CreateTableBuilder {
|
|||
partition_by: None,
|
||||
cluster_by: None,
|
||||
clustered_by: None,
|
||||
options: None,
|
||||
inherits: None,
|
||||
strict: false,
|
||||
copy_grants: false,
|
||||
|
@ -157,7 +168,6 @@ impl CreateTableBuilder {
|
|||
catalog: None,
|
||||
catalog_sync: None,
|
||||
storage_serialization_policy: None,
|
||||
table_options: CreateTableOptions::None,
|
||||
}
|
||||
}
|
||||
pub fn or_replace(mut self, or_replace: bool) -> Self {
|
||||
|
@ -220,6 +230,15 @@ impl CreateTableBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn table_properties(mut self, table_properties: Vec<SqlOption>) -> Self {
|
||||
self.table_properties = table_properties;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_options(mut self, with_options: Vec<SqlOption>) -> Self {
|
||||
self.with_options = with_options;
|
||||
self
|
||||
}
|
||||
pub fn file_format(mut self, file_format: Option<FileFormat>) -> Self {
|
||||
self.file_format = file_format;
|
||||
self
|
||||
|
@ -249,11 +268,31 @@ impl CreateTableBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn comment_after_column_def(mut self, comment: Option<CommentDef>) -> Self {
|
||||
pub fn engine(mut self, engine: Option<TableEngine>) -> Self {
|
||||
self.engine = engine;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn comment(mut self, comment: Option<CommentDef>) -> Self {
|
||||
self.comment = comment;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn auto_increment_offset(mut self, offset: Option<u32>) -> Self {
|
||||
self.auto_increment_offset = offset;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn default_charset(mut self, default_charset: Option<String>) -> Self {
|
||||
self.default_charset = default_charset;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn collation(mut self, collation: Option<String>) -> Self {
|
||||
self.collation = collation;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_commit(mut self, on_commit: Option<OnCommit>) -> Self {
|
||||
self.on_commit = on_commit;
|
||||
self
|
||||
|
@ -279,7 +318,7 @@ impl CreateTableBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn cluster_by(mut self, cluster_by: Option<WrappedCollection<Vec<Expr>>>) -> Self {
|
||||
pub fn cluster_by(mut self, cluster_by: Option<WrappedCollection<Vec<Ident>>>) -> Self {
|
||||
self.cluster_by = cluster_by;
|
||||
self
|
||||
}
|
||||
|
@ -289,6 +328,11 @@ impl CreateTableBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn options(mut self, options: Option<Vec<SqlOption>>) -> Self {
|
||||
self.options = options;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn inherits(mut self, inherits: Option<Vec<ObjectName>>) -> Self {
|
||||
self.inherits = inherits;
|
||||
self
|
||||
|
@ -378,31 +422,6 @@ impl CreateTableBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn table_options(mut self, table_options: CreateTableOptions) -> Self {
|
||||
self.table_options = table_options;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns true if the statement has exactly one source of info on the schema of the new table.
|
||||
/// This is Snowflake-specific, some dialects allow more than one source.
|
||||
pub(crate) fn validate_schema_info(&self) -> bool {
|
||||
let mut sources = 0;
|
||||
if !self.columns.is_empty() {
|
||||
sources += 1;
|
||||
}
|
||||
if self.query.is_some() {
|
||||
sources += 1;
|
||||
}
|
||||
if self.like.is_some() {
|
||||
sources += 1;
|
||||
}
|
||||
if self.clone.is_some() {
|
||||
sources += 1;
|
||||
}
|
||||
|
||||
sources == 1
|
||||
}
|
||||
|
||||
pub fn build(self) -> Statement {
|
||||
Statement::CreateTable(CreateTable {
|
||||
or_replace: self.or_replace,
|
||||
|
@ -418,13 +437,19 @@ impl CreateTableBuilder {
|
|||
constraints: self.constraints,
|
||||
hive_distribution: self.hive_distribution,
|
||||
hive_formats: self.hive_formats,
|
||||
table_properties: self.table_properties,
|
||||
with_options: self.with_options,
|
||||
file_format: self.file_format,
|
||||
location: self.location,
|
||||
query: self.query,
|
||||
without_rowid: self.without_rowid,
|
||||
like: self.like,
|
||||
clone: self.clone,
|
||||
engine: self.engine,
|
||||
comment: self.comment,
|
||||
auto_increment_offset: self.auto_increment_offset,
|
||||
default_charset: self.default_charset,
|
||||
collation: self.collation,
|
||||
on_commit: self.on_commit,
|
||||
on_cluster: self.on_cluster,
|
||||
primary_key: self.primary_key,
|
||||
|
@ -432,6 +457,7 @@ impl CreateTableBuilder {
|
|||
partition_by: self.partition_by,
|
||||
cluster_by: self.cluster_by,
|
||||
clustered_by: self.clustered_by,
|
||||
options: self.options,
|
||||
inherits: self.inherits,
|
||||
strict: self.strict,
|
||||
copy_grants: self.copy_grants,
|
||||
|
@ -448,7 +474,6 @@ impl CreateTableBuilder {
|
|||
catalog: self.catalog,
|
||||
catalog_sync: self.catalog_sync,
|
||||
storage_serialization_policy: self.storage_serialization_policy,
|
||||
table_options: self.table_options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -474,13 +499,19 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
|||
constraints,
|
||||
hive_distribution,
|
||||
hive_formats,
|
||||
table_properties,
|
||||
with_options,
|
||||
file_format,
|
||||
location,
|
||||
query,
|
||||
without_rowid,
|
||||
like,
|
||||
clone,
|
||||
engine,
|
||||
comment,
|
||||
auto_increment_offset,
|
||||
default_charset,
|
||||
collation,
|
||||
on_commit,
|
||||
on_cluster,
|
||||
primary_key,
|
||||
|
@ -488,6 +519,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
|||
partition_by,
|
||||
cluster_by,
|
||||
clustered_by,
|
||||
options,
|
||||
inherits,
|
||||
strict,
|
||||
copy_grants,
|
||||
|
@ -504,7 +536,6 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
|||
catalog,
|
||||
catalog_sync,
|
||||
storage_serialization_policy,
|
||||
table_options,
|
||||
}) => Ok(Self {
|
||||
or_replace,
|
||||
temporary,
|
||||
|
@ -517,13 +548,19 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
|||
constraints,
|
||||
hive_distribution,
|
||||
hive_formats,
|
||||
table_properties,
|
||||
with_options,
|
||||
file_format,
|
||||
location,
|
||||
query,
|
||||
without_rowid,
|
||||
like,
|
||||
clone,
|
||||
engine,
|
||||
comment,
|
||||
auto_increment_offset,
|
||||
default_charset,
|
||||
collation,
|
||||
on_commit,
|
||||
on_cluster,
|
||||
primary_key,
|
||||
|
@ -531,6 +568,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
|||
partition_by,
|
||||
cluster_by,
|
||||
clustered_by,
|
||||
options,
|
||||
inherits,
|
||||
strict,
|
||||
iceberg,
|
||||
|
@ -549,7 +587,6 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
|||
catalog,
|
||||
catalog_sync,
|
||||
storage_serialization_policy,
|
||||
table_options,
|
||||
}),
|
||||
_ => Err(ParserError::ParserError(format!(
|
||||
"Expected create table statement, but received: {stmt}"
|
||||
|
@ -562,9 +599,9 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
|||
#[derive(Default)]
|
||||
pub(crate) struct CreateTableConfiguration {
|
||||
pub partition_by: Option<Box<Expr>>,
|
||||
pub cluster_by: Option<WrappedCollection<Vec<Expr>>>,
|
||||
pub cluster_by: Option<WrappedCollection<Vec<Ident>>>,
|
||||
pub options: Option<Vec<SqlOption>>,
|
||||
pub inherits: Option<Vec<ObjectName>>,
|
||||
pub table_options: CreateTableOptions,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::string::String;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::vec::Vec;
|
||||
use core::fmt;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
|
|
1569
src/ast/mod.rs
1569
src/ast/mod.rs
File diff suppressed because it is too large
Load diff
|
@ -139,11 +139,6 @@ pub enum BinaryOperator {
|
|||
DuckIntegerDivide,
|
||||
/// MySQL [`DIV`](https://dev.mysql.com/doc/refman/8.0/en/arithmetic-functions.html) integer division
|
||||
MyIntegerDivide,
|
||||
/// MATCH operator, e.g. `a MATCH b` (SQLite-specific)
|
||||
/// See <https://www.sqlite.org/lang_expr.html#the_like_glob_regexp_match_and_extract_operators>
|
||||
Match,
|
||||
/// REGEXP operator, e.g. `a REGEXP b` (SQLite-specific)
|
||||
Regexp,
|
||||
/// Support for custom operators (such as Postgres custom operators)
|
||||
Custom(String),
|
||||
/// Bitwise XOR, e.g. `a # b` (PostgreSQL-specific)
|
||||
|
@ -355,8 +350,6 @@ impl fmt::Display for BinaryOperator {
|
|||
BinaryOperator::BitwiseXor => f.write_str("^"),
|
||||
BinaryOperator::DuckIntegerDivide => f.write_str("//"),
|
||||
BinaryOperator::MyIntegerDivide => f.write_str("DIV"),
|
||||
BinaryOperator::Match => f.write_str("MATCH"),
|
||||
BinaryOperator::Regexp => f.write_str("REGEXP"),
|
||||
BinaryOperator::Custom(s) => f.write_str(s),
|
||||
BinaryOperator::PGBitwiseXor => f.write_str("#"),
|
||||
BinaryOperator::PGBitwiseShiftLeft => f.write_str("<<"),
|
||||
|
|
771
src/ast/query.rs
771
src/ast/query.rs
File diff suppressed because it is too large
Load diff
226
src/ast/spans.rs
226
src/ast/spans.rs
|
@ -15,7 +15,7 @@
|
|||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
use crate::ast::{query::SelectItemQualifiedWildcardKind, ColumnOptions, ExportData, TypedString};
|
||||
use crate::ast::query::SelectItemQualifiedWildcardKind;
|
||||
use core::iter;
|
||||
|
||||
use crate::tokenizer::Span;
|
||||
|
@ -28,16 +28,15 @@ use super::{
|
|||
ConstraintCharacteristics, CopySource, CreateIndex, CreateTable, CreateTableOptions, Cte,
|
||||
Delete, DoUpdate, ExceptSelectItem, ExcludeSelectItem, Expr, ExprWithAlias, Fetch, FromTable,
|
||||
Function, FunctionArg, FunctionArgExpr, FunctionArgumentClause, FunctionArgumentList,
|
||||
FunctionArguments, GroupByExpr, HavingBound, IfStatement, IlikeSelectItem, IndexColumn, Insert,
|
||||
Interpolate, InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem,
|
||||
LateralView, LimitClause, MatchRecognizePattern, Measure, NamedParenthesizedList,
|
||||
NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction,
|
||||
OnInsert, OpenStatement, OrderBy, OrderByExpr, OrderByKind, Partition, PivotValueSource,
|
||||
ProjectionSelect, Query, RaiseStatement, RaiseStatementValue, ReferentialAction,
|
||||
RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem,
|
||||
SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef,
|
||||
TableConstraint, TableFactor, TableObject, TableOptionsClustered, TableWithJoins,
|
||||
UpdateTableFromKind, Use, Value, Values, ViewColumnDef, WhileStatement,
|
||||
FunctionArguments, GroupByExpr, HavingBound, IfStatement, IlikeSelectItem, Insert, Interpolate,
|
||||
InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView,
|
||||
LimitClause, MatchRecognizePattern, Measure, NamedWindowDefinition, ObjectName, ObjectNamePart,
|
||||
Offset, OnConflict, OnConflictAction, OnInsert, OrderBy, OrderByExpr, OrderByKind, Partition,
|
||||
PivotValueSource, ProjectionSelect, Query, RaiseStatement, RaiseStatementValue,
|
||||
ReferentialAction, RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select,
|
||||
SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias,
|
||||
TableAliasColumnDef, TableConstraint, TableFactor, TableObject, TableOptionsClustered,
|
||||
TableWithJoins, UpdateTableFromKind, Use, Value, Values, ViewColumnDef,
|
||||
WildcardAdditionalOptions, With, WithFill,
|
||||
};
|
||||
|
||||
|
@ -99,11 +98,10 @@ impl Spanned for Query {
|
|||
order_by,
|
||||
limit_clause,
|
||||
fetch,
|
||||
locks: _, // todo
|
||||
for_clause: _, // todo, mssql specific
|
||||
settings: _, // todo, clickhouse specific
|
||||
format_clause: _, // todo, clickhouse specific
|
||||
pipe_operators: _, // todo bigquery specific
|
||||
locks: _, // todo
|
||||
for_clause: _, // todo, mssql specific
|
||||
settings: _, // todo, clickhouse specific
|
||||
format_clause: _, // todo, clickhouse specific
|
||||
} = self;
|
||||
|
||||
union_spans(
|
||||
|
@ -312,6 +310,7 @@ impl Spanned for Statement {
|
|||
table_names,
|
||||
partitions,
|
||||
table: _,
|
||||
only: _,
|
||||
identity: _,
|
||||
cascade: _,
|
||||
on_cluster: _,
|
||||
|
@ -339,7 +338,6 @@ impl Spanned for Statement {
|
|||
} => source.span(),
|
||||
Statement::Case(stmt) => stmt.span(),
|
||||
Statement::If(stmt) => stmt.span(),
|
||||
Statement::While(stmt) => stmt.span(),
|
||||
Statement::Raise(stmt) => stmt.span(),
|
||||
Statement::Call(function) => function.span(),
|
||||
Statement::Copy {
|
||||
|
@ -366,7 +364,6 @@ impl Spanned for Statement {
|
|||
from_query: _,
|
||||
partition: _,
|
||||
} => Span::empty(),
|
||||
Statement::Open(open) => open.span(),
|
||||
Statement::Close { cursor } => match cursor {
|
||||
CloseCursor::All => Span::empty(),
|
||||
CloseCursor::Specific { name } => name.span,
|
||||
|
@ -400,7 +397,6 @@ impl Spanned for Statement {
|
|||
if_not_exists: _,
|
||||
temporary: _,
|
||||
to,
|
||||
name_before_not_exists: _,
|
||||
params: _,
|
||||
} => union_spans(
|
||||
core::iter::once(name.span())
|
||||
|
@ -424,7 +420,6 @@ impl Spanned for Statement {
|
|||
Statement::CreateIndex(create_index) => create_index.span(),
|
||||
Statement::CreateRole { .. } => Span::empty(),
|
||||
Statement::CreateSecret { .. } => Span::empty(),
|
||||
Statement::CreateServer { .. } => Span::empty(),
|
||||
Statement::CreateConnector { .. } => Span::empty(),
|
||||
Statement::AlterTable {
|
||||
name,
|
||||
|
@ -433,7 +428,6 @@ impl Spanned for Statement {
|
|||
operations,
|
||||
location: _,
|
||||
on_cluster,
|
||||
iceberg: _,
|
||||
} => union_spans(
|
||||
core::iter::once(name.span())
|
||||
.chain(operations.iter().map(|i| i.span()))
|
||||
|
@ -460,7 +454,6 @@ impl Spanned for Statement {
|
|||
Statement::DetachDuckDBDatabase { .. } => Span::empty(),
|
||||
Statement::Drop { .. } => Span::empty(),
|
||||
Statement::DropFunction { .. } => Span::empty(),
|
||||
Statement::DropDomain { .. } => Span::empty(),
|
||||
Statement::DropProcedure { .. } => Span::empty(),
|
||||
Statement::DropSecret { .. } => Span::empty(),
|
||||
Statement::Declare { .. } => Span::empty(),
|
||||
|
@ -478,7 +471,6 @@ impl Spanned for Statement {
|
|||
Statement::ShowColumns { .. } => Span::empty(),
|
||||
Statement::ShowTables { .. } => Span::empty(),
|
||||
Statement::ShowCollation { .. } => Span::empty(),
|
||||
Statement::ShowCharset { .. } => Span::empty(),
|
||||
Statement::Use(u) => u.span(),
|
||||
Statement::StartTransaction { .. } => Span::empty(),
|
||||
Statement::Comment { .. } => Span::empty(),
|
||||
|
@ -487,7 +479,6 @@ impl Spanned for Statement {
|
|||
Statement::CreateSchema { .. } => Span::empty(),
|
||||
Statement::CreateDatabase { .. } => Span::empty(),
|
||||
Statement::CreateFunction { .. } => Span::empty(),
|
||||
Statement::CreateDomain { .. } => Span::empty(),
|
||||
Statement::CreateTrigger { .. } => Span::empty(),
|
||||
Statement::DropTrigger { .. } => Span::empty(),
|
||||
Statement::CreateProcedure { .. } => Span::empty(),
|
||||
|
@ -495,7 +486,6 @@ impl Spanned for Statement {
|
|||
Statement::CreateStage { .. } => Span::empty(),
|
||||
Statement::Assert { .. } => Span::empty(),
|
||||
Statement::Grant { .. } => Span::empty(),
|
||||
Statement::Deny { .. } => Span::empty(),
|
||||
Statement::Revoke { .. } => Span::empty(),
|
||||
Statement::Deallocate { .. } => Span::empty(),
|
||||
Statement::Execute { .. } => Span::empty(),
|
||||
|
@ -533,18 +523,6 @@ impl Spanned for Statement {
|
|||
Statement::Print { .. } => Span::empty(),
|
||||
Statement::Return { .. } => Span::empty(),
|
||||
Statement::List(..) | Statement::Remove(..) => Span::empty(),
|
||||
Statement::ExportData(ExportData {
|
||||
options,
|
||||
query,
|
||||
connection,
|
||||
}) => union_spans(
|
||||
options
|
||||
.iter()
|
||||
.map(|i| i.span())
|
||||
.chain(core::iter::once(query.span()))
|
||||
.chain(connection.iter().map(|i| i.span())),
|
||||
),
|
||||
Statement::CreateUser(..) => Span::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -585,20 +563,27 @@ impl Spanned for CreateTable {
|
|||
constraints,
|
||||
hive_distribution: _, // hive specific
|
||||
hive_formats: _, // hive specific
|
||||
file_format: _, // enum
|
||||
location: _, // string, no span
|
||||
table_properties,
|
||||
with_options,
|
||||
file_format: _, // enum
|
||||
location: _, // string, no span
|
||||
query,
|
||||
without_rowid: _, // bool
|
||||
like,
|
||||
clone,
|
||||
comment: _, // todo, no span
|
||||
on_commit: _,
|
||||
engine: _, // todo
|
||||
comment: _, // todo, no span
|
||||
auto_increment_offset: _, // u32, no span
|
||||
default_charset: _, // string, no span
|
||||
collation: _, // string, no span
|
||||
on_commit: _, // enum
|
||||
on_cluster: _, // todo, clickhouse specific
|
||||
primary_key: _, // todo, clickhouse specific
|
||||
order_by: _, // todo, clickhouse specific
|
||||
partition_by: _, // todo, BigQuery specific
|
||||
cluster_by: _, // todo, BigQuery specific
|
||||
clustered_by: _, // todo, Hive specific
|
||||
options: _, // todo, BigQuery specific
|
||||
inherits: _, // todo, PostgreSQL specific
|
||||
strict: _, // bool
|
||||
copy_grants: _, // bool
|
||||
|
@ -614,15 +599,15 @@ impl Spanned for CreateTable {
|
|||
base_location: _, // todo, Snowflake specific
|
||||
catalog: _, // todo, Snowflake specific
|
||||
catalog_sync: _, // todo, Snowflake specific
|
||||
storage_serialization_policy: _,
|
||||
table_options,
|
||||
storage_serialization_policy: _, // todo, Snowflake specific
|
||||
} = self;
|
||||
|
||||
union_spans(
|
||||
core::iter::once(name.span())
|
||||
.chain(core::iter::once(table_options.span()))
|
||||
.chain(columns.iter().map(|i| i.span()))
|
||||
.chain(constraints.iter().map(|i| i.span()))
|
||||
.chain(table_properties.iter().map(|i| i.span()))
|
||||
.chain(with_options.iter().map(|i| i.span()))
|
||||
.chain(query.iter().map(|i| i.span()))
|
||||
.chain(like.iter().map(|i| i.span()))
|
||||
.chain(clone.iter().map(|i| i.span())),
|
||||
|
@ -666,7 +651,7 @@ impl Spanned for TableConstraint {
|
|||
name.iter()
|
||||
.map(|i| i.span)
|
||||
.chain(index_name.iter().map(|i| i.span))
|
||||
.chain(columns.iter().map(|i| i.span()))
|
||||
.chain(columns.iter().map(|i| i.span))
|
||||
.chain(characteristics.iter().map(|i| i.span())),
|
||||
),
|
||||
TableConstraint::PrimaryKey {
|
||||
|
@ -680,13 +665,12 @@ impl Spanned for TableConstraint {
|
|||
name.iter()
|
||||
.map(|i| i.span)
|
||||
.chain(index_name.iter().map(|i| i.span))
|
||||
.chain(columns.iter().map(|i| i.span()))
|
||||
.chain(columns.iter().map(|i| i.span))
|
||||
.chain(characteristics.iter().map(|i| i.span())),
|
||||
),
|
||||
TableConstraint::ForeignKey {
|
||||
name,
|
||||
columns,
|
||||
index_name,
|
||||
foreign_table,
|
||||
referred_columns,
|
||||
on_delete,
|
||||
|
@ -695,7 +679,6 @@ impl Spanned for TableConstraint {
|
|||
} => union_spans(
|
||||
name.iter()
|
||||
.map(|i| i.span)
|
||||
.chain(index_name.iter().map(|i| i.span))
|
||||
.chain(columns.iter().map(|i| i.span))
|
||||
.chain(core::iter::once(foreign_table.span()))
|
||||
.chain(referred_columns.iter().map(|i| i.span))
|
||||
|
@ -703,11 +686,9 @@ impl Spanned for TableConstraint {
|
|||
.chain(on_update.iter().map(|i| i.span()))
|
||||
.chain(characteristics.iter().map(|i| i.span())),
|
||||
),
|
||||
TableConstraint::Check {
|
||||
name,
|
||||
expr,
|
||||
enforced: _,
|
||||
} => expr.span().union_opt(&name.as_ref().map(|i| i.span)),
|
||||
TableConstraint::Check { name, expr } => {
|
||||
expr.span().union_opt(&name.as_ref().map(|i| i.span))
|
||||
}
|
||||
TableConstraint::Index {
|
||||
display_as_key: _,
|
||||
name,
|
||||
|
@ -716,7 +697,7 @@ impl Spanned for TableConstraint {
|
|||
} => union_spans(
|
||||
name.iter()
|
||||
.map(|i| i.span)
|
||||
.chain(columns.iter().map(|i| i.span())),
|
||||
.chain(columns.iter().map(|i| i.span)),
|
||||
),
|
||||
TableConstraint::FulltextOrSpatial {
|
||||
fulltext: _,
|
||||
|
@ -727,7 +708,7 @@ impl Spanned for TableConstraint {
|
|||
opt_index_name
|
||||
.iter()
|
||||
.map(|i| i.span)
|
||||
.chain(columns.iter().map(|i| i.span())),
|
||||
.chain(columns.iter().map(|i| i.span)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -761,12 +742,6 @@ impl Spanned for CreateIndex {
|
|||
}
|
||||
}
|
||||
|
||||
impl Spanned for IndexColumn {
|
||||
fn span(&self) -> Span {
|
||||
self.column.span()
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for CaseStatement {
|
||||
fn span(&self) -> Span {
|
||||
let CaseStatement {
|
||||
|
@ -799,14 +774,6 @@ impl Spanned for IfStatement {
|
|||
}
|
||||
}
|
||||
|
||||
impl Spanned for WhileStatement {
|
||||
fn span(&self) -> Span {
|
||||
let WhileStatement { while_block } = self;
|
||||
|
||||
while_block.span()
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ConditionalStatements {
|
||||
fn span(&self) -> Span {
|
||||
match self {
|
||||
|
@ -897,7 +864,6 @@ impl Spanned for ColumnOption {
|
|||
ColumnOption::OnConflict(..) => Span::empty(),
|
||||
ColumnOption::Policy(..) => Span::empty(),
|
||||
ColumnOption::Tags(..) => Span::empty(),
|
||||
ColumnOption::Srid(..) => Span::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -939,7 +905,6 @@ impl Spanned for AlterColumnOperation {
|
|||
AlterColumnOperation::SetDataType {
|
||||
data_type: _,
|
||||
using,
|
||||
had_set: _,
|
||||
} => using.as_ref().map_or(Span::empty(), |u| u.span()),
|
||||
AlterColumnOperation::AddGenerated { .. } => Span::empty(),
|
||||
}
|
||||
|
@ -1007,13 +972,10 @@ impl Spanned for ViewColumnDef {
|
|||
options,
|
||||
} = self;
|
||||
|
||||
name.span.union_opt(&options.as_ref().map(|o| o.span()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ColumnOptions {
|
||||
fn span(&self) -> Span {
|
||||
union_spans(self.as_slice().iter().map(|i| i.span()))
|
||||
union_spans(
|
||||
core::iter::once(name.span)
|
||||
.chain(options.iter().flat_map(|i| i.iter().map(|k| k.span()))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1030,14 +992,6 @@ impl Spanned for SqlOption {
|
|||
} => union_spans(
|
||||
core::iter::once(column_name.span).chain(for_values.iter().map(|i| i.span())),
|
||||
),
|
||||
SqlOption::TableSpace(_) => Span::empty(),
|
||||
SqlOption::Comment(_) => Span::empty(),
|
||||
SqlOption::NamedParenthesizedList(NamedParenthesizedList {
|
||||
key: name,
|
||||
name: value,
|
||||
values,
|
||||
}) => union_spans(core::iter::once(name.span).chain(values.iter().map(|i| i.span)))
|
||||
.union_opt(&value.as_ref().map(|i| i.span)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1074,11 +1028,7 @@ impl Spanned for CreateTableOptions {
|
|||
match self {
|
||||
CreateTableOptions::None => Span::empty(),
|
||||
CreateTableOptions::With(vec) => union_spans(vec.iter().map(|i| i.span())),
|
||||
CreateTableOptions::Options(vec) => {
|
||||
union_spans(vec.as_slice().iter().map(|i| i.span()))
|
||||
}
|
||||
CreateTableOptions::Plain(vec) => union_spans(vec.iter().map(|i| i.span())),
|
||||
CreateTableOptions::TableProperties(vec) => union_spans(vec.iter().map(|i| i.span())),
|
||||
CreateTableOptions::Options(vec) => union_spans(vec.iter().map(|i| i.span())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1090,10 +1040,7 @@ impl Spanned for CreateTableOptions {
|
|||
impl Spanned for AlterTableOperation {
|
||||
fn span(&self) -> Span {
|
||||
match self {
|
||||
AlterTableOperation::AddConstraint {
|
||||
constraint,
|
||||
not_valid: _,
|
||||
} => constraint.span(),
|
||||
AlterTableOperation::AddConstraint(table_constraint) => table_constraint.span(),
|
||||
AlterTableOperation::AddColumn {
|
||||
column_keyword: _,
|
||||
if_not_exists: _,
|
||||
|
@ -1125,11 +1072,10 @@ impl Spanned for AlterTableOperation {
|
|||
drop_behavior: _,
|
||||
} => name.span,
|
||||
AlterTableOperation::DropColumn {
|
||||
has_column_keyword: _,
|
||||
column_names,
|
||||
column_name,
|
||||
if_exists: _,
|
||||
drop_behavior: _,
|
||||
} => union_spans(column_names.iter().map(|i| i.span)),
|
||||
} => column_name.span,
|
||||
AlterTableOperation::AttachPartition { partition } => partition.span(),
|
||||
AlterTableOperation::DetachPartition { partition } => partition.span(),
|
||||
AlterTableOperation::FreezePartition {
|
||||
|
@ -1146,7 +1092,6 @@ impl Spanned for AlterTableOperation {
|
|||
.union_opt(&with_name.as_ref().map(|n| n.span)),
|
||||
AlterTableOperation::DropPrimaryKey => Span::empty(),
|
||||
AlterTableOperation::DropForeignKey { name } => name.span,
|
||||
AlterTableOperation::DropIndex { name } => name.span,
|
||||
AlterTableOperation::EnableAlwaysRule { name } => name.span,
|
||||
AlterTableOperation::EnableAlwaysTrigger { name } => name.span,
|
||||
AlterTableOperation::EnableReplicaRule { name } => name.span,
|
||||
|
@ -1213,11 +1158,6 @@ impl Spanned for AlterTableOperation {
|
|||
AlterTableOperation::Algorithm { .. } => Span::empty(),
|
||||
AlterTableOperation::AutoIncrement { value, .. } => value.span(),
|
||||
AlterTableOperation::Lock { .. } => Span::empty(),
|
||||
AlterTableOperation::ReplicaIdentity { .. } => Span::empty(),
|
||||
AlterTableOperation::ValidateConstraint { name } => name.span,
|
||||
AlterTableOperation::SetOptionsParens { options } => {
|
||||
union_spans(options.iter().map(|i| i.span()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1433,6 +1373,7 @@ impl Spanned for AssignmentTarget {
|
|||
/// f.e. `IS NULL <expr>` reports as `<expr>::span`.
|
||||
///
|
||||
/// Missing spans:
|
||||
/// - [Expr::TypedString] # missing span for data_type
|
||||
/// - [Expr::MatchAgainst] # MySQL specific
|
||||
/// - [Expr::RLike] # MySQL specific
|
||||
/// - [Expr::Struct] # BigQuery specific
|
||||
|
@ -1527,7 +1468,7 @@ impl Spanned for Expr {
|
|||
.union(&union_spans(collation.0.iter().map(|i| i.span()))),
|
||||
Expr::Nested(expr) => expr.span(),
|
||||
Expr::Value(value) => value.span(),
|
||||
Expr::TypedString(TypedString { value, .. }) => value.span(),
|
||||
Expr::TypedString { value, .. } => value.span(),
|
||||
Expr::Function(function) => function.span(),
|
||||
Expr::GroupingSets(vec) => {
|
||||
union_spans(vec.iter().flat_map(|i| i.iter().map(|k| k.span())))
|
||||
|
@ -1604,24 +1545,18 @@ impl Spanned for Expr {
|
|||
),
|
||||
Expr::Prefixed { value, .. } => value.span(),
|
||||
Expr::Case {
|
||||
case_token,
|
||||
end_token,
|
||||
operand,
|
||||
conditions,
|
||||
else_result,
|
||||
} => union_spans(
|
||||
iter::once(case_token.0.span)
|
||||
.chain(
|
||||
operand
|
||||
.as_ref()
|
||||
.map(|i| i.span())
|
||||
.into_iter()
|
||||
.chain(conditions.iter().flat_map(|case_when| {
|
||||
[case_when.condition.span(), case_when.result.span()]
|
||||
}))
|
||||
.chain(else_result.as_ref().map(|i| i.span())),
|
||||
)
|
||||
.chain(iter::once(end_token.0.span)),
|
||||
operand
|
||||
.as_ref()
|
||||
.map(|i| i.span())
|
||||
.into_iter()
|
||||
.chain(conditions.iter().flat_map(|case_when| {
|
||||
[case_when.condition.span(), case_when.result.span()]
|
||||
}))
|
||||
.chain(else_result.as_ref().map(|i| i.span())),
|
||||
),
|
||||
Expr::Exists { subquery, .. } => subquery.span(),
|
||||
Expr::Subquery(query) => query.span(),
|
||||
|
@ -1641,7 +1576,6 @@ impl Spanned for Expr {
|
|||
Expr::OuterJoin(expr) => expr.span(),
|
||||
Expr::Prior(expr) => expr.span(),
|
||||
Expr::Lambda(_) => Span::empty(),
|
||||
Expr::MemberOf(member_of) => member_of.value.span().union(&member_of.array.span()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1688,10 +1622,6 @@ impl Spanned for ObjectNamePart {
|
|||
fn span(&self) -> Span {
|
||||
match self {
|
||||
ObjectNamePart::Identifier(ident) => ident.span,
|
||||
ObjectNamePart::Function(func) => func
|
||||
.name
|
||||
.span
|
||||
.union(&union_spans(func.args.iter().map(|i| i.span()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1996,15 +1926,14 @@ impl Spanned for TableFactor {
|
|||
TableFactor::Unpivot {
|
||||
table,
|
||||
value,
|
||||
null_inclusion: _,
|
||||
name,
|
||||
columns,
|
||||
alias,
|
||||
} => union_spans(
|
||||
core::iter::once(table.span())
|
||||
.chain(core::iter::once(value.span()))
|
||||
.chain(core::iter::once(value.span))
|
||||
.chain(core::iter::once(name.span))
|
||||
.chain(columns.iter().map(|ilist| ilist.span()))
|
||||
.chain(columns.iter().map(|i| i.span))
|
||||
.chain(alias.as_ref().map(|alias| alias.span())),
|
||||
),
|
||||
TableFactor::MatchRecognize {
|
||||
|
@ -2237,7 +2166,6 @@ impl Spanned for Select {
|
|||
distinct: _, // todo
|
||||
top: _, // todo, mysql specific
|
||||
projection,
|
||||
exclude: _,
|
||||
into,
|
||||
from,
|
||||
lateral_views,
|
||||
|
@ -2367,13 +2295,6 @@ impl Spanned for BeginEndStatements {
|
|||
}
|
||||
}
|
||||
|
||||
impl Spanned for OpenStatement {
|
||||
fn span(&self) -> Span {
|
||||
let OpenStatement { cursor_name } = self;
|
||||
cursor_name.span
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use crate::dialect::{Dialect, GenericDialect, SnowflakeDialect};
|
||||
|
@ -2513,39 +2434,4 @@ pub mod tests {
|
|||
|
||||
assert_eq!(test.get_source(body_span), "SELECT cte.* FROM cte");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_case_expr_span() {
|
||||
let dialect = &GenericDialect;
|
||||
let mut test = SpanTest::new(dialect, "CASE 1 WHEN 2 THEN 3 ELSE 4 END");
|
||||
let expr = test.0.parse_expr().unwrap();
|
||||
let expr_span = expr.span();
|
||||
assert_eq!(
|
||||
test.get_source(expr_span),
|
||||
"CASE 1 WHEN 2 THEN 3 ELSE 4 END"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_placeholder_span() {
|
||||
let sql = "\nSELECT\n :fooBar";
|
||||
let r = Parser::parse_sql(&GenericDialect, sql).unwrap();
|
||||
assert_eq!(1, r.len());
|
||||
match &r[0] {
|
||||
Statement::Query(q) => {
|
||||
let col = &q.body.as_select().unwrap().projection[0];
|
||||
match col {
|
||||
SelectItem::UnnamedExpr(Expr::Value(ValueWithSpan {
|
||||
value: Value::Placeholder(s),
|
||||
span,
|
||||
})) => {
|
||||
assert_eq!(":fooBar", s);
|
||||
assert_eq!(&Span::new((3, 3).into(), (3, 10).into()), span);
|
||||
}
|
||||
_ => panic!("expected unnamed expression; got {col:?}"),
|
||||
}
|
||||
}
|
||||
stmt => panic!("expected query; got {stmt:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,7 +110,6 @@ impl fmt::Display for TriggerEvent {
|
|||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum TriggerPeriod {
|
||||
For,
|
||||
After,
|
||||
Before,
|
||||
InsteadOf,
|
||||
|
@ -119,7 +118,6 @@ pub enum TriggerPeriod {
|
|||
impl fmt::Display for TriggerPeriod {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
TriggerPeriod::For => write!(f, "FOR"),
|
||||
TriggerPeriod::After => write!(f, "AFTER"),
|
||||
TriggerPeriod::Before => write!(f, "BEFORE"),
|
||||
TriggerPeriod::InsteadOf => write!(f, "INSTEAD OF"),
|
||||
|
|
|
@ -116,6 +116,7 @@ impl From<ValueWithSpan> for Value {
|
|||
derive(Visit, VisitMut),
|
||||
visit(with = "visit_value")
|
||||
)]
|
||||
|
||||
pub enum Value {
|
||||
/// Numeric literal
|
||||
#[cfg(not(feature = "bigdecimal"))]
|
||||
|
@ -455,38 +456,30 @@ impl fmt::Display for EscapeQuotedString<'_> {
|
|||
// | `"A\"B\"A"` | default | `DoubleQuotedString(String::from("A\"B\"A"))` | `"A""B""A"` |
|
||||
let quote = self.quote;
|
||||
let mut previous_char = char::default();
|
||||
let mut start_idx = 0;
|
||||
let mut peekable_chars = self.string.char_indices().peekable();
|
||||
while let Some(&(idx, ch)) = peekable_chars.peek() {
|
||||
let mut peekable_chars = self.string.chars().peekable();
|
||||
while let Some(&ch) = peekable_chars.peek() {
|
||||
match ch {
|
||||
char if char == quote => {
|
||||
if previous_char == '\\' {
|
||||
// the quote is already escaped with a backslash, skip
|
||||
write!(f, "{char}")?;
|
||||
peekable_chars.next();
|
||||
continue;
|
||||
}
|
||||
peekable_chars.next();
|
||||
match peekable_chars.peek() {
|
||||
Some((_, c)) if *c == quote => {
|
||||
// the quote is already escaped with another quote, skip
|
||||
peekable_chars.next();
|
||||
}
|
||||
_ => {
|
||||
// The quote is not escaped.
|
||||
// Including idx in the range, so the quote at idx will be printed twice:
|
||||
// in this call to write_str() and in the next one.
|
||||
f.write_str(&self.string[start_idx..=idx])?;
|
||||
start_idx = idx;
|
||||
}
|
||||
if peekable_chars.peek().map(|c| *c == quote).unwrap_or(false) {
|
||||
write!(f, "{char}{char}")?;
|
||||
peekable_chars.next();
|
||||
} else {
|
||||
write!(f, "{char}{char}")?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
write!(f, "{ch}")?;
|
||||
peekable_chars.next();
|
||||
}
|
||||
}
|
||||
previous_char = ch;
|
||||
}
|
||||
f.write_str(&self.string[start_idx..])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -550,16 +543,16 @@ impl fmt::Display for EscapeUnicodeStringLiteral<'_> {
|
|||
write!(f, r#"\\"#)?;
|
||||
}
|
||||
x if x.is_ascii() => {
|
||||
write!(f, "{c}")?;
|
||||
write!(f, "{}", c)?;
|
||||
}
|
||||
_ => {
|
||||
let codepoint = c as u32;
|
||||
// if the character fits in 32 bits, we can use the \XXXX format
|
||||
// otherwise, we need to use the \+XXXXXX format
|
||||
if codepoint <= 0xFFFF {
|
||||
write!(f, "\\{codepoint:04X}")?;
|
||||
write!(f, "\\{:04X}", codepoint)?;
|
||||
} else {
|
||||
write!(f, "\\+{codepoint:06X}")?;
|
||||
write!(f, "\\+{:06X}", codepoint)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -741,7 +741,7 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn do_visit<V: Visitor<Break = ()>>(sql: &str, visitor: &mut V) -> Statement {
|
||||
fn do_visit<V: Visitor>(sql: &str, visitor: &mut V) -> Statement {
|
||||
let dialect = GenericDialect {};
|
||||
let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
|
||||
let s = Parser::new(&dialect)
|
||||
|
@ -749,8 +749,7 @@ mod tests {
|
|||
.parse_statement()
|
||||
.unwrap();
|
||||
|
||||
let flow = s.visit(visitor);
|
||||
assert_eq!(flow, ControlFlow::Continue(()));
|
||||
s.visit(visitor);
|
||||
s
|
||||
}
|
||||
|
||||
|
@ -926,10 +925,10 @@ mod tests {
|
|||
#[test]
|
||||
fn overflow() {
|
||||
let cond = (0..1000)
|
||||
.map(|n| format!("X = {n}"))
|
||||
.map(|n| format!("X = {}", n))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" OR ");
|
||||
let sql = format!("SELECT x where {cond}");
|
||||
let sql = format!("SELECT x where {0}", cond);
|
||||
|
||||
let dialect = GenericDialect {};
|
||||
let tokens = Tokenizer::new(&dialect, sql.as_str()).tokenize().unwrap();
|
||||
|
@ -939,8 +938,7 @@ mod tests {
|
|||
.unwrap();
|
||||
|
||||
let mut visitor = QuickVisitor {};
|
||||
let flow = s.visit(&mut visitor);
|
||||
assert_eq!(flow, ControlFlow::Continue(()));
|
||||
s.visit(&mut visitor);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -971,7 +969,7 @@ mod visit_mut_tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn do_visit_mut<V: VisitorMut<Break = ()>>(sql: &str, visitor: &mut V) -> Statement {
|
||||
fn do_visit_mut<V: VisitorMut>(sql: &str, visitor: &mut V) -> Statement {
|
||||
let dialect = GenericDialect {};
|
||||
let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
|
||||
let mut s = Parser::new(&dialect)
|
||||
|
@ -979,8 +977,7 @@ mod visit_mut_tests {
|
|||
.parse_statement()
|
||||
.unwrap();
|
||||
|
||||
let flow = s.visit(visitor);
|
||||
assert_eq!(flow, ControlFlow::Continue(()));
|
||||
s.visit(visitor);
|
||||
s
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ use crate::ast::Statement;
|
|||
use crate::dialect::Dialect;
|
||||
use crate::keywords::Keyword;
|
||||
use crate::parser::{Parser, ParserError};
|
||||
use crate::tokenizer::Token;
|
||||
|
||||
/// These keywords are disallowed as column identifiers. Such that
|
||||
/// `SELECT 5 AS <col> FROM T` is rejected by BigQuery.
|
||||
|
@ -47,18 +46,7 @@ pub struct BigQueryDialect;
|
|||
|
||||
impl Dialect for BigQueryDialect {
|
||||
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
|
||||
if parser.parse_keyword(Keyword::BEGIN) {
|
||||
if parser.peek_keyword(Keyword::TRANSACTION)
|
||||
|| parser.peek_token_ref().token == Token::SemiColon
|
||||
|| parser.peek_token_ref().token == Token::EOF
|
||||
{
|
||||
parser.prev_token();
|
||||
return None;
|
||||
}
|
||||
return Some(parser.parse_begin_exception_end());
|
||||
}
|
||||
|
||||
None
|
||||
self.maybe_parse_statement(parser)
|
||||
}
|
||||
|
||||
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#identifiers>
|
||||
|
@ -148,12 +136,49 @@ impl Dialect for BigQueryDialect {
|
|||
fn is_column_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
|
||||
!RESERVED_FOR_COLUMN_ALIAS.contains(kw)
|
||||
}
|
||||
}
|
||||
|
||||
fn supports_pipe_operator(&self) -> bool {
|
||||
true
|
||||
impl BigQueryDialect {
|
||||
fn maybe_parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
|
||||
if parser.peek_keyword(Keyword::BEGIN) {
|
||||
return Some(self.parse_begin(parser));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_create_table_multi_schema_info_sources(&self) -> bool {
|
||||
true
|
||||
/// Parse a `BEGIN` statement.
|
||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
|
||||
fn parse_begin(&self, parser: &mut Parser) -> Result<Statement, ParserError> {
|
||||
parser.expect_keyword(Keyword::BEGIN)?;
|
||||
|
||||
let statements = parser.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?;
|
||||
|
||||
let has_exception_when_clause = parser.parse_keywords(&[
|
||||
Keyword::EXCEPTION,
|
||||
Keyword::WHEN,
|
||||
Keyword::ERROR,
|
||||
Keyword::THEN,
|
||||
]);
|
||||
let exception_statements = if has_exception_when_clause {
|
||||
if !parser.peek_keyword(Keyword::END) {
|
||||
Some(parser.parse_statement_list(&[Keyword::END])?)
|
||||
} else {
|
||||
Some(Default::default())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
parser.expect_keyword(Keyword::END)?;
|
||||
|
||||
Ok(Statement::StartTransaction {
|
||||
begin: true,
|
||||
statements,
|
||||
exception_statements,
|
||||
has_end_keyword: true,
|
||||
transaction: None,
|
||||
modifier: None,
|
||||
modes: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ impl Dialect for DuckDbDialect {
|
|||
true
|
||||
}
|
||||
|
||||
/// See <https://duckdb.org/docs/stable/sql/functions/lambda>
|
||||
/// See <https://duckdb.org/docs/sql/functions/lambda.html>
|
||||
fn supports_lambda_functions(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
@ -94,14 +94,4 @@ impl Dialect for DuckDbDialect {
|
|||
fn supports_order_by_all(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_select_wildcard_exclude(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// DuckDB supports `NOTNULL` as an alias for `IS NOT NULL`,
|
||||
/// see DuckDB Comparisons <https://duckdb.org/docs/stable/sql/expressions/comparison_operators#between-and-is-not-null>
|
||||
fn supports_notnull_operator(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,10 +52,6 @@ impl Dialect for GenericDialect {
|
|||
true
|
||||
}
|
||||
|
||||
fn supports_left_associative_joins_without_parens(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_connect_by(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
@ -112,14 +108,6 @@ impl Dialect for GenericDialect {
|
|||
true
|
||||
}
|
||||
|
||||
fn supports_from_first_select(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_projection_trailing_commas(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_asc_desc_in_column_definition(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
@ -179,16 +167,4 @@ impl Dialect for GenericDialect {
|
|||
fn supports_filter_during_aggregation(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_select_wildcard_exclude(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_data_type_signed_suffix(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_interval_options(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ pub use self::postgresql::PostgreSqlDialect;
|
|||
pub use self::redshift::RedshiftSqlDialect;
|
||||
pub use self::snowflake::SnowflakeDialect;
|
||||
pub use self::sqlite::SQLiteDialect;
|
||||
use crate::ast::{ColumnOption, Expr, GranteesType, Ident, ObjectNamePart, Statement};
|
||||
use crate::ast::{ColumnOption, Expr, Statement};
|
||||
pub use crate::keywords;
|
||||
use crate::keywords::Keyword;
|
||||
use crate::parser::{Parser, ParserError};
|
||||
|
@ -278,34 +278,6 @@ pub trait Dialect: Debug + Any {
|
|||
false
|
||||
}
|
||||
|
||||
/// Indicates whether the dialect supports left-associative join parsing
|
||||
/// by default when parentheses are omitted in nested joins.
|
||||
///
|
||||
/// Most dialects (like MySQL or Postgres) assume **left-associative** precedence,
|
||||
/// so a query like:
|
||||
///
|
||||
/// ```sql
|
||||
/// SELECT * FROM t1 NATURAL JOIN t5 INNER JOIN t0 ON ...
|
||||
/// ```
|
||||
/// is interpreted as:
|
||||
/// ```sql
|
||||
/// ((t1 NATURAL JOIN t5) INNER JOIN t0 ON ...)
|
||||
/// ```
|
||||
/// and internally represented as a **flat list** of joins.
|
||||
///
|
||||
/// In contrast, some dialects (e.g. **Snowflake**) assume **right-associative**
|
||||
/// precedence and interpret the same query as:
|
||||
/// ```sql
|
||||
/// (t1 NATURAL JOIN (t5 INNER JOIN t0 ON ...))
|
||||
/// ```
|
||||
/// which results in a **nested join** structure in the AST.
|
||||
///
|
||||
/// If this method returns `false`, the parser must build nested join trees
|
||||
/// even in the absence of parentheses to reflect the correct associativity
|
||||
fn supports_left_associative_joins_without_parens(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns true if the dialect supports the `(+)` syntax for OUTER JOIN.
|
||||
fn supports_outer_join_operator(&self) -> bool {
|
||||
false
|
||||
|
@ -546,20 +518,6 @@ pub trait Dialect: Debug + Any {
|
|||
false
|
||||
}
|
||||
|
||||
/// Return true if the dialect supports pipe operator.
|
||||
///
|
||||
/// Example:
|
||||
/// ```sql
|
||||
/// SELECT *
|
||||
/// FROM table
|
||||
/// |> limit 1
|
||||
/// ```
|
||||
///
|
||||
/// See <https://cloud.google.com/bigquery/docs/pipe-syntax-guide#basic_syntax>
|
||||
fn supports_pipe_operator(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Does the dialect support MySQL-style `'user'@'host'` grantee syntax?
|
||||
fn supports_user_host_grantee(&self) -> bool {
|
||||
false
|
||||
|
@ -570,33 +528,6 @@ pub trait Dialect: Debug + Any {
|
|||
false
|
||||
}
|
||||
|
||||
/// Returns true if the dialect supports an exclude option
|
||||
/// following a wildcard in the projection section. For example:
|
||||
/// `SELECT * EXCLUDE col1 FROM tbl`.
|
||||
///
|
||||
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_EXCLUDE_list.html)
|
||||
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/select)
|
||||
fn supports_select_wildcard_exclude(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the dialect supports an exclude option
|
||||
/// as the last item in the projection section, not necessarily
|
||||
/// after a wildcard. For example:
|
||||
/// `SELECT *, c1, c2 EXCLUDE c3 FROM tbl`
|
||||
///
|
||||
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_EXCLUDE_list.html)
|
||||
fn supports_select_exclude(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returne true if the dialect supports specifying multiple options
|
||||
/// in a `CREATE TABLE` statement for the structure of the new table. For example:
|
||||
/// `CREATE TABLE t (a INT, b INT) AS SELECT 1 AS b, 2 AS a`
|
||||
fn supports_create_table_multi_schema_info_sources(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Dialect-specific infix parser override
|
||||
///
|
||||
/// This method is called to parse the next infix expression.
|
||||
|
@ -642,7 +573,7 @@ pub trait Dialect: Debug + Any {
|
|||
}
|
||||
|
||||
let token = parser.peek_token();
|
||||
debug!("get_next_precedence_full() {token:?}");
|
||||
debug!("get_next_precedence_full() {:?}", token);
|
||||
match token.token {
|
||||
Token::Word(w) if w.keyword == Keyword::OR => Ok(p!(Or)),
|
||||
Token::Word(w) if w.keyword == Keyword::AND => Ok(p!(And)),
|
||||
|
@ -674,19 +605,9 @@ pub trait Dialect: Debug + Any {
|
|||
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::RLIKE => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::REGEXP => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::MEMBER => Ok(p!(Like)),
|
||||
Token::Word(w)
|
||||
if w.keyword == Keyword::NULL && !parser.in_column_definition_state() =>
|
||||
{
|
||||
Ok(p!(Is))
|
||||
}
|
||||
_ => Ok(self.prec_unknown()),
|
||||
},
|
||||
Token::Word(w) if w.keyword == Keyword::NOTNULL && self.supports_notnull_operator() => {
|
||||
Ok(p!(Is))
|
||||
}
|
||||
Token::Word(w) if w.keyword == Keyword::IS => Ok(p!(Is)),
|
||||
Token::Word(w) if w.keyword == Keyword::IN => Ok(p!(Between)),
|
||||
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(p!(Between)),
|
||||
|
@ -695,9 +616,7 @@ pub trait Dialect: Debug + Any {
|
|||
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::RLIKE => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::REGEXP => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::MEMBER => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::OPERATOR => Ok(p!(Between)),
|
||||
Token::Word(w) if w.keyword == Keyword::DIV => Ok(p!(MulDivModOp)),
|
||||
Token::Period => Ok(p!(Period)),
|
||||
|
@ -963,17 +882,18 @@ pub trait Dialect: Debug + Any {
|
|||
keywords::RESERVED_FOR_IDENTIFIER.contains(&kw)
|
||||
}
|
||||
|
||||
/// Returns reserved keywords when looking to parse a `TableFactor`.
|
||||
/// See [Self::supports_from_trailing_commas]
|
||||
fn get_reserved_keywords_for_table_factor(&self) -> &[Keyword] {
|
||||
keywords::RESERVED_FOR_TABLE_FACTOR
|
||||
}
|
||||
|
||||
/// Returns reserved keywords that may prefix a select item expression
|
||||
/// e.g. `SELECT CONNECT_BY_ROOT name FROM Tbl2` (Snowflake)
|
||||
fn get_reserved_keywords_for_select_item_operator(&self) -> &[Keyword] {
|
||||
&[]
|
||||
}
|
||||
|
||||
/// Returns grantee types that should be treated as identifiers
|
||||
fn get_reserved_grantees_types(&self) -> &[GranteesType] {
|
||||
&[]
|
||||
}
|
||||
|
||||
/// Returns true if this dialect supports the `TABLESAMPLE` option
|
||||
/// before the table alias option. For example:
|
||||
///
|
||||
|
@ -1021,23 +941,11 @@ pub trait Dialect: Debug + Any {
|
|||
explicit || self.is_column_alias(kw, parser)
|
||||
}
|
||||
|
||||
/// Returns true if the specified keyword should be parsed as a table factor identifier.
|
||||
/// See [keywords::RESERVED_FOR_TABLE_FACTOR]
|
||||
fn is_table_factor(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
|
||||
!keywords::RESERVED_FOR_TABLE_FACTOR.contains(kw)
|
||||
}
|
||||
|
||||
/// Returns true if the specified keyword should be parsed as a table factor alias.
|
||||
/// See [keywords::RESERVED_FOR_TABLE_ALIAS]
|
||||
fn is_table_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
|
||||
!keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw)
|
||||
}
|
||||
|
||||
/// Returns true if the specified keyword should be parsed as a table factor alias.
|
||||
/// When explicit is true, the keyword is preceded by an `AS` word. Parser is provided
|
||||
/// to enable looking ahead if needed.
|
||||
fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
|
||||
explicit || self.is_table_alias(kw, parser)
|
||||
fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, _parser: &mut Parser) -> bool {
|
||||
explicit || !keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw)
|
||||
}
|
||||
|
||||
/// Returns true if this dialect supports querying historical table data
|
||||
|
@ -1099,70 +1007,6 @@ pub trait Dialect: Debug + Any {
|
|||
fn supports_set_names(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn supports_space_separated_column_options(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the dialect supports the `USING` clause in an `ALTER COLUMN` statement.
|
||||
/// Example:
|
||||
/// ```sql
|
||||
/// ALTER TABLE tbl ALTER COLUMN col SET DATA TYPE <type> USING <exp>`
|
||||
/// ```
|
||||
fn supports_alter_column_type_using(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the dialect supports `ALTER TABLE tbl DROP COLUMN c1, ..., cn`
|
||||
fn supports_comma_separated_drop_column_list(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the dialect considers the specified ident as a function
|
||||
/// that returns an identifier. Typically used to generate identifiers
|
||||
/// programmatically.
|
||||
///
|
||||
/// - [Snowflake](https://docs.snowflake.com/en/sql-reference/identifier-literal)
|
||||
fn is_identifier_generating_function_name(
|
||||
&self,
|
||||
_ident: &Ident,
|
||||
_name_parts: &[ObjectNamePart],
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the dialect supports the `x NOTNULL`
|
||||
/// operator expression.
|
||||
fn supports_notnull_operator(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if this dialect allows an optional `SIGNED` suffix after integer data types.
|
||||
///
|
||||
/// Example:
|
||||
/// ```sql
|
||||
/// CREATE TABLE t (i INT(20) SIGNED);
|
||||
/// ```
|
||||
///
|
||||
/// Note that this is canonicalized to `INT(20)`.
|
||||
fn supports_data_type_signed_suffix(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the dialect supports the `INTERVAL` data type with [Postgres]-style options.
|
||||
///
|
||||
/// Examples:
|
||||
/// ```sql
|
||||
/// CREATE TABLE t (i INTERVAL YEAR TO MONTH);
|
||||
/// SELECT '1 second'::INTERVAL HOUR TO SECOND(3);
|
||||
/// ```
|
||||
///
|
||||
/// See [`crate::ast::DataType::Interval`] and [`crate::ast::IntervalFields`].
|
||||
///
|
||||
/// [Postgres]: https://www.postgresql.org/docs/17/datatype-datetime.html
|
||||
fn supports_interval_options(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents the operators for which precedence must be defined
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
|
||||
use crate::ast::helpers::attached_token::AttachedToken;
|
||||
use crate::ast::{
|
||||
BeginEndStatements, ConditionalStatementBlock, ConditionalStatements, GranteesType,
|
||||
IfStatement, Statement, TriggerObject,
|
||||
BeginEndStatements, ConditionalStatementBlock, ConditionalStatements, IfStatement, Statement,
|
||||
};
|
||||
use crate::dialect::Dialect;
|
||||
use crate::keywords::{self, Keyword};
|
||||
|
@ -52,10 +51,6 @@ impl Dialect for MsSqlDialect {
|
|||
|| ch == '_'
|
||||
}
|
||||
|
||||
fn identifier_quote_style(&self, _identifier: &str) -> Option<char> {
|
||||
Some('[')
|
||||
}
|
||||
|
||||
/// SQL Server has `CONVERT(type, value)` instead of `CONVERT(value, type)`
|
||||
/// <https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16>
|
||||
fn convert_type_before_value(&self) -> bool {
|
||||
|
@ -123,11 +118,6 @@ impl Dialect for MsSqlDialect {
|
|||
true
|
||||
}
|
||||
|
||||
/// See <https://learn.microsoft.com/en-us/sql/relational-databases/security/authentication-access/server-level-roles>
|
||||
fn get_reserved_grantees_types(&self) -> &[GranteesType] {
|
||||
&[GranteesType::Public]
|
||||
}
|
||||
|
||||
fn is_column_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
|
||||
!keywords::RESERVED_FOR_COLUMN_ALIAS.contains(kw) && !RESERVED_FOR_COLUMN_ALIAS.contains(kw)
|
||||
}
|
||||
|
@ -135,15 +125,6 @@ impl Dialect for MsSqlDialect {
|
|||
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
|
||||
if parser.peek_keyword(Keyword::IF) {
|
||||
Some(self.parse_if_stmt(parser))
|
||||
} else if parser.parse_keywords(&[Keyword::CREATE, Keyword::TRIGGER]) {
|
||||
Some(self.parse_create_trigger(parser, false))
|
||||
} else if parser.parse_keywords(&[
|
||||
Keyword::CREATE,
|
||||
Keyword::OR,
|
||||
Keyword::ALTER,
|
||||
Keyword::TRIGGER,
|
||||
]) {
|
||||
Some(self.parse_create_trigger(parser, true))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -234,42 +215,6 @@ impl MsSqlDialect {
|
|||
}))
|
||||
}
|
||||
|
||||
/// Parse `CREATE TRIGGER` for [MsSql]
|
||||
///
|
||||
/// [MsSql]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql
|
||||
fn parse_create_trigger(
|
||||
&self,
|
||||
parser: &mut Parser,
|
||||
or_alter: bool,
|
||||
) -> Result<Statement, ParserError> {
|
||||
let name = parser.parse_object_name(false)?;
|
||||
parser.expect_keyword_is(Keyword::ON)?;
|
||||
let table_name = parser.parse_object_name(false)?;
|
||||
let period = parser.parse_trigger_period()?;
|
||||
let events = parser.parse_comma_separated(Parser::parse_trigger_event)?;
|
||||
|
||||
parser.expect_keyword_is(Keyword::AS)?;
|
||||
let statements = Some(parser.parse_conditional_statements(&[Keyword::END])?);
|
||||
|
||||
Ok(Statement::CreateTrigger {
|
||||
or_alter,
|
||||
or_replace: false,
|
||||
is_constraint: false,
|
||||
name,
|
||||
period,
|
||||
events,
|
||||
table_name,
|
||||
referenced_table_name: None,
|
||||
referencing: Vec::new(),
|
||||
trigger_object: TriggerObject::Statement,
|
||||
include_each: false,
|
||||
condition: None,
|
||||
exec_body: None,
|
||||
statements,
|
||||
characteristics: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a sequence of statements, optionally separated by semicolon.
|
||||
///
|
||||
/// Stops parsing when reaching EOF or the given keyword.
|
||||
|
|
|
@ -43,19 +43,15 @@ impl Dialect for MySqlDialect {
|
|||
// See https://dev.mysql.com/doc/refman/8.0/en/identifiers.html.
|
||||
// Identifiers which begin with a digit are recognized while tokenizing numbers,
|
||||
// so they can be distinguished from exponent numeric literals.
|
||||
// MySQL also implements non ascii utf-8 charecters
|
||||
ch.is_alphabetic()
|
||||
|| ch == '_'
|
||||
|| ch == '$'
|
||||
|| ch == '@'
|
||||
|| ('\u{0080}'..='\u{ffff}').contains(&ch)
|
||||
|| !ch.is_ascii()
|
||||
}
|
||||
|
||||
fn is_identifier_part(&self, ch: char) -> bool {
|
||||
self.is_identifier_start(ch) || ch.is_ascii_digit() ||
|
||||
// MySQL implements Unicode characters in identifiers.
|
||||
!ch.is_ascii()
|
||||
self.is_identifier_start(ch) || ch.is_ascii_digit()
|
||||
}
|
||||
|
||||
fn is_delimited_identifier_start(&self, ch: char) -> bool {
|
||||
|
@ -154,10 +150,6 @@ impl Dialect for MySqlDialect {
|
|||
fn supports_comma_separated_set_assignments(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_data_type_signed_suffix(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// `LOCK TABLES`
|
||||
|
|
|
@ -65,15 +65,14 @@ impl Dialect for PostgreSqlDialect {
|
|||
}
|
||||
|
||||
fn is_identifier_start(&self, ch: char) -> bool {
|
||||
ch.is_alphabetic() || ch == '_' ||
|
||||
// PostgreSQL implements Unicode characters in identifiers.
|
||||
!ch.is_ascii()
|
||||
// See https://www.postgresql.org/docs/11/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
||||
// We don't yet support identifiers beginning with "letters with
|
||||
// diacritical marks"
|
||||
ch.is_alphabetic() || ch == '_'
|
||||
}
|
||||
|
||||
fn is_identifier_part(&self, ch: char) -> bool {
|
||||
ch.is_alphabetic() || ch.is_ascii_digit() || ch == '$' || ch == '_' ||
|
||||
// PostgreSQL implements Unicode characters in identifiers.
|
||||
!ch.is_ascii()
|
||||
ch.is_alphabetic() || ch.is_ascii_digit() || ch == '$' || ch == '_'
|
||||
}
|
||||
|
||||
fn supports_unicode_string_literal(&self) -> bool {
|
||||
|
@ -105,7 +104,7 @@ impl Dialect for PostgreSqlDialect {
|
|||
|
||||
fn get_next_precedence(&self, parser: &Parser) -> Option<Result<u8, ParserError>> {
|
||||
let token = parser.peek_token();
|
||||
debug!("get_next_precedence() {token:?}");
|
||||
debug!("get_next_precedence() {:?}", token);
|
||||
|
||||
// we only return some custom value here when the behaviour (not merely the numeric value) differs
|
||||
// from the default implementation
|
||||
|
@ -259,21 +258,4 @@ impl Dialect for PostgreSqlDialect {
|
|||
fn supports_set_names(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_alter_column_type_using(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Postgres supports `NOTNULL` as an alias for `IS NOT NULL`
|
||||
/// See: <https://www.postgresql.org/docs/17/functions-comparison.html>
|
||||
fn supports_notnull_operator(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// [Postgres] supports optional field and precision options for `INTERVAL` data type.
|
||||
///
|
||||
/// [Postgres]: https://www.postgresql.org/docs/17/datatype-datetime.html
|
||||
fn supports_interval_options(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,14 +80,12 @@ impl Dialect for RedshiftSqlDialect {
|
|||
}
|
||||
|
||||
fn is_identifier_start(&self, ch: char) -> bool {
|
||||
// UTF-8 multibyte characters are supported in identifiers via the PostgreSqlDialect.
|
||||
// https://docs.aws.amazon.com/redshift/latest/dg/r_names.html
|
||||
// Extends Postgres dialect with sharp
|
||||
PostgreSqlDialect {}.is_identifier_start(ch) || ch == '#'
|
||||
}
|
||||
|
||||
fn is_identifier_part(&self, ch: char) -> bool {
|
||||
// UTF-8 multibyte characters are supported in identifiers via the PostgreSqlDialect.
|
||||
// https://docs.aws.amazon.com/redshift/latest/dg/r_names.html
|
||||
// Extends Postgres dialect with sharp
|
||||
PostgreSqlDialect {}.is_identifier_part(ch) || ch == '#'
|
||||
}
|
||||
|
||||
|
@ -131,12 +129,4 @@ impl Dialect for RedshiftSqlDialect {
|
|||
fn supports_string_literal_backslash_escape(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_select_wildcard_exclude(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_select_exclude(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,25 +17,21 @@
|
|||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use crate::alloc::string::ToString;
|
||||
use crate::ast::helpers::key_value_options::{
|
||||
KeyValueOption, KeyValueOptionType, KeyValueOptions, KeyValueOptionsDelimiter,
|
||||
};
|
||||
use crate::ast::helpers::stmt_create_database::CreateDatabaseBuilder;
|
||||
use crate::ast::helpers::key_value_options::{KeyValueOption, KeyValueOptionType, KeyValueOptions};
|
||||
use crate::ast::helpers::stmt_create_table::CreateTableBuilder;
|
||||
use crate::ast::helpers::stmt_data_loading::{
|
||||
FileStagingCommand, StageLoadSelectItem, StageLoadSelectItemKind, StageParamsObject,
|
||||
};
|
||||
use crate::ast::{
|
||||
CatalogSyncNamespaceMode, ColumnOption, ColumnPolicy, ColumnPolicyProperty, ContactEntry,
|
||||
CopyIntoSnowflakeKind, DollarQuotedString, Ident, IdentityParameters, IdentityProperty,
|
||||
IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, ObjectName,
|
||||
ObjectNamePart, RowAccessPolicy, ShowObjects, SqlOption, Statement, StorageSerializationPolicy,
|
||||
TagsColumnOption, WrappedCollection,
|
||||
ColumnOption, ColumnPolicy, ColumnPolicyProperty, CopyIntoSnowflakeKind, Ident,
|
||||
IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind,
|
||||
IdentityPropertyOrder, ObjectName, RowAccessPolicy, ShowObjects, Statement, TagsColumnOption,
|
||||
WrappedCollection,
|
||||
};
|
||||
use crate::dialect::{Dialect, Precedence};
|
||||
use crate::keywords::Keyword;
|
||||
use crate::parser::{IsOptional, Parser, ParserError};
|
||||
use crate::tokenizer::Token;
|
||||
use crate::tokenizer::{Token, Word};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::boxed::Box;
|
||||
#[cfg(not(feature = "std"))]
|
||||
|
@ -46,84 +42,9 @@ use alloc::vec::Vec;
|
|||
use alloc::{format, vec};
|
||||
|
||||
use super::keywords::RESERVED_FOR_IDENTIFIER;
|
||||
use sqlparser::ast::StorageSerializationPolicy;
|
||||
|
||||
const RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR: [Keyword; 1] = [Keyword::CONNECT_BY_ROOT];
|
||||
|
||||
// See: <https://docs.snowflake.com/en/sql-reference/reserved-keywords>
|
||||
const RESERVED_KEYWORDS_FOR_TABLE_FACTOR: &[Keyword] = &[
|
||||
Keyword::ALL,
|
||||
Keyword::ALTER,
|
||||
Keyword::AND,
|
||||
Keyword::ANY,
|
||||
Keyword::AS,
|
||||
Keyword::BETWEEN,
|
||||
Keyword::BY,
|
||||
Keyword::CHECK,
|
||||
Keyword::COLUMN,
|
||||
Keyword::CONNECT,
|
||||
Keyword::CREATE,
|
||||
Keyword::CROSS,
|
||||
Keyword::CURRENT,
|
||||
Keyword::DELETE,
|
||||
Keyword::DISTINCT,
|
||||
Keyword::DROP,
|
||||
Keyword::ELSE,
|
||||
Keyword::EXISTS,
|
||||
Keyword::FOLLOWING,
|
||||
Keyword::FOR,
|
||||
Keyword::FROM,
|
||||
Keyword::FULL,
|
||||
Keyword::GRANT,
|
||||
Keyword::GROUP,
|
||||
Keyword::HAVING,
|
||||
Keyword::ILIKE,
|
||||
Keyword::IN,
|
||||
Keyword::INCREMENT,
|
||||
Keyword::INNER,
|
||||
Keyword::INSERT,
|
||||
Keyword::INTERSECT,
|
||||
Keyword::INTO,
|
||||
Keyword::IS,
|
||||
Keyword::JOIN,
|
||||
Keyword::LEFT,
|
||||
Keyword::LIKE,
|
||||
Keyword::MINUS,
|
||||
Keyword::NATURAL,
|
||||
Keyword::NOT,
|
||||
Keyword::NULL,
|
||||
Keyword::OF,
|
||||
Keyword::ON,
|
||||
Keyword::OR,
|
||||
Keyword::ORDER,
|
||||
Keyword::QUALIFY,
|
||||
Keyword::REGEXP,
|
||||
Keyword::REVOKE,
|
||||
Keyword::RIGHT,
|
||||
Keyword::RLIKE,
|
||||
Keyword::ROW,
|
||||
Keyword::ROWS,
|
||||
Keyword::SAMPLE,
|
||||
Keyword::SELECT,
|
||||
Keyword::SET,
|
||||
Keyword::SOME,
|
||||
Keyword::START,
|
||||
Keyword::TABLE,
|
||||
Keyword::TABLESAMPLE,
|
||||
Keyword::THEN,
|
||||
Keyword::TO,
|
||||
Keyword::TRIGGER,
|
||||
Keyword::UNION,
|
||||
Keyword::UNIQUE,
|
||||
Keyword::UPDATE,
|
||||
Keyword::USING,
|
||||
Keyword::VALUES,
|
||||
Keyword::WHEN,
|
||||
Keyword::WHENEVER,
|
||||
Keyword::WHERE,
|
||||
Keyword::WINDOW,
|
||||
Keyword::WITH,
|
||||
];
|
||||
|
||||
/// A [`Dialect`] for [Snowflake](https://www.snowflake.com/)
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SnowflakeDialect;
|
||||
|
@ -210,10 +131,6 @@ impl Dialect for SnowflakeDialect {
|
|||
}
|
||||
|
||||
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
|
||||
if parser.parse_keyword(Keyword::BEGIN) {
|
||||
return Some(parser.parse_begin_exception_end());
|
||||
}
|
||||
|
||||
if parser.parse_keywords(&[Keyword::ALTER, Keyword::SESSION]) {
|
||||
// ALTER SESSION
|
||||
let set = match parser.parse_one_of_keywords(&[Keyword::SET, Keyword::UNSET]) {
|
||||
|
@ -261,8 +178,6 @@ impl Dialect for SnowflakeDialect {
|
|||
return Some(parse_create_table(
|
||||
or_replace, global, temporary, volatile, transient, iceberg, parser,
|
||||
));
|
||||
} else if parser.parse_keyword(Keyword::DATABASE) {
|
||||
return Some(parse_create_database(or_replace, transient, parser));
|
||||
} else {
|
||||
// need to go back with the cursor
|
||||
let mut back = 1;
|
||||
|
@ -364,10 +279,6 @@ impl Dialect for SnowflakeDialect {
|
|||
true
|
||||
}
|
||||
|
||||
fn supports_left_associative_joins_without_parens(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_reserved_for_identifier(&self, kw: Keyword) -> bool {
|
||||
// Unreserve some keywords that Snowflake accepts as identifiers
|
||||
// See: https://docs.snowflake.com/en/sql-reference/reserved-keywords
|
||||
|
@ -382,28 +293,27 @@ impl Dialect for SnowflakeDialect {
|
|||
true
|
||||
}
|
||||
|
||||
fn is_column_alias(&self, kw: &Keyword, parser: &mut Parser) -> bool {
|
||||
match kw {
|
||||
fn is_select_item_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
|
||||
explicit
|
||||
|| match kw {
|
||||
// The following keywords can be considered an alias as long as
|
||||
// they are not followed by other tokens that may change their meaning
|
||||
// e.g. `SELECT * EXCEPT (col1) FROM tbl`
|
||||
Keyword::EXCEPT
|
||||
// e.g. `SELECT 1 LIMIT 5`
|
||||
| Keyword::LIMIT
|
||||
// e.g. `SELECT 1 OFFSET 5 ROWS`
|
||||
| Keyword::OFFSET
|
||||
// e.g. `INSERT INTO t SELECT 1 RETURNING *`
|
||||
| Keyword::RETURNING if !matches!(parser.peek_token_ref().token, Token::Comma | Token::EOF) =>
|
||||
{
|
||||
false
|
||||
}
|
||||
|
||||
// e.g. `SELECT 1 LIMIT 5` - not an alias
|
||||
// e.g. `SELECT 1 OFFSET 5 ROWS` - not an alias
|
||||
Keyword::LIMIT | Keyword::OFFSET if peek_for_limit_options(parser) => false,
|
||||
|
||||
// `FETCH` can be considered an alias as long as it's not followed by `FIRST`` or `NEXT`
|
||||
// which would give it a different meanings, for example:
|
||||
// `SELECT 1 FETCH FIRST 10 ROWS` - not an alias
|
||||
// `SELECT 1 FETCH 10` - not an alias
|
||||
Keyword::FETCH if parser.peek_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT]).is_some()
|
||||
|| peek_for_limit_options(parser) =>
|
||||
// which would give it a different meanings, for example: `SELECT 1 FETCH FIRST 10 ROWS` - not an alias
|
||||
Keyword::FETCH
|
||||
if parser.peek_keyword(Keyword::FIRST) || parser.peek_keyword(Keyword::NEXT) =>
|
||||
{
|
||||
false
|
||||
}
|
||||
|
@ -428,97 +338,6 @@ impl Dialect for SnowflakeDialect {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_table_alias(&self, kw: &Keyword, parser: &mut Parser) -> bool {
|
||||
match kw {
|
||||
// The following keywords can be considered an alias as long as
|
||||
// they are not followed by other tokens that may change their meaning
|
||||
Keyword::RETURNING
|
||||
| Keyword::INNER
|
||||
| Keyword::USING
|
||||
| Keyword::PIVOT
|
||||
| Keyword::UNPIVOT
|
||||
| Keyword::EXCEPT
|
||||
| Keyword::MATCH_RECOGNIZE
|
||||
if !matches!(parser.peek_token_ref().token, Token::SemiColon | Token::EOF) =>
|
||||
{
|
||||
false
|
||||
}
|
||||
|
||||
// `LIMIT` can be considered an alias as long as it's not followed by a value. For example:
|
||||
// `SELECT * FROM tbl LIMIT WHERE 1=1` - alias
|
||||
// `SELECT * FROM tbl LIMIT 3` - not an alias
|
||||
Keyword::LIMIT | Keyword::OFFSET if peek_for_limit_options(parser) => false,
|
||||
|
||||
// `FETCH` can be considered an alias as long as it's not followed by `FIRST`` or `NEXT`
|
||||
// which would give it a different meanings, for example:
|
||||
// `SELECT * FROM tbl FETCH FIRST 10 ROWS` - not an alias
|
||||
// `SELECT * FROM tbl FETCH 10` - not an alias
|
||||
Keyword::FETCH
|
||||
if parser
|
||||
.peek_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT])
|
||||
.is_some()
|
||||
|| peek_for_limit_options(parser) =>
|
||||
{
|
||||
false
|
||||
}
|
||||
|
||||
// All sorts of join-related keywords can be considered aliases unless additional
|
||||
// keywords change their meaning.
|
||||
Keyword::RIGHT | Keyword::LEFT | Keyword::SEMI | Keyword::ANTI
|
||||
if parser
|
||||
.peek_one_of_keywords(&[Keyword::JOIN, Keyword::OUTER])
|
||||
.is_some() =>
|
||||
{
|
||||
false
|
||||
}
|
||||
|
||||
Keyword::GLOBAL if parser.peek_keyword(Keyword::FULL) => false,
|
||||
|
||||
// Reserved keywords by the Snowflake dialect, which seem to be less strictive
|
||||
// than what is listed in `keywords::RESERVED_FOR_TABLE_ALIAS`. The following
|
||||
// keywords were tested with the this statement: `SELECT <KW>.* FROM tbl <KW>`.
|
||||
Keyword::WITH
|
||||
| Keyword::ORDER
|
||||
| Keyword::SELECT
|
||||
| Keyword::WHERE
|
||||
| Keyword::GROUP
|
||||
| Keyword::HAVING
|
||||
| Keyword::LATERAL
|
||||
| Keyword::UNION
|
||||
| Keyword::INTERSECT
|
||||
| Keyword::MINUS
|
||||
| Keyword::ON
|
||||
| Keyword::JOIN
|
||||
| Keyword::INNER
|
||||
| Keyword::CROSS
|
||||
| Keyword::FULL
|
||||
| Keyword::LEFT
|
||||
| Keyword::RIGHT
|
||||
| Keyword::NATURAL
|
||||
| Keyword::USING
|
||||
| Keyword::ASOF
|
||||
| Keyword::MATCH_CONDITION
|
||||
| Keyword::SET
|
||||
| Keyword::QUALIFY
|
||||
| Keyword::FOR
|
||||
| Keyword::START
|
||||
| Keyword::CONNECT
|
||||
| Keyword::SAMPLE
|
||||
| Keyword::TABLESAMPLE
|
||||
| Keyword::FROM => false,
|
||||
|
||||
// Any other word is considered an alias
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_table_factor(&self, kw: &Keyword, parser: &mut Parser) -> bool {
|
||||
match kw {
|
||||
Keyword::LIMIT if peek_for_limit_options(parser) => false,
|
||||
_ => !RESERVED_KEYWORDS_FOR_TABLE_FACTOR.contains(kw),
|
||||
}
|
||||
}
|
||||
|
||||
/// See: <https://docs.snowflake.com/en/sql-reference/constructs/at-before>
|
||||
fn supports_timestamp_versioning(&self) -> bool {
|
||||
true
|
||||
|
@ -533,47 +352,6 @@ impl Dialect for SnowflakeDialect {
|
|||
fn get_reserved_keywords_for_select_item_operator(&self) -> &[Keyword] {
|
||||
&RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR
|
||||
}
|
||||
|
||||
fn supports_space_separated_column_options(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_comma_separated_drop_column_list(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn is_identifier_generating_function_name(
|
||||
&self,
|
||||
ident: &Ident,
|
||||
name_parts: &[ObjectNamePart],
|
||||
) -> bool {
|
||||
ident.quote_style.is_none()
|
||||
&& ident.value.to_lowercase() == "identifier"
|
||||
&& !name_parts
|
||||
.iter()
|
||||
.any(|p| matches!(p, ObjectNamePart::Function(_)))
|
||||
}
|
||||
|
||||
// For example: `SELECT IDENTIFIER('alias1').* FROM tbl AS alias1`
|
||||
fn supports_select_expr_star(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_select_wildcard_exclude(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// Peeks ahead to identify tokens that are expected after
|
||||
// a LIMIT/FETCH keyword.
|
||||
fn peek_for_limit_options(parser: &Parser) -> bool {
|
||||
match &parser.peek_token_ref().token {
|
||||
Token::Number(_, _) | Token::Placeholder(_) => true,
|
||||
Token::SingleQuotedString(val) if val.is_empty() => true,
|
||||
Token::DollarQuotedString(DollarQuotedString { value, .. }) if value.is_empty() => true,
|
||||
Token::Word(w) if w.keyword == Keyword::NULL => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result<Statement, ParserError> {
|
||||
|
@ -604,7 +382,6 @@ fn parse_alter_session(parser: &mut Parser, set: bool) -> Result<Statement, Pars
|
|||
set,
|
||||
session_params: KeyValueOptions {
|
||||
options: session_options,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -640,8 +417,6 @@ pub fn parse_create_table(
|
|||
// "CREATE TABLE x COPY GRANTS (c INT)" and "CREATE TABLE x (c INT) COPY GRANTS" are both
|
||||
// accepted by Snowflake
|
||||
|
||||
let mut plain_options = vec![];
|
||||
|
||||
loop {
|
||||
let next_token = parser.next_token();
|
||||
match &next_token.token {
|
||||
|
@ -653,27 +428,28 @@ pub fn parse_create_table(
|
|||
Keyword::COMMENT => {
|
||||
// Rewind the COMMENT keyword
|
||||
parser.prev_token();
|
||||
if let Some(comment_def) = parser.parse_optional_inline_comment()? {
|
||||
plain_options.push(SqlOption::Comment(comment_def))
|
||||
}
|
||||
builder = builder.comment(parser.parse_optional_inline_comment()?);
|
||||
}
|
||||
Keyword::AS => {
|
||||
let query = parser.parse_query()?;
|
||||
builder = builder.query(Some(query));
|
||||
break;
|
||||
}
|
||||
Keyword::CLONE => {
|
||||
let clone = parser.parse_object_name(false).ok();
|
||||
builder = builder.clone_clause(clone);
|
||||
break;
|
||||
}
|
||||
Keyword::LIKE => {
|
||||
let like = parser.parse_object_name(false).ok();
|
||||
builder = builder.like(like);
|
||||
break;
|
||||
}
|
||||
Keyword::CLUSTER => {
|
||||
parser.expect_keyword_is(Keyword::BY)?;
|
||||
parser.expect_token(&Token::LParen)?;
|
||||
let cluster_by = Some(WrappedCollection::Parentheses(
|
||||
parser.parse_comma_separated(|p| p.parse_expr())?,
|
||||
parser.parse_comma_separated(|p| p.parse_identifier())?,
|
||||
));
|
||||
parser.expect_token(&Token::RParen)?;
|
||||
|
||||
|
@ -681,11 +457,29 @@ pub fn parse_create_table(
|
|||
}
|
||||
Keyword::ENABLE_SCHEMA_EVOLUTION => {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
builder = builder.enable_schema_evolution(Some(parser.parse_boolean_string()?));
|
||||
let enable_schema_evolution =
|
||||
match parser.parse_one_of_keywords(&[Keyword::TRUE, Keyword::FALSE]) {
|
||||
Some(Keyword::TRUE) => true,
|
||||
Some(Keyword::FALSE) => false,
|
||||
_ => {
|
||||
return parser.expected("TRUE or FALSE", next_token);
|
||||
}
|
||||
};
|
||||
|
||||
builder = builder.enable_schema_evolution(Some(enable_schema_evolution));
|
||||
}
|
||||
Keyword::CHANGE_TRACKING => {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
builder = builder.change_tracking(Some(parser.parse_boolean_string()?));
|
||||
let change_tracking =
|
||||
match parser.parse_one_of_keywords(&[Keyword::TRUE, Keyword::FALSE]) {
|
||||
Some(Keyword::TRUE) => true,
|
||||
Some(Keyword::FALSE) => false,
|
||||
_ => {
|
||||
return parser.expected("TRUE or FALSE", next_token);
|
||||
}
|
||||
};
|
||||
|
||||
builder = builder.change_tracking(Some(change_tracking));
|
||||
}
|
||||
Keyword::DATA_RETENTION_TIME_IN_DAYS => {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
|
@ -762,9 +556,6 @@ pub fn parse_create_table(
|
|||
builder.storage_serialization_policy =
|
||||
Some(parse_storage_serialization_policy(parser)?);
|
||||
}
|
||||
Keyword::IF if parser.parse_keywords(&[Keyword::NOT, Keyword::EXISTS]) => {
|
||||
builder = builder.if_not_exists(true);
|
||||
}
|
||||
_ => {
|
||||
return parser.expected("end of statement", next_token);
|
||||
}
|
||||
|
@ -775,7 +566,7 @@ pub fn parse_create_table(
|
|||
builder = builder.columns(columns).constraints(constraints);
|
||||
}
|
||||
Token::EOF => {
|
||||
if !builder.validate_schema_info() {
|
||||
if builder.columns.is_empty() {
|
||||
return Err(ParserError::ParserError(
|
||||
"unexpected end of input".to_string(),
|
||||
));
|
||||
|
@ -784,7 +575,7 @@ pub fn parse_create_table(
|
|||
break;
|
||||
}
|
||||
Token::SemiColon => {
|
||||
if !builder.validate_schema_info() {
|
||||
if builder.columns.is_empty() {
|
||||
return Err(ParserError::ParserError(
|
||||
"unexpected end of input".to_string(),
|
||||
));
|
||||
|
@ -798,13 +589,6 @@ pub fn parse_create_table(
|
|||
}
|
||||
}
|
||||
}
|
||||
let table_options = if !plain_options.is_empty() {
|
||||
crate::ast::CreateTableOptions::Plain(plain_options)
|
||||
} else {
|
||||
crate::ast::CreateTableOptions::None
|
||||
};
|
||||
|
||||
builder = builder.table_options(table_options);
|
||||
|
||||
if iceberg && builder.base_location.is_none() {
|
||||
return Err(ParserError::ParserError(
|
||||
|
@ -815,115 +599,6 @@ pub fn parse_create_table(
|
|||
Ok(builder.build())
|
||||
}
|
||||
|
||||
/// Parse snowflake create database statement.
|
||||
/// <https://docs.snowflake.com/en/sql-reference/sql/create-database>
|
||||
pub fn parse_create_database(
|
||||
or_replace: bool,
|
||||
transient: bool,
|
||||
parser: &mut Parser,
|
||||
) -> Result<Statement, ParserError> {
|
||||
let if_not_exists = parser.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
||||
let name = parser.parse_object_name(false)?;
|
||||
|
||||
let mut builder = CreateDatabaseBuilder::new(name)
|
||||
.or_replace(or_replace)
|
||||
.transient(transient)
|
||||
.if_not_exists(if_not_exists);
|
||||
|
||||
loop {
|
||||
let next_token = parser.next_token();
|
||||
match &next_token.token {
|
||||
Token::Word(word) => match word.keyword {
|
||||
Keyword::CLONE => {
|
||||
builder = builder.clone_clause(Some(parser.parse_object_name(false)?));
|
||||
}
|
||||
Keyword::DATA_RETENTION_TIME_IN_DAYS => {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
builder =
|
||||
builder.data_retention_time_in_days(Some(parser.parse_literal_uint()?));
|
||||
}
|
||||
Keyword::MAX_DATA_EXTENSION_TIME_IN_DAYS => {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
builder =
|
||||
builder.max_data_extension_time_in_days(Some(parser.parse_literal_uint()?));
|
||||
}
|
||||
Keyword::EXTERNAL_VOLUME => {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
builder = builder.external_volume(Some(parser.parse_literal_string()?));
|
||||
}
|
||||
Keyword::CATALOG => {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
builder = builder.catalog(Some(parser.parse_literal_string()?));
|
||||
}
|
||||
Keyword::REPLACE_INVALID_CHARACTERS => {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
builder =
|
||||
builder.replace_invalid_characters(Some(parser.parse_boolean_string()?));
|
||||
}
|
||||
Keyword::DEFAULT_DDL_COLLATION => {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
builder = builder.default_ddl_collation(Some(parser.parse_literal_string()?));
|
||||
}
|
||||
Keyword::STORAGE_SERIALIZATION_POLICY => {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
let policy = parse_storage_serialization_policy(parser)?;
|
||||
builder = builder.storage_serialization_policy(Some(policy));
|
||||
}
|
||||
Keyword::COMMENT => {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
builder = builder.comment(Some(parser.parse_literal_string()?));
|
||||
}
|
||||
Keyword::CATALOG_SYNC => {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
builder = builder.catalog_sync(Some(parser.parse_literal_string()?));
|
||||
}
|
||||
Keyword::CATALOG_SYNC_NAMESPACE_FLATTEN_DELIMITER => {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
builder = builder.catalog_sync_namespace_flatten_delimiter(Some(
|
||||
parser.parse_literal_string()?,
|
||||
));
|
||||
}
|
||||
Keyword::CATALOG_SYNC_NAMESPACE_MODE => {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
let mode =
|
||||
match parser.parse_one_of_keywords(&[Keyword::NEST, Keyword::FLATTEN]) {
|
||||
Some(Keyword::NEST) => CatalogSyncNamespaceMode::Nest,
|
||||
Some(Keyword::FLATTEN) => CatalogSyncNamespaceMode::Flatten,
|
||||
_ => {
|
||||
return parser.expected("NEST or FLATTEN", next_token);
|
||||
}
|
||||
};
|
||||
builder = builder.catalog_sync_namespace_mode(Some(mode));
|
||||
}
|
||||
Keyword::WITH => {
|
||||
if parser.parse_keyword(Keyword::TAG) {
|
||||
parser.expect_token(&Token::LParen)?;
|
||||
let tags = parser.parse_comma_separated(Parser::parse_tag)?;
|
||||
parser.expect_token(&Token::RParen)?;
|
||||
builder = builder.with_tags(Some(tags));
|
||||
} else if parser.parse_keyword(Keyword::CONTACT) {
|
||||
parser.expect_token(&Token::LParen)?;
|
||||
let contacts = parser.parse_comma_separated(|p| {
|
||||
let purpose = p.parse_identifier()?.value;
|
||||
p.expect_token(&Token::Eq)?;
|
||||
let contact = p.parse_identifier()?.value;
|
||||
Ok(ContactEntry { purpose, contact })
|
||||
})?;
|
||||
parser.expect_token(&Token::RParen)?;
|
||||
builder = builder.with_contacts(Some(contacts));
|
||||
} else {
|
||||
return parser.expected("TAG or CONTACT", next_token);
|
||||
}
|
||||
}
|
||||
_ => return parser.expected("end of statement", next_token),
|
||||
},
|
||||
Token::SemiColon | Token::EOF => break,
|
||||
_ => return parser.expected("end of statement", next_token),
|
||||
}
|
||||
}
|
||||
Ok(builder.build())
|
||||
}
|
||||
|
||||
pub fn parse_storage_serialization_policy(
|
||||
parser: &mut Parser,
|
||||
) -> Result<StorageSerializationPolicy, ParserError> {
|
||||
|
@ -957,19 +632,19 @@ pub fn parse_create_stage(
|
|||
// [ directoryTableParams ]
|
||||
if parser.parse_keyword(Keyword::DIRECTORY) {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
directory_table_params = parser.parse_key_value_options(true, &[])?;
|
||||
directory_table_params = parse_parentheses_options(parser)?;
|
||||
}
|
||||
|
||||
// [ file_format]
|
||||
if parser.parse_keyword(Keyword::FILE_FORMAT) {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
file_format = parser.parse_key_value_options(true, &[])?;
|
||||
file_format = parse_parentheses_options(parser)?;
|
||||
}
|
||||
|
||||
// [ copy_options ]
|
||||
if parser.parse_keyword(Keyword::COPY_OPTIONS) {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
copy_options = parser.parse_key_value_options(true, &[])?;
|
||||
copy_options = parse_parentheses_options(parser)?;
|
||||
}
|
||||
|
||||
// [ comment ]
|
||||
|
@ -986,15 +661,12 @@ pub fn parse_create_stage(
|
|||
stage_params,
|
||||
directory_table_params: KeyValueOptions {
|
||||
options: directory_table_params,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
file_format: KeyValueOptions {
|
||||
options: file_format,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
copy_options: KeyValueOptions {
|
||||
options: copy_options,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
comment,
|
||||
})
|
||||
|
@ -1017,8 +689,6 @@ pub fn parse_stage_name_identifier(parser: &mut Parser) -> Result<Ident, ParserE
|
|||
Token::Tilde => ident.push('~'),
|
||||
Token::Mod => ident.push('%'),
|
||||
Token::Div => ident.push('/'),
|
||||
Token::Plus => ident.push('+'),
|
||||
Token::Number(n, _) => ident.push_str(n),
|
||||
Token::Word(w) => ident.push_str(&w.to_string()),
|
||||
_ => return parser.expected("stage name identifier", parser.peek_token()),
|
||||
}
|
||||
|
@ -1063,16 +733,10 @@ pub fn parse_copy_into(parser: &mut Parser) -> Result<Statement, ParserError> {
|
|||
let mut from_stage = None;
|
||||
let mut stage_params = StageParamsObject {
|
||||
url: None,
|
||||
encryption: KeyValueOptions {
|
||||
options: vec![],
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
encryption: KeyValueOptions { options: vec![] },
|
||||
endpoint: None,
|
||||
storage_integration: None,
|
||||
credentials: KeyValueOptions {
|
||||
options: vec![],
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
credentials: KeyValueOptions { options: vec![] },
|
||||
};
|
||||
let mut from_query = None;
|
||||
let mut partition = None;
|
||||
|
@ -1134,7 +798,7 @@ pub fn parse_copy_into(parser: &mut Parser) -> Result<Statement, ParserError> {
|
|||
// FILE_FORMAT
|
||||
if parser.parse_keyword(Keyword::FILE_FORMAT) {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
file_format = parser.parse_key_value_options(true, &[])?;
|
||||
file_format = parse_parentheses_options(parser)?;
|
||||
// PARTITION BY
|
||||
} else if parser.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
|
||||
partition = Some(Box::new(parser.parse_expr()?))
|
||||
|
@ -1172,14 +836,14 @@ pub fn parse_copy_into(parser: &mut Parser) -> Result<Statement, ParserError> {
|
|||
// COPY OPTIONS
|
||||
} else if parser.parse_keyword(Keyword::COPY_OPTIONS) {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
copy_options = parser.parse_key_value_options(true, &[])?;
|
||||
copy_options = parse_parentheses_options(parser)?;
|
||||
} else {
|
||||
match parser.next_token().token {
|
||||
Token::SemiColon | Token::EOF => break,
|
||||
Token::Comma => continue,
|
||||
// In `COPY INTO <location>` the copy options do not have a shared key
|
||||
// like in `COPY INTO <table>`
|
||||
Token::Word(key) => copy_options.push(parser.parse_key_value_option(key)?),
|
||||
Token::Word(key) => copy_options.push(parse_option(parser, key)?),
|
||||
_ => return parser.expected("another copy option, ; or EOF'", parser.peek_token()),
|
||||
}
|
||||
}
|
||||
|
@ -1198,11 +862,9 @@ pub fn parse_copy_into(parser: &mut Parser) -> Result<Statement, ParserError> {
|
|||
pattern,
|
||||
file_format: KeyValueOptions {
|
||||
options: file_format,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
copy_options: KeyValueOptions {
|
||||
options: copy_options,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
validation_mode,
|
||||
partition,
|
||||
|
@ -1302,14 +964,8 @@ fn parse_select_item_for_data_load(
|
|||
|
||||
fn parse_stage_params(parser: &mut Parser) -> Result<StageParamsObject, ParserError> {
|
||||
let (mut url, mut storage_integration, mut endpoint) = (None, None, None);
|
||||
let mut encryption: KeyValueOptions = KeyValueOptions {
|
||||
options: vec![],
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
};
|
||||
let mut credentials: KeyValueOptions = KeyValueOptions {
|
||||
options: vec![],
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
};
|
||||
let mut encryption: KeyValueOptions = KeyValueOptions { options: vec![] };
|
||||
let mut credentials: KeyValueOptions = KeyValueOptions { options: vec![] };
|
||||
|
||||
// URL
|
||||
if parser.parse_keyword(Keyword::URL) {
|
||||
|
@ -1339,8 +995,7 @@ fn parse_stage_params(parser: &mut Parser) -> Result<StageParamsObject, ParserEr
|
|||
if parser.parse_keyword(Keyword::CREDENTIALS) {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
credentials = KeyValueOptions {
|
||||
options: parser.parse_key_value_options(true, &[])?,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
options: parse_parentheses_options(parser)?,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1348,8 +1003,7 @@ fn parse_stage_params(parser: &mut Parser) -> Result<StageParamsObject, ParserEr
|
|||
if parser.parse_keyword(Keyword::ENCRYPTION) {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
encryption = KeyValueOptions {
|
||||
options: parser.parse_key_value_options(true, &[])?,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
options: parse_parentheses_options(parser)?,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1383,7 +1037,7 @@ fn parse_session_options(
|
|||
Token::Word(key) => {
|
||||
parser.advance_token();
|
||||
if set {
|
||||
let option = parser.parse_key_value_option(key)?;
|
||||
let option = parse_option(parser, key)?;
|
||||
options.push(option);
|
||||
} else {
|
||||
options.push(KeyValueOption {
|
||||
|
@ -1407,6 +1061,63 @@ fn parse_session_options(
|
|||
}
|
||||
}
|
||||
|
||||
/// Parses options provided within parentheses like:
|
||||
/// ( ENABLE = { TRUE | FALSE }
|
||||
/// [ AUTO_REFRESH = { TRUE | FALSE } ]
|
||||
/// [ REFRESH_ON_CREATE = { TRUE | FALSE } ]
|
||||
/// [ NOTIFICATION_INTEGRATION = '<notification_integration_name>' ] )
|
||||
///
|
||||
fn parse_parentheses_options(parser: &mut Parser) -> Result<Vec<KeyValueOption>, ParserError> {
|
||||
let mut options: Vec<KeyValueOption> = Vec::new();
|
||||
parser.expect_token(&Token::LParen)?;
|
||||
loop {
|
||||
match parser.next_token().token {
|
||||
Token::RParen => break,
|
||||
Token::Comma => continue,
|
||||
Token::Word(key) => options.push(parse_option(parser, key)?),
|
||||
_ => return parser.expected("another option or ')'", parser.peek_token()),
|
||||
};
|
||||
}
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
/// Parses a `KEY = VALUE` construct based on the specified key
|
||||
fn parse_option(parser: &mut Parser, key: Word) -> Result<KeyValueOption, ParserError> {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
if parser.parse_keyword(Keyword::TRUE) {
|
||||
Ok(KeyValueOption {
|
||||
option_name: key.value,
|
||||
option_type: KeyValueOptionType::BOOLEAN,
|
||||
value: "TRUE".to_string(),
|
||||
})
|
||||
} else if parser.parse_keyword(Keyword::FALSE) {
|
||||
Ok(KeyValueOption {
|
||||
option_name: key.value,
|
||||
option_type: KeyValueOptionType::BOOLEAN,
|
||||
value: "FALSE".to_string(),
|
||||
})
|
||||
} else {
|
||||
match parser.next_token().token {
|
||||
Token::SingleQuotedString(value) => Ok(KeyValueOption {
|
||||
option_name: key.value,
|
||||
option_type: KeyValueOptionType::STRING,
|
||||
value,
|
||||
}),
|
||||
Token::Word(word) => Ok(KeyValueOption {
|
||||
option_name: key.value,
|
||||
option_type: KeyValueOptionType::ENUM,
|
||||
value: word.value,
|
||||
}),
|
||||
Token::Number(n, _) => Ok(KeyValueOption {
|
||||
option_name: key.value,
|
||||
option_type: KeyValueOptionType::NUMBER,
|
||||
value: n,
|
||||
}),
|
||||
_ => parser.expected("expected option value", parser.peek_token()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parsing a property of identity or autoincrement column option
|
||||
/// Syntax:
|
||||
/// ```sql
|
||||
|
@ -1452,7 +1163,7 @@ fn parse_column_policy_property(
|
|||
parser: &mut Parser,
|
||||
with: bool,
|
||||
) -> Result<ColumnPolicyProperty, ParserError> {
|
||||
let policy_name = parser.parse_object_name(false)?;
|
||||
let policy_name = parser.parse_identifier()?;
|
||||
let using_columns = if parser.parse_keyword(Keyword::USING) {
|
||||
parser.expect_token(&Token::LParen)?;
|
||||
let columns = parser.parse_comma_separated(|p| p.parse_identifier())?;
|
||||
|
|
|
@ -15,11 +15,7 @@
|
|||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::boxed::Box;
|
||||
|
||||
use crate::ast::BinaryOperator;
|
||||
use crate::ast::{Expr, Statement};
|
||||
use crate::ast::Statement;
|
||||
use crate::dialect::Dialect;
|
||||
use crate::keywords::Keyword;
|
||||
use crate::parser::{Parser, ParserError};
|
||||
|
@ -74,27 +70,6 @@ impl Dialect for SQLiteDialect {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_infix(
|
||||
&self,
|
||||
parser: &mut crate::parser::Parser,
|
||||
expr: &crate::ast::Expr,
|
||||
_precedence: u8,
|
||||
) -> Option<Result<crate::ast::Expr, ParserError>> {
|
||||
// Parse MATCH and REGEXP as operators
|
||||
// See <https://www.sqlite.org/lang_expr.html#the_like_glob_regexp_match_and_extract_operators>
|
||||
for (keyword, op) in [
|
||||
(Keyword::REGEXP, BinaryOperator::Regexp),
|
||||
(Keyword::MATCH, BinaryOperator::Match),
|
||||
] {
|
||||
if parser.parse_keyword(keyword) {
|
||||
let left = Box::new(expr.clone());
|
||||
let right = Box::new(parser.parse_expr().unwrap());
|
||||
return Some(Ok(Expr::BinaryOp { left, op, right }));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_in_empty_list(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
@ -110,10 +85,4 @@ impl Dialect for SQLiteDialect {
|
|||
fn supports_dollar_placeholder(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// SQLite supports `NOTNULL` as aliases for `IS NOT NULL`
|
||||
/// See: <https://sqlite.org/syntax/expr.html>
|
||||
fn supports_notnull_operator(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
//! Utilities for formatting SQL AST nodes with pretty printing support.
|
||||
//!
|
||||
//! The module provides formatters that implement the `Display` trait with support
|
||||
//! for both regular (`{}`) and pretty (`{:#}`) formatting modes. Pretty printing
|
||||
//! adds proper indentation and line breaks to make SQL statements more readable.
|
||||
|
||||
use core::fmt::{self, Display, Write};
|
||||
|
||||
/// A wrapper around a value that adds an indent to the value when displayed with {:#}.
|
||||
pub(crate) struct Indent<T>(pub T);
|
||||
|
||||
const INDENT: &str = " ";
|
||||
|
||||
impl<T> Display for Indent<T>
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
f.write_str(INDENT)?;
|
||||
write!(Indent(f), "{:#}", self.0)
|
||||
} else {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an indent to the inner writer
|
||||
impl<T> Write for Indent<T>
|
||||
where
|
||||
T: Write,
|
||||
{
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
self.0.write_str(s)?;
|
||||
// Our NewLine and SpaceOrNewline utils always print individual newlines as a single-character string.
|
||||
if s == "\n" {
|
||||
self.0.write_str(INDENT)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that inserts a newline when displayed with {:#}, but not when displayed with {}.
|
||||
pub(crate) struct NewLine;
|
||||
|
||||
impl Display for NewLine {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
f.write_char('\n')
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that inserts a space when displayed with {}, but a newline when displayed with {:#}.
|
||||
pub(crate) struct SpaceOrNewline;
|
||||
|
||||
impl Display for SpaceOrNewline {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
f.write_char('\n')
|
||||
} else {
|
||||
f.write_char(' ')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that displays a comma-separated list of values.
|
||||
/// When pretty-printed (using {:#}), it displays each value on a new line.
|
||||
pub(crate) struct DisplayCommaSeparated<'a, T: fmt::Display>(pub(crate) &'a [T]);
|
||||
|
||||
impl<T: fmt::Display> fmt::Display for DisplayCommaSeparated<'_, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut first = true;
|
||||
for t in self.0 {
|
||||
if !first {
|
||||
f.write_char(',')?;
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
}
|
||||
first = false;
|
||||
t.fmt(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays a whitespace, followed by a comma-separated list that is indented when pretty-printed.
|
||||
pub(crate) fn indented_list<T: fmt::Display>(f: &mut fmt::Formatter, items: &[T]) -> fmt::Result {
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
Indent(DisplayCommaSeparated(items)).fmt(f)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_indent() {
|
||||
struct TwoLines;
|
||||
|
||||
impl Display for TwoLines {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("line 1")?;
|
||||
SpaceOrNewline.fmt(f)?;
|
||||
f.write_str("line 2")
|
||||
}
|
||||
}
|
||||
|
||||
let indent = Indent(TwoLines);
|
||||
assert_eq!(
|
||||
indent.to_string(),
|
||||
TwoLines.to_string(),
|
||||
"Only the alternate form should be indented"
|
||||
);
|
||||
assert_eq!(format!("{:#}", indent), " line 1\n line 2");
|
||||
}
|
||||
}
|
|
@ -83,7 +83,6 @@ define_keywords!(
|
|||
ADMIN,
|
||||
AFTER,
|
||||
AGAINST,
|
||||
AGGREGATE,
|
||||
AGGREGATION,
|
||||
ALERT,
|
||||
ALGORITHM,
|
||||
|
@ -116,11 +115,9 @@ define_keywords!(
|
|||
AUTHENTICATION,
|
||||
AUTHORIZATION,
|
||||
AUTO,
|
||||
AUTOEXTEND_SIZE,
|
||||
AUTOINCREMENT,
|
||||
AUTO_INCREMENT,
|
||||
AVG,
|
||||
AVG_ROW_LENGTH,
|
||||
AVRO,
|
||||
BACKWARD,
|
||||
BASE64,
|
||||
|
@ -166,8 +163,6 @@ define_keywords!(
|
|||
CAST,
|
||||
CATALOG,
|
||||
CATALOG_SYNC,
|
||||
CATALOG_SYNC_NAMESPACE_FLATTEN_DELIMITER,
|
||||
CATALOG_SYNC_NAMESPACE_MODE,
|
||||
CATCH,
|
||||
CEIL,
|
||||
CEILING,
|
||||
|
@ -184,7 +179,6 @@ define_keywords!(
|
|||
CHARSET,
|
||||
CHAR_LENGTH,
|
||||
CHECK,
|
||||
CHECKSUM,
|
||||
CIRCLE,
|
||||
CLEAR,
|
||||
CLOB,
|
||||
|
@ -215,7 +209,6 @@ define_keywords!(
|
|||
CONNECTOR,
|
||||
CONNECT_BY_ROOT,
|
||||
CONSTRAINT,
|
||||
CONTACT,
|
||||
CONTAINS,
|
||||
CONTINUE,
|
||||
CONVERT,
|
||||
|
@ -275,13 +268,11 @@ define_keywords!(
|
|||
DEFINED,
|
||||
DEFINER,
|
||||
DELAYED,
|
||||
DELAY_KEY_WRITE,
|
||||
DELETE,
|
||||
DELIMITED,
|
||||
DELIMITER,
|
||||
DELTA,
|
||||
DENSE_RANK,
|
||||
DENY,
|
||||
DEREF,
|
||||
DESC,
|
||||
DESCRIBE,
|
||||
|
@ -296,7 +287,6 @@ define_keywords!(
|
|||
DISTRIBUTE,
|
||||
DIV,
|
||||
DO,
|
||||
DOMAIN,
|
||||
DOUBLE,
|
||||
DOW,
|
||||
DOY,
|
||||
|
@ -321,7 +311,6 @@ define_keywords!(
|
|||
END_PARTITION,
|
||||
ENFORCED,
|
||||
ENGINE,
|
||||
ENGINE_ATTRIBUTE,
|
||||
ENUM,
|
||||
ENUM16,
|
||||
ENUM8,
|
||||
|
@ -349,7 +338,6 @@ define_keywords!(
|
|||
EXPLAIN,
|
||||
EXPLICIT,
|
||||
EXPORT,
|
||||
EXTEND,
|
||||
EXTENDED,
|
||||
EXTENSION,
|
||||
EXTERNAL,
|
||||
|
@ -369,7 +357,6 @@ define_keywords!(
|
|||
FIRST,
|
||||
FIRST_VALUE,
|
||||
FIXEDSTRING,
|
||||
FLATTEN,
|
||||
FLOAT,
|
||||
FLOAT32,
|
||||
FLOAT4,
|
||||
|
@ -399,7 +386,6 @@ define_keywords!(
|
|||
FUNCTION,
|
||||
FUNCTIONS,
|
||||
FUSION,
|
||||
FUTURE,
|
||||
GENERAL,
|
||||
GENERATE,
|
||||
GENERATED,
|
||||
|
@ -455,7 +441,6 @@ define_keywords!(
|
|||
INPUTFORMAT,
|
||||
INSENSITIVE,
|
||||
INSERT,
|
||||
INSERT_METHOD,
|
||||
INSTALL,
|
||||
INSTANT,
|
||||
INSTEAD,
|
||||
|
@ -492,7 +477,6 @@ define_keywords!(
|
|||
JULIAN,
|
||||
KEY,
|
||||
KEYS,
|
||||
KEY_BLOCK_SIZE,
|
||||
KILL,
|
||||
LAG,
|
||||
LANGUAGE,
|
||||
|
@ -546,7 +530,6 @@ define_keywords!(
|
|||
MAX,
|
||||
MAXVALUE,
|
||||
MAX_DATA_EXTENSION_TIME_IN_DAYS,
|
||||
MAX_ROWS,
|
||||
MEASURES,
|
||||
MEDIUMBLOB,
|
||||
MEDIUMINT,
|
||||
|
@ -568,7 +551,6 @@ define_keywords!(
|
|||
MINUTE,
|
||||
MINUTES,
|
||||
MINVALUE,
|
||||
MIN_ROWS,
|
||||
MOD,
|
||||
MODE,
|
||||
MODIFIES,
|
||||
|
@ -588,7 +570,6 @@ define_keywords!(
|
|||
NATURAL,
|
||||
NCHAR,
|
||||
NCLOB,
|
||||
NEST,
|
||||
NESTED,
|
||||
NETWORK,
|
||||
NEW,
|
||||
|
@ -613,7 +594,6 @@ define_keywords!(
|
|||
NOT,
|
||||
NOTHING,
|
||||
NOTIFY,
|
||||
NOTNULL,
|
||||
NOWAIT,
|
||||
NO_WRITE_TO_BINLOG,
|
||||
NTH_VALUE,
|
||||
|
@ -653,7 +633,6 @@ define_keywords!(
|
|||
ORDER,
|
||||
ORDINALITY,
|
||||
ORGANIZATION,
|
||||
OTHER,
|
||||
OUT,
|
||||
OUTER,
|
||||
OUTPUT,
|
||||
|
@ -669,7 +648,6 @@ define_keywords!(
|
|||
OWNERSHIP,
|
||||
PACKAGE,
|
||||
PACKAGES,
|
||||
PACK_KEYS,
|
||||
PARALLEL,
|
||||
PARAMETER,
|
||||
PARQUET,
|
||||
|
@ -761,7 +739,6 @@ define_keywords!(
|
|||
REPAIR,
|
||||
REPEATABLE,
|
||||
REPLACE,
|
||||
REPLACE_INVALID_CHARACTERS,
|
||||
REPLICA,
|
||||
REPLICATE,
|
||||
REPLICATION,
|
||||
|
@ -793,7 +770,6 @@ define_keywords!(
|
|||
ROW,
|
||||
ROWID,
|
||||
ROWS,
|
||||
ROW_FORMAT,
|
||||
ROW_NUMBER,
|
||||
RULE,
|
||||
RUN,
|
||||
|
@ -808,7 +784,6 @@ define_keywords!(
|
|||
SEARCH,
|
||||
SECOND,
|
||||
SECONDARY,
|
||||
SECONDARY_ENGINE_ATTRIBUTE,
|
||||
SECONDS,
|
||||
SECRET,
|
||||
SECURITY,
|
||||
|
@ -823,7 +798,6 @@ define_keywords!(
|
|||
SERDE,
|
||||
SERDEPROPERTIES,
|
||||
SERIALIZABLE,
|
||||
SERVER,
|
||||
SERVICE,
|
||||
SESSION,
|
||||
SESSION_USER,
|
||||
|
@ -854,7 +828,6 @@ define_keywords!(
|
|||
SQLSTATE,
|
||||
SQLWARNING,
|
||||
SQRT,
|
||||
SRID,
|
||||
STABLE,
|
||||
STAGE,
|
||||
START,
|
||||
|
@ -862,21 +835,16 @@ define_keywords!(
|
|||
STATEMENT,
|
||||
STATIC,
|
||||
STATISTICS,
|
||||
STATS_AUTO_RECALC,
|
||||
STATS_PERSISTENT,
|
||||
STATS_SAMPLE_PAGES,
|
||||
STATUS,
|
||||
STDDEV_POP,
|
||||
STDDEV_SAMP,
|
||||
STDIN,
|
||||
STDOUT,
|
||||
STEP,
|
||||
STORAGE,
|
||||
STORAGE_INTEGRATION,
|
||||
STORAGE_SERIALIZATION_POLICY,
|
||||
STORED,
|
||||
STRAIGHT_JOIN,
|
||||
STREAM,
|
||||
STRICT,
|
||||
STRING,
|
||||
STRUCT,
|
||||
|
@ -899,7 +867,6 @@ define_keywords!(
|
|||
TABLE,
|
||||
TABLES,
|
||||
TABLESAMPLE,
|
||||
TABLESPACE,
|
||||
TAG,
|
||||
TARGET,
|
||||
TASK,
|
||||
|
@ -945,8 +912,6 @@ define_keywords!(
|
|||
TRY,
|
||||
TRY_CAST,
|
||||
TRY_CONVERT,
|
||||
TSQUERY,
|
||||
TSVECTOR,
|
||||
TUPLE,
|
||||
TYPE,
|
||||
UBIGINT,
|
||||
|
@ -990,7 +955,6 @@ define_keywords!(
|
|||
UUID,
|
||||
VACUUM,
|
||||
VALID,
|
||||
VALIDATE,
|
||||
VALIDATION_MODE,
|
||||
VALUE,
|
||||
VALUES,
|
||||
|
@ -1018,7 +982,6 @@ define_keywords!(
|
|||
WHEN,
|
||||
WHENEVER,
|
||||
WHERE,
|
||||
WHILE,
|
||||
WIDTH_BUCKET,
|
||||
WINDOW,
|
||||
WITH,
|
||||
|
@ -1026,7 +989,6 @@ define_keywords!(
|
|||
WITHOUT,
|
||||
WITHOUT_ARRAY_WRAPPER,
|
||||
WORK,
|
||||
WRAPPER,
|
||||
WRITE,
|
||||
XML,
|
||||
XMLNAMESPACES,
|
||||
|
@ -1103,7 +1065,6 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
|
|||
Keyword::SAMPLE,
|
||||
Keyword::TABLESAMPLE,
|
||||
Keyword::FROM,
|
||||
Keyword::OPEN,
|
||||
];
|
||||
|
||||
/// Can't be used as a column alias, so that `SELECT <expr> alias`
|
||||
|
@ -1127,7 +1088,6 @@ pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
|
|||
Keyword::FETCH,
|
||||
Keyword::UNION,
|
||||
Keyword::EXCEPT,
|
||||
Keyword::EXCLUDE,
|
||||
Keyword::INTERSECT,
|
||||
Keyword::MINUS,
|
||||
Keyword::CLUSTER,
|
||||
|
|
26
src/lib.rs
26
src/lib.rs
|
@ -64,27 +64,6 @@
|
|||
//! // The original SQL text can be generated from the AST
|
||||
//! assert_eq!(ast[0].to_string(), sql);
|
||||
//! ```
|
||||
//!
|
||||
//! # Pretty Printing
|
||||
//!
|
||||
//! SQL statements can be pretty-printed with proper indentation and line breaks using the alternate flag (`{:#}`):
|
||||
//!
|
||||
//! ```
|
||||
//! # use sqlparser::dialect::GenericDialect;
|
||||
//! # use sqlparser::parser::Parser;
|
||||
//! let sql = "SELECT a, b FROM table_1";
|
||||
//! let ast = Parser::parse_sql(&GenericDialect, sql).unwrap();
|
||||
//!
|
||||
//! // Pretty print with indentation and line breaks
|
||||
//! let pretty_sql = format!("{:#}", ast[0]);
|
||||
//! assert_eq!(pretty_sql, r#"
|
||||
//! SELECT
|
||||
//! a,
|
||||
//! b
|
||||
//! FROM
|
||||
//! table_1
|
||||
//! "#.trim());
|
||||
//! ```
|
||||
//! [sqlparser crates.io page]: https://crates.io/crates/sqlparser
|
||||
//! [`Parser::parse_sql`]: crate::parser::Parser::parse_sql
|
||||
//! [`Parser::new`]: crate::parser::Parser::new
|
||||
|
@ -149,10 +128,6 @@
|
|||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
// Permit large enum variants to keep a unified, expressive AST.
|
||||
// Splitting complex nodes (expressions, statements, types) into separate types
|
||||
// would bloat the API and hide intent. Extra memory is a worthwhile tradeoff.
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
|
||||
// Allow proc-macros to find this crate
|
||||
extern crate self as sqlparser;
|
||||
|
@ -167,7 +142,6 @@ extern crate pretty_assertions;
|
|||
pub mod ast;
|
||||
#[macro_use]
|
||||
pub mod dialect;
|
||||
mod display_utils;
|
||||
pub mod keywords;
|
||||
pub mod parser;
|
||||
pub mod tokenizer;
|
||||
|
|
2402
src/parser/mod.rs
2402
src/parser/mod.rs
File diff suppressed because it is too large
Load diff
|
@ -151,8 +151,6 @@ impl TestedDialects {
|
|||
///
|
||||
/// 2. re-serializing the result of parsing `sql` produces the same
|
||||
/// `canonical` sql string
|
||||
///
|
||||
/// For multiple statements, use [`statements_parse_to`].
|
||||
pub fn one_statement_parses_to(&self, sql: &str, canonical: &str) -> Statement {
|
||||
let mut statements = self.parse_sql_statements(sql).expect(sql);
|
||||
assert_eq!(statements.len(), 1);
|
||||
|
@ -168,24 +166,6 @@ impl TestedDialects {
|
|||
only_statement
|
||||
}
|
||||
|
||||
/// The same as [`one_statement_parses_to`] but it works for a multiple statements
|
||||
pub fn statements_parse_to(&self, sql: &str, canonical: &str) -> Vec<Statement> {
|
||||
let statements = self.parse_sql_statements(sql).expect(sql);
|
||||
if !canonical.is_empty() && sql != canonical {
|
||||
assert_eq!(self.parse_sql_statements(canonical).unwrap(), statements);
|
||||
} else {
|
||||
assert_eq!(
|
||||
sql,
|
||||
statements
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("; ")
|
||||
);
|
||||
}
|
||||
statements
|
||||
}
|
||||
|
||||
/// Ensures that `sql` parses as an [`Expr`], and that
|
||||
/// re-serializing the parse result produces canonical
|
||||
pub fn expr_parses_to(&self, sql: &str, canonical: &str) -> Expr {
|
||||
|
@ -270,7 +250,7 @@ impl TestedDialects {
|
|||
tokenizer = tokenizer.with_unescape(options.unescape);
|
||||
}
|
||||
let tokens = tokenizer.tokenize().unwrap();
|
||||
assert_eq!(expected, tokens, "Tokenized differently for {dialect:?}");
|
||||
assert_eq!(expected, tokens, "Tokenized differently for {:?}", dialect);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -294,11 +274,6 @@ pub fn all_dialects() -> TestedDialects {
|
|||
])
|
||||
}
|
||||
|
||||
// Returns all available dialects with the specified parser options
|
||||
pub fn all_dialects_with_options(options: ParserOptions) -> TestedDialects {
|
||||
TestedDialects::new_with_options(all_dialects().dialects, options)
|
||||
}
|
||||
|
||||
/// Returns all dialects matching the given predicate.
|
||||
pub fn all_dialects_where<F>(predicate: F) -> TestedDialects
|
||||
where
|
||||
|
@ -350,12 +325,10 @@ pub fn alter_table_op_with_name(stmt: Statement, expected_name: &str) -> AlterTa
|
|||
operations,
|
||||
on_cluster: _,
|
||||
location: _,
|
||||
iceberg,
|
||||
} => {
|
||||
assert_eq!(name.to_string(), expected_name);
|
||||
assert!(!if_exists);
|
||||
assert!(!is_only);
|
||||
assert!(!iceberg);
|
||||
only(operations)
|
||||
}
|
||||
_ => panic!("Expected ALTER TABLE statement"),
|
||||
|
@ -371,11 +344,6 @@ pub fn number(n: &str) -> Value {
|
|||
Value::Number(n.parse().unwrap(), false)
|
||||
}
|
||||
|
||||
/// Creates a [Value::SingleQuotedString]
|
||||
pub fn single_quoted_string(s: impl Into<String>) -> Value {
|
||||
Value::SingleQuotedString(s.into())
|
||||
}
|
||||
|
||||
pub fn table_alias(name: impl Into<String>) -> Option<TableAlias> {
|
||||
Some(TableAlias {
|
||||
name: Ident::new(name),
|
||||
|
@ -458,52 +426,3 @@ pub fn call(function: &str, args: impl IntoIterator<Item = Expr>) -> Expr {
|
|||
within_group: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the first index column (mysql calls it a key part) of the first index found in a
|
||||
/// [`Statement::CreateIndex`], [`Statement::CreateTable`], or [`Statement::AlterTable`].
|
||||
pub fn index_column(stmt: Statement) -> Expr {
|
||||
match stmt {
|
||||
Statement::CreateIndex(CreateIndex { columns, .. }) => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
Statement::CreateTable(CreateTable { constraints, .. }) => {
|
||||
match constraints.first().unwrap() {
|
||||
TableConstraint::Index { columns, .. } => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
TableConstraint::Unique { columns, .. } => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
TableConstraint::PrimaryKey { columns, .. } => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
TableConstraint::FulltextOrSpatial { columns, .. } => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
_ => panic!("Expected an index, unique, primary, full text, or spatial constraint (foreign key does not support general key part expressions)"),
|
||||
}
|
||||
}
|
||||
Statement::AlterTable { operations, .. } => match operations.first().unwrap() {
|
||||
AlterTableOperation::AddConstraint { constraint, .. } => {
|
||||
match constraint {
|
||||
TableConstraint::Index { columns, .. } => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
TableConstraint::Unique { columns, .. } => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
TableConstraint::PrimaryKey { columns, .. } => {
|
||||
columns.first().unwrap().column.expr.clone()
|
||||
}
|
||||
TableConstraint::FulltextOrSpatial {
|
||||
columns,
|
||||
..
|
||||
} => columns.first().unwrap().column.expr.clone(),
|
||||
_ => panic!("Expected an index, unique, primary, full text, or spatial constraint (foreign key does not support general key part expressions)"),
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected a constraint"),
|
||||
},
|
||||
_ => panic!("Expected CREATE INDEX, ALTER TABLE, or CREATE TABLE, got: {stmt:?}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -246,8 +246,6 @@ pub enum Token {
|
|||
ShiftLeftVerticalBar,
|
||||
/// `|>> PostgreSQL/Redshift geometrical binary operator (Is strictly above?)
|
||||
VerticalBarShiftRight,
|
||||
/// `|> BigQuery pipe operator
|
||||
VerticalBarRightAngleBracket,
|
||||
/// `#>>`, extracts JSON sub-object at the specified path as text
|
||||
HashLongArrow,
|
||||
/// jsonb @> jsonb -> boolean: Test whether left json contains the right json
|
||||
|
@ -361,7 +359,6 @@ impl fmt::Display for Token {
|
|||
Token::AmpersandRightAngleBracket => f.write_str("&>"),
|
||||
Token::AmpersandLeftAngleBracketVerticalBar => f.write_str("&<|"),
|
||||
Token::VerticalBarAmpersandRightAngleBracket => f.write_str("|&>"),
|
||||
Token::VerticalBarRightAngleBracket => f.write_str("|>"),
|
||||
Token::TwoWayArrow => f.write_str("<->"),
|
||||
Token::LeftAngleBracketCaret => f.write_str("<^"),
|
||||
Token::RightAngleBracketCaret => f.write_str(">^"),
|
||||
|
@ -1191,22 +1188,6 @@ impl<'a> Tokenizer<'a> {
|
|||
}
|
||||
// numbers and period
|
||||
'0'..='9' | '.' => {
|
||||
// special case where if ._ is encountered after a word then that word
|
||||
// is a table and the _ is the start of the col name.
|
||||
// if the prev token is not a word, then this is not a valid sql
|
||||
// word or number.
|
||||
if ch == '.' && chars.peekable.clone().nth(1) == Some('_') {
|
||||
if let Some(Token::Word(_)) = prev_token {
|
||||
chars.next();
|
||||
return Ok(Some(Token::Period));
|
||||
}
|
||||
|
||||
return self.tokenizer_error(
|
||||
chars.location(),
|
||||
"Unexpected character '_'".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
// Some dialects support underscore as number separator
|
||||
// There can only be one at a time and it must be followed by another digit
|
||||
let is_number_separator = |ch: char, next_char: Option<char>| {
|
||||
|
@ -1422,9 +1403,6 @@ impl<'a> Tokenizer<'a> {
|
|||
_ => self.start_binop_opt(chars, "|>", None),
|
||||
}
|
||||
}
|
||||
Some('>') if self.dialect.supports_pipe_operator() => {
|
||||
self.consume_for_binop(chars, "|>", Token::VerticalBarRightAngleBracket)
|
||||
}
|
||||
// Bitshift '|' operator
|
||||
_ => self.start_binop(chars, "|", Token::Pipe),
|
||||
}
|
||||
|
@ -1751,7 +1729,7 @@ impl<'a> Tokenizer<'a> {
|
|||
(None, Some(tok)) => Ok(Some(tok)),
|
||||
(None, None) => self.tokenizer_error(
|
||||
chars.location(),
|
||||
format!("Expected a valid binary operator after '{prefix}'"),
|
||||
format!("Expected a valid binary operator after '{}'", prefix),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -1809,7 +1787,7 @@ impl<'a> Tokenizer<'a> {
|
|||
chars.next();
|
||||
|
||||
let mut temp = String::new();
|
||||
let end_delimiter = format!("${value}$");
|
||||
let end_delimiter = format!("${}$", value);
|
||||
|
||||
loop {
|
||||
match chars.next() {
|
||||
|
@ -2402,13 +2380,13 @@ fn take_char_from_hex_digits(
|
|||
location: chars.location(),
|
||||
})?;
|
||||
let digit = next_char.to_digit(16).ok_or_else(|| TokenizerError {
|
||||
message: format!("Invalid hex digit in escaped unicode string: {next_char}"),
|
||||
message: format!("Invalid hex digit in escaped unicode string: {}", next_char),
|
||||
location: chars.location(),
|
||||
})?;
|
||||
result = result * 16 + digit;
|
||||
}
|
||||
char::from_u32(result).ok_or_else(|| TokenizerError {
|
||||
message: format!("Invalid unicode character: {result:x}"),
|
||||
message: format!("Invalid unicode character: {:x}", result),
|
||||
location: chars.location(),
|
||||
})
|
||||
}
|
||||
|
@ -3504,7 +3482,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn check_unescape(s: &str, expected: Option<&str>) {
|
||||
let s = format!("'{s}'");
|
||||
let s = format!("'{}'", s);
|
||||
let mut state = State {
|
||||
peekable: s.chars().peekable(),
|
||||
line: 0,
|
||||
|
@ -4034,40 +4012,4 @@ mod tests {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokenize_period_underscore() {
|
||||
let sql = String::from("SELECT table._col");
|
||||
// a dialect that supports underscores in numeric literals
|
||||
let dialect = PostgreSqlDialect {};
|
||||
let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
|
||||
|
||||
let expected = vec![
|
||||
Token::make_keyword("SELECT"),
|
||||
Token::Whitespace(Whitespace::Space),
|
||||
Token::Word(Word {
|
||||
value: "table".to_string(),
|
||||
quote_style: None,
|
||||
keyword: Keyword::TABLE,
|
||||
}),
|
||||
Token::Period,
|
||||
Token::Word(Word {
|
||||
value: "_col".to_string(),
|
||||
quote_style: None,
|
||||
keyword: Keyword::NoKeyword,
|
||||
}),
|
||||
];
|
||||
|
||||
compare(expected, tokens);
|
||||
|
||||
let sql = String::from("SELECT ._123");
|
||||
if let Ok(tokens) = Tokenizer::new(&dialect, &sql).tokenize() {
|
||||
panic!("Tokenizer should have failed on {sql}, but it succeeded with {tokens:?}");
|
||||
}
|
||||
|
||||
let sql = String::from("SELECT ._abc");
|
||||
if let Ok(tokens) = Tokenizer::new(&dialect, &sql).tokenize() {
|
||||
panic!("Tokenizer should have failed on {sql}, but it succeeded with {tokens:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,414 +0,0 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
use sqlparser::dialect::GenericDialect;
|
||||
use sqlparser::parser::Parser;
|
||||
|
||||
fn prettify(sql: &str) -> String {
|
||||
let ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap();
|
||||
format!("{:#}", ast[0])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_select() {
|
||||
assert_eq!(
|
||||
prettify("SELECT a, b, c FROM my_table WHERE x = 1 AND y = 2"),
|
||||
r#"
|
||||
SELECT
|
||||
a,
|
||||
b,
|
||||
c
|
||||
FROM
|
||||
my_table
|
||||
WHERE
|
||||
x = 1 AND y = 2
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_join() {
|
||||
assert_eq!(
|
||||
prettify("SELECT a FROM table1 JOIN table2 ON table1.id = table2.id"),
|
||||
r#"
|
||||
SELECT
|
||||
a
|
||||
FROM
|
||||
table1
|
||||
JOIN table2 ON table1.id = table2.id
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_subquery() {
|
||||
assert_eq!(
|
||||
prettify("SELECT * FROM (SELECT a, b FROM my_table) AS subquery"),
|
||||
r#"
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
a,
|
||||
b
|
||||
FROM
|
||||
my_table
|
||||
) AS subquery
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_union() {
|
||||
assert_eq!(
|
||||
prettify("SELECT a FROM table1 UNION SELECT b FROM table2"),
|
||||
r#"
|
||||
SELECT
|
||||
a
|
||||
FROM
|
||||
table1
|
||||
UNION
|
||||
SELECT
|
||||
b
|
||||
FROM
|
||||
table2
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_group_by() {
|
||||
assert_eq!(
|
||||
prettify("SELECT a, COUNT(*) FROM my_table GROUP BY a HAVING COUNT(*) > 1"),
|
||||
r#"
|
||||
SELECT
|
||||
a,
|
||||
COUNT(*)
|
||||
FROM
|
||||
my_table
|
||||
GROUP BY
|
||||
a
|
||||
HAVING
|
||||
COUNT(*) > 1
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_cte() {
|
||||
assert_eq!(
|
||||
prettify("WITH cte AS (SELECT a, b FROM my_table) SELECT * FROM cte"),
|
||||
r#"
|
||||
WITH cte AS (
|
||||
SELECT
|
||||
a,
|
||||
b
|
||||
FROM
|
||||
my_table
|
||||
)
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
cte
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_case_when() {
|
||||
assert_eq!(
|
||||
prettify("SELECT CASE WHEN x > 0 THEN 'positive' WHEN x < 0 THEN 'negative' ELSE 'zero' END FROM my_table"),
|
||||
r#"
|
||||
SELECT
|
||||
CASE
|
||||
WHEN x > 0 THEN
|
||||
'positive'
|
||||
WHEN x < 0 THEN
|
||||
'negative'
|
||||
ELSE
|
||||
'zero'
|
||||
END
|
||||
FROM
|
||||
my_table
|
||||
"#.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_window_function() {
|
||||
assert_eq!(
|
||||
prettify("SELECT id, value, ROW_NUMBER() OVER (PARTITION BY category ORDER BY value DESC) as rank FROM my_table"),
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
value,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY category
|
||||
ORDER BY value DESC
|
||||
) AS rank
|
||||
FROM
|
||||
my_table
|
||||
"#.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_multiline_string() {
|
||||
assert_eq!(
|
||||
prettify("SELECT 'multiline\nstring' AS str"),
|
||||
r#"
|
||||
SELECT
|
||||
'multiline
|
||||
string' AS str
|
||||
"#
|
||||
.trim(),
|
||||
"A literal string with a newline should be kept as is. The contents of the string should not be indented."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_insert_values() {
|
||||
assert_eq!(
|
||||
prettify("INSERT INTO my_table (a, b, c) VALUES (1, 2, 3), (4, 5, 6)"),
|
||||
r#"
|
||||
INSERT INTO my_table (a, b, c)
|
||||
VALUES
|
||||
(1, 2, 3),
|
||||
(4, 5, 6)
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_insert_select() {
|
||||
assert_eq!(
|
||||
prettify("INSERT INTO my_table (a, b) SELECT x, y FROM source_table RETURNING a AS id"),
|
||||
r#"
|
||||
INSERT INTO my_table (a, b)
|
||||
SELECT
|
||||
x,
|
||||
y
|
||||
FROM
|
||||
source_table
|
||||
RETURNING
|
||||
a AS id
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_update() {
|
||||
assert_eq!(
|
||||
prettify("UPDATE my_table SET a = 1, b = 2 WHERE x > 0 RETURNING id, name"),
|
||||
r#"
|
||||
UPDATE my_table
|
||||
SET
|
||||
a = 1,
|
||||
b = 2
|
||||
WHERE
|
||||
x > 0
|
||||
RETURNING
|
||||
id,
|
||||
name
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_delete() {
|
||||
assert_eq!(
|
||||
prettify("DELETE FROM my_table WHERE x > 0 RETURNING id, name"),
|
||||
r#"
|
||||
DELETE FROM
|
||||
my_table
|
||||
WHERE
|
||||
x > 0
|
||||
RETURNING
|
||||
id,
|
||||
name
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
prettify("DELETE table1, table2"),
|
||||
r#"
|
||||
DELETE
|
||||
table1,
|
||||
table2
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_create_table() {
|
||||
assert_eq!(
|
||||
prettify("CREATE TABLE my_table (id INT PRIMARY KEY, name VARCHAR(255) NOT NULL, CONSTRAINT fk_other FOREIGN KEY (id) REFERENCES other_table(id))"),
|
||||
r#"
|
||||
CREATE TABLE my_table (
|
||||
id INT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
CONSTRAINT fk_other FOREIGN KEY (id) REFERENCES other_table(id)
|
||||
)
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_create_view() {
|
||||
assert_eq!(
|
||||
prettify("CREATE VIEW my_view AS SELECT a, b FROM my_table WHERE x > 0"),
|
||||
r#"
|
||||
CREATE VIEW my_view AS
|
||||
SELECT
|
||||
a,
|
||||
b
|
||||
FROM
|
||||
my_table
|
||||
WHERE
|
||||
x > 0
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "https://github.com/apache/datafusion-sqlparser-rs/issues/1850"]
|
||||
fn test_pretty_print_create_function() {
|
||||
assert_eq!(
|
||||
prettify("CREATE FUNCTION my_func() RETURNS INT BEGIN SELECT COUNT(*) INTO @count FROM my_table; RETURN @count; END"),
|
||||
r#"
|
||||
CREATE FUNCTION my_func() RETURNS INT
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO @count FROM my_table;
|
||||
RETURN @count;
|
||||
END
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "https://github.com/apache/datafusion-sqlparser-rs/issues/1850"]
|
||||
fn test_pretty_print_json_table() {
|
||||
assert_eq!(
|
||||
prettify("SELECT * FROM JSON_TABLE(@json, '$[*]' COLUMNS (id INT PATH '$.id', name VARCHAR(255) PATH '$.name')) AS jt"),
|
||||
r#"
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
JSON_TABLE(
|
||||
@json,
|
||||
'$[*]' COLUMNS (
|
||||
id INT PATH '$.id',
|
||||
name VARCHAR(255) PATH '$.name'
|
||||
)
|
||||
) AS jt
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "https://github.com/apache/datafusion-sqlparser-rs/issues/1850"]
|
||||
fn test_pretty_print_transaction_blocks() {
|
||||
assert_eq!(
|
||||
prettify("BEGIN; UPDATE my_table SET x = 1; COMMIT;"),
|
||||
r#"
|
||||
BEGIN;
|
||||
UPDATE my_table SET x = 1;
|
||||
COMMIT;
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "https://github.com/apache/datafusion-sqlparser-rs/issues/1850"]
|
||||
fn test_pretty_print_control_flow() {
|
||||
assert_eq!(
|
||||
prettify("IF x > 0 THEN SELECT 'positive'; ELSE SELECT 'negative'; END IF;"),
|
||||
r#"
|
||||
IF x > 0 THEN
|
||||
SELECT 'positive';
|
||||
ELSE
|
||||
SELECT 'negative';
|
||||
END IF;
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "https://github.com/apache/datafusion-sqlparser-rs/issues/1850"]
|
||||
fn test_pretty_print_merge() {
|
||||
assert_eq!(
|
||||
prettify("MERGE INTO target_table t USING source_table s ON t.id = s.id WHEN MATCHED THEN UPDATE SET t.value = s.value WHEN NOT MATCHED THEN INSERT (id, value) VALUES (s.id, s.value)"),
|
||||
r#"
|
||||
MERGE INTO target_table t
|
||||
USING source_table s ON t.id = s.id
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET t.value = s.value
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (id, value) VALUES (s.id, s.value)
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "https://github.com/apache/datafusion-sqlparser-rs/issues/1850"]
|
||||
fn test_pretty_print_create_index() {
|
||||
assert_eq!(
|
||||
prettify("CREATE INDEX idx_name ON my_table (column1, column2)"),
|
||||
r#"
|
||||
CREATE INDEX idx_name
|
||||
ON my_table (column1, column2)
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "https://github.com/apache/datafusion-sqlparser-rs/issues/1850"]
|
||||
fn test_pretty_print_explain() {
|
||||
assert_eq!(
|
||||
prettify("EXPLAIN ANALYZE SELECT * FROM my_table WHERE x > 0"),
|
||||
r#"
|
||||
EXPLAIN ANALYZE
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
my_table
|
||||
WHERE
|
||||
x > 0
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -28,7 +28,7 @@ use test_utils::*;
|
|||
use sqlparser::ast::Expr::{BinaryOp, Identifier};
|
||||
use sqlparser::ast::SelectItem::UnnamedExpr;
|
||||
use sqlparser::ast::TableFactor::Table;
|
||||
use sqlparser::ast::Value::Boolean;
|
||||
use sqlparser::ast::Value::Number;
|
||||
use sqlparser::ast::*;
|
||||
use sqlparser::dialect::ClickHouseDialect;
|
||||
use sqlparser::dialect::GenericDialect;
|
||||
|
@ -60,7 +60,6 @@ fn parse_map_access_expr() {
|
|||
),
|
||||
})],
|
||||
})],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident::new("foos")])),
|
||||
|
@ -220,14 +219,10 @@ fn parse_delimited_identifiers() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_table() {
|
||||
clickhouse().verified_stmt(r#"CREATE TABLE "x" ("a" "int") ENGINE = MergeTree ORDER BY ("x")"#);
|
||||
clickhouse().verified_stmt(r#"CREATE TABLE "x" ("a" "int") ENGINE = MergeTree ORDER BY "x""#);
|
||||
clickhouse().verified_stmt(r#"CREATE TABLE "x" ("a" "int") ENGINE=MergeTree ORDER BY ("x")"#);
|
||||
clickhouse().verified_stmt(r#"CREATE TABLE "x" ("a" "int") ENGINE=MergeTree ORDER BY "x""#);
|
||||
clickhouse().verified_stmt(
|
||||
r#"CREATE TABLE "x" ("a" "int") ENGINE = MergeTree ORDER BY "x" AS SELECT * FROM "t" WHERE true"#,
|
||||
);
|
||||
clickhouse().one_statement_parses_to(
|
||||
"CREATE TABLE x (a int) ENGINE = MergeTree() ORDER BY a",
|
||||
"CREATE TABLE x (a INT) ENGINE = MergeTree ORDER BY a",
|
||||
r#"CREATE TABLE "x" ("a" "int") ENGINE=MergeTree ORDER BY "x" AS SELECT * FROM "t" WHERE true"#,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -594,7 +589,7 @@ fn parse_clickhouse_data_types() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_table_with_nullable() {
|
||||
let sql = r#"CREATE TABLE table (k UInt8, `a` Nullable(String), `b` Nullable(DateTime64(9, 'UTC')), c Nullable(DateTime64(9)), d Date32 NULL) ENGINE = MergeTree ORDER BY (`k`)"#;
|
||||
let sql = r#"CREATE TABLE table (k UInt8, `a` Nullable(String), `b` Nullable(DateTime64(9, 'UTC')), c Nullable(DateTime64(9)), d Date32 NULL) ENGINE=MergeTree ORDER BY (`k`)"#;
|
||||
// ClickHouse has a case-sensitive definition of data type, but canonical representation is not
|
||||
let canonical_sql = sql.replace("String", "STRING");
|
||||
|
||||
|
@ -674,13 +669,11 @@ fn parse_create_table_with_nested_data_types() {
|
|||
DataType::Tuple(vec![
|
||||
StructField {
|
||||
field_name: None,
|
||||
field_type: DataType::FixedString(128),
|
||||
options: None,
|
||||
field_type: DataType::FixedString(128)
|
||||
},
|
||||
StructField {
|
||||
field_name: None,
|
||||
field_type: DataType::Int128,
|
||||
options: None,
|
||||
field_type: DataType::Int128
|
||||
}
|
||||
])
|
||||
))),
|
||||
|
@ -692,14 +685,12 @@ fn parse_create_table_with_nested_data_types() {
|
|||
StructField {
|
||||
field_name: Some("a".into()),
|
||||
field_type: DataType::Datetime64(9, None),
|
||||
options: None,
|
||||
},
|
||||
StructField {
|
||||
field_name: Some("b".into()),
|
||||
field_type: DataType::Array(ArrayElemTypeDef::Parenthesis(
|
||||
Box::new(DataType::Uuid)
|
||||
)),
|
||||
options: None,
|
||||
))
|
||||
},
|
||||
]),
|
||||
options: vec![],
|
||||
|
@ -723,14 +714,14 @@ fn parse_create_table_with_nested_data_types() {
|
|||
fn parse_create_table_with_primary_key() {
|
||||
match clickhouse_and_generic().verified_stmt(concat!(
|
||||
r#"CREATE TABLE db.table (`i` INT, `k` INT)"#,
|
||||
" ENGINE = SharedMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}')",
|
||||
" ENGINE=SharedMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}')",
|
||||
" PRIMARY KEY tuple(i)",
|
||||
" ORDER BY tuple(i)",
|
||||
)) {
|
||||
Statement::CreateTable(CreateTable {
|
||||
name,
|
||||
columns,
|
||||
table_options,
|
||||
engine,
|
||||
primary_key,
|
||||
order_by,
|
||||
..
|
||||
|
@ -751,23 +742,16 @@ fn parse_create_table_with_primary_key() {
|
|||
],
|
||||
columns
|
||||
);
|
||||
|
||||
let plain_options = match table_options {
|
||||
CreateTableOptions::Plain(options) => options,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
assert!(plain_options.contains(&SqlOption::NamedParenthesizedList(
|
||||
NamedParenthesizedList {
|
||||
key: Ident::new("ENGINE"),
|
||||
name: Some(Ident::new("SharedMergeTree")),
|
||||
values: vec![
|
||||
assert_eq!(
|
||||
engine,
|
||||
Some(TableEngine {
|
||||
name: "SharedMergeTree".to_string(),
|
||||
parameters: Some(vec![
|
||||
Ident::with_quote('\'', "/clickhouse/tables/{uuid}/{shard}"),
|
||||
Ident::with_quote('\'', "{replica}"),
|
||||
]
|
||||
}
|
||||
)));
|
||||
|
||||
]),
|
||||
})
|
||||
);
|
||||
fn assert_function(actual: &Function, name: &str, arg: &str) -> bool {
|
||||
assert_eq!(actual.name, ObjectName::from(vec![Ident::new(name)]));
|
||||
assert_eq!(
|
||||
|
@ -814,7 +798,7 @@ fn parse_create_table_with_variant_default_expressions() {
|
|||
" b DATETIME EPHEMERAL now(),",
|
||||
" c DATETIME EPHEMERAL,",
|
||||
" d STRING ALIAS toString(c)",
|
||||
") ENGINE = MergeTree"
|
||||
") ENGINE=MergeTree"
|
||||
);
|
||||
match clickhouse_and_generic().verified_stmt(sql) {
|
||||
Statement::CreateTable(CreateTable { columns, .. }) => {
|
||||
|
@ -919,7 +903,7 @@ fn parse_create_view_with_fields_data_types() {
|
|||
}]),
|
||||
vec![]
|
||||
)),
|
||||
options: None,
|
||||
options: None
|
||||
},
|
||||
ViewColumnDef {
|
||||
name: "f".into(),
|
||||
|
@ -931,7 +915,7 @@ fn parse_create_view_with_fields_data_types() {
|
|||
}]),
|
||||
vec![]
|
||||
)),
|
||||
options: None,
|
||||
options: None
|
||||
},
|
||||
]
|
||||
);
|
||||
|
@ -970,103 +954,38 @@ fn parse_limit_by() {
|
|||
|
||||
#[test]
|
||||
fn parse_settings_in_query() {
|
||||
fn check_settings(sql: &str, expected: Vec<Setting>) {
|
||||
match clickhouse_and_generic().verified_stmt(sql) {
|
||||
Statement::Query(q) => {
|
||||
assert_eq!(q.settings, Some(expected));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
match clickhouse_and_generic()
|
||||
.verified_stmt(r#"SELECT * FROM t SETTINGS max_threads = 1, max_block_size = 10000"#)
|
||||
{
|
||||
Statement::Query(query) => {
|
||||
assert_eq!(
|
||||
query.settings,
|
||||
Some(vec![
|
||||
Setting {
|
||||
key: Ident::new("max_threads"),
|
||||
value: Number("1".parse().unwrap(), false)
|
||||
},
|
||||
Setting {
|
||||
key: Ident::new("max_block_size"),
|
||||
value: Number("10000".parse().unwrap(), false)
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (sql, expected_settings) in [
|
||||
(
|
||||
r#"SELECT * FROM t SETTINGS max_threads = 1, max_block_size = 10000"#,
|
||||
vec![
|
||||
Setting {
|
||||
key: Ident::new("max_threads"),
|
||||
value: Expr::value(number("1")),
|
||||
},
|
||||
Setting {
|
||||
key: Ident::new("max_block_size"),
|
||||
value: Expr::value(number("10000")),
|
||||
},
|
||||
],
|
||||
),
|
||||
(
|
||||
r#"SELECT * FROM t SETTINGS additional_table_filters = {'table_1': 'x != 2'}"#,
|
||||
vec![Setting {
|
||||
key: Ident::new("additional_table_filters"),
|
||||
value: Expr::Dictionary(vec![DictionaryField {
|
||||
key: Ident::with_quote('\'', "table_1"),
|
||||
value: Expr::value(single_quoted_string("x != 2")).into(),
|
||||
}]),
|
||||
}],
|
||||
),
|
||||
(
|
||||
r#"SELECT * FROM t SETTINGS additional_result_filter = 'x != 2', query_plan_optimize_lazy_materialization = false"#,
|
||||
vec![
|
||||
Setting {
|
||||
key: Ident::new("additional_result_filter"),
|
||||
value: Expr::value(single_quoted_string("x != 2")),
|
||||
},
|
||||
Setting {
|
||||
key: Ident::new("query_plan_optimize_lazy_materialization"),
|
||||
value: Expr::value(Boolean(false)),
|
||||
},
|
||||
],
|
||||
),
|
||||
] {
|
||||
check_settings(sql, expected_settings);
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let invalid_cases = vec![
|
||||
("SELECT * FROM t SETTINGS a", "Expected: =, found: EOF"),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a=",
|
||||
"Expected: an expression, found: EOF",
|
||||
),
|
||||
("SELECT * FROM t SETTINGS a=1, b", "Expected: =, found: EOF"),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a=1, b=",
|
||||
"Expected: an expression, found: EOF",
|
||||
),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a = {",
|
||||
"Expected: identifier, found: EOF",
|
||||
),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a = {'b'",
|
||||
"Expected: :, found: EOF",
|
||||
),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a = {'b': ",
|
||||
"Expected: an expression, found: EOF",
|
||||
),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a = {'b': 'c',}",
|
||||
"Expected: identifier, found: }",
|
||||
),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a = {'b': 'c', 'd'}",
|
||||
"Expected: :, found: }",
|
||||
),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a = {'b': 'c', 'd': }",
|
||||
"Expected: an expression, found: }",
|
||||
),
|
||||
(
|
||||
"SELECT * FROM t SETTINGS a = {ANY(b)}",
|
||||
"Expected: :, found: (",
|
||||
),
|
||||
"SELECT * FROM t SETTINGS a",
|
||||
"SELECT * FROM t SETTINGS a=",
|
||||
"SELECT * FROM t SETTINGS a=1, b",
|
||||
"SELECT * FROM t SETTINGS a=1, b=",
|
||||
"SELECT * FROM t SETTINGS a=1, b=c",
|
||||
];
|
||||
for (sql, error_msg) in invalid_cases {
|
||||
assert_eq!(
|
||||
clickhouse_and_generic()
|
||||
.parse_sql_statements(sql)
|
||||
.unwrap_err(),
|
||||
ParserError(error_msg.to_string())
|
||||
);
|
||||
for sql in invalid_cases {
|
||||
clickhouse_and_generic()
|
||||
.parse_sql_statements(sql)
|
||||
.expect_err("Expected: SETTINGS key = value, found: ");
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
|
@ -1415,7 +1334,7 @@ fn parse_use() {
|
|||
for object_name in &valid_object_names {
|
||||
// Test single identifier without quotes
|
||||
assert_eq!(
|
||||
clickhouse().verified_stmt(&format!("USE {object_name}")),
|
||||
clickhouse().verified_stmt(&format!("USE {}", object_name)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
|
||||
object_name.to_string()
|
||||
)])))
|
||||
|
@ -1423,7 +1342,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with different type of quotes
|
||||
assert_eq!(
|
||||
clickhouse().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
|
||||
clickhouse().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
object_name.to_string(),
|
||||
|
@ -1437,7 +1356,7 @@ fn parse_use() {
|
|||
fn test_query_with_format_clause() {
|
||||
let format_options = vec!["TabSeparated", "JSONCompact", "NULL"];
|
||||
for format in &format_options {
|
||||
let sql = format!("SELECT * FROM t FORMAT {format}");
|
||||
let sql = format!("SELECT * FROM t FORMAT {}", format);
|
||||
match clickhouse_and_generic().verified_stmt(&sql) {
|
||||
Statement::Query(query) => {
|
||||
if *format == "NULL" {
|
||||
|
@ -1620,11 +1539,11 @@ fn parse_select_table_function_settings() {
|
|||
settings: Some(vec![
|
||||
Setting {
|
||||
key: "s0".into(),
|
||||
value: Expr::value(number("3")),
|
||||
value: Value::Number("3".parse().unwrap(), false),
|
||||
},
|
||||
Setting {
|
||||
key: "s1".into(),
|
||||
value: Expr::value(single_quoted_string("s")),
|
||||
value: Value::SingleQuotedString("s".into()),
|
||||
},
|
||||
]),
|
||||
},
|
||||
|
@ -1645,11 +1564,11 @@ fn parse_select_table_function_settings() {
|
|||
settings: Some(vec![
|
||||
Setting {
|
||||
key: "s0".into(),
|
||||
value: Expr::value(number("3")),
|
||||
value: Value::Number("3".parse().unwrap(), false),
|
||||
},
|
||||
Setting {
|
||||
key: "s1".into(),
|
||||
value: Expr::value(single_quoted_string("s")),
|
||||
value: Value::SingleQuotedString("s".into()),
|
||||
},
|
||||
]),
|
||||
},
|
||||
|
@ -1659,6 +1578,7 @@ fn parse_select_table_function_settings() {
|
|||
"SELECT * FROM t(SETTINGS a=)",
|
||||
"SELECT * FROM t(SETTINGS a=1, b)",
|
||||
"SELECT * FROM t(SETTINGS a=1, b=)",
|
||||
"SELECT * FROM t(SETTINGS a=1, b=c)",
|
||||
];
|
||||
for sql in invalid_cases {
|
||||
clickhouse_and_generic()
|
||||
|
@ -1705,30 +1625,6 @@ fn parse_table_sample() {
|
|||
clickhouse().verified_stmt("SELECT * FROM tbl SAMPLE 1 / 10 OFFSET 1 / 2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_not_null_in_column_options() {
|
||||
// In addition to DEFAULT and CHECK ClickHouse also supports MATERIALIZED, all of which
|
||||
// can contain `IS NOT NULL` and thus `NOT NULL` as an alias.
|
||||
let canonical = concat!(
|
||||
"CREATE TABLE foo (",
|
||||
"abc INT DEFAULT (42 IS NOT NULL) NOT NULL,",
|
||||
" not_null BOOL MATERIALIZED (abc IS NOT NULL),",
|
||||
" CHECK (abc IS NOT NULL)",
|
||||
")",
|
||||
);
|
||||
clickhouse().verified_stmt(canonical);
|
||||
clickhouse().one_statement_parses_to(
|
||||
concat!(
|
||||
"CREATE TABLE foo (",
|
||||
"abc INT DEFAULT (42 NOT NULL) NOT NULL,",
|
||||
" not_null BOOL MATERIALIZED (abc NOT NULL),",
|
||||
" CHECK (abc NOT NULL)",
|
||||
")",
|
||||
),
|
||||
canonical,
|
||||
);
|
||||
}
|
||||
|
||||
fn clickhouse() -> TestedDialects {
|
||||
TestedDialects::new(vec![Box::new(ClickHouseDialect {})])
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,11 +15,9 @@
|
|||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
use sqlparser::ast::helpers::attached_token::AttachedToken;
|
||||
use sqlparser::ast::*;
|
||||
use sqlparser::dialect::{DatabricksDialect, GenericDialect};
|
||||
use sqlparser::parser::ParserError;
|
||||
use sqlparser::tokenizer::Span;
|
||||
use test_utils::*;
|
||||
|
||||
#[macro_use]
|
||||
|
@ -110,8 +108,6 @@ fn test_databricks_lambdas() {
|
|||
Expr::Lambda(LambdaFunction {
|
||||
params: OneOrManyWithParens::Many(vec![Ident::new("p1"), Ident::new("p2")]),
|
||||
body: Box::new(Expr::Case {
|
||||
case_token: AttachedToken::empty(),
|
||||
end_token: AttachedToken::empty(),
|
||||
operand: None,
|
||||
conditions: vec![
|
||||
CaseWhen {
|
||||
|
@ -214,7 +210,7 @@ fn parse_use() {
|
|||
for object_name in &valid_object_names {
|
||||
// Test single identifier without quotes
|
||||
assert_eq!(
|
||||
databricks().verified_stmt(&format!("USE {object_name}")),
|
||||
databricks().verified_stmt(&format!("USE {}", object_name)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
|
||||
object_name.to_string()
|
||||
)])))
|
||||
|
@ -222,7 +218,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with different type of quotes
|
||||
assert_eq!(
|
||||
databricks().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
|
||||
databricks().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
object_name.to_string(),
|
||||
|
@ -234,21 +230,21 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with keyword and different type of quotes
|
||||
assert_eq!(
|
||||
databricks().verified_stmt(&format!("USE CATALOG {quote}my_catalog{quote}")),
|
||||
databricks().verified_stmt(&format!("USE CATALOG {0}my_catalog{0}", quote)),
|
||||
Statement::Use(Use::Catalog(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
"my_catalog".to_string(),
|
||||
)])))
|
||||
);
|
||||
assert_eq!(
|
||||
databricks().verified_stmt(&format!("USE DATABASE {quote}my_database{quote}")),
|
||||
databricks().verified_stmt(&format!("USE DATABASE {0}my_database{0}", quote)),
|
||||
Statement::Use(Use::Database(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
"my_database".to_string(),
|
||||
)])))
|
||||
);
|
||||
assert_eq!(
|
||||
databricks().verified_stmt(&format!("USE SCHEMA {quote}my_schema{quote}")),
|
||||
databricks().verified_stmt(&format!("USE SCHEMA {0}my_schema{0}", quote)),
|
||||
Statement::Use(Use::Schema(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
"my_schema".to_string(),
|
||||
|
@ -327,14 +323,10 @@ fn data_type_timestamp_ntz() {
|
|||
// Literal
|
||||
assert_eq!(
|
||||
databricks().verified_expr("TIMESTAMP_NTZ '2025-03-29T18:52:00'"),
|
||||
Expr::TypedString(TypedString {
|
||||
Expr::TypedString {
|
||||
data_type: DataType::TimestampNtz,
|
||||
value: ValueWithSpan {
|
||||
value: Value::SingleQuotedString("2025-03-29T18:52:00".to_owned()),
|
||||
span: Span::empty(),
|
||||
},
|
||||
uses_odbc_syntax: false
|
||||
})
|
||||
value: Value::SingleQuotedString("2025-03-29T18:52:00".to_owned())
|
||||
}
|
||||
);
|
||||
|
||||
// Cast
|
||||
|
@ -362,6 +354,6 @@ fn data_type_timestamp_ntz() {
|
|||
}]
|
||||
);
|
||||
}
|
||||
s => panic!("Unexpected statement: {s:?}"),
|
||||
s => panic!("Unexpected statement: {:?}", s),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ use test_utils::*;
|
|||
|
||||
use sqlparser::ast::*;
|
||||
use sqlparser::dialect::{DuckDbDialect, GenericDialect};
|
||||
use sqlparser::parser::ParserError;
|
||||
|
||||
fn duckdb() -> TestedDialects {
|
||||
TestedDialects::new(vec![Box::new(DuckDbDialect {})])
|
||||
|
@ -45,12 +44,10 @@ fn test_struct() {
|
|||
StructField {
|
||||
field_name: Some(Ident::new("v")),
|
||||
field_type: DataType::Varchar(None),
|
||||
options: None,
|
||||
},
|
||||
StructField {
|
||||
field_name: Some(Ident::new("i")),
|
||||
field_type: DataType::Integer(None),
|
||||
options: None,
|
||||
},
|
||||
],
|
||||
StructBracketKind::Parentheses,
|
||||
|
@ -87,7 +84,6 @@ fn test_struct() {
|
|||
StructField {
|
||||
field_name: Some(Ident::new("v")),
|
||||
field_type: DataType::Varchar(None),
|
||||
options: None,
|
||||
},
|
||||
StructField {
|
||||
field_name: Some(Ident::new("s")),
|
||||
|
@ -96,17 +92,14 @@ fn test_struct() {
|
|||
StructField {
|
||||
field_name: Some(Ident::new("a1")),
|
||||
field_type: DataType::Integer(None),
|
||||
options: None,
|
||||
},
|
||||
StructField {
|
||||
field_name: Some(Ident::new("a2")),
|
||||
field_type: DataType::Varchar(None),
|
||||
options: None,
|
||||
},
|
||||
],
|
||||
StructBracketKind::Parentheses,
|
||||
),
|
||||
options: None,
|
||||
},
|
||||
],
|
||||
StructBracketKind::Parentheses,
|
||||
|
@ -269,7 +262,6 @@ fn test_select_union_by_name() {
|
|||
distinct: None,
|
||||
top: None,
|
||||
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions::default())],
|
||||
exclude: None,
|
||||
top_before_distinct: false,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
|
@ -300,7 +292,6 @@ fn test_select_union_by_name() {
|
|||
distinct: None,
|
||||
top: None,
|
||||
projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions::default())],
|
||||
exclude: None,
|
||||
top_before_distinct: false,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
|
@ -371,7 +362,7 @@ fn test_duckdb_specific_int_types() {
|
|||
("HUGEINT", DataType::HugeInt),
|
||||
];
|
||||
for (dtype_string, data_type) in duckdb_dtypes {
|
||||
let sql = format!("SELECT 123::{dtype_string}");
|
||||
let sql = format!("SELECT 123::{}", dtype_string);
|
||||
let select = duckdb().verified_only_select(&sql);
|
||||
assert_eq!(
|
||||
&Expr::Cast {
|
||||
|
@ -744,13 +735,19 @@ fn test_duckdb_union_datatype() {
|
|||
storage: Default::default(),
|
||||
location: Default::default()
|
||||
}),
|
||||
table_properties: Default::default(),
|
||||
with_options: Default::default(),
|
||||
file_format: Default::default(),
|
||||
location: Default::default(),
|
||||
query: Default::default(),
|
||||
without_rowid: Default::default(),
|
||||
like: Default::default(),
|
||||
clone: Default::default(),
|
||||
engine: Default::default(),
|
||||
comment: Default::default(),
|
||||
auto_increment_offset: Default::default(),
|
||||
default_charset: Default::default(),
|
||||
collation: Default::default(),
|
||||
on_commit: Default::default(),
|
||||
on_cluster: Default::default(),
|
||||
primary_key: Default::default(),
|
||||
|
@ -758,6 +755,7 @@ fn test_duckdb_union_datatype() {
|
|||
partition_by: Default::default(),
|
||||
cluster_by: Default::default(),
|
||||
clustered_by: Default::default(),
|
||||
options: Default::default(),
|
||||
inherits: Default::default(),
|
||||
strict: Default::default(),
|
||||
copy_grants: Default::default(),
|
||||
|
@ -774,7 +772,6 @@ fn test_duckdb_union_datatype() {
|
|||
catalog: Default::default(),
|
||||
catalog_sync: Default::default(),
|
||||
storage_serialization_policy: Default::default(),
|
||||
table_options: CreateTableOptions::None
|
||||
}),
|
||||
stmt
|
||||
);
|
||||
|
@ -795,7 +792,7 @@ fn parse_use() {
|
|||
for object_name in &valid_object_names {
|
||||
// Test single identifier without quotes
|
||||
assert_eq!(
|
||||
duckdb().verified_stmt(&format!("USE {object_name}")),
|
||||
duckdb().verified_stmt(&format!("USE {}", object_name)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
|
||||
object_name.to_string()
|
||||
)])))
|
||||
|
@ -803,7 +800,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with different type of quotes
|
||||
assert_eq!(
|
||||
duckdb().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
|
||||
duckdb().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
object_name.to_string(),
|
||||
|
@ -815,9 +812,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test double identifier with different type of quotes
|
||||
assert_eq!(
|
||||
duckdb().verified_stmt(&format!(
|
||||
"USE {quote}CATALOG{quote}.{quote}my_schema{quote}"
|
||||
)),
|
||||
duckdb().verified_stmt(&format!("USE {0}CATALOG{0}.{0}my_schema{0}", quote)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![
|
||||
Ident::with_quote(quote, "CATALOG"),
|
||||
Ident::with_quote(quote, "my_schema")
|
||||
|
@ -833,32 +828,3 @@ fn parse_use() {
|
|||
])))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duckdb_trim() {
|
||||
let real_sql = r#"SELECT customer_id, TRIM(item_price_id, '"', "a") AS item_price_id FROM models_staging.subscriptions"#;
|
||||
assert_eq!(duckdb().verified_stmt(real_sql).to_string(), real_sql);
|
||||
|
||||
let sql_only_select = "SELECT TRIM('xyz', 'a')";
|
||||
let select = duckdb().verified_only_select(sql_only_select);
|
||||
assert_eq!(
|
||||
&Expr::Trim {
|
||||
expr: Box::new(Expr::Value(
|
||||
Value::SingleQuotedString("xyz".to_owned()).with_empty_span()
|
||||
)),
|
||||
trim_where: None,
|
||||
trim_what: None,
|
||||
trim_characters: Some(vec![Expr::Value(
|
||||
Value::SingleQuotedString("a".to_owned()).with_empty_span()
|
||||
)]),
|
||||
},
|
||||
expr_from_projection(only(&select.projection))
|
||||
);
|
||||
|
||||
// missing comma separation
|
||||
let error_sql = "SELECT TRIM('xyz' 'a')";
|
||||
assert_eq!(
|
||||
ParserError::ParserError("Expected: ), found: 'a'".to_owned()),
|
||||
duckdb().parse_sql_statements(error_sql).unwrap_err()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -133,7 +133,9 @@ fn create_table_with_comment() {
|
|||
Statement::CreateTable(CreateTable { comment, .. }) => {
|
||||
assert_eq!(
|
||||
comment,
|
||||
Some(CommentDef::WithoutEq("table comment".to_string()))
|
||||
Some(CommentDef::AfterColumnDefsWithoutEq(
|
||||
"table comment".to_string()
|
||||
))
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
|
@ -341,9 +343,6 @@ fn lateral_view() {
|
|||
fn sort_by() {
|
||||
let sort_by = "SELECT * FROM db.table SORT BY a";
|
||||
hive().verified_stmt(sort_by);
|
||||
|
||||
let sort_by_with_direction = "SELECT * FROM db.table SORT BY a, b DESC";
|
||||
hive().verified_stmt(sort_by_with_direction);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -524,7 +523,7 @@ fn parse_use() {
|
|||
for object_name in &valid_object_names {
|
||||
// Test single identifier without quotes
|
||||
assert_eq!(
|
||||
hive().verified_stmt(&format!("USE {object_name}")),
|
||||
hive().verified_stmt(&format!("USE {}", object_name)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
|
||||
object_name.to_string()
|
||||
)])))
|
||||
|
@ -532,7 +531,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with different type of quotes
|
||||
assert_eq!(
|
||||
hive().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
|
||||
hive().verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
object_name.to_string(),
|
||||
|
|
|
@ -23,8 +23,7 @@
|
|||
mod test_utils;
|
||||
|
||||
use helpers::attached_token::AttachedToken;
|
||||
use sqlparser::keywords::Keyword;
|
||||
use sqlparser::tokenizer::{Location, Span, Token, TokenWithSpan, Word};
|
||||
use sqlparser::tokenizer::{Location, Span};
|
||||
use test_utils::*;
|
||||
|
||||
use sqlparser::ast::DataType::{Int, Text, Varbinary};
|
||||
|
@ -32,7 +31,7 @@ use sqlparser::ast::DeclareAssignment::MsSqlAssignment;
|
|||
use sqlparser::ast::Value::SingleQuotedString;
|
||||
use sqlparser::ast::*;
|
||||
use sqlparser::dialect::{GenericDialect, MsSqlDialect};
|
||||
use sqlparser::parser::{Parser, ParserError, ParserOptions};
|
||||
use sqlparser::parser::{Parser, ParserError};
|
||||
|
||||
#[test]
|
||||
fn parse_mssql_identifiers() {
|
||||
|
@ -100,53 +99,47 @@ fn parse_mssql_delimited_identifiers() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_procedure() {
|
||||
let sql = "CREATE OR ALTER PROCEDURE test (@foo INT, @bar VARCHAR(256)) AS BEGIN SELECT 1; END";
|
||||
let sql = "CREATE OR ALTER PROCEDURE test (@foo INT, @bar VARCHAR(256)) AS BEGIN SELECT 1 END";
|
||||
|
||||
assert_eq!(
|
||||
ms().verified_stmt(sql),
|
||||
Statement::CreateProcedure {
|
||||
or_alter: true,
|
||||
body: ConditionalStatements::BeginEnd(BeginEndStatements {
|
||||
begin_token: AttachedToken::empty(),
|
||||
statements: vec![Statement::Query(Box::new(Query {
|
||||
with: None,
|
||||
limit_clause: None,
|
||||
fetch: None,
|
||||
locks: vec![],
|
||||
for_clause: None,
|
||||
order_by: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
body: Box::new(SetExpr::Select(Box::new(Select {
|
||||
select_token: AttachedToken::empty(),
|
||||
distinct: None,
|
||||
top: None,
|
||||
top_before_distinct: false,
|
||||
projection: vec![SelectItem::UnnamedExpr(Expr::Value(
|
||||
(number("1")).with_empty_span()
|
||||
))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
prewhere: None,
|
||||
selection: None,
|
||||
group_by: GroupByExpr::Expressions(vec![], vec![]),
|
||||
cluster_by: vec![],
|
||||
distribute_by: vec![],
|
||||
sort_by: vec![],
|
||||
having: None,
|
||||
named_window: vec![],
|
||||
window_before_qualify: false,
|
||||
qualify: None,
|
||||
value_table_mode: None,
|
||||
connect_by: None,
|
||||
flavor: SelectFlavor::Standard,
|
||||
})))
|
||||
}))],
|
||||
end_token: AttachedToken::empty(),
|
||||
}),
|
||||
body: vec![Statement::Query(Box::new(Query {
|
||||
with: None,
|
||||
limit_clause: None,
|
||||
fetch: None,
|
||||
locks: vec![],
|
||||
for_clause: None,
|
||||
order_by: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
body: Box::new(SetExpr::Select(Box::new(Select {
|
||||
select_token: AttachedToken::empty(),
|
||||
distinct: None,
|
||||
top: None,
|
||||
top_before_distinct: false,
|
||||
projection: vec![SelectItem::UnnamedExpr(Expr::Value(
|
||||
(number("1")).with_empty_span()
|
||||
))],
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
prewhere: None,
|
||||
selection: None,
|
||||
group_by: GroupByExpr::Expressions(vec![], vec![]),
|
||||
cluster_by: vec![],
|
||||
distribute_by: vec![],
|
||||
sort_by: vec![],
|
||||
having: None,
|
||||
named_window: vec![],
|
||||
window_before_qualify: false,
|
||||
qualify: None,
|
||||
value_table_mode: None,
|
||||
connect_by: None,
|
||||
flavor: SelectFlavor::Standard,
|
||||
})))
|
||||
}))],
|
||||
params: Some(vec![
|
||||
ProcedureParam {
|
||||
name: Ident {
|
||||
|
@ -154,8 +147,7 @@ fn parse_create_procedure() {
|
|||
quote_style: None,
|
||||
span: Span::empty(),
|
||||
},
|
||||
data_type: DataType::Int(None),
|
||||
mode: None,
|
||||
data_type: DataType::Int(None)
|
||||
},
|
||||
ProcedureParam {
|
||||
name: Ident {
|
||||
|
@ -166,36 +158,33 @@ fn parse_create_procedure() {
|
|||
data_type: DataType::Varchar(Some(CharacterLength::IntegerLength {
|
||||
length: 256,
|
||||
unit: None
|
||||
})),
|
||||
mode: None,
|
||||
}))
|
||||
}
|
||||
]),
|
||||
name: ObjectName::from(vec![Ident {
|
||||
value: "test".into(),
|
||||
quote_style: None,
|
||||
span: Span::empty(),
|
||||
}]),
|
||||
language: None,
|
||||
}])
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_mssql_create_procedure() {
|
||||
let _ = ms_and_generic().verified_stmt("CREATE OR ALTER PROCEDURE foo AS SELECT 1;");
|
||||
let _ = ms_and_generic().verified_stmt("CREATE OR ALTER PROCEDURE foo AS BEGIN SELECT 1; END");
|
||||
let _ = ms_and_generic().verified_stmt("CREATE PROCEDURE foo AS BEGIN SELECT 1; END");
|
||||
let _ = ms_and_generic().verified_stmt("CREATE OR ALTER PROCEDURE foo AS BEGIN SELECT 1 END");
|
||||
let _ = ms_and_generic().verified_stmt("CREATE PROCEDURE foo AS BEGIN SELECT 1 END");
|
||||
let _ = ms().verified_stmt(
|
||||
"CREATE PROCEDURE foo AS BEGIN SELECT [myColumn] FROM [myschema].[mytable]; END",
|
||||
"CREATE PROCEDURE foo AS BEGIN SELECT [myColumn] FROM [myschema].[mytable] END",
|
||||
);
|
||||
let _ = ms_and_generic().verified_stmt(
|
||||
"CREATE PROCEDURE foo (@CustomerName NVARCHAR(50)) AS BEGIN SELECT * FROM DEV; END",
|
||||
"CREATE PROCEDURE foo (@CustomerName NVARCHAR(50)) AS BEGIN SELECT * FROM DEV END",
|
||||
);
|
||||
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test'; END");
|
||||
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test' END");
|
||||
// Test a statement with END in it
|
||||
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN SELECT [foo], CASE WHEN [foo] IS NULL THEN 'empty' ELSE 'notempty' END AS [foo]; END");
|
||||
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN SELECT [foo], CASE WHEN [foo] IS NULL THEN 'empty' ELSE 'notempty' END AS [foo] END");
|
||||
// Multiple statements
|
||||
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test'; SELECT [foo] FROM BAR WHERE [FOO] > 10; END");
|
||||
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test'; SELECT [foo] FROM BAR WHERE [FOO] > 10 END");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -233,7 +222,7 @@ fn parse_create_function() {
|
|||
value: Some(ReturnStatementValue::Expr(Expr::Value(
|
||||
(number("1")).with_empty_span()
|
||||
))),
|
||||
})],
|
||||
}),],
|
||||
end_token: AttachedToken::empty(),
|
||||
})),
|
||||
behavior: None,
|
||||
|
@ -258,12 +247,6 @@ fn parse_create_function() {
|
|||
";
|
||||
let _ = ms().verified_stmt(multi_statement_function);
|
||||
|
||||
let multi_statement_function_without_as = multi_statement_function.replace(" AS", "");
|
||||
let _ = ms().one_statement_parses_to(
|
||||
&multi_statement_function_without_as,
|
||||
multi_statement_function,
|
||||
);
|
||||
|
||||
let create_function_with_conditional = "\
|
||||
CREATE FUNCTION some_scalar_udf() \
|
||||
RETURNS INT \
|
||||
|
@ -288,137 +271,6 @@ fn parse_create_function() {
|
|||
END\
|
||||
";
|
||||
let _ = ms().verified_stmt(create_or_alter_function);
|
||||
|
||||
let create_function_with_return_expression = "\
|
||||
CREATE FUNCTION some_scalar_udf(@foo INT, @bar VARCHAR(256)) \
|
||||
RETURNS INT \
|
||||
AS \
|
||||
BEGIN \
|
||||
RETURN CONVERT(INT, 1) + 2; \
|
||||
END\
|
||||
";
|
||||
let _ = ms().verified_stmt(create_function_with_return_expression);
|
||||
|
||||
let create_inline_table_value_function = "\
|
||||
CREATE FUNCTION some_inline_tvf(@foo INT, @bar VARCHAR(256)) \
|
||||
RETURNS TABLE \
|
||||
AS \
|
||||
RETURN (SELECT 1 AS col_1)\
|
||||
";
|
||||
let _ = ms().verified_stmt(create_inline_table_value_function);
|
||||
|
||||
let create_inline_table_value_function_without_parentheses = "\
|
||||
CREATE FUNCTION some_inline_tvf(@foo INT, @bar VARCHAR(256)) \
|
||||
RETURNS TABLE \
|
||||
AS \
|
||||
RETURN SELECT 1 AS col_1\
|
||||
";
|
||||
let _ = ms().verified_stmt(create_inline_table_value_function_without_parentheses);
|
||||
|
||||
let create_inline_table_value_function_without_as =
|
||||
create_inline_table_value_function.replace(" AS", "");
|
||||
let _ = ms().one_statement_parses_to(
|
||||
&create_inline_table_value_function_without_as,
|
||||
create_inline_table_value_function,
|
||||
);
|
||||
|
||||
let create_multi_statement_table_value_function = "\
|
||||
CREATE FUNCTION some_multi_statement_tvf(@foo INT, @bar VARCHAR(256)) \
|
||||
RETURNS @t TABLE (col_1 INT) \
|
||||
AS \
|
||||
BEGIN \
|
||||
INSERT INTO @t SELECT 1; \
|
||||
RETURN; \
|
||||
END\
|
||||
";
|
||||
let _ = ms().verified_stmt(create_multi_statement_table_value_function);
|
||||
|
||||
let create_multi_statement_table_value_function_without_as =
|
||||
create_multi_statement_table_value_function.replace(" AS", "");
|
||||
let _ = ms().one_statement_parses_to(
|
||||
&create_multi_statement_table_value_function_without_as,
|
||||
create_multi_statement_table_value_function,
|
||||
);
|
||||
|
||||
let create_multi_statement_table_value_function_with_constraints = "\
|
||||
CREATE FUNCTION some_multi_statement_tvf(@foo INT, @bar VARCHAR(256)) \
|
||||
RETURNS @t TABLE (col_1 INT NOT NULL) \
|
||||
AS \
|
||||
BEGIN \
|
||||
INSERT INTO @t SELECT 1; \
|
||||
RETURN @t; \
|
||||
END\
|
||||
";
|
||||
let _ = ms().verified_stmt(create_multi_statement_table_value_function_with_constraints);
|
||||
|
||||
let create_multi_statement_tvf_without_table_definition = "\
|
||||
CREATE FUNCTION incorrect_tvf(@foo INT, @bar VARCHAR(256)) \
|
||||
RETURNS @t TABLE ()
|
||||
AS \
|
||||
BEGIN \
|
||||
INSERT INTO @t SELECT 1; \
|
||||
RETURN @t; \
|
||||
END\
|
||||
";
|
||||
assert_eq!(
|
||||
ParserError::ParserError("Unparsable function body".to_owned()),
|
||||
ms().parse_sql_statements(create_multi_statement_tvf_without_table_definition)
|
||||
.unwrap_err()
|
||||
);
|
||||
|
||||
let create_inline_tvf_without_subquery_or_bare_select = "\
|
||||
CREATE FUNCTION incorrect_tvf(@foo INT, @bar VARCHAR(256)) \
|
||||
RETURNS TABLE
|
||||
AS \
|
||||
RETURN 'hi'\
|
||||
";
|
||||
assert_eq!(
|
||||
ParserError::ParserError(
|
||||
"Expected a subquery (or bare SELECT statement) after RETURN".to_owned()
|
||||
),
|
||||
ms().parse_sql_statements(create_inline_tvf_without_subquery_or_bare_select)
|
||||
.unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_function_parameter_default_values() {
|
||||
let single_default_sql =
|
||||
"CREATE FUNCTION test_func(@param1 INT = 42) RETURNS INT AS BEGIN RETURN @param1; END";
|
||||
assert_eq!(
|
||||
ms().verified_stmt(single_default_sql),
|
||||
Statement::CreateFunction(CreateFunction {
|
||||
or_alter: false,
|
||||
or_replace: false,
|
||||
temporary: false,
|
||||
if_not_exists: false,
|
||||
name: ObjectName::from(vec![Ident::new("test_func")]),
|
||||
args: Some(vec![OperateFunctionArg {
|
||||
mode: None,
|
||||
name: Some(Ident::new("@param1")),
|
||||
data_type: DataType::Int(None),
|
||||
default_expr: Some(Expr::Value((number("42")).with_empty_span())),
|
||||
},]),
|
||||
return_type: Some(DataType::Int(None)),
|
||||
function_body: Some(CreateFunctionBody::AsBeginEnd(BeginEndStatements {
|
||||
begin_token: AttachedToken::empty(),
|
||||
statements: vec![Statement::Return(ReturnStatement {
|
||||
value: Some(ReturnStatementValue::Expr(Expr::Identifier(Ident::new(
|
||||
"@param1"
|
||||
)))),
|
||||
})],
|
||||
end_token: AttachedToken::empty(),
|
||||
})),
|
||||
behavior: None,
|
||||
called_on_null: None,
|
||||
parallel: None,
|
||||
using: None,
|
||||
language: None,
|
||||
determinism_specifier: None,
|
||||
options: None,
|
||||
remote_connection: None,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1369,7 +1221,6 @@ fn parse_substring_in_select() {
|
|||
special: true,
|
||||
shorthand: false,
|
||||
})],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident {
|
||||
|
@ -1401,7 +1252,6 @@ fn parse_substring_in_select() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
}),
|
||||
query
|
||||
);
|
||||
|
@ -1504,8 +1354,6 @@ fn parse_mssql_declare() {
|
|||
order_by: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
|
||||
body: Box::new(SetExpr::Select(Box::new(Select {
|
||||
select_token: AttachedToken::empty(),
|
||||
distinct: None,
|
||||
|
@ -1518,7 +1366,6 @@ fn parse_mssql_declare() {
|
|||
(Value::Number("4".parse().unwrap(), false)).with_empty_span()
|
||||
)),
|
||||
})],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
@ -1546,85 +1393,6 @@ fn parse_mssql_declare() {
|
|||
let _ = ms().verified_stmt(declare_cursor_for_select);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mssql_cursor() {
|
||||
let full_cursor_usage = "\
|
||||
DECLARE Employee_Cursor CURSOR FOR \
|
||||
SELECT LastName, FirstName \
|
||||
FROM AdventureWorks2022.HumanResources.vEmployee \
|
||||
WHERE LastName LIKE 'B%'; \
|
||||
\
|
||||
OPEN Employee_Cursor; \
|
||||
\
|
||||
FETCH NEXT FROM Employee_Cursor; \
|
||||
\
|
||||
WHILE @@FETCH_STATUS = 0 \
|
||||
BEGIN \
|
||||
FETCH NEXT FROM Employee_Cursor; \
|
||||
END; \
|
||||
\
|
||||
CLOSE Employee_Cursor; \
|
||||
DEALLOCATE Employee_Cursor\
|
||||
";
|
||||
let _ = ms().statements_parse_to(full_cursor_usage, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mssql_while_statement() {
|
||||
let while_single_statement = "WHILE 1 = 0 PRINT 'Hello World';";
|
||||
let stmt = ms().verified_stmt(while_single_statement);
|
||||
assert_eq!(
|
||||
stmt,
|
||||
Statement::While(sqlparser::ast::WhileStatement {
|
||||
while_block: ConditionalStatementBlock {
|
||||
start_token: AttachedToken(TokenWithSpan {
|
||||
token: Token::Word(Word {
|
||||
value: "WHILE".to_string(),
|
||||
quote_style: None,
|
||||
keyword: Keyword::WHILE
|
||||
}),
|
||||
span: Span::empty()
|
||||
}),
|
||||
condition: Some(Expr::BinaryOp {
|
||||
left: Box::new(Expr::Value(
|
||||
(Value::Number("1".parse().unwrap(), false)).with_empty_span()
|
||||
)),
|
||||
op: BinaryOperator::Eq,
|
||||
right: Box::new(Expr::Value(
|
||||
(Value::Number("0".parse().unwrap(), false)).with_empty_span()
|
||||
)),
|
||||
}),
|
||||
then_token: None,
|
||||
conditional_statements: ConditionalStatements::Sequence {
|
||||
statements: vec![Statement::Print(PrintStatement {
|
||||
message: Box::new(Expr::Value(
|
||||
(Value::SingleQuotedString("Hello World".to_string()))
|
||||
.with_empty_span()
|
||||
)),
|
||||
})],
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
let while_begin_end = "\
|
||||
WHILE @@FETCH_STATUS = 0 \
|
||||
BEGIN \
|
||||
FETCH NEXT FROM Employee_Cursor; \
|
||||
END\
|
||||
";
|
||||
let _ = ms().verified_stmt(while_begin_end);
|
||||
|
||||
let while_begin_end_multiple_statements = "\
|
||||
WHILE @@FETCH_STATUS = 0 \
|
||||
BEGIN \
|
||||
FETCH NEXT FROM Employee_Cursor; \
|
||||
PRINT 'Hello World'; \
|
||||
END\
|
||||
";
|
||||
let _ = ms().verified_stmt(while_begin_end_multiple_statements);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_raiserror() {
|
||||
let sql = r#"RAISERROR('This is a test', 16, 1)"#;
|
||||
|
@ -1676,7 +1444,7 @@ fn parse_use() {
|
|||
for object_name in &valid_object_names {
|
||||
// Test single identifier without quotes
|
||||
assert_eq!(
|
||||
ms().verified_stmt(&format!("USE {object_name}")),
|
||||
ms().verified_stmt(&format!("USE {}", object_name)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
|
||||
object_name.to_string()
|
||||
)])))
|
||||
|
@ -1684,7 +1452,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with different type of quotes
|
||||
assert_eq!(
|
||||
ms().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
|
||||
ms().verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
object_name.to_string(),
|
||||
|
@ -1873,6 +1641,7 @@ fn parse_create_table_with_valid_options() {
|
|||
span: Span::empty(),
|
||||
},
|
||||
data_type: Int(None,),
|
||||
|
||||
options: vec![],
|
||||
},
|
||||
ColumnDef {
|
||||
|
@ -1882,6 +1651,7 @@ fn parse_create_table_with_valid_options() {
|
|||
span: Span::empty(),
|
||||
},
|
||||
data_type: Int(None,),
|
||||
|
||||
options: vec![],
|
||||
},
|
||||
],
|
||||
|
@ -1893,13 +1663,19 @@ fn parse_create_table_with_valid_options() {
|
|||
storage: None,
|
||||
location: None,
|
||||
},),
|
||||
table_properties: vec![],
|
||||
with_options,
|
||||
file_format: None,
|
||||
location: None,
|
||||
query: None,
|
||||
without_rowid: false,
|
||||
like: None,
|
||||
clone: None,
|
||||
engine: None,
|
||||
comment: None,
|
||||
auto_increment_offset: None,
|
||||
default_charset: None,
|
||||
collation: None,
|
||||
on_commit: None,
|
||||
on_cluster: None,
|
||||
primary_key: None,
|
||||
|
@ -1907,6 +1683,7 @@ fn parse_create_table_with_valid_options() {
|
|||
partition_by: None,
|
||||
cluster_by: None,
|
||||
clustered_by: None,
|
||||
options: None,
|
||||
inherits: None,
|
||||
strict: false,
|
||||
iceberg: false,
|
||||
|
@ -1924,7 +1701,6 @@ fn parse_create_table_with_valid_options() {
|
|||
catalog: None,
|
||||
catalog_sync: None,
|
||||
storage_serialization_policy: None,
|
||||
table_options: CreateTableOptions::With(with_options)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -2058,13 +1834,19 @@ fn parse_create_table_with_identity_column() {
|
|||
storage: None,
|
||||
location: None,
|
||||
},),
|
||||
table_properties: vec![],
|
||||
with_options: vec![],
|
||||
file_format: None,
|
||||
location: None,
|
||||
query: None,
|
||||
without_rowid: false,
|
||||
like: None,
|
||||
clone: None,
|
||||
engine: None,
|
||||
comment: None,
|
||||
auto_increment_offset: None,
|
||||
default_charset: None,
|
||||
collation: None,
|
||||
on_commit: None,
|
||||
on_cluster: None,
|
||||
primary_key: None,
|
||||
|
@ -2072,6 +1854,7 @@ fn parse_create_table_with_identity_column() {
|
|||
partition_by: None,
|
||||
cluster_by: None,
|
||||
clustered_by: None,
|
||||
options: None,
|
||||
inherits: None,
|
||||
strict: false,
|
||||
copy_grants: false,
|
||||
|
@ -2088,7 +1871,6 @@ fn parse_create_table_with_identity_column() {
|
|||
catalog: None,
|
||||
catalog_sync: None,
|
||||
storage_serialization_policy: None,
|
||||
table_options: CreateTableOptions::None
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -2190,7 +1972,7 @@ fn parse_mssql_if_else() {
|
|||
"IF 1 = 1 BEGIN SET @A = 1; END ELSE SET @A = 2;"
|
||||
);
|
||||
}
|
||||
_ => panic!("Unexpected statements: {stmts:?}"),
|
||||
_ => panic!("Unexpected statements: {:?}", stmts),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2240,7 +2022,7 @@ fn test_mssql_if_statements_span() {
|
|||
Span::new(Location::new(1, 21), Location::new(1, 36))
|
||||
);
|
||||
}
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
}
|
||||
|
||||
// Blocks
|
||||
|
@ -2261,7 +2043,7 @@ fn test_mssql_if_statements_span() {
|
|||
Span::new(Location::new(1, 32), Location::new(1, 57))
|
||||
);
|
||||
}
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2327,18 +2109,6 @@ fn ms() -> TestedDialects {
|
|||
TestedDialects::new(vec![Box::new(MsSqlDialect {})])
|
||||
}
|
||||
|
||||
// MS SQL dialect with support for optional semi-colon statement delimiters
|
||||
fn tsql() -> TestedDialects {
|
||||
TestedDialects::new_with_options(
|
||||
vec![Box::new(MsSqlDialect {})],
|
||||
ParserOptions {
|
||||
trailing_commas: false,
|
||||
unescape: true,
|
||||
require_semicolon_stmt_delimiter: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn ms_and_generic() -> TestedDialects {
|
||||
TestedDialects::new(vec![Box::new(MsSqlDialect {}), Box::new(GenericDialect {})])
|
||||
}
|
||||
|
@ -2359,101 +2129,6 @@ fn parse_mssql_merge_with_output() {
|
|||
ms_and_generic().verified_stmt(stmt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_trigger() {
|
||||
let create_trigger = "\
|
||||
CREATE OR ALTER TRIGGER reminder1 \
|
||||
ON Sales.Customer \
|
||||
AFTER INSERT, UPDATE \
|
||||
AS RAISERROR('Notify Customer Relations', 16, 10);\
|
||||
";
|
||||
let create_stmt = ms().verified_stmt(create_trigger);
|
||||
assert_eq!(
|
||||
create_stmt,
|
||||
Statement::CreateTrigger {
|
||||
or_alter: true,
|
||||
or_replace: false,
|
||||
is_constraint: false,
|
||||
name: ObjectName::from(vec![Ident::new("reminder1")]),
|
||||
period: TriggerPeriod::After,
|
||||
events: vec![TriggerEvent::Insert, TriggerEvent::Update(vec![]),],
|
||||
table_name: ObjectName::from(vec![Ident::new("Sales"), Ident::new("Customer")]),
|
||||
referenced_table_name: None,
|
||||
referencing: vec![],
|
||||
trigger_object: TriggerObject::Statement,
|
||||
include_each: false,
|
||||
condition: None,
|
||||
exec_body: None,
|
||||
statements: Some(ConditionalStatements::Sequence {
|
||||
statements: vec![Statement::RaisError {
|
||||
message: Box::new(Expr::Value(
|
||||
(Value::SingleQuotedString("Notify Customer Relations".to_string()))
|
||||
.with_empty_span()
|
||||
)),
|
||||
severity: Box::new(Expr::Value(
|
||||
(Value::Number("16".parse().unwrap(), false)).with_empty_span()
|
||||
)),
|
||||
state: Box::new(Expr::Value(
|
||||
(Value::Number("10".parse().unwrap(), false)).with_empty_span()
|
||||
)),
|
||||
arguments: vec![],
|
||||
options: vec![],
|
||||
}],
|
||||
}),
|
||||
characteristics: None,
|
||||
}
|
||||
);
|
||||
|
||||
let multi_statement_as_trigger = "\
|
||||
CREATE TRIGGER some_trigger ON some_table FOR INSERT \
|
||||
AS \
|
||||
DECLARE @var INT; \
|
||||
RAISERROR('Trigger fired', 10, 1);\
|
||||
";
|
||||
let _ = ms().verified_stmt(multi_statement_as_trigger);
|
||||
|
||||
let multi_statement_trigger = "\
|
||||
CREATE TRIGGER some_trigger ON some_table FOR INSERT \
|
||||
AS \
|
||||
BEGIN \
|
||||
DECLARE @var INT; \
|
||||
RAISERROR('Trigger fired', 10, 1); \
|
||||
END\
|
||||
";
|
||||
let _ = ms().verified_stmt(multi_statement_trigger);
|
||||
|
||||
let create_trigger_with_return = "\
|
||||
CREATE TRIGGER some_trigger ON some_table FOR INSERT \
|
||||
AS \
|
||||
BEGIN \
|
||||
RETURN; \
|
||||
END\
|
||||
";
|
||||
let _ = ms().verified_stmt(create_trigger_with_return);
|
||||
|
||||
let create_trigger_with_return = "\
|
||||
CREATE TRIGGER some_trigger ON some_table FOR INSERT \
|
||||
AS \
|
||||
BEGIN \
|
||||
RETURN; \
|
||||
END\
|
||||
";
|
||||
let _ = ms().verified_stmt(create_trigger_with_return);
|
||||
|
||||
let create_trigger_with_conditional = "\
|
||||
CREATE TRIGGER some_trigger ON some_table FOR INSERT \
|
||||
AS \
|
||||
BEGIN \
|
||||
IF 1 = 2 \
|
||||
BEGIN \
|
||||
RAISERROR('Trigger fired', 10, 1); \
|
||||
END; \
|
||||
RETURN; \
|
||||
END\
|
||||
";
|
||||
let _ = ms().verified_stmt(create_trigger_with_conditional);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_drop_trigger() {
|
||||
let sql_drop_trigger = "DROP TRIGGER emp_stamp;";
|
||||
|
@ -2485,25 +2160,3 @@ fn parse_print() {
|
|||
let _ = ms().verified_stmt("PRINT N'Hello, ⛄️!'");
|
||||
let _ = ms().verified_stmt("PRINT @my_variable");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_mssql_grant() {
|
||||
ms().verified_stmt("GRANT SELECT ON my_table TO public, db_admin");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_mssql_deny() {
|
||||
ms().verified_stmt("DENY SELECT ON my_table TO public, db_admin");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tsql_no_semicolon_delimiter() {
|
||||
let sql = r#"
|
||||
DECLARE @X AS NVARCHAR(MAX)='x'
|
||||
DECLARE @Y AS NVARCHAR(MAX)='y'
|
||||
"#;
|
||||
|
||||
let stmts = tsql().parse_sql_statements(sql).unwrap();
|
||||
assert_eq!(stmts.len(), 2);
|
||||
assert!(stmts.iter().all(|s| matches!(s, Statement::Declare { .. })));
|
||||
}
|
||||
|
|
|
@ -593,7 +593,7 @@ fn parse_use() {
|
|||
for object_name in &valid_object_names {
|
||||
// Test single identifier without quotes
|
||||
assert_eq!(
|
||||
mysql_and_generic().verified_stmt(&format!("USE {object_name}")),
|
||||
mysql_and_generic().verified_stmt(&format!("USE {}", object_name)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
|
||||
object_name.to_string()
|
||||
)])))
|
||||
|
@ -601,7 +601,8 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with different type of quotes
|
||||
assert_eq!(
|
||||
mysql_and_generic().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
|
||||
mysql_and_generic()
|
||||
.verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
object_name.to_string(),
|
||||
|
@ -669,20 +670,6 @@ fn table_constraint_unique_primary_ctor(
|
|||
characteristics: Option<ConstraintCharacteristics>,
|
||||
unique_index_type_display: Option<KeyOrIndexDisplay>,
|
||||
) -> TableConstraint {
|
||||
let columns = columns
|
||||
.into_iter()
|
||||
.map(|ident| IndexColumn {
|
||||
column: OrderByExpr {
|
||||
expr: Expr::Identifier(ident),
|
||||
options: OrderByOptions {
|
||||
asc: None,
|
||||
nulls_first: None,
|
||||
},
|
||||
with_fill: None,
|
||||
},
|
||||
operator_class: None,
|
||||
})
|
||||
.collect();
|
||||
match unique_index_type_display {
|
||||
Some(index_type_display) => TableConstraint::Unique {
|
||||
name,
|
||||
|
@ -808,67 +795,6 @@ fn parse_create_table_primary_and_unique_key_with_index_options() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_prefix_key_part() {
|
||||
let expected = vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::value(
|
||||
number("10"),
|
||||
)))];
|
||||
for sql in [
|
||||
"CREATE INDEX idx_index ON t(textcol(10))",
|
||||
"ALTER TABLE tab ADD INDEX idx_index (textcol(10))",
|
||||
"ALTER TABLE tab ADD PRIMARY KEY (textcol(10))",
|
||||
"ALTER TABLE tab ADD UNIQUE KEY (textcol(10))",
|
||||
"ALTER TABLE tab ADD UNIQUE KEY (textcol(10))",
|
||||
"ALTER TABLE tab ADD FULLTEXT INDEX (textcol(10))",
|
||||
"CREATE TABLE t (textcol TEXT, INDEX idx_index (textcol(10)))",
|
||||
] {
|
||||
match index_column(mysql_and_generic().verified_stmt(sql)) {
|
||||
Expr::Function(Function {
|
||||
name,
|
||||
args: FunctionArguments::List(FunctionArgumentList { args, .. }),
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(name.to_string(), "textcol");
|
||||
assert_eq!(args, expected);
|
||||
}
|
||||
expr => panic!("unexpected expression {expr} for {sql}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_functional_key_part() {
|
||||
assert_eq!(
|
||||
index_column(
|
||||
mysql_and_generic()
|
||||
.verified_stmt("CREATE INDEX idx_index ON t((col COLLATE utf8mb4_bin) DESC)")
|
||||
),
|
||||
Expr::Nested(Box::new(Expr::Collate {
|
||||
expr: Box::new(Expr::Identifier("col".into())),
|
||||
collation: ObjectName(vec![sqlparser::ast::ObjectNamePart::Identifier(
|
||||
Ident::new("utf8mb4_bin")
|
||||
)]),
|
||||
}))
|
||||
);
|
||||
assert_eq!(
|
||||
index_column(mysql_and_generic().verified_stmt(
|
||||
r#"CREATE TABLE t (jsoncol JSON, PRIMARY KEY ((CAST(col ->> '$.id' AS UNSIGNED)) ASC))"#
|
||||
)),
|
||||
Expr::Nested(Box::new(Expr::Cast {
|
||||
kind: CastKind::Cast,
|
||||
expr: Box::new(Expr::BinaryOp {
|
||||
left: Box::new(Expr::Identifier(Ident::new("col"))),
|
||||
op: BinaryOperator::LongArrow,
|
||||
right: Box::new(Expr::Value(
|
||||
Value::SingleQuotedString("$.id".to_string()).with_empty_span()
|
||||
)),
|
||||
}),
|
||||
data_type: DataType::Unsigned,
|
||||
format: None,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_primary_and_unique_key_with_index_type() {
|
||||
let sqls = ["UNIQUE", "PRIMARY KEY"].map(|key_ty| {
|
||||
|
@ -922,23 +848,9 @@ fn parse_create_table_comment() {
|
|||
|
||||
for sql in [without_equal, with_equal] {
|
||||
match mysql().verified_stmt(sql) {
|
||||
Statement::CreateTable(CreateTable {
|
||||
name,
|
||||
table_options,
|
||||
..
|
||||
}) => {
|
||||
Statement::CreateTable(CreateTable { name, comment, .. }) => {
|
||||
assert_eq!(name.to_string(), "foo");
|
||||
|
||||
let plain_options = match table_options {
|
||||
CreateTableOptions::Plain(options) => options,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let comment = match plain_options.first().unwrap() {
|
||||
SqlOption::Comment(CommentDef::WithEq(c))
|
||||
| SqlOption::Comment(CommentDef::WithoutEq(c)) => c,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
assert_eq!(comment, "baz");
|
||||
assert_eq!(comment.expect("Should exist").to_string(), "baz");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -947,226 +859,29 @@ fn parse_create_table_comment() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_table_auto_increment_offset() {
|
||||
let sql =
|
||||
"CREATE TABLE foo (bar INT NOT NULL AUTO_INCREMENT) ENGINE = InnoDB AUTO_INCREMENT = 123";
|
||||
let canonical =
|
||||
"CREATE TABLE foo (bar INT NOT NULL AUTO_INCREMENT) ENGINE=InnoDB AUTO_INCREMENT 123";
|
||||
let with_equal =
|
||||
"CREATE TABLE foo (bar INT NOT NULL AUTO_INCREMENT) ENGINE=InnoDB AUTO_INCREMENT=123";
|
||||
|
||||
match mysql().verified_stmt(sql) {
|
||||
Statement::CreateTable(CreateTable {
|
||||
name,
|
||||
table_options,
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(name.to_string(), "foo");
|
||||
|
||||
let plain_options = match table_options {
|
||||
CreateTableOptions::Plain(options) => options,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("AUTO_INCREMENT"),
|
||||
value: Expr::Value(test_utils::number("123").with_empty_span())
|
||||
}));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_multiple_options_order_independent() {
|
||||
let sql1 = "CREATE TABLE mytable (id INT) ENGINE=InnoDB ROW_FORMAT=DYNAMIC KEY_BLOCK_SIZE=8 COMMENT='abc'";
|
||||
let sql2 = "CREATE TABLE mytable (id INT) KEY_BLOCK_SIZE=8 COMMENT='abc' ENGINE=InnoDB ROW_FORMAT=DYNAMIC";
|
||||
let sql3 = "CREATE TABLE mytable (id INT) ROW_FORMAT=DYNAMIC KEY_BLOCK_SIZE=8 COMMENT='abc' ENGINE=InnoDB";
|
||||
|
||||
for sql in [sql1, sql2, sql3] {
|
||||
match mysql().parse_sql_statements(sql).unwrap().pop().unwrap() {
|
||||
for sql in [canonical, with_equal] {
|
||||
match mysql().one_statement_parses_to(sql, canonical) {
|
||||
Statement::CreateTable(CreateTable {
|
||||
name,
|
||||
table_options,
|
||||
auto_increment_offset,
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(name.to_string(), "mytable");
|
||||
|
||||
let plain_options = match table_options {
|
||||
CreateTableOptions::Plain(options) => options,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
assert!(plain_options.contains(&SqlOption::NamedParenthesizedList(
|
||||
NamedParenthesizedList {
|
||||
key: Ident::new("ENGINE"),
|
||||
name: Some(Ident::new("InnoDB")),
|
||||
values: vec![]
|
||||
}
|
||||
)));
|
||||
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("KEY_BLOCK_SIZE"),
|
||||
value: Expr::Value(test_utils::number("8").with_empty_span())
|
||||
}));
|
||||
|
||||
assert!(plain_options
|
||||
.contains(&SqlOption::Comment(CommentDef::WithEq("abc".to_owned()))));
|
||||
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("ROW_FORMAT"),
|
||||
value: Expr::Identifier(Ident::new("DYNAMIC".to_owned()))
|
||||
}));
|
||||
assert_eq!(name.to_string(), "foo");
|
||||
assert_eq!(
|
||||
auto_increment_offset.expect("Should exist").to_string(),
|
||||
"123"
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_with_all_table_options() {
|
||||
let sql =
|
||||
"CREATE TABLE foo (bar INT NOT NULL AUTO_INCREMENT) ENGINE = InnoDB AUTO_INCREMENT = 123 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci INSERT_METHOD = FIRST KEY_BLOCK_SIZE = 8 ROW_FORMAT = DYNAMIC DATA DIRECTORY = '/var/lib/mysql/data' INDEX DIRECTORY = '/var/lib/mysql/index' PACK_KEYS = 1 STATS_AUTO_RECALC = 1 STATS_PERSISTENT = 0 STATS_SAMPLE_PAGES = 128 DELAY_KEY_WRITE = 1 COMPRESSION = 'ZLIB' ENCRYPTION = 'Y' MAX_ROWS = 10000 MIN_ROWS = 10 AUTOEXTEND_SIZE = 64 AVG_ROW_LENGTH = 128 CHECKSUM = 1 CONNECTION = 'mysql://localhost' ENGINE_ATTRIBUTE = 'primary' PASSWORD = 'secure_password' SECONDARY_ENGINE_ATTRIBUTE = 'secondary_attr' START TRANSACTION TABLESPACE my_tablespace STORAGE DISK UNION = (table1, table2, table3)";
|
||||
|
||||
match mysql().verified_stmt(sql) {
|
||||
Statement::CreateTable(CreateTable {
|
||||
name,
|
||||
table_options,
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(name, vec![Ident::new("foo".to_owned())].into());
|
||||
|
||||
let plain_options = match table_options {
|
||||
CreateTableOptions::Plain(options) => options,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
assert!(plain_options.contains(&SqlOption::NamedParenthesizedList(
|
||||
NamedParenthesizedList {
|
||||
key: Ident::new("ENGINE"),
|
||||
name: Some(Ident::new("InnoDB")),
|
||||
values: vec![]
|
||||
}
|
||||
)));
|
||||
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("COLLATE"),
|
||||
value: Expr::Identifier(Ident::new("utf8mb4_0900_ai_ci".to_owned()))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("DEFAULT CHARSET"),
|
||||
value: Expr::Identifier(Ident::new("utf8mb4".to_owned()))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("AUTO_INCREMENT"),
|
||||
value: Expr::value(test_utils::number("123"))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("KEY_BLOCK_SIZE"),
|
||||
value: Expr::value(test_utils::number("8"))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("ROW_FORMAT"),
|
||||
value: Expr::Identifier(Ident::new("DYNAMIC".to_owned()))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("PACK_KEYS"),
|
||||
value: Expr::value(test_utils::number("1"))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("STATS_AUTO_RECALC"),
|
||||
value: Expr::value(test_utils::number("1"))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("STATS_PERSISTENT"),
|
||||
value: Expr::value(test_utils::number("0"))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("STATS_SAMPLE_PAGES"),
|
||||
value: Expr::value(test_utils::number("128"))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("STATS_SAMPLE_PAGES"),
|
||||
value: Expr::value(test_utils::number("128"))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("INSERT_METHOD"),
|
||||
value: Expr::Identifier(Ident::new("FIRST".to_owned()))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("COMPRESSION"),
|
||||
value: Expr::value(Value::SingleQuotedString("ZLIB".to_owned()))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("ENCRYPTION"),
|
||||
value: Expr::value(Value::SingleQuotedString("Y".to_owned()))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("MAX_ROWS"),
|
||||
value: Expr::value(test_utils::number("10000"))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("MIN_ROWS"),
|
||||
value: Expr::value(test_utils::number("10"))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("AUTOEXTEND_SIZE"),
|
||||
value: Expr::value(test_utils::number("64"))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("AVG_ROW_LENGTH"),
|
||||
value: Expr::value(test_utils::number("128"))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("CHECKSUM"),
|
||||
value: Expr::value(test_utils::number("1"))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("CONNECTION"),
|
||||
value: Expr::value(Value::SingleQuotedString("mysql://localhost".to_owned()))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("ENGINE_ATTRIBUTE"),
|
||||
value: Expr::value(Value::SingleQuotedString("primary".to_owned()))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("PASSWORD"),
|
||||
value: Expr::value(Value::SingleQuotedString("secure_password".to_owned()))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("SECONDARY_ENGINE_ATTRIBUTE"),
|
||||
value: Expr::value(Value::SingleQuotedString("secondary_attr".to_owned()))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::Ident(Ident::new(
|
||||
"START TRANSACTION".to_owned()
|
||||
))));
|
||||
assert!(
|
||||
plain_options.contains(&SqlOption::TableSpace(TablespaceOption {
|
||||
name: "my_tablespace".to_string(),
|
||||
storage: Some(StorageType::Disk),
|
||||
}))
|
||||
);
|
||||
|
||||
assert!(plain_options.contains(&SqlOption::NamedParenthesizedList(
|
||||
NamedParenthesizedList {
|
||||
key: Ident::new("UNION"),
|
||||
name: None,
|
||||
values: vec![
|
||||
Ident::new("table1".to_string()),
|
||||
Ident::new("table2".to_string()),
|
||||
Ident::new("table3".to_string())
|
||||
]
|
||||
}
|
||||
)));
|
||||
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("DATA DIRECTORY"),
|
||||
value: Expr::value(Value::SingleQuotedString("/var/lib/mysql/data".to_owned()))
|
||||
}));
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("INDEX DIRECTORY"),
|
||||
value: Expr::value(Value::SingleQuotedString("/var/lib/mysql/index".to_owned()))
|
||||
}));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_set_enum() {
|
||||
let sql = "CREATE TABLE foo (bar SET('a', 'b'), baz ENUM('a', 'b'))";
|
||||
|
@ -1201,12 +916,13 @@ fn parse_create_table_set_enum() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_table_engine_default_charset() {
|
||||
let sql = "CREATE TABLE foo (id INT(11)) ENGINE = InnoDB DEFAULT CHARSET = utf8mb3";
|
||||
let sql = "CREATE TABLE foo (id INT(11)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3";
|
||||
match mysql().verified_stmt(sql) {
|
||||
Statement::CreateTable(CreateTable {
|
||||
name,
|
||||
columns,
|
||||
table_options,
|
||||
engine,
|
||||
default_charset,
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(name.to_string(), "foo");
|
||||
|
@ -1218,24 +934,14 @@ fn parse_create_table_engine_default_charset() {
|
|||
},],
|
||||
columns
|
||||
);
|
||||
|
||||
let plain_options = match table_options {
|
||||
CreateTableOptions::Plain(options) => options,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("DEFAULT CHARSET"),
|
||||
value: Expr::Identifier(Ident::new("utf8mb3".to_owned()))
|
||||
}));
|
||||
|
||||
assert!(plain_options.contains(&SqlOption::NamedParenthesizedList(
|
||||
NamedParenthesizedList {
|
||||
key: Ident::new("ENGINE"),
|
||||
name: Some(Ident::new("InnoDB")),
|
||||
values: vec![]
|
||||
}
|
||||
)));
|
||||
assert_eq!(
|
||||
engine,
|
||||
Some(TableEngine {
|
||||
name: "InnoDB".to_string(),
|
||||
parameters: None
|
||||
})
|
||||
);
|
||||
assert_eq!(default_charset, Some("utf8mb3".to_string()));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -1243,12 +949,12 @@ fn parse_create_table_engine_default_charset() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_table_collate() {
|
||||
let sql = "CREATE TABLE foo (id INT(11)) COLLATE = utf8mb4_0900_ai_ci";
|
||||
let sql = "CREATE TABLE foo (id INT(11)) COLLATE=utf8mb4_0900_ai_ci";
|
||||
match mysql().verified_stmt(sql) {
|
||||
Statement::CreateTable(CreateTable {
|
||||
name,
|
||||
columns,
|
||||
table_options,
|
||||
collation,
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(name.to_string(), "foo");
|
||||
|
@ -1260,16 +966,7 @@ fn parse_create_table_collate() {
|
|||
},],
|
||||
columns
|
||||
);
|
||||
|
||||
let plain_options = match table_options {
|
||||
CreateTableOptions::Plain(options) => options,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("COLLATE"),
|
||||
value: Expr::Identifier(Ident::new("utf8mb4_0900_ai_ci".to_owned()))
|
||||
}));
|
||||
assert_eq!(collation, Some("utf8mb4_0900_ai_ci".to_string()));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -1277,26 +974,16 @@ fn parse_create_table_collate() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_table_both_options_and_as_query() {
|
||||
let sql = "CREATE TABLE foo (id INT(11)) ENGINE = InnoDB DEFAULT CHARSET = utf8mb3 COLLATE = utf8mb4_0900_ai_ci AS SELECT 1";
|
||||
let sql = "CREATE TABLE foo (id INT(11)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb4_0900_ai_ci AS SELECT 1";
|
||||
match mysql_and_generic().verified_stmt(sql) {
|
||||
Statement::CreateTable(CreateTable {
|
||||
name,
|
||||
collation,
|
||||
query,
|
||||
table_options,
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(name.to_string(), "foo");
|
||||
|
||||
let plain_options = match table_options {
|
||||
CreateTableOptions::Plain(options) => options,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
assert!(plain_options.contains(&SqlOption::KeyValue {
|
||||
key: Ident::new("COLLATE"),
|
||||
value: Expr::Identifier(Ident::new("utf8mb4_0900_ai_ci".to_owned()))
|
||||
}));
|
||||
|
||||
assert_eq!(collation, Some("utf8mb4_0900_ai_ci".to_string()));
|
||||
assert_eq!(
|
||||
query.unwrap().body.as_select().unwrap().projection,
|
||||
vec![SelectItem::UnnamedExpr(Expr::Value(
|
||||
|
@ -1307,8 +994,7 @@ fn parse_create_table_both_options_and_as_query() {
|
|||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let sql =
|
||||
r"CREATE TABLE foo (id INT(11)) ENGINE = InnoDB AS SELECT 1 DEFAULT CHARSET = utf8mb3";
|
||||
let sql = r"CREATE TABLE foo (id INT(11)) ENGINE=InnoDB AS SELECT 1 DEFAULT CHARSET=utf8mb3";
|
||||
assert!(matches!(
|
||||
mysql_and_generic().parse_sql_statements(sql),
|
||||
Err(ParserError::ParserError(_))
|
||||
|
@ -1403,7 +1089,6 @@ fn parse_escaped_quote_identifiers_with_escape() {
|
|||
quote_style: Some('`'),
|
||||
span: Span::empty(),
|
||||
}))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
@ -1428,7 +1113,6 @@ fn parse_escaped_quote_identifiers_with_escape() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
@ -1442,7 +1126,6 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
|
|||
ParserOptions {
|
||||
trailing_commas: false,
|
||||
unescape: false,
|
||||
require_semicolon_stmt_delimiter: true,
|
||||
}
|
||||
)
|
||||
.verified_stmt(sql),
|
||||
|
@ -1458,7 +1141,6 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
|
|||
quote_style: Some('`'),
|
||||
span: Span::empty(),
|
||||
}))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
@ -1483,7 +1165,6 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
@ -1506,7 +1187,6 @@ fn parse_escaped_backticks_with_escape() {
|
|||
quote_style: Some('`'),
|
||||
span: Span::empty(),
|
||||
}))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
@ -1531,7 +1211,6 @@ fn parse_escaped_backticks_with_escape() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
@ -1558,7 +1237,6 @@ fn parse_escaped_backticks_with_no_escape() {
|
|||
quote_style: Some('`'),
|
||||
span: Span::empty(),
|
||||
}))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
|
@ -1583,7 +1261,6 @@ fn parse_escaped_backticks_with_no_escape() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
@ -1705,51 +1382,6 @@ fn parse_create_table_unsigned() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_signed_data_types() {
|
||||
let sql = "CREATE TABLE foo (bar_tinyint TINYINT(3) SIGNED, bar_smallint SMALLINT(5) SIGNED, bar_mediumint MEDIUMINT(13) SIGNED, bar_int INT(11) SIGNED, bar_bigint BIGINT(20) SIGNED)";
|
||||
let canonical = "CREATE TABLE foo (bar_tinyint TINYINT(3), bar_smallint SMALLINT(5), bar_mediumint MEDIUMINT(13), bar_int INT(11), bar_bigint BIGINT(20))";
|
||||
match mysql().one_statement_parses_to(sql, canonical) {
|
||||
Statement::CreateTable(CreateTable { name, columns, .. }) => {
|
||||
assert_eq!(name.to_string(), "foo");
|
||||
assert_eq!(
|
||||
vec![
|
||||
ColumnDef {
|
||||
name: Ident::new("bar_tinyint"),
|
||||
data_type: DataType::TinyInt(Some(3)),
|
||||
options: vec![],
|
||||
},
|
||||
ColumnDef {
|
||||
name: Ident::new("bar_smallint"),
|
||||
data_type: DataType::SmallInt(Some(5)),
|
||||
options: vec![],
|
||||
},
|
||||
ColumnDef {
|
||||
name: Ident::new("bar_mediumint"),
|
||||
data_type: DataType::MediumInt(Some(13)),
|
||||
options: vec![],
|
||||
},
|
||||
ColumnDef {
|
||||
name: Ident::new("bar_int"),
|
||||
data_type: DataType::Int(Some(11)),
|
||||
options: vec![],
|
||||
},
|
||||
ColumnDef {
|
||||
name: Ident::new("bar_bigint"),
|
||||
data_type: DataType::BigInt(Some(20)),
|
||||
options: vec![],
|
||||
},
|
||||
],
|
||||
columns
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
all_dialects_except(|d| d.supports_data_type_signed_suffix())
|
||||
.run_parser_method(sql, |p| p.parse_statement())
|
||||
.expect_err("SIGNED suffix should not be allowed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_simple_insert() {
|
||||
let sql = r"INSERT INTO tasks (title, priority) VALUES ('Test Some Inserts', 1), ('Test Entry 2', 2), ('Test Entry 3', 3)";
|
||||
|
@ -1804,7 +1436,6 @@ fn parse_simple_insert() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
})),
|
||||
source
|
||||
);
|
||||
|
@ -1853,7 +1484,6 @@ fn parse_ignore_insert() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
})),
|
||||
source
|
||||
);
|
||||
|
@ -1902,7 +1532,6 @@ fn parse_priority_insert() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
})),
|
||||
source
|
||||
);
|
||||
|
@ -1948,7 +1577,6 @@ fn parse_priority_insert() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
})),
|
||||
source
|
||||
);
|
||||
|
@ -1996,7 +1624,6 @@ fn parse_insert_as() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
})),
|
||||
source
|
||||
);
|
||||
|
@ -2059,7 +1686,6 @@ fn parse_insert_as() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
})),
|
||||
source
|
||||
);
|
||||
|
@ -2109,7 +1735,6 @@ fn parse_replace_insert() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
})),
|
||||
source
|
||||
);
|
||||
|
@ -2150,7 +1775,6 @@ fn parse_empty_row_insert() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
})),
|
||||
source
|
||||
);
|
||||
|
@ -2215,7 +1839,6 @@ fn parse_insert_with_on_duplicate_update() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
})),
|
||||
source
|
||||
);
|
||||
|
@ -2275,7 +1898,6 @@ fn parse_select_with_numeric_prefix_column_name() {
|
|||
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(
|
||||
"123col_$@123abc"
|
||||
)))],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident::with_quote(
|
||||
|
@ -2313,11 +1935,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() {
|
|||
Some(SelectItem::UnnamedExpr(Expr::CompoundIdentifier(parts))) => {
|
||||
assert_eq!(&[Ident::new("t"), Ident::new("15to29")], &parts[..]);
|
||||
}
|
||||
proj => panic!("Unexpected projection: {proj:?}"),
|
||||
proj => panic!("Unexpected projection: {:?}", proj),
|
||||
},
|
||||
body => panic!("Unexpected statement body: {body:?}"),
|
||||
body => panic!("Unexpected statement body: {:?}", body),
|
||||
},
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
}
|
||||
|
||||
// Case 2: Qualified column name that starts with digits and on its own represents a number.
|
||||
|
@ -2327,11 +1949,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() {
|
|||
Some(SelectItem::UnnamedExpr(Expr::CompoundIdentifier(parts))) => {
|
||||
assert_eq!(&[Ident::new("t"), Ident::new("15e29")], &parts[..]);
|
||||
}
|
||||
proj => panic!("Unexpected projection: {proj:?}"),
|
||||
proj => panic!("Unexpected projection: {:?}", proj),
|
||||
},
|
||||
body => panic!("Unexpected statement body: {body:?}"),
|
||||
body => panic!("Unexpected statement body: {:?}", body),
|
||||
},
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
}
|
||||
|
||||
// Case 3: Unqualified, the same token is parsed as a number.
|
||||
|
@ -2345,11 +1967,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() {
|
|||
Some(SelectItem::UnnamedExpr(Expr::Value(ValueWithSpan { value, .. }))) => {
|
||||
assert_eq!(&number("15e29"), value);
|
||||
}
|
||||
proj => panic!("Unexpected projection: {proj:?}"),
|
||||
proj => panic!("Unexpected projection: {:?}", proj),
|
||||
},
|
||||
body => panic!("Unexpected statement body: {body:?}"),
|
||||
body => panic!("Unexpected statement body: {:?}", body),
|
||||
},
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
}
|
||||
|
||||
// Case 4: Quoted simple identifier.
|
||||
|
@ -2359,11 +1981,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() {
|
|||
Some(SelectItem::UnnamedExpr(Expr::Identifier(name))) => {
|
||||
assert_eq!(&Ident::with_quote('`', "15e29"), name);
|
||||
}
|
||||
proj => panic!("Unexpected projection: {proj:?}"),
|
||||
proj => panic!("Unexpected projection: {:?}", proj),
|
||||
},
|
||||
body => panic!("Unexpected statement body: {body:?}"),
|
||||
body => panic!("Unexpected statement body: {:?}", body),
|
||||
},
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
}
|
||||
|
||||
// Case 5: Quoted compound identifier.
|
||||
|
@ -2376,11 +1998,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() {
|
|||
&parts[..]
|
||||
);
|
||||
}
|
||||
proj => panic!("Unexpected projection: {proj:?}"),
|
||||
proj => panic!("Unexpected projection: {:?}", proj),
|
||||
},
|
||||
body => panic!("Unexpected statement body: {body:?}"),
|
||||
body => panic!("Unexpected statement body: {:?}", body),
|
||||
},
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
}
|
||||
|
||||
// Case 6: Multi-level compound identifiers.
|
||||
|
@ -2397,11 +2019,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() {
|
|||
&parts[..]
|
||||
);
|
||||
}
|
||||
proj => panic!("Unexpected projection: {proj:?}"),
|
||||
proj => panic!("Unexpected projection: {:?}", proj),
|
||||
},
|
||||
body => panic!("Unexpected statement body: {body:?}"),
|
||||
body => panic!("Unexpected statement body: {:?}", body),
|
||||
},
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
}
|
||||
|
||||
// Case 7: Multi-level compound quoted identifiers.
|
||||
|
@ -2418,11 +2040,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() {
|
|||
&parts[..]
|
||||
);
|
||||
}
|
||||
proj => panic!("Unexpected projection: {proj:?}"),
|
||||
proj => panic!("Unexpected projection: {:?}", proj),
|
||||
},
|
||||
body => panic!("Unexpected statement body: {body:?}"),
|
||||
body => panic!("Unexpected statement body: {:?}", body),
|
||||
},
|
||||
stmt => panic!("Unexpected statement: {stmt:?}"),
|
||||
stmt => panic!("Unexpected statement: {:?}", stmt),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2443,6 +2065,7 @@ fn parse_select_with_concatenation_of_exp_number_and_numeric_prefix_column() {
|
|||
q.body,
|
||||
Box::new(SetExpr::Select(Box::new(Select {
|
||||
select_token: AttachedToken::empty(),
|
||||
|
||||
distinct: None,
|
||||
top: None,
|
||||
top_before_distinct: false,
|
||||
|
@ -2450,7 +2073,6 @@ fn parse_select_with_concatenation_of_exp_number_and_numeric_prefix_column() {
|
|||
SelectItem::UnnamedExpr(Expr::value(number("123e4"))),
|
||||
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("123col_$@123abc")))
|
||||
],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident::with_quote(
|
||||
|
@ -2632,13 +2254,11 @@ fn parse_alter_table_add_column() {
|
|||
if_exists,
|
||||
only,
|
||||
operations,
|
||||
iceberg,
|
||||
location: _,
|
||||
on_cluster: _,
|
||||
} => {
|
||||
assert_eq!(name.to_string(), "tab");
|
||||
assert!(!if_exists);
|
||||
assert!(!iceberg);
|
||||
assert!(!only);
|
||||
assert_eq!(
|
||||
operations,
|
||||
|
@ -2663,7 +2283,8 @@ fn parse_alter_table_add_column() {
|
|||
if_exists,
|
||||
only,
|
||||
operations,
|
||||
..
|
||||
location: _,
|
||||
on_cluster: _,
|
||||
} => {
|
||||
assert_eq!(name.to_string(), "tab");
|
||||
assert!(!if_exists);
|
||||
|
@ -2700,7 +2321,8 @@ fn parse_alter_table_add_columns() {
|
|||
if_exists,
|
||||
only,
|
||||
operations,
|
||||
..
|
||||
location: _,
|
||||
on_cluster: _,
|
||||
} => {
|
||||
assert_eq!(name.to_string(), "tab");
|
||||
assert!(!if_exists);
|
||||
|
@ -2926,8 +2548,7 @@ fn parse_alter_table_with_algorithm() {
|
|||
operations,
|
||||
vec![
|
||||
AlterTableOperation::DropColumn {
|
||||
has_column_keyword: true,
|
||||
column_names: vec![Ident::new("password_digest")],
|
||||
column_name: Ident::new("password_digest"),
|
||||
if_exists: false,
|
||||
drop_behavior: None,
|
||||
},
|
||||
|
@ -2974,8 +2595,7 @@ fn parse_alter_table_with_lock() {
|
|||
operations,
|
||||
vec![
|
||||
AlterTableOperation::DropColumn {
|
||||
has_column_keyword: true,
|
||||
column_names: vec![Ident::new("password_digest")],
|
||||
column_name: Ident::new("password_digest"),
|
||||
if_exists: false,
|
||||
drop_behavior: None,
|
||||
},
|
||||
|
@ -3094,7 +2714,6 @@ fn parse_substring_in_select() {
|
|||
special: true,
|
||||
shorthand: false,
|
||||
})],
|
||||
exclude: None,
|
||||
into: None,
|
||||
from: vec![TableWithJoins {
|
||||
relation: table_from_name(ObjectName::from(vec![Ident {
|
||||
|
@ -3126,7 +2745,6 @@ fn parse_substring_in_select() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
}),
|
||||
query
|
||||
);
|
||||
|
@ -3409,7 +3027,6 @@ fn parse_hex_string_introducer() {
|
|||
)
|
||||
.into(),
|
||||
})],
|
||||
exclude: None,
|
||||
from: vec![],
|
||||
lateral_views: vec![],
|
||||
prewhere: None,
|
||||
|
@ -3434,7 +3051,6 @@ fn parse_hex_string_introducer() {
|
|||
for_clause: None,
|
||||
settings: None,
|
||||
format_clause: None,
|
||||
pipe_operators: vec![],
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
@ -3667,9 +3283,7 @@ fn parse_grant() {
|
|||
objects,
|
||||
grantees,
|
||||
with_grant_option,
|
||||
as_grantor: _,
|
||||
granted_by,
|
||||
current_grants: _,
|
||||
} = stmt
|
||||
{
|
||||
assert_eq!(
|
||||
|
@ -3875,11 +3489,6 @@ fn parse_begin_without_transaction() {
|
|||
mysql().verified_stmt("BEGIN");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_geometric_types_srid_option() {
|
||||
mysql_and_generic().verified_stmt("CREATE TABLE t (a geometry SRID 4326)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_double_precision() {
|
||||
mysql().verified_stmt("CREATE TABLE foo (bar DOUBLE)");
|
||||
|
@ -3915,7 +3524,6 @@ fn parse_create_trigger() {
|
|||
assert_eq!(
|
||||
create_stmt,
|
||||
Statement::CreateTrigger {
|
||||
or_alter: false,
|
||||
or_replace: false,
|
||||
is_constraint: false,
|
||||
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
|
||||
|
@ -3927,14 +3535,13 @@ fn parse_create_trigger() {
|
|||
trigger_object: TriggerObject::Row,
|
||||
include_each: true,
|
||||
condition: None,
|
||||
exec_body: Some(TriggerExecBody {
|
||||
exec_body: TriggerExecBody {
|
||||
exec_type: TriggerExecBodyType::Function,
|
||||
func_desc: FunctionDesc {
|
||||
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
|
||||
args: None,
|
||||
}
|
||||
}),
|
||||
statements: None,
|
||||
},
|
||||
characteristics: None,
|
||||
}
|
||||
);
|
||||
|
@ -4115,91 +3722,3 @@ fn parse_straight_join() {
|
|||
mysql()
|
||||
.verified_stmt("SELECT a.*, b.* FROM table_a STRAIGHT_JOIN table_b AS b ON a.b_id = b.id");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mysql_foreign_key_with_index_name() {
|
||||
mysql().verified_stmt(
|
||||
"CREATE TABLE orders (customer_id INT, INDEX idx_customer (customer_id), CONSTRAINT fk_customer FOREIGN KEY idx_customer (customer_id) REFERENCES customers(id))",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_drop_index() {
|
||||
let sql = "DROP INDEX idx_name ON table_name";
|
||||
match mysql().verified_stmt(sql) {
|
||||
Statement::Drop {
|
||||
object_type,
|
||||
if_exists,
|
||||
names,
|
||||
cascade,
|
||||
restrict,
|
||||
purge,
|
||||
temporary,
|
||||
table,
|
||||
} => {
|
||||
assert!(!if_exists);
|
||||
assert_eq!(ObjectType::Index, object_type);
|
||||
assert_eq!(
|
||||
vec!["idx_name"],
|
||||
names.iter().map(ToString::to_string).collect::<Vec<_>>()
|
||||
);
|
||||
assert!(!cascade);
|
||||
assert!(!restrict);
|
||||
assert!(!purge);
|
||||
assert!(!temporary);
|
||||
assert!(table.is_some());
|
||||
assert_eq!("table_name", table.unwrap().to_string());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_alter_table_drop_index() {
|
||||
assert_matches!(
|
||||
alter_table_op(
|
||||
mysql_and_generic().verified_stmt("ALTER TABLE tab DROP INDEX idx_index")
|
||||
),
|
||||
AlterTableOperation::DropIndex { name } if name.value == "idx_index"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_json_member_of() {
|
||||
mysql().verified_stmt(r#"SELECT 17 MEMBER OF('[23, "abc", 17, "ab", 10]')"#);
|
||||
let sql = r#"SELECT 'ab' MEMBER OF('[23, "abc", 17, "ab", 10]')"#;
|
||||
let stmt = mysql().verified_stmt(sql);
|
||||
match stmt {
|
||||
Statement::Query(query) => {
|
||||
let select = query.body.as_select().unwrap();
|
||||
assert_eq!(
|
||||
select.projection,
|
||||
vec![SelectItem::UnnamedExpr(Expr::MemberOf(MemberOf {
|
||||
value: Box::new(Expr::Value(
|
||||
Value::SingleQuotedString("ab".to_string()).into()
|
||||
)),
|
||||
array: Box::new(Expr::Value(
|
||||
Value::SingleQuotedString(r#"[23, "abc", 17, "ab", 10]"#.to_string())
|
||||
.into()
|
||||
)),
|
||||
}))]
|
||||
);
|
||||
}
|
||||
_ => panic!("Unexpected statement {stmt}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_show_charset() {
|
||||
let res = mysql().verified_stmt("SHOW CHARACTER SET");
|
||||
assert_eq!(
|
||||
res,
|
||||
Statement::ShowCharset(ShowCharset {
|
||||
is_shorthand: false,
|
||||
filter: None
|
||||
})
|
||||
);
|
||||
mysql().verified_stmt("SHOW CHARACTER SET LIKE 'utf8mb4%'");
|
||||
mysql().verified_stmt("SHOW CHARSET WHERE charset = 'utf8mb4%'");
|
||||
mysql().verified_stmt("SHOW CHARSET LIKE 'utf8mb4%'");
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -402,8 +402,3 @@ fn parse_extract_single_quotes() {
|
|||
fn parse_string_literal_backslash_escape() {
|
||||
redshift().one_statement_parses_to(r#"SELECT 'l\'auto'"#, "SELECT 'l''auto'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_utf8_multibyte_idents() {
|
||||
redshift().verified_stmt("SELECT 🚀.city AS 🎸 FROM customers AS 🚀");
|
||||
}
|
||||
|
|
|
@ -270,8 +270,8 @@ fn test_snowflake_create_table_with_tag() {
|
|||
assert_eq!("my_table", name.to_string());
|
||||
assert_eq!(
|
||||
Some(vec![
|
||||
Tag::new(ObjectName::from(vec![Ident::new("A")]), "TAG A".to_string()),
|
||||
Tag::new(ObjectName::from(vec![Ident::new("B")]), "TAG B".to_string())
|
||||
Tag::new("A".into(), "TAG A".to_string()),
|
||||
Tag::new("B".into(), "TAG B".to_string())
|
||||
]),
|
||||
with_tags
|
||||
);
|
||||
|
@ -291,8 +291,8 @@ fn test_snowflake_create_table_with_tag() {
|
|||
assert_eq!("my_table", name.to_string());
|
||||
assert_eq!(
|
||||
Some(vec![
|
||||
Tag::new(ObjectName::from(vec![Ident::new("A")]), "TAG A".to_string()),
|
||||
Tag::new(ObjectName::from(vec![Ident::new("B")]), "TAG B".to_string())
|
||||
Tag::new("A".into(), "TAG A".to_string()),
|
||||
Tag::new("B".into(), "TAG B".to_string())
|
||||
]),
|
||||
with_tags
|
||||
);
|
||||
|
@ -446,56 +446,19 @@ fn test_snowflake_create_table_if_not_exists() {
|
|||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
for (sql, parse_to) in [
|
||||
(
|
||||
r#"CREATE TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#,
|
||||
r#"CREATE TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#,
|
||||
),
|
||||
(
|
||||
r#"CREATE TABLE "A"."B"."C" IF NOT EXISTS (v VARIANT)"#,
|
||||
r#"CREATE TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#,
|
||||
),
|
||||
(
|
||||
r#"CREATE TRANSIENT TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#,
|
||||
r#"CREATE TRANSIENT TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#,
|
||||
),
|
||||
(
|
||||
r#"CREATE TRANSIENT TABLE "A"."B"."C" IF NOT EXISTS (v VARIANT)"#,
|
||||
r#"CREATE TRANSIENT TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#,
|
||||
),
|
||||
] {
|
||||
snowflake().one_statement_parses_to(sql, parse_to);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snowflake_create_table_cluster_by() {
|
||||
match snowflake().verified_stmt("CREATE TABLE my_table (a INT) CLUSTER BY (a, b, my_func(c))") {
|
||||
match snowflake().verified_stmt("CREATE TABLE my_table (a INT) CLUSTER BY (a, b)") {
|
||||
Statement::CreateTable(CreateTable {
|
||||
name, cluster_by, ..
|
||||
}) => {
|
||||
assert_eq!("my_table", name.to_string());
|
||||
assert_eq!(
|
||||
Some(WrappedCollection::Parentheses(vec![
|
||||
Expr::Identifier(Ident::new("a")),
|
||||
Expr::Identifier(Ident::new("b")),
|
||||
Expr::Function(Function {
|
||||
name: ObjectName::from(vec![Ident::new("my_func")]),
|
||||
uses_odbc_syntax: false,
|
||||
parameters: FunctionArguments::None,
|
||||
args: FunctionArguments::List(FunctionArgumentList {
|
||||
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
|
||||
Expr::Identifier(Ident::new("c"))
|
||||
))],
|
||||
duplicate_treatment: None,
|
||||
clauses: vec![],
|
||||
}),
|
||||
filter: None,
|
||||
null_treatment: None,
|
||||
over: None,
|
||||
within_group: vec![],
|
||||
}),
|
||||
Ident::new("a"),
|
||||
Ident::new("b"),
|
||||
])),
|
||||
cluster_by
|
||||
)
|
||||
|
@ -507,22 +470,9 @@ fn test_snowflake_create_table_cluster_by() {
|
|||
#[test]
|
||||
fn test_snowflake_create_table_comment() {
|
||||
match snowflake().verified_stmt("CREATE TABLE my_table (a INT) COMMENT = 'some comment'") {
|
||||
Statement::CreateTable(CreateTable {
|
||||
name,
|
||||
table_options,
|
||||
..
|
||||
}) => {
|
||||
Statement::CreateTable(CreateTable { name, comment, .. }) => {
|
||||
assert_eq!("my_table", name.to_string());
|
||||
let plain_options = match table_options {
|
||||
CreateTableOptions::Plain(options) => options,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let comment = match plain_options.first().unwrap() {
|
||||
SqlOption::Comment(CommentDef::WithEq(c))
|
||||
| SqlOption::Comment(CommentDef::WithoutEq(c)) => c,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
assert_eq!("some comment", comment);
|
||||
assert_eq!("some comment", comment.unwrap().to_string());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -731,7 +681,7 @@ fn test_snowflake_create_table_with_columns_masking_policy() {
|
|||
option: ColumnOption::Policy(ColumnPolicy::MaskingPolicy(
|
||||
ColumnPolicyProperty {
|
||||
with,
|
||||
policy_name: ObjectName::from(vec![Ident::new("p")]),
|
||||
policy_name: "p".into(),
|
||||
using_columns,
|
||||
}
|
||||
))
|
||||
|
@ -765,7 +715,7 @@ fn test_snowflake_create_table_with_columns_projection_policy() {
|
|||
option: ColumnOption::Policy(ColumnPolicy::ProjectionPolicy(
|
||||
ColumnPolicyProperty {
|
||||
with,
|
||||
policy_name: ObjectName::from(vec![Ident::new("p")]),
|
||||
policy_name: "p".into(),
|
||||
using_columns: None,
|
||||
}
|
||||
))
|
||||
|
@ -802,14 +752,8 @@ fn test_snowflake_create_table_with_columns_tags() {
|
|||
option: ColumnOption::Tags(TagsColumnOption {
|
||||
with,
|
||||
tags: vec![
|
||||
Tag::new(
|
||||
ObjectName::from(vec![Ident::new("A")]),
|
||||
"TAG A".into()
|
||||
),
|
||||
Tag::new(
|
||||
ObjectName::from(vec![Ident::new("B")]),
|
||||
"TAG B".into()
|
||||
),
|
||||
Tag::new("A".into(), "TAG A".into()),
|
||||
Tag::new("B".into(), "TAG B".into()),
|
||||
]
|
||||
}),
|
||||
}],
|
||||
|
@ -852,7 +796,7 @@ fn test_snowflake_create_table_with_several_column_options() {
|
|||
option: ColumnOption::Policy(ColumnPolicy::MaskingPolicy(
|
||||
ColumnPolicyProperty {
|
||||
with: true,
|
||||
policy_name: ObjectName::from(vec![Ident::new("p1")]),
|
||||
policy_name: "p1".into(),
|
||||
using_columns: Some(vec!["a".into(), "b".into()]),
|
||||
}
|
||||
)),
|
||||
|
@ -862,14 +806,8 @@ fn test_snowflake_create_table_with_several_column_options() {
|
|||
option: ColumnOption::Tags(TagsColumnOption {
|
||||
with: true,
|
||||
tags: vec![
|
||||
Tag::new(
|
||||
ObjectName::from(vec![Ident::new("A")]),
|
||||
"TAG A".into()
|
||||
),
|
||||
Tag::new(
|
||||
ObjectName::from(vec![Ident::new("B")]),
|
||||
"TAG B".into()
|
||||
),
|
||||
Tag::new("A".into(), "TAG A".into()),
|
||||
Tag::new("B".into(), "TAG B".into()),
|
||||
]
|
||||
}),
|
||||
}
|
||||
|
@ -890,7 +828,7 @@ fn test_snowflake_create_table_with_several_column_options() {
|
|||
option: ColumnOption::Policy(ColumnPolicy::ProjectionPolicy(
|
||||
ColumnPolicyProperty {
|
||||
with: false,
|
||||
policy_name: ObjectName::from(vec![Ident::new("p2")]),
|
||||
policy_name: "p2".into(),
|
||||
using_columns: None,
|
||||
}
|
||||
)),
|
||||
|
@ -900,14 +838,8 @@ fn test_snowflake_create_table_with_several_column_options() {
|
|||
option: ColumnOption::Tags(TagsColumnOption {
|
||||
with: false,
|
||||
tags: vec![
|
||||
Tag::new(
|
||||
ObjectName::from(vec![Ident::new("C")]),
|
||||
"TAG C".into()
|
||||
),
|
||||
Tag::new(
|
||||
ObjectName::from(vec![Ident::new("D")]),
|
||||
"TAG D".into()
|
||||
),
|
||||
Tag::new("C".into(), "TAG C".into()),
|
||||
Tag::new("D".into(), "TAG D".into()),
|
||||
]
|
||||
}),
|
||||
}
|
||||
|
@ -937,8 +869,8 @@ fn test_snowflake_create_iceberg_table_all_options() {
|
|||
assert_eq!("my_table", name.to_string());
|
||||
assert_eq!(
|
||||
Some(WrappedCollection::Parentheses(vec![
|
||||
Expr::Identifier(Ident::new("a")),
|
||||
Expr::Identifier(Ident::new("b")),
|
||||
Ident::new("a"),
|
||||
Ident::new("b"),
|
||||
])),
|
||||
cluster_by
|
||||
);
|
||||
|
@ -960,8 +892,8 @@ fn test_snowflake_create_iceberg_table_all_options() {
|
|||
with_aggregation_policy.map(|name| name.to_string())
|
||||
);
|
||||
assert_eq!(Some(vec![
|
||||
Tag::new(ObjectName::from(vec![Ident::new("A")]), "TAG A".into()),
|
||||
Tag::new(ObjectName::from(vec![Ident::new("B")]), "TAG B".into()),
|
||||
Tag::new("A".into(), "TAG A".into()),
|
||||
Tag::new("B".into(), "TAG B".into()),
|
||||
]), with_tags);
|
||||
|
||||
}
|
||||
|
@ -995,51 +927,6 @@ fn test_snowflake_create_iceberg_table_without_location() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snowflake_create_table_trailing_options() {
|
||||
// Serialization to SQL assume that in `CREATE TABLE AS` the options come before the `AS (<query>)`
|
||||
// but Snowflake supports also the other way around
|
||||
snowflake()
|
||||
.verified_stmt("CREATE TEMPORARY TABLE dst ON COMMIT PRESERVE ROWS AS (SELECT * FROM src)");
|
||||
snowflake()
|
||||
.parse_sql_statements(
|
||||
"CREATE TEMPORARY TABLE dst AS (SELECT * FROM src) ON COMMIT PRESERVE ROWS",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Same for `CREATE TABLE LIKE|CLONE`:
|
||||
snowflake().verified_stmt("CREATE TEMPORARY TABLE dst LIKE src ON COMMIT PRESERVE ROWS");
|
||||
snowflake()
|
||||
.parse_sql_statements("CREATE TEMPORARY TABLE dst ON COMMIT PRESERVE ROWS LIKE src")
|
||||
.unwrap();
|
||||
|
||||
snowflake().verified_stmt("CREATE TEMPORARY TABLE dst CLONE src ON COMMIT PRESERVE ROWS");
|
||||
snowflake()
|
||||
.parse_sql_statements("CREATE TEMPORARY TABLE dst ON COMMIT PRESERVE ROWS CLONE src")
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snowflake_create_table_valid_schema_info() {
|
||||
// Validate there's exactly one source of information on the schema of the new table
|
||||
assert_eq!(
|
||||
snowflake()
|
||||
.parse_sql_statements("CREATE TABLE dst")
|
||||
.is_err(),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
snowflake().parse_sql_statements("CREATE OR REPLACE TEMP TABLE dst LIKE src AS (SELECT * FROM CUSTOMERS) ON COMMIT PRESERVE ROWS").is_err(),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
snowflake()
|
||||
.parse_sql_statements("CREATE OR REPLACE TEMP TABLE dst CLONE customers LIKE customer2")
|
||||
.is_err(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_sf_create_or_replace_view_with_comment_missing_equal() {
|
||||
assert!(snowflake_and_generic()
|
||||
|
@ -1691,13 +1578,6 @@ fn test_alter_table_clustering() {
|
|||
snowflake_and_generic().verified_stmt("ALTER TABLE tbl RESUME RECLUSTER");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alter_iceberg_table() {
|
||||
snowflake_and_generic().verified_stmt("ALTER ICEBERG TABLE tbl DROP CLUSTERING KEY");
|
||||
snowflake_and_generic().verified_stmt("ALTER ICEBERG TABLE tbl SUSPEND RECLUSTER");
|
||||
snowflake_and_generic().verified_stmt("ALTER ICEBERG TABLE tbl RESUME RECLUSTER");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drop_stage() {
|
||||
match snowflake_and_generic().verified_stmt("DROP STAGE s1") {
|
||||
|
@ -2573,7 +2453,10 @@ fn test_snowflake_stage_object_names_into_location() {
|
|||
.zip(allowed_object_names.iter_mut())
|
||||
{
|
||||
let (formatted_name, object_name) = it;
|
||||
let sql = format!("COPY INTO {formatted_name} FROM 'gcs://mybucket/./../a.csv'");
|
||||
let sql = format!(
|
||||
"COPY INTO {} FROM 'gcs://mybucket/./../a.csv'",
|
||||
formatted_name
|
||||
);
|
||||
match snowflake().verified_stmt(&sql) {
|
||||
Statement::CopyIntoSnowflake { into, .. } => {
|
||||
assert_eq!(into.0, object_name.0)
|
||||
|
@ -2596,7 +2479,10 @@ fn test_snowflake_stage_object_names_into_table() {
|
|||
.zip(allowed_object_names.iter_mut())
|
||||
{
|
||||
let (formatted_name, object_name) = it;
|
||||
let sql = format!("COPY INTO {formatted_name} FROM 'gcs://mybucket/./../a.csv'");
|
||||
let sql = format!(
|
||||
"COPY INTO {} FROM 'gcs://mybucket/./../a.csv'",
|
||||
formatted_name
|
||||
);
|
||||
match snowflake().verified_stmt(&sql) {
|
||||
Statement::CopyIntoSnowflake { into, .. } => {
|
||||
assert_eq!(into.0, object_name.0)
|
||||
|
@ -2626,26 +2512,6 @@ fn test_snowflake_copy_into() {
|
|||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Test for non-ident characters in stage names
|
||||
let sql = "COPY INTO a.b FROM @namespace.stage_name/x@x~x%x+/20250723_data";
|
||||
assert_eq!(snowflake().verified_stmt(sql).to_string(), sql);
|
||||
match snowflake().verified_stmt(sql) {
|
||||
Statement::CopyIntoSnowflake { into, from_obj, .. } => {
|
||||
assert_eq!(
|
||||
into,
|
||||
ObjectName::from(vec![Ident::new("a"), Ident::new("b")])
|
||||
);
|
||||
assert_eq!(
|
||||
from_obj,
|
||||
Some(ObjectName::from(vec![
|
||||
Ident::new("@namespace"),
|
||||
Ident::new("stage_name/x@x~x%x+/20250723_data")
|
||||
]))
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -3097,7 +2963,7 @@ fn parse_use() {
|
|||
for object_name in &valid_object_names {
|
||||
// Test single identifier without quotes
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!("USE {object_name}")),
|
||||
snowflake().verified_stmt(&format!("USE {}", object_name)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
|
||||
object_name.to_string()
|
||||
)])))
|
||||
|
@ -3105,7 +2971,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single identifier with different type of quotes
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
|
||||
snowflake().verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
object_name.to_string(),
|
||||
|
@ -3117,9 +2983,7 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test double identifier with different type of quotes
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!(
|
||||
"USE {quote}CATALOG{quote}.{quote}my_schema{quote}"
|
||||
)),
|
||||
snowflake().verified_stmt(&format!("USE {0}CATALOG{0}.{0}my_schema{0}", quote)),
|
||||
Statement::Use(Use::Object(ObjectName::from(vec![
|
||||
Ident::with_quote(quote, "CATALOG"),
|
||||
Ident::with_quote(quote, "my_schema")
|
||||
|
@ -3138,37 +3002,35 @@ fn parse_use() {
|
|||
for "e in "e_styles {
|
||||
// Test single and double identifier with keyword and different type of quotes
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!("USE DATABASE {quote}my_database{quote}")),
|
||||
snowflake().verified_stmt(&format!("USE DATABASE {0}my_database{0}", quote)),
|
||||
Statement::Use(Use::Database(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
"my_database".to_string(),
|
||||
)])))
|
||||
);
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!("USE SCHEMA {quote}my_schema{quote}")),
|
||||
snowflake().verified_stmt(&format!("USE SCHEMA {0}my_schema{0}", quote)),
|
||||
Statement::Use(Use::Schema(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
"my_schema".to_string(),
|
||||
)])))
|
||||
);
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!(
|
||||
"USE SCHEMA {quote}CATALOG{quote}.{quote}my_schema{quote}"
|
||||
)),
|
||||
snowflake().verified_stmt(&format!("USE SCHEMA {0}CATALOG{0}.{0}my_schema{0}", quote)),
|
||||
Statement::Use(Use::Schema(ObjectName::from(vec![
|
||||
Ident::with_quote(quote, "CATALOG"),
|
||||
Ident::with_quote(quote, "my_schema")
|
||||
])))
|
||||
);
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!("USE ROLE {quote}my_role{quote}")),
|
||||
snowflake().verified_stmt(&format!("USE ROLE {0}my_role{0}", quote)),
|
||||
Statement::Use(Use::Role(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
"my_role".to_string(),
|
||||
)])))
|
||||
);
|
||||
assert_eq!(
|
||||
snowflake().verified_stmt(&format!("USE WAREHOUSE {quote}my_wh{quote}")),
|
||||
snowflake().verified_stmt(&format!("USE WAREHOUSE {0}my_wh{0}", quote)),
|
||||
Statement::Use(Use::Warehouse(ObjectName::from(vec![Ident::with_quote(
|
||||
quote,
|
||||
"my_wh".to_string(),
|
||||
|
@ -3205,7 +3067,7 @@ fn view_comment_option_should_be_after_column_list() {
|
|||
"CREATE OR REPLACE VIEW v (a COMMENT 'a comment', b, c COMMENT 'c comment') COMMENT = 'Comment' AS SELECT a FROM t",
|
||||
"CREATE OR REPLACE VIEW v (a COMMENT 'a comment', b, c COMMENT 'c comment') WITH (foo = bar) COMMENT = 'Comment' AS SELECT a FROM t",
|
||||
] {
|
||||
snowflake()
|
||||
snowflake_and_generic()
|
||||
.verified_stmt(sql);
|
||||
}
|
||||
}
|
||||
|
@ -3214,7 +3076,7 @@ fn view_comment_option_should_be_after_column_list() {
|
|||
fn parse_view_column_descriptions() {
|
||||
let sql = "CREATE OR REPLACE VIEW v (a COMMENT 'Comment', b) AS SELECT a, b FROM table1";
|
||||
|
||||
match snowflake().verified_stmt(sql) {
|
||||
match snowflake_and_generic().verified_stmt(sql) {
|
||||
Statement::CreateView { name, columns, .. } => {
|
||||
assert_eq!(name.to_string(), "v");
|
||||
assert_eq!(
|
||||
|
@ -3223,9 +3085,7 @@ fn parse_view_column_descriptions() {
|
|||
ViewColumnDef {
|
||||
name: Ident::new("a"),
|
||||
data_type: None,
|
||||
options: Some(ColumnOptions::SpaceSeparated(vec![ColumnOption::Comment(
|
||||
"Comment".to_string()
|
||||
)])),
|
||||
options: Some(vec![ColumnOption::Comment("Comment".to_string())]),
|
||||
},
|
||||
ViewColumnDef {
|
||||
name: Ident::new("b"),
|
||||
|
@ -3480,38 +3340,10 @@ fn parse_ls_and_rm() {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sql_keywords_as_select_item_ident() {
|
||||
// Some keywords that should be parsed as an alias
|
||||
let unreserved_kws = vec!["CLUSTER", "FETCH", "RETURNING", "LIMIT", "EXCEPT", "SORT"];
|
||||
for kw in unreserved_kws {
|
||||
snowflake().verified_stmt(&format!("SELECT 1, {kw}"));
|
||||
}
|
||||
|
||||
// Some keywords that should not be parsed as an alias
|
||||
let reserved_kws = vec![
|
||||
"FROM",
|
||||
"GROUP",
|
||||
"HAVING",
|
||||
"INTERSECT",
|
||||
"INTO",
|
||||
"ORDER",
|
||||
"SELECT",
|
||||
"UNION",
|
||||
"WHERE",
|
||||
"WITH",
|
||||
];
|
||||
for kw in reserved_kws {
|
||||
assert!(snowflake()
|
||||
.parse_sql_statements(&format!("SELECT 1, {kw}"))
|
||||
.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sql_keywords_as_select_item_aliases() {
|
||||
// Some keywords that should be parsed as an alias
|
||||
let unreserved_kws = vec!["CLUSTER", "FETCH", "RETURNING", "LIMIT", "EXCEPT", "SORT"];
|
||||
let unreserved_kws = vec!["CLUSTER", "FETCH", "RETURNING", "LIMIT", "EXCEPT"];
|
||||
for kw in unreserved_kws {
|
||||
snowflake()
|
||||
.one_statement_parses_to(&format!("SELECT 1 {kw}"), &format!("SELECT 1 AS {kw}"));
|
||||
|
@ -3535,87 +3367,6 @@ fn test_sql_keywords_as_select_item_aliases() {
|
|||
.parse_sql_statements(&format!("SELECT 1 {kw}"))
|
||||
.is_err());
|
||||
}
|
||||
|
||||
// LIMIT is alias
|
||||
snowflake().one_statement_parses_to("SELECT 1 LIMIT", "SELECT 1 AS LIMIT");
|
||||
// LIMIT is not an alias
|
||||
snowflake().verified_stmt("SELECT 1 LIMIT 1");
|
||||
snowflake().verified_stmt("SELECT 1 LIMIT $1");
|
||||
snowflake().verified_stmt("SELECT 1 LIMIT ''");
|
||||
snowflake().verified_stmt("SELECT 1 LIMIT NULL");
|
||||
snowflake().verified_stmt("SELECT 1 LIMIT $$$$");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sql_keywords_as_table_aliases() {
|
||||
// Some keywords that should be parsed as an alias implicitly
|
||||
let unreserved_kws = vec![
|
||||
"VIEW",
|
||||
"EXPLAIN",
|
||||
"ANALYZE",
|
||||
"SORT",
|
||||
"PIVOT",
|
||||
"UNPIVOT",
|
||||
"TOP",
|
||||
"LIMIT",
|
||||
"OFFSET",
|
||||
"FETCH",
|
||||
"EXCEPT",
|
||||
"CLUSTER",
|
||||
"DISTRIBUTE",
|
||||
"GLOBAL",
|
||||
"ANTI",
|
||||
"SEMI",
|
||||
"RETURNING",
|
||||
"OUTER",
|
||||
"WINDOW",
|
||||
"END",
|
||||
"PARTITION",
|
||||
"PREWHERE",
|
||||
"SETTINGS",
|
||||
"FORMAT",
|
||||
"MATCH_RECOGNIZE",
|
||||
"OPEN",
|
||||
];
|
||||
|
||||
for kw in unreserved_kws {
|
||||
snowflake().verified_stmt(&format!("SELECT * FROM tbl AS {kw}"));
|
||||
snowflake().one_statement_parses_to(
|
||||
&format!("SELECT * FROM tbl {kw}"),
|
||||
&format!("SELECT * FROM tbl AS {kw}"),
|
||||
);
|
||||
}
|
||||
|
||||
// Some keywords that should not be parsed as an alias implicitly
|
||||
let reserved_kws = vec![
|
||||
"FROM", "GROUP", "HAVING", "ORDER", "SELECT", "UNION", "WHERE", "WITH",
|
||||
];
|
||||
for kw in reserved_kws {
|
||||
assert!(snowflake()
|
||||
.parse_sql_statements(&format!("SELECT * FROM tbl {kw}"))
|
||||
.is_err());
|
||||
}
|
||||
|
||||
// LIMIT is alias
|
||||
snowflake().one_statement_parses_to("SELECT * FROM tbl LIMIT", "SELECT * FROM tbl AS LIMIT");
|
||||
// LIMIT is not an alias
|
||||
snowflake().verified_stmt("SELECT * FROM tbl LIMIT 1");
|
||||
snowflake().verified_stmt("SELECT * FROM tbl LIMIT $1");
|
||||
snowflake().verified_stmt("SELECT * FROM tbl LIMIT ''");
|
||||
snowflake().verified_stmt("SELECT * FROM tbl LIMIT NULL");
|
||||
snowflake().verified_stmt("SELECT * FROM tbl LIMIT $$$$");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sql_keywords_as_table_factor() {
|
||||
// LIMIT is a table factor, Snowflake does not reserve it
|
||||
snowflake().one_statement_parses_to("SELECT * FROM tbl, LIMIT", "SELECT * FROM tbl, LIMIT");
|
||||
// LIMIT is not a table factor
|
||||
snowflake().one_statement_parses_to("SELECT * FROM tbl, LIMIT 1", "SELECT * FROM tbl LIMIT 1");
|
||||
// ORDER is reserved
|
||||
assert!(snowflake()
|
||||
.parse_sql_statements("SELECT * FROM tbl, order")
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -3819,7 +3570,7 @@ fn test_alter_session_followed_by_statement() {
|
|||
.unwrap();
|
||||
match stmts[..] {
|
||||
[Statement::AlterSession { .. }, Statement::Query { .. }] => {}
|
||||
_ => panic!("Unexpected statements: {stmts:?}"),
|
||||
_ => panic!("Unexpected statements: {:?}", stmts),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4274,296 +4025,3 @@ fn parse_connect_by_root_operator() {
|
|||
"sql parser error: Expected an expression, found: FROM"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_begin_exception_end() {
|
||||
for sql in [
|
||||
"BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE; END",
|
||||
"BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE EX_1; END",
|
||||
"BEGIN SELECT 1; EXCEPTION WHEN FOO THEN SELECT 2; WHEN OTHER THEN SELECT 3; RAISE; END",
|
||||
"BEGIN BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE; END; END",
|
||||
] {
|
||||
snowflake().verified_stmt(sql);
|
||||
}
|
||||
|
||||
let sql = r#"
|
||||
DECLARE
|
||||
EXCEPTION_1 EXCEPTION (-20001, 'I caught the expected exception.');
|
||||
EXCEPTION_2 EXCEPTION (-20002, 'Not the expected exception!');
|
||||
EXCEPTION_3 EXCEPTION (-20003, 'The worst exception...');
|
||||
BEGIN
|
||||
BEGIN
|
||||
SELECT 1;
|
||||
EXCEPTION
|
||||
WHEN EXCEPTION_1 THEN
|
||||
SELECT 1;
|
||||
WHEN EXCEPTION_2 OR EXCEPTION_3 THEN
|
||||
SELECT 2;
|
||||
SELECT 3;
|
||||
WHEN OTHER THEN
|
||||
SELECT 4;
|
||||
RAISE;
|
||||
END;
|
||||
END
|
||||
"#;
|
||||
|
||||
// Outer `BEGIN` of the two nested `BEGIN` statements.
|
||||
let Statement::StartTransaction { mut statements, .. } = snowflake()
|
||||
.parse_sql_statements(sql)
|
||||
.unwrap()
|
||||
.pop()
|
||||
.unwrap()
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
// Inner `BEGIN` of the two nested `BEGIN` statements.
|
||||
let Statement::StartTransaction {
|
||||
statements,
|
||||
exception,
|
||||
has_end_keyword,
|
||||
..
|
||||
} = statements.pop().unwrap()
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
assert_eq!(1, statements.len());
|
||||
assert!(has_end_keyword);
|
||||
|
||||
let exception = exception.unwrap();
|
||||
assert_eq!(3, exception.len());
|
||||
assert_eq!(1, exception[0].idents.len());
|
||||
assert_eq!(1, exception[0].statements.len());
|
||||
assert_eq!(2, exception[1].idents.len());
|
||||
assert_eq!(2, exception[1].statements.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snowflake_fetch_clause_syntax() {
|
||||
let canonical = "SELECT c1 FROM fetch_test FETCH FIRST 2 ROWS ONLY";
|
||||
snowflake().verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH 2", canonical);
|
||||
|
||||
snowflake()
|
||||
.verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH FIRST 2", canonical);
|
||||
snowflake()
|
||||
.verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH NEXT 2", canonical);
|
||||
|
||||
snowflake()
|
||||
.verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH 2 ROW", canonical);
|
||||
|
||||
snowflake().verified_only_select_with_canonical(
|
||||
"SELECT c1 FROM fetch_test FETCH FIRST 2 ROWS",
|
||||
canonical,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snowflake_create_view_with_multiple_column_options() {
|
||||
let create_view_with_tag =
|
||||
r#"CREATE VIEW X (COL WITH TAG (pii='email') COMMENT 'foobar') AS SELECT * FROM Y"#;
|
||||
snowflake().verified_stmt(create_view_with_tag);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snowflake_create_view_with_composite_tag() {
|
||||
let create_view_with_tag =
|
||||
r#"CREATE VIEW X (COL WITH TAG (foo.bar.baz.pii='email')) AS SELECT * FROM Y"#;
|
||||
snowflake().verified_stmt(create_view_with_tag);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snowflake_create_view_with_composite_policy_name() {
|
||||
let create_view_with_tag =
|
||||
r#"CREATE VIEW X (COL WITH MASKING POLICY foo.bar.baz) AS SELECT * FROM Y"#;
|
||||
snowflake().verified_stmt(create_view_with_tag);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snowflake_identifier_function() {
|
||||
// Using IDENTIFIER to reference a column
|
||||
match &snowflake()
|
||||
.verified_only_select("SELECT identifier('email') FROM customers")
|
||||
.projection[0]
|
||||
{
|
||||
SelectItem::UnnamedExpr(Expr::Function(Function { name, args, .. })) => {
|
||||
assert_eq!(*name, ObjectName::from(vec![Ident::new("identifier")]));
|
||||
assert_eq!(
|
||||
*args,
|
||||
FunctionArguments::List(FunctionArgumentList {
|
||||
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
|
||||
Value::SingleQuotedString("email".to_string()).into()
|
||||
)))],
|
||||
clauses: vec![],
|
||||
duplicate_treatment: None
|
||||
})
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Using IDENTIFIER to reference a case-sensitive column
|
||||
match &snowflake()
|
||||
.verified_only_select(r#"SELECT identifier('"Email"') FROM customers"#)
|
||||
.projection[0]
|
||||
{
|
||||
SelectItem::UnnamedExpr(Expr::Function(Function { name, args, .. })) => {
|
||||
assert_eq!(*name, ObjectName::from(vec![Ident::new("identifier")]));
|
||||
assert_eq!(
|
||||
*args,
|
||||
FunctionArguments::List(FunctionArgumentList {
|
||||
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
|
||||
Value::SingleQuotedString("\"Email\"".to_string()).into()
|
||||
)))],
|
||||
clauses: vec![],
|
||||
duplicate_treatment: None
|
||||
})
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Using IDENTIFIER to reference an alias of a table
|
||||
match &snowflake()
|
||||
.verified_only_select("SELECT identifier('alias1').* FROM tbl AS alias1")
|
||||
.projection[0]
|
||||
{
|
||||
SelectItem::QualifiedWildcard(
|
||||
SelectItemQualifiedWildcardKind::Expr(Expr::Function(Function { name, args, .. })),
|
||||
_,
|
||||
) => {
|
||||
assert_eq!(*name, ObjectName::from(vec![Ident::new("identifier")]));
|
||||
assert_eq!(
|
||||
*args,
|
||||
FunctionArguments::List(FunctionArgumentList {
|
||||
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
|
||||
Value::SingleQuotedString("alias1".to_string()).into()
|
||||
)))],
|
||||
clauses: vec![],
|
||||
duplicate_treatment: None
|
||||
})
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Using IDENTIFIER to reference a database
|
||||
match snowflake().verified_stmt("CREATE DATABASE IDENTIFIER('tbl')") {
|
||||
Statement::CreateDatabase { db_name, .. } => {
|
||||
assert_eq!(
|
||||
db_name,
|
||||
ObjectName(vec![ObjectNamePart::Function(ObjectNamePartFunction {
|
||||
name: Ident::new("IDENTIFIER"),
|
||||
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
|
||||
Value::SingleQuotedString("tbl".to_string()).into()
|
||||
)))]
|
||||
})])
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Using IDENTIFIER to reference a schema
|
||||
match snowflake().verified_stmt("CREATE SCHEMA IDENTIFIER('db1.sc1')") {
|
||||
Statement::CreateSchema { schema_name, .. } => {
|
||||
assert_eq!(
|
||||
schema_name,
|
||||
SchemaName::Simple(ObjectName(vec![ObjectNamePart::Function(
|
||||
ObjectNamePartFunction {
|
||||
name: Ident::new("IDENTIFIER"),
|
||||
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
|
||||
Value::SingleQuotedString("db1.sc1".to_string()).into()
|
||||
)))]
|
||||
}
|
||||
)]))
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Using IDENTIFIER to reference a table
|
||||
match snowflake().verified_stmt("CREATE TABLE IDENTIFIER('tbl') (id INT)") {
|
||||
Statement::CreateTable(CreateTable { name, .. }) => {
|
||||
assert_eq!(
|
||||
name,
|
||||
ObjectName(vec![ObjectNamePart::Function(ObjectNamePartFunction {
|
||||
name: Ident::new("IDENTIFIER"),
|
||||
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
|
||||
Value::SingleQuotedString("tbl".to_string()).into()
|
||||
)))]
|
||||
})])
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Cannot have more than one IDENTIFIER part in an object name
|
||||
assert_eq!(
|
||||
snowflake()
|
||||
.parse_sql_statements(
|
||||
"CREATE TABLE IDENTIFIER('db1').IDENTIFIER('sc1').IDENTIFIER('tbl') (id INT)"
|
||||
)
|
||||
.is_err(),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
snowflake()
|
||||
.parse_sql_statements("CREATE TABLE IDENTIFIER('db1')..IDENTIFIER('tbl') (id INT)")
|
||||
.is_err(),
|
||||
true
|
||||
);
|
||||
|
||||
snowflake().verified_stmt("GRANT ROLE IDENTIFIER('AAA') TO USER IDENTIFIER('AAA')");
|
||||
snowflake().verified_stmt("REVOKE ROLE IDENTIFIER('AAA') FROM USER IDENTIFIER('AAA')");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_database() {
|
||||
snowflake().verified_stmt("CREATE DATABASE my_db");
|
||||
snowflake().verified_stmt("CREATE OR REPLACE DATABASE my_db");
|
||||
snowflake().verified_stmt("CREATE TRANSIENT DATABASE IF NOT EXISTS my_db");
|
||||
snowflake().verified_stmt("CREATE DATABASE my_db CLONE src_db");
|
||||
snowflake().verified_stmt(
|
||||
"CREATE OR REPLACE DATABASE my_db CLONE src_db DATA_RETENTION_TIME_IN_DAYS = 1",
|
||||
);
|
||||
snowflake().one_statement_parses_to(
|
||||
r#"
|
||||
CREATE OR REPLACE TRANSIENT DATABASE IF NOT EXISTS my_db
|
||||
CLONE src_db
|
||||
DATA_RETENTION_TIME_IN_DAYS = 1
|
||||
MAX_DATA_EXTENSION_TIME_IN_DAYS = 5
|
||||
EXTERNAL_VOLUME = 'volume1'
|
||||
CATALOG = 'my_catalog'
|
||||
REPLACE_INVALID_CHARACTERS = TRUE
|
||||
DEFAULT_DDL_COLLATION = 'en-ci'
|
||||
STORAGE_SERIALIZATION_POLICY = COMPATIBLE
|
||||
COMMENT = 'This is my database'
|
||||
CATALOG_SYNC = 'sync_integration'
|
||||
CATALOG_SYNC_NAMESPACE_MODE = NEST
|
||||
CATALOG_SYNC_NAMESPACE_FLATTEN_DELIMITER = '/'
|
||||
WITH TAG (env = 'prod', team = 'data')
|
||||
WITH CONTACT (owner = 'admin', dpo = 'compliance')
|
||||
"#,
|
||||
"CREATE OR REPLACE TRANSIENT DATABASE IF NOT EXISTS \
|
||||
my_db CLONE src_db DATA_RETENTION_TIME_IN_DAYS = 1 MAX_DATA_EXTENSION_TIME_IN_DAYS = 5 \
|
||||
EXTERNAL_VOLUME = 'volume1' CATALOG = 'my_catalog' \
|
||||
REPLACE_INVALID_CHARACTERS = TRUE DEFAULT_DDL_COLLATION = 'en-ci' \
|
||||
STORAGE_SERIALIZATION_POLICY = COMPATIBLE COMMENT = 'This is my database' \
|
||||
CATALOG_SYNC = 'sync_integration' CATALOG_SYNC_NAMESPACE_MODE = NEST \
|
||||
CATALOG_SYNC_NAMESPACE_FLATTEN_DELIMITER = '/' \
|
||||
WITH TAG (env='prod', team='data') \
|
||||
WITH CONTACT (owner = admin, dpo = compliance)",
|
||||
);
|
||||
|
||||
let err = snowflake()
|
||||
.parse_sql_statements("CREATE DATABASE")
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(err.contains("Expected"), "Unexpected error: {err}");
|
||||
|
||||
let err = snowflake()
|
||||
.parse_sql_statements("CREATE DATABASE my_db CLONE")
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(err.contains("Expected"), "Unexpected error: {err}");
|
||||
}
|
||||
|
|
|
@ -324,7 +324,7 @@ fn parse_create_table_on_conflict_col() {
|
|||
Keyword::IGNORE,
|
||||
Keyword::REPLACE,
|
||||
] {
|
||||
let sql = format!("CREATE TABLE t1 (a INT, b INT ON CONFLICT {keyword:?})");
|
||||
let sql = format!("CREATE TABLE t1 (a INT, b INT ON CONFLICT {:?})", keyword);
|
||||
match sqlite_and_generic().verified_stmt(&sql) {
|
||||
Statement::CreateTable(CreateTable { columns, .. }) => {
|
||||
assert_eq!(
|
||||
|
@ -410,7 +410,7 @@ fn parse_window_function_with_filter() {
|
|||
"count",
|
||||
"user_defined_function",
|
||||
] {
|
||||
let sql = format!("SELECT {func_name}(x) FILTER (WHERE y) OVER () FROM t");
|
||||
let sql = format!("SELECT {}(x) FILTER (WHERE y) OVER () FROM t", func_name);
|
||||
let select = sqlite().verified_only_select(&sql);
|
||||
assert_eq!(select.to_string(), sql);
|
||||
assert_eq!(
|
||||
|
@ -444,7 +444,7 @@ fn parse_window_function_with_filter() {
|
|||
fn parse_attach_database() {
|
||||
let sql = "ATTACH DATABASE 'test.db' AS test";
|
||||
let verified_stmt = sqlite().verified_stmt(sql);
|
||||
assert_eq!(sql, format!("{verified_stmt}"));
|
||||
assert_eq!(sql, format!("{}", verified_stmt));
|
||||
match verified_stmt {
|
||||
Statement::AttachDatabase {
|
||||
schema_name,
|
||||
|
@ -562,36 +562,6 @@ fn test_dollar_identifier_as_placeholder() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_match_operator() {
|
||||
assert_eq!(
|
||||
sqlite().verified_expr("col MATCH 'pattern'"),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::Match,
|
||||
left: Box::new(Expr::Identifier(Ident::new("col"))),
|
||||
right: Box::new(Expr::Value(
|
||||
(Value::SingleQuotedString("pattern".to_string())).with_empty_span()
|
||||
))
|
||||
}
|
||||
);
|
||||
sqlite().verified_only_select("SELECT * FROM email WHERE email MATCH 'fts5'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_regexp_operator() {
|
||||
assert_eq!(
|
||||
sqlite().verified_expr("col REGEXP 'pattern'"),
|
||||
Expr::BinaryOp {
|
||||
op: BinaryOperator::Regexp,
|
||||
left: Box::new(Expr::Identifier(Ident::new("col"))),
|
||||
right: Box::new(Expr::Value(
|
||||
(Value::SingleQuotedString("pattern".to_string())).with_empty_span()
|
||||
))
|
||||
}
|
||||
);
|
||||
sqlite().verified_only_select(r#"SELECT count(*) FROM messages WHERE msg_text REGEXP '\d+'"#);
|
||||
}
|
||||
|
||||
fn sqlite() -> TestedDialects {
|
||||
TestedDialects::new(vec![Box::new(SQLiteDialect {})])
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue