Skip to content

Commit 543df7c

Browse files
committed
Add Teradata dialect
Adds base implementation for the Teradata SQL dialect.
1 parent 53dcc82 commit 543df7c

File tree

6 files changed

+168
-1
lines changed

6 files changed

+168
-1
lines changed

src/dialect/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ mod redshift;
3030
mod snowflake;
3131
mod spark;
3232
mod sqlite;
33+
mod teradata;
3334

3435
use core::any::{Any, TypeId};
3536
use core::fmt::Debug;
@@ -54,6 +55,7 @@ pub use self::snowflake::parse_snowflake_stage_name;
5455
pub use self::snowflake::SnowflakeDialect;
5556
pub use self::spark::SparkSqlDialect;
5657
pub use self::sqlite::SQLiteDialect;
58+
pub use self::teradata::TeradataDialect;
5759

5860
/// Macro for streamlining the creation of derived `Dialect` objects.
5961
/// The generated struct includes `new()` and `default()` constructors.
@@ -1841,6 +1843,7 @@ pub fn dialect_from_str(dialect_name: impl AsRef<str>) -> Option<Box<dyn Dialect
18411843
"databricks" => Some(Box::new(DatabricksDialect {})),
18421844
"spark" | "sparksql" => Some(Box::new(SparkSqlDialect {})),
18431845
"oracle" => Some(Box::new(OracleDialect {})),
1846+
"teradata" => Some(Box::new(TeradataDialect {})),
18441847
_ => None,
18451848
}
18461849
}
@@ -1894,6 +1897,8 @@ mod tests {
18941897
assert!(parse_dialect("DuckDb").is::<DuckDbDialect>());
18951898
assert!(parse_dialect("DataBricks").is::<DatabricksDialect>());
18961899
assert!(parse_dialect("databricks").is::<DatabricksDialect>());
1900+
assert!(parse_dialect("teradata").is::<TeradataDialect>());
1901+
assert!(parse_dialect("Teradata").is::<TeradataDialect>());
18971902

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

src/dialect/teradata.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use crate::dialect::Dialect;
19+
20+
/// A [`Dialect`] for [Teradata](https://docs.teradata.com/).
21+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
22+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23+
pub struct TeradataDialect;
24+
25+
impl Dialect for TeradataDialect {
26+
/// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Fundamentals/Basic-SQL-Syntax/Object-Names>
27+
fn identifier_quote_style(&self, _identifier: &str) -> Option<char> {
28+
Some('"')
29+
}
30+
31+
/// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Fundamentals/Basic-SQL-Syntax/Working-with-Unicode-Delimited-Identifiers>
32+
fn is_delimited_identifier_start(&self, ch: char) -> bool {
33+
ch == '"'
34+
}
35+
36+
/// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/International-Character-Set-Support/Managing-International-Language-Support/Object-Names/Rules-for-Object-Naming>
37+
fn is_identifier_start(&self, ch: char) -> bool {
38+
ch.is_alphabetic() || ch == '_' || ch == '#' || ch == '$'
39+
}
40+
41+
// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Fundamentals/Basic-SQL-Syntax/Object-Names>
42+
fn is_identifier_part(&self, ch: char) -> bool {
43+
ch.is_alphanumeric() || self.is_identifier_start(ch)
44+
}
45+
46+
/// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Manipulation-Language/SELECT-Statements/GROUP-BY-Clause/GROUP-BY-Clause-Syntax>
47+
fn supports_group_by_expr(&self) -> bool {
48+
true
49+
}
50+
51+
/// Teradata has no native `BOOLEAN` data type.
52+
///
53+
/// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals>
54+
fn supports_boolean_literals(&self) -> bool {
55+
false
56+
}
57+
58+
/// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Literals/Interval-Literals>
59+
fn require_interval_qualifier(&self) -> bool {
60+
true
61+
}
62+
63+
/// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Definition-Language-Syntax-and-Examples/Comment-Help-and-Show-Statements/COMMENT-Comment-Placing-Form>
64+
fn supports_comment_on(&self) -> bool {
65+
true
66+
}
67+
68+
/// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Definition-Language-Syntax-and-Examples/Table-Statements/CREATE-TABLE-and-CREATE-TABLE-AS>
69+
fn supports_create_table_select(&self) -> bool {
70+
true
71+
}
72+
73+
/// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Stored-Procedures-and-Embedded-SQL/Dynamic-Embedded-SQL-Statements/Dynamic-SQL-Statement-Syntax/EXECUTE-IMMEDIATE>
74+
fn supports_execute_immediate(&self) -> bool {
75+
true
76+
}
77+
78+
/// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Manipulation-Language/SELECT-Statements/Select-List-Syntax/TOP-Clause>
79+
fn supports_top_before_distinct(&self) -> bool {
80+
true
81+
}
82+
83+
/// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Functions-Expressions-and-Predicates/Ordered-Analytical/Window-Aggregate-Functions>
84+
fn supports_window_function_null_treatment_arg(&self) -> bool {
85+
true
86+
}
87+
88+
/// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Literals/Character-String-Literals>
89+
fn supports_string_literal_concatenation(&self) -> bool {
90+
true
91+
}
92+
}

src/test_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ pub fn all_dialects() -> TestedDialects {
292292
Box::new(DatabricksDialect {}),
293293
Box::new(ClickHouseDialect {}),
294294
Box::new(OracleDialect {}),
295+
Box::new(TeradataDialect {}),
295296
])
296297
}
297298

tests/sqlparser_common.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18180,7 +18180,7 @@ fn test_parse_semantic_view_table_factor() {
1818018180

1818118181
#[test]
1818218182
fn parse_adjacent_string_literal_concatenation() {
18183-
let sql = r#"SELECT 'M' "y" 'S' "q" 'l'"#;
18183+
let sql = r#"SELECT 'M' 'y' 'S' 'q' 'l'"#;
1818418184
let dialects = all_dialects_where(|d| d.supports_string_literal_concatenation());
1818518185
dialects.one_statement_parses_to(sql, r"SELECT 'MySql'");
1818618186

tests/sqlparser_mysql.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4891,3 +4891,9 @@ fn parse_create_database_with_charset_option_ordering() {
48914891
"CREATE DATABASE mydb DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci",
48924892
);
48934893
}
4894+
4895+
#[test]
4896+
fn parse_adjacent_string_literal_concatenation() {
4897+
let sql = r#"SELECT 'M' "y" 'S' "q" 'l'"#;
4898+
mysql().one_statement_parses_to(sql, r"SELECT 'MySql'");
4899+
}

tests/sqlparser_teradata.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//! Test SQL syntax, specific to [sqlparser::dialect::TeradataDialect].
19+
20+
use sqlparser::dialect::{Dialect, TeradataDialect};
21+
use test_utils::TestedDialects;
22+
23+
mod test_utils;
24+
25+
fn teradata() -> TestedDialects {
26+
TestedDialects::new(vec![Box::new(TeradataDialect)])
27+
}
28+
29+
#[test]
30+
fn dialect_methods() {
31+
let d: &dyn Dialect = &TeradataDialect;
32+
assert_eq!(d.identifier_quote_style("x"), Some('"'));
33+
assert!(d.is_delimited_identifier_start('"'));
34+
assert!(!d.is_delimited_identifier_start('`'));
35+
assert!(d.is_identifier_start('$'));
36+
assert!(d.is_identifier_start('#'));
37+
assert!(d.is_identifier_part('$'));
38+
assert!(d.is_identifier_part('#'));
39+
assert!(d.supports_group_by_expr());
40+
assert!(!d.supports_boolean_literals());
41+
assert!(d.require_interval_qualifier());
42+
assert!(d.supports_comment_on());
43+
assert!(d.supports_create_table_select());
44+
assert!(d.supports_execute_immediate());
45+
assert!(d.supports_top_before_distinct());
46+
assert!(d.supports_window_function_null_treatment_arg());
47+
assert!(d.supports_string_literal_concatenation());
48+
}
49+
50+
#[test]
51+
fn parse_identifier() {
52+
teradata().verified_stmt(concat!(
53+
"SELECT ",
54+
"NULL AS foo, ",
55+
"NULL AS _bar, ",
56+
"NULL AS #baz, ",
57+
"NULL AS $qux, ",
58+
"NULL AS a$1, ",
59+
"NULL AS a#1, ",
60+
"NULL AS a_1, ",
61+
r#"NULL AS "quoted id""#
62+
));
63+
}

0 commit comments

Comments
 (0)