datafusion-sqlparse/tests/sqlparser_mssql.rs
2025-06-27 14:21:17 -04:00

2482 lines
87 KiB
Rust

// 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.
#![warn(clippy::all)]
//! Test SQL syntax specific to Microsoft's T-SQL. The parser based on the
//! generic dialect is also tested (on the inputs it can handle).
#[macro_use]
mod test_utils;
use helpers::attached_token::AttachedToken;
use sqlparser::keywords::Keyword;
use sqlparser::tokenizer::{Location, Span, Token, TokenWithSpan, Word};
use test_utils::*;
use sqlparser::ast::DataType::{Int, Text, Varbinary};
use sqlparser::ast::DeclareAssignment::MsSqlAssignment;
use sqlparser::ast::Value::SingleQuotedString;
use sqlparser::ast::*;
use sqlparser::dialect::{GenericDialect, MsSqlDialect};
use sqlparser::parser::{Parser, ParserError};
#[test]
fn parse_mssql_identifiers() {
let sql = "SELECT @@version, _foo$123 FROM ##temp";
let select = ms_and_generic().verified_only_select(sql);
assert_eq!(
&Expr::Identifier(Ident::new("@@version")),
expr_from_projection(&select.projection[0]),
);
assert_eq!(
&Expr::Identifier(Ident::new("_foo$123")),
expr_from_projection(&select.projection[1]),
);
assert_eq!(2, select.projection.len());
match &only(&select.from).relation {
TableFactor::Table { name, .. } => {
assert_eq!("##temp".to_string(), name.to_string());
}
_ => unreachable!(),
};
}
#[test]
fn parse_table_time_travel() {
let version = "2023-08-18 23:08:18".to_string();
let sql = format!("SELECT 1 FROM t1 FOR SYSTEM_TIME AS OF '{version}'");
let select = ms().verified_only_select(&sql);
assert_eq!(
select.from,
vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("t1")]),
alias: None,
args: None,
with_hints: vec![],
version: Some(TableVersion::ForSystemTimeAsOf(Expr::Value(
(Value::SingleQuotedString(version)).with_empty_span()
))),
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![]
},
joins: vec![]
},]
);
let sql = "SELECT 1 FROM t1 FOR SYSTEM TIME AS OF 'some_timestamp'".to_string();
assert!(ms().parse_sql_statements(&sql).is_err());
}
#[test]
fn parse_mssql_single_quoted_aliases() {
let _ = ms_and_generic().one_statement_parses_to("SELECT foo 'alias'", "SELECT foo AS 'alias'");
}
#[test]
fn parse_mssql_delimited_identifiers() {
let _ = ms().one_statement_parses_to(
"SELECT [a.b!] [FROM] FROM foo [WHERE]",
"SELECT [a.b!] AS [FROM] FROM foo AS [WHERE]",
);
}
#[test]
fn parse_create_procedure() {
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()
))],
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(),
}),
params: Some(vec![
ProcedureParam {
name: Ident {
value: "@foo".into(),
quote_style: None,
span: Span::empty(),
},
data_type: DataType::Int(None),
mode: None,
},
ProcedureParam {
name: Ident {
value: "@bar".into(),
quote_style: None,
span: Span::empty(),
},
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().verified_stmt(
"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",
);
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");
// Multiple statements
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test'; SELECT [foo] FROM BAR WHERE [FOO] > 10; END");
}
#[test]
fn parse_create_function() {
let return_expression_function = "CREATE FUNCTION some_scalar_udf(@foo INT, @bar VARCHAR(256)) RETURNS INT AS BEGIN RETURN 1; END";
assert_eq!(
ms().verified_stmt(return_expression_function),
sqlparser::ast::Statement::CreateFunction(CreateFunction {
or_alter: false,
or_replace: false,
temporary: false,
if_not_exists: false,
name: ObjectName::from(vec![Ident::new("some_scalar_udf")]),
args: Some(vec![
OperateFunctionArg {
mode: None,
name: Some(Ident::new("@foo")),
data_type: DataType::Int(None),
default_expr: None,
},
OperateFunctionArg {
mode: None,
name: Some(Ident::new("@bar")),
data_type: DataType::Varchar(Some(CharacterLength::IntegerLength {
length: 256,
unit: None
})),
default_expr: None,
},
]),
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::Value(
(number("1")).with_empty_span()
))),
})],
end_token: AttachedToken::empty(),
})),
behavior: None,
called_on_null: None,
parallel: None,
using: None,
language: None,
determinism_specifier: None,
options: None,
remote_connection: None,
}),
);
let multi_statement_function = "\
CREATE FUNCTION some_scalar_udf(@foo INT, @bar VARCHAR(256)) \
RETURNS INT \
AS \
BEGIN \
SET @foo = @foo + 1; \
RETURN @foo; \
END\
";
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 \
AS \
BEGIN \
IF 1 = 2 \
BEGIN \
RETURN 1; \
END; \
RETURN 0; \
END\
";
let _ = ms().verified_stmt(create_function_with_conditional);
let create_or_alter_function = "\
CREATE OR ALTER FUNCTION some_scalar_udf(@foo INT, @bar VARCHAR(256)) \
RETURNS INT \
AS \
BEGIN \
SET @foo = @foo + 1; \
RETURN @foo; \
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]
fn parse_mssql_apply_join() {
let _ = ms_and_generic().verified_only_select(
"SELECT * FROM sys.dm_exec_query_stats AS deqs \
CROSS APPLY sys.dm_exec_query_plan(deqs.plan_handle)",
);
let _ = ms_and_generic().verified_only_select(
"SELECT * FROM sys.dm_exec_query_stats AS deqs \
OUTER APPLY sys.dm_exec_query_plan(deqs.plan_handle)",
);
let _ = ms_and_generic().verified_only_select(
"SELECT * FROM foo \
OUTER APPLY (SELECT foo.x + 1) AS bar",
);
}
#[test]
fn parse_mssql_openjson() {
let select = ms().verified_only_select(
"SELECT B.kind, B.id_list \
FROM t_test_table AS A \
CROSS APPLY OPENJSON(A.param, '$.config') WITH (kind VARCHAR(20) '$.kind', [id_list] NVARCHAR(MAX) '$.id_list' AS JSON) AS B",
);
assert_eq!(
vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("t_test_table")]),
alias: Some(TableAlias {
name: Ident::new("A"),
columns: vec![]
}),
args: None,
with_hints: vec![],
version: None,
with_ordinality: false,
partitions: vec![],
json_path: None,
sample: None,
index_hints: vec![]
},
joins: vec![Join {
relation: TableFactor::OpenJsonTable {
json_expr: Expr::CompoundIdentifier(
vec![Ident::new("A"), Ident::new("param"),]
),
json_path: Some(Value::SingleQuotedString("$.config".into())),
columns: vec![
OpenJsonTableColumn {
name: Ident::new("kind"),
r#type: DataType::Varchar(Some(CharacterLength::IntegerLength {
length: 20,
unit: None
})),
path: Some("$.kind".into()),
as_json: false
},
OpenJsonTableColumn {
name: Ident {
value: "id_list".into(),
quote_style: Some('['),
span: Span::empty(),
},
r#type: DataType::Nvarchar(Some(CharacterLength::Max)),
path: Some("$.id_list".into()),
as_json: true
}
],
alias: Some(TableAlias {
name: Ident::new("B"),
columns: vec![]
})
},
global: false,
join_operator: JoinOperator::CrossApply
}]
}],
select.from
);
let select = ms().verified_only_select(
"SELECT B.kind, B.id_list \
FROM t_test_table AS A \
CROSS APPLY OPENJSON(A.param) WITH (kind VARCHAR(20) '$.kind', [id_list] NVARCHAR(MAX) '$.id_list' AS JSON) AS B",
);
assert_eq!(
vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("t_test_table"),]),
alias: Some(TableAlias {
name: Ident::new("A"),
columns: vec![]
}),
args: None,
with_hints: vec![],
version: None,
with_ordinality: false,
partitions: vec![],
json_path: None,
sample: None,
index_hints: vec![]
},
joins: vec![Join {
relation: TableFactor::OpenJsonTable {
json_expr: Expr::CompoundIdentifier(
vec![Ident::new("A"), Ident::new("param"),]
),
json_path: None,
columns: vec![
OpenJsonTableColumn {
name: Ident::new("kind"),
r#type: DataType::Varchar(Some(CharacterLength::IntegerLength {
length: 20,
unit: None
})),
path: Some("$.kind".into()),
as_json: false
},
OpenJsonTableColumn {
name: Ident {
value: "id_list".into(),
quote_style: Some('['),
span: Span::empty(),
},
r#type: DataType::Nvarchar(Some(CharacterLength::Max)),
path: Some("$.id_list".into()),
as_json: true
}
],
alias: Some(TableAlias {
name: Ident::new("B"),
columns: vec![]
})
},
global: false,
join_operator: JoinOperator::CrossApply
}]
}],
select.from
);
let select = ms().verified_only_select(
"SELECT B.kind, B.id_list \
FROM t_test_table AS A \
CROSS APPLY OPENJSON(A.param) WITH (kind VARCHAR(20), [id_list] NVARCHAR(MAX)) AS B",
);
assert_eq!(
vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("t_test_table")]),
alias: Some(TableAlias {
name: Ident::new("A"),
columns: vec![]
}),
args: None,
with_hints: vec![],
version: None,
with_ordinality: false,
partitions: vec![],
json_path: None,
sample: None,
index_hints: vec![]
},
joins: vec![Join {
relation: TableFactor::OpenJsonTable {
json_expr: Expr::CompoundIdentifier(
vec![Ident::new("A"), Ident::new("param"),]
),
json_path: None,
columns: vec![
OpenJsonTableColumn {
name: Ident::new("kind"),
r#type: DataType::Varchar(Some(CharacterLength::IntegerLength {
length: 20,
unit: None
})),
path: None,
as_json: false
},
OpenJsonTableColumn {
name: Ident {
value: "id_list".into(),
quote_style: Some('['),
span: Span::empty(),
},
r#type: DataType::Nvarchar(Some(CharacterLength::Max)),
path: None,
as_json: false
}
],
alias: Some(TableAlias {
name: Ident::new("B"),
columns: vec![]
})
},
global: false,
join_operator: JoinOperator::CrossApply
}]
}],
select.from
);
let select = ms_and_generic().verified_only_select(
"SELECT B.kind, B.id_list \
FROM t_test_table AS A \
CROSS APPLY OPENJSON(A.param, '$.config') AS B",
);
assert_eq!(
vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("t_test_table")]),
alias: Some(TableAlias {
name: Ident::new("A"),
columns: vec![]
}),
args: None,
with_hints: vec![],
version: None,
with_ordinality: false,
partitions: vec![],
json_path: None,
sample: None,
index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::OpenJsonTable {
json_expr: Expr::CompoundIdentifier(
vec![Ident::new("A"), Ident::new("param"),]
),
json_path: Some(Value::SingleQuotedString("$.config".into())),
columns: vec![],
alias: Some(TableAlias {
name: Ident::new("B"),
columns: vec![]
})
},
global: false,
join_operator: JoinOperator::CrossApply
}]
}],
select.from
);
let select = ms_and_generic().verified_only_select(
"SELECT B.kind, B.id_list \
FROM t_test_table AS A \
CROSS APPLY OPENJSON(A.param) AS B",
);
assert_eq!(
vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("t_test_table")]),
alias: Some(TableAlias {
name: Ident::new("A"),
columns: vec![]
}),
args: None,
with_hints: vec![],
version: None,
with_ordinality: false,
partitions: vec![],
json_path: None,
sample: None,
index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::OpenJsonTable {
json_expr: Expr::CompoundIdentifier(
vec![Ident::new("A"), Ident::new("param"),]
),
json_path: None,
columns: vec![],
alias: Some(TableAlias {
name: Ident::new("B"),
columns: vec![]
})
},
global: false,
join_operator: JoinOperator::CrossApply
}]
}],
select.from
);
}
#[test]
fn parse_mssql_top_paren() {
let sql = "SELECT TOP (5) * FROM foo";
let select = ms_and_generic().verified_only_select(sql);
let top = select.top.unwrap();
assert_eq!(
Some(TopQuantity::Expr(Expr::Value(
(number("5")).with_empty_span()
))),
top.quantity
);
assert!(!top.percent);
}
#[test]
fn parse_mssql_top_percent() {
let sql = "SELECT TOP (5) PERCENT * FROM foo";
let select = ms_and_generic().verified_only_select(sql);
let top = select.top.unwrap();
assert_eq!(
Some(TopQuantity::Expr(Expr::Value(
(number("5")).with_empty_span()
))),
top.quantity
);
assert!(top.percent);
}
#[test]
fn parse_mssql_top_with_ties() {
let sql = "SELECT TOP (5) WITH TIES * FROM foo";
let select = ms_and_generic().verified_only_select(sql);
let top = select.top.unwrap();
assert_eq!(
Some(TopQuantity::Expr(Expr::Value(
(number("5")).with_empty_span()
))),
top.quantity
);
assert!(top.with_ties);
}
#[test]
fn parse_mssql_top_percent_with_ties() {
let sql = "SELECT TOP (10) PERCENT WITH TIES * FROM foo";
let select = ms_and_generic().verified_only_select(sql);
let top = select.top.unwrap();
assert_eq!(
Some(TopQuantity::Expr(Expr::Value(
(number("10")).with_empty_span()
))),
top.quantity
);
assert!(top.percent);
}
#[test]
fn parse_mssql_top() {
let sql = "SELECT TOP 5 bar, baz FROM foo";
let _ = ms_and_generic().one_statement_parses_to(sql, "SELECT TOP 5 bar, baz FROM foo");
}
#[test]
fn parse_mssql_bin_literal() {
let _ = ms_and_generic().one_statement_parses_to("SELECT 0xdeadBEEF", "SELECT X'deadBEEF'");
}
#[test]
fn parse_mssql_create_role() {
let sql = "CREATE ROLE mssql AUTHORIZATION helena";
match ms().verified_stmt(sql) {
Statement::CreateRole {
names,
authorization_owner,
..
} => {
assert_eq_vec(&["mssql"], &names);
assert_eq!(
authorization_owner,
Some(ObjectName::from(vec![Ident {
value: "helena".into(),
quote_style: None,
span: Span::empty(),
}]))
);
}
_ => unreachable!(),
}
}
#[test]
fn parse_alter_role() {
let sql = "ALTER ROLE old_name WITH NAME = new_name";
assert_eq!(
ms().parse_sql_statements(sql).unwrap(),
[Statement::AlterRole {
name: Ident {
value: "old_name".into(),
quote_style: None,
span: Span::empty(),
},
operation: AlterRoleOperation::RenameRole {
role_name: Ident {
value: "new_name".into(),
quote_style: None,
span: Span::empty(),
}
},
}]
);
let sql = "ALTER ROLE role_name ADD MEMBER new_member";
assert_eq!(
ms().verified_stmt(sql),
Statement::AlterRole {
name: Ident {
value: "role_name".into(),
quote_style: None,
span: Span::empty(),
},
operation: AlterRoleOperation::AddMember {
member_name: Ident {
value: "new_member".into(),
quote_style: None,
span: Span::empty(),
}
},
}
);
let sql = "ALTER ROLE role_name DROP MEMBER old_member";
assert_eq!(
ms().verified_stmt(sql),
Statement::AlterRole {
name: Ident {
value: "role_name".into(),
quote_style: None,
span: Span::empty(),
},
operation: AlterRoleOperation::DropMember {
member_name: Ident {
value: "old_member".into(),
quote_style: None,
span: Span::empty(),
}
},
}
);
}
#[test]
fn parse_delimited_identifiers() {
// check that quoted identifiers in any position remain quoted after serialization
let select = ms_and_generic().verified_only_select(
r#"SELECT "alias"."bar baz", "myfun"(), "simple id" AS "column alias" FROM "a table" AS "alias""#,
);
// check FROM
match only(select.from).relation {
TableFactor::Table {
name,
alias,
args,
with_hints,
version,
..
} => {
assert_eq!(
ObjectName::from(vec![Ident::with_quote('"', "a table")]),
name
);
assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name);
assert!(args.is_none());
assert!(with_hints.is_empty());
assert!(version.is_none());
}
_ => panic!("Expecting TableFactor::Table"),
}
// check SELECT
assert_eq!(3, select.projection.len());
assert_eq!(
&Expr::CompoundIdentifier(vec![
Ident::with_quote('"', "alias"),
Ident::with_quote('"', "bar baz"),
]),
expr_from_projection(&select.projection[0]),
);
assert_eq!(
&Expr::Function(Function {
name: ObjectName::from(vec![Ident::with_quote('"', "myfun")]),
uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![],
clauses: vec![],
}),
null_treatment: None,
filter: None,
over: None,
within_group: vec![],
}),
expr_from_projection(&select.projection[1]),
);
match &select.projection[2] {
SelectItem::ExprWithAlias { expr, alias } => {
assert_eq!(&Expr::Identifier(Ident::with_quote('"', "simple id")), expr);
assert_eq!(&Ident::with_quote('"', "column alias"), alias);
}
_ => panic!("Expected ExprWithAlias"),
}
ms_and_generic().verified_stmt(r#"CREATE TABLE "foo" ("bar" "int")"#);
ms_and_generic().verified_stmt(r#"ALTER TABLE foo ADD CONSTRAINT "bar" PRIMARY KEY (baz)"#);
//TODO verified_stmt(r#"UPDATE foo SET "bar" = 5"#);
}
#[test]
fn parse_table_name_in_square_brackets() {
let select = ms().verified_only_select(r#"SELECT [a column] FROM [a schema].[a table]"#);
if let TableFactor::Table { name, .. } = only(select.from).relation {
assert_eq!(
ObjectName::from(vec![
Ident::with_quote('[', "a schema"),
Ident::with_quote('[', "a table")
]),
name
);
} else {
panic!("Expecting TableFactor::Table");
}
assert_eq!(
&Expr::Identifier(Ident::with_quote('[', "a column")),
expr_from_projection(&select.projection[0]),
);
}
#[test]
fn parse_for_clause() {
ms_and_generic().verified_stmt("SELECT a FROM t FOR JSON PATH");
ms_and_generic().verified_stmt("SELECT b FROM t FOR JSON AUTO");
ms_and_generic().verified_stmt("SELECT c FROM t FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER");
ms_and_generic().verified_stmt("SELECT 1 FROM t FOR JSON PATH, ROOT('x'), INCLUDE_NULL_VALUES");
ms_and_generic().verified_stmt("SELECT 2 FROM t FOR XML AUTO");
ms_and_generic().verified_stmt("SELECT 3 FROM t FOR XML AUTO, TYPE, ELEMENTS");
ms_and_generic().verified_stmt("SELECT * FROM t WHERE x FOR XML AUTO, ELEMENTS");
ms_and_generic().verified_stmt("SELECT x FROM t ORDER BY y FOR XML AUTO, ELEMENTS");
ms_and_generic().verified_stmt("SELECT y FROM t FOR XML PATH('x'), ROOT('y'), ELEMENTS");
ms_and_generic().verified_stmt("SELECT z FROM t FOR XML EXPLICIT, BINARY BASE64");
ms_and_generic().verified_stmt("SELECT * FROM t FOR XML RAW('x')");
ms_and_generic().verified_stmt("SELECT * FROM t FOR BROWSE");
}
#[test]
fn dont_parse_trailing_for() {
assert!(ms()
.run_parser_method("SELECT * FROM foo FOR", |p| p.parse_query())
.is_err());
}
#[test]
fn parse_for_json_expect_ast() {
assert_eq!(
ms().verified_query("SELECT * FROM t FOR JSON PATH, ROOT('root')")
.for_clause
.unwrap(),
ForClause::Json {
for_json: ForJson::Path,
root: Some("root".into()),
without_array_wrapper: false,
include_null_values: false,
}
);
}
#[test]
fn parse_mssql_json_object() {
let select = ms().verified_only_select(
"SELECT JSON_OBJECT('user_name' : USER_NAME(), LOWER(@id_key) : @id_value, 'sid' : (SELECT @@SPID) ABSENT ON NULL)",
);
match expr_from_projection(&select.projection[0]) {
Expr::Function(Function {
args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }),
..
}) => {
assert!(matches!(
args[0],
FunctionArg::ExprNamed {
name: Expr::Value(ValueWithSpan {
value: Value::SingleQuotedString(_),
span: _
}),
arg: FunctionArgExpr::Expr(Expr::Function(_)),
operator: FunctionArgOperator::Colon
}
));
assert!(matches!(
args[1],
FunctionArg::ExprNamed {
name: Expr::Function(_),
arg: FunctionArgExpr::Expr(Expr::Identifier(_)),
operator: FunctionArgOperator::Colon
}
));
assert!(matches!(
args[2],
FunctionArg::ExprNamed {
name: Expr::Value(ValueWithSpan {
value: Value::SingleQuotedString(_),
span: _
}),
arg: FunctionArgExpr::Expr(Expr::Subquery(_)),
operator: FunctionArgOperator::Colon
}
));
assert_eq!(
&[FunctionArgumentClause::JsonNullClause(
JsonNullClause::AbsentOnNull
)],
&clauses[..]
);
}
_ => unreachable!(),
}
let select = ms().verified_only_select(
"SELECT s.session_id, JSON_OBJECT('security_id' : s.security_id, 'login' : s.login_name, 'status' : s.status) AS info \
FROM sys.dm_exec_sessions AS s \
WHERE s.is_user_process = 1",
);
match &select.projection[1] {
SelectItem::ExprWithAlias {
expr:
Expr::Function(Function {
args: FunctionArguments::List(FunctionArgumentList { args, .. }),
..
}),
..
} => {
assert!(matches!(
args[0],
FunctionArg::ExprNamed {
name: Expr::Value(ValueWithSpan {
value: Value::SingleQuotedString(_),
span: _
}),
arg: FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)),
operator: FunctionArgOperator::Colon
}
));
assert!(matches!(
args[1],
FunctionArg::ExprNamed {
name: Expr::Value(ValueWithSpan {
value: Value::SingleQuotedString(_),
span: _
}),
arg: FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)),
operator: FunctionArgOperator::Colon
}
));
assert!(matches!(
args[2],
FunctionArg::ExprNamed {
name: Expr::Value(ValueWithSpan {
value: Value::SingleQuotedString(_),
span: _
}),
arg: FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)),
operator: FunctionArgOperator::Colon
}
));
}
_ => unreachable!(),
}
}
#[test]
fn parse_mssql_json_array() {
let select = ms().verified_only_select("SELECT JSON_ARRAY('a', 1, NULL, 2 NULL ON NULL)");
match expr_from_projection(&select.projection[0]) {
Expr::Function(Function {
args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }),
..
}) => {
assert_eq!(
&[
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
(Value::SingleQuotedString("a".into())).with_empty_span()
))),
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
(number("1")).with_empty_span()
))),
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
(Value::Null).with_empty_span()
))),
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
(number("2")).with_empty_span()
))),
],
&args[..]
);
assert_eq!(
&[FunctionArgumentClause::JsonNullClause(
JsonNullClause::NullOnNull
)],
&clauses[..]
);
}
_ => unreachable!(),
}
let select = ms().verified_only_select("SELECT JSON_ARRAY('a', 1, NULL, 2 ABSENT ON NULL)");
match expr_from_projection(&select.projection[0]) {
Expr::Function(Function {
args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }),
..
}) => {
assert_eq!(
&[
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
(Value::SingleQuotedString("a".into())).with_empty_span()
))),
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
(number("1")).with_empty_span()
))),
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
(Value::Null).with_empty_span()
))),
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
(number("2")).with_empty_span()
))),
],
&args[..]
);
assert_eq!(
&[FunctionArgumentClause::JsonNullClause(
JsonNullClause::AbsentOnNull
)],
&clauses[..]
);
}
_ => unreachable!(),
}
let select = ms().verified_only_select("SELECT JSON_ARRAY(NULL ON NULL)");
match expr_from_projection(&select.projection[0]) {
Expr::Function(Function {
args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }),
..
}) => {
assert!(args.is_empty());
assert_eq!(
&[FunctionArgumentClause::JsonNullClause(
JsonNullClause::NullOnNull
)],
&clauses[..]
);
}
_ => unreachable!(),
}
let select = ms().verified_only_select("SELECT JSON_ARRAY(ABSENT ON NULL)");
match expr_from_projection(&select.projection[0]) {
Expr::Function(Function {
args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }),
..
}) => {
assert!(args.is_empty());
assert_eq!(
&[FunctionArgumentClause::JsonNullClause(
JsonNullClause::AbsentOnNull
)],
&clauses[..]
);
}
_ => unreachable!(),
}
let select = ms().verified_only_select(
"SELECT JSON_ARRAY('a', JSON_OBJECT('name' : 'value', 'type' : 1) NULL ON NULL)",
);
match expr_from_projection(&select.projection[0]) {
Expr::Function(Function {
args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }),
..
}) => {
assert_eq!(
&FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
(Value::SingleQuotedString("a".into())).with_empty_span()
))),
&args[0]
);
assert!(matches!(
args[1],
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(_)))
));
assert_eq!(
&[FunctionArgumentClause::JsonNullClause(
JsonNullClause::NullOnNull
)],
&clauses[..]
);
}
_ => unreachable!(),
}
let select = ms().verified_only_select(
"SELECT JSON_ARRAY('a', JSON_OBJECT('name' : 'value', 'type' : 1), JSON_ARRAY(1, NULL, 2 NULL ON NULL))",
);
match expr_from_projection(&select.projection[0]) {
Expr::Function(Function {
args: FunctionArguments::List(FunctionArgumentList { args, .. }),
..
}) => {
assert_eq!(
&FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
(Value::SingleQuotedString("a".into())).with_empty_span()
))),
&args[0]
);
assert!(matches!(
args[1],
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(_)))
));
assert!(matches!(
args[2],
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(_)))
));
}
_ => unreachable!(),
}
let select = ms().verified_only_select("SELECT JSON_ARRAY(1, @id_value, (SELECT @@SPID))");
match expr_from_projection(&select.projection[0]) {
Expr::Function(Function {
args: FunctionArguments::List(FunctionArgumentList { args, .. }),
..
}) => {
assert_eq!(
&FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
(number("1")).with_empty_span()
))),
&args[0]
);
assert!(matches!(
args[1],
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Identifier(_)))
));
assert!(matches!(
args[2],
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Subquery(_)))
));
}
_ => unreachable!(),
}
let select = ms().verified_only_select(
"SELECT s.session_id, JSON_ARRAY(s.host_name, s.program_name, s.client_interface_name NULL ON NULL) AS info \
FROM sys.dm_exec_sessions AS s \
WHERE s.is_user_process = 1",
);
match &select.projection[1] {
SelectItem::ExprWithAlias {
expr:
Expr::Function(Function {
args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }),
..
}),
..
} => {
assert!(matches!(
args[0],
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)))
));
assert!(matches!(
args[1],
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)))
));
assert!(matches!(
args[2],
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)))
));
assert_eq!(
&[FunctionArgumentClause::JsonNullClause(
JsonNullClause::NullOnNull
)],
&clauses[..]
);
}
_ => unreachable!(),
}
}
#[test]
fn parse_ampersand_arobase() {
// In SQL Server, a&@b means (a) & (@b), in PostgreSQL it means (a) &@ (b)
ms().expr_parses_to("a&@b", "a & @b");
}
#[test]
fn parse_cast_varchar_max() {
ms_and_generic().verified_expr("CAST('foo' AS VARCHAR(MAX))");
ms_and_generic().verified_expr("CAST('foo' AS NVARCHAR(MAX))");
}
#[test]
fn parse_convert() {
let sql = "CONVERT(INT, 1, 2, 3, NULL)";
let Expr::Convert {
is_try,
expr,
data_type,
charset,
target_before_value,
styles,
} = ms().verified_expr(sql)
else {
unreachable!()
};
assert!(!is_try);
assert_eq!(Expr::value(number("1")), *expr);
assert_eq!(Some(DataType::Int(None)), data_type);
assert!(charset.is_none());
assert!(target_before_value);
assert_eq!(
vec![
Expr::value(number("2")),
Expr::value(number("3")),
Expr::Value((Value::Null).with_empty_span()),
],
styles
);
ms().verified_expr("CONVERT(VARCHAR(MAX), 'foo')");
ms().verified_expr("CONVERT(VARCHAR(10), 'foo')");
ms().verified_expr("CONVERT(DECIMAL(10,5), 12.55)");
let error_sql = "SELECT CONVERT(INT, 'foo',) FROM T";
assert_eq!(
ParserError::ParserError("Expected: an expression, found: )".to_owned()),
ms().parse_sql_statements(error_sql).unwrap_err()
);
}
#[test]
fn parse_substring_in_select() {
let sql = "SELECT DISTINCT SUBSTRING(description, 0, 1) FROM test";
match ms().one_statement_parses_to(
sql,
"SELECT DISTINCT SUBSTRING(description, 0, 1) FROM test",
) {
Statement::Query(query) => {
assert_eq!(
Box::new(Query {
with: None,
body: Box::new(SetExpr::Select(Box::new(Select {
select_token: AttachedToken::empty(),
distinct: Some(Distinct::Distinct),
top: None,
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Substring {
expr: Box::new(Expr::Identifier(Ident {
value: "description".to_string(),
quote_style: None,
span: Span::empty(),
})),
substring_from: Some(Box::new(Expr::Value(
(number("0")).with_empty_span()
))),
substring_for: Some(Box::new(Expr::Value(
(number("1")).with_empty_span()
))),
special: true,
shorthand: false,
})],
into: None,
from: vec![TableWithJoins {
relation: table_from_name(ObjectName::from(vec![Ident {
value: "test".to_string(),
quote_style: None,
span: Span::empty(),
}])),
joins: 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![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
flavor: SelectFlavor::Standard,
}))),
order_by: None,
limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
settings: None,
format_clause: None,
pipe_operators: vec![],
}),
query
);
}
_ => unreachable!(),
}
}
#[test]
fn parse_mssql_declare() {
let sql = "DECLARE @foo CURSOR, @bar INT, @baz AS TEXT = 'foobar';";
let ast = ms().parse_sql_statements(sql).unwrap();
assert_eq!(
vec![Statement::Declare {
stmts: vec![
Declare {
names: vec![Ident {
value: "@foo".to_string(),
quote_style: None,
span: Span::empty(),
}],
data_type: None,
assignment: None,
declare_type: Some(DeclareType::Cursor),
binary: None,
sensitive: None,
scroll: None,
hold: None,
for_query: None
},
Declare {
names: vec![Ident {
value: "@bar".to_string(),
quote_style: None,
span: Span::empty(),
}],
data_type: Some(Int(None)),
assignment: None,
declare_type: None,
binary: None,
sensitive: None,
scroll: None,
hold: None,
for_query: None
},
Declare {
names: vec![Ident {
value: "@baz".to_string(),
quote_style: None,
span: Span::empty(),
}],
data_type: Some(Text),
assignment: Some(MsSqlAssignment(Box::new(Expr::Value(
(SingleQuotedString("foobar".to_string())).with_empty_span()
)))),
declare_type: None,
binary: None,
sensitive: None,
scroll: None,
hold: None,
for_query: None
}
]
}],
ast
);
let sql = "DECLARE @bar INT;SET @bar = 2;SELECT @bar * 4";
let ast = ms().parse_sql_statements(sql).unwrap();
assert_eq!(
vec![
Statement::Declare {
stmts: vec![Declare {
names: vec![Ident::new("@bar"),],
data_type: Some(Int(None)),
assignment: None,
declare_type: None,
binary: None,
sensitive: None,
scroll: None,
hold: None,
for_query: None
}]
},
Statement::Set(Set::SingleAssignment {
scope: None,
hivevar: false,
variable: ObjectName::from(vec![Ident::new("@bar")]),
values: vec![Expr::Value(
(Value::Number("2".parse().unwrap(), false)).with_empty_span()
)],
}),
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::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("@bar"))),
op: BinaryOperator::Multiply,
right: Box::new(Expr::Value(
(Value::Number("4".parse().unwrap(), false)).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,
})))
}))
],
ast
);
let declare_cursor_for_select =
"DECLARE vend_cursor CURSOR FOR SELECT * FROM Purchasing.Vendor";
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)"#;
let s = ms().verified_stmt(sql);
assert_eq!(
s,
Statement::RaisError {
message: Box::new(Expr::Value(
(Value::SingleQuotedString("This is a test".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("1".parse().unwrap(), false)).with_empty_span()
)),
arguments: vec![],
options: vec![],
}
);
let sql = r#"RAISERROR('This is a test', 16, 1) WITH NOWAIT"#;
let _ = ms().verified_stmt(sql);
let sql = r#"RAISERROR('This is a test', 16, 1, 'ARG') WITH SETERROR, LOG"#;
let _ = ms().verified_stmt(sql);
let sql = r#"RAISERROR(N'This is message %s %d.', 10, 1, N'number', 5)"#;
let _ = ms().verified_stmt(sql);
let sql = r#"RAISERROR(N'<<%*.*s>>', 10, 1, 7, 3, N'abcde')"#;
let _ = ms().verified_stmt(sql);
let sql = r#"RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState)"#;
let _ = ms().verified_stmt(sql);
}
#[test]
fn parse_use() {
let valid_object_names = [
"mydb",
"SCHEMA",
"DATABASE",
"CATALOG",
"WAREHOUSE",
"DEFAULT",
];
let quote_styles = ['\'', '"'];
for object_name in &valid_object_names {
// Test single identifier without quotes
assert_eq!(
ms().verified_stmt(&format!("USE {object_name}")),
Statement::Use(Use::Object(ObjectName::from(vec![Ident::new(
object_name.to_string()
)])))
);
for &quote in &quote_styles {
// Test single identifier with different type of quotes
assert_eq!(
ms().verified_stmt(&format!("USE {quote}{object_name}{quote}")),
Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote(
quote,
object_name.to_string(),
)])))
);
}
}
}
#[test]
fn parse_create_table_with_valid_options() {
let options = [
(
"CREATE TABLE mytable (column_a INT, column_b INT, column_c INT) WITH (DISTRIBUTION = ROUND_ROBIN, PARTITION (column_a RANGE FOR VALUES (10, 11)))",
vec![
SqlOption::KeyValue {
key: Ident {
value: "DISTRIBUTION".to_string(),
quote_style: None,
span: Span::empty(),
},
value: Expr::Identifier(Ident {
value: "ROUND_ROBIN".to_string(),
quote_style: None,
span: Span::empty(),
})
},
SqlOption::Partition {
column_name: "column_a".into(),
range_direction: None,
for_values: vec![Expr::Value((test_utils::number("10")).with_empty_span()), Expr::Value((test_utils::number("11")).with_empty_span())] ,
},
],
),
(
"CREATE TABLE mytable (column_a INT, column_b INT, column_c INT) WITH (PARTITION (column_a RANGE LEFT FOR VALUES (10, 11)))",
vec![
SqlOption::Partition {
column_name: "column_a".into(),
range_direction: Some(PartitionRangeDirection::Left),
for_values: vec![
Expr::Value((test_utils::number("10")).with_empty_span()),
Expr::Value((test_utils::number("11")).with_empty_span()),
],
}
],
),
(
"CREATE TABLE mytable (column_a INT, column_b INT, column_c INT) WITH (CLUSTERED COLUMNSTORE INDEX)",
vec![SqlOption::Clustered(TableOptionsClustered::ColumnstoreIndex)],
),
(
"CREATE TABLE mytable (column_a INT, column_b INT, column_c INT) WITH (CLUSTERED COLUMNSTORE INDEX ORDER (column_a, column_b))",
vec![
SqlOption::Clustered(TableOptionsClustered::ColumnstoreIndexOrder(vec![
"column_a".into(),
"column_b".into(),
]))
],
),
(
"CREATE TABLE mytable (column_a INT, column_b INT, column_c INT) WITH (CLUSTERED INDEX (column_a ASC, column_b DESC, column_c))",
vec![
SqlOption::Clustered(TableOptionsClustered::Index(vec![
ClusteredIndex {
name: Ident {
value: "column_a".to_string(),
quote_style: None,
span: Span::empty(),
},
asc: Some(true),
},
ClusteredIndex {
name: Ident {
value: "column_b".to_string(),
quote_style: None,
span: Span::empty(),
},
asc: Some(false),
},
ClusteredIndex {
name: Ident {
value: "column_c".to_string(),
quote_style: None,
span: Span::empty(),
},
asc: None,
},
]))
],
),
(
"CREATE TABLE mytable (column_a INT, column_b INT, column_c INT) WITH (DISTRIBUTION = HASH(column_a, column_b), HEAP)",
vec![
SqlOption::KeyValue {
key: Ident {
value: "DISTRIBUTION".to_string(),
quote_style: None,
span: Span::empty(),
},
value: Expr::Function(
Function {
name: ObjectName::from(
vec![
Ident {
value: "HASH".to_string(),
quote_style: None,
span: Span::empty(),
},
],
),
uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(
FunctionArgumentList {
duplicate_treatment: None,
args: vec![
FunctionArg::Unnamed(
FunctionArgExpr::Expr(
Expr::Identifier(
Ident {
value: "column_a".to_string(),
quote_style: None,
span: Span::empty(),
},
),
),
),
FunctionArg::Unnamed(
FunctionArgExpr::Expr(
Expr::Identifier(
Ident {
value: "column_b".to_string(),
quote_style: None,
span: Span::empty(),
},
),
),
),
],
clauses: vec![],
},
),
filter: None,
null_treatment: None,
over: None,
within_group: vec![],
},
),
},
SqlOption::Ident("HEAP".into()),
],
),
];
for (sql, with_options) in options {
assert_eq!(
ms_and_generic().verified_stmt(sql),
Statement::CreateTable(CreateTable {
or_replace: false,
temporary: false,
external: false,
global: None,
if_not_exists: false,
transient: false,
volatile: false,
name: ObjectName::from(vec![Ident {
value: "mytable".to_string(),
quote_style: None,
span: Span::empty(),
},],),
columns: vec![
ColumnDef {
name: Ident {
value: "column_a".to_string(),
quote_style: None,
span: Span::empty(),
},
data_type: Int(None,),
options: vec![],
},
ColumnDef {
name: Ident {
value: "column_b".to_string(),
quote_style: None,
span: Span::empty(),
},
data_type: Int(None,),
options: vec![],
},
ColumnDef {
name: Ident {
value: "column_c".to_string(),
quote_style: None,
span: Span::empty(),
},
data_type: Int(None,),
options: vec![],
},
],
constraints: vec![],
hive_distribution: HiveDistributionStyle::NONE,
hive_formats: Some(HiveFormat {
row_format: None,
serde_properties: None,
storage: None,
location: None,
},),
file_format: None,
location: None,
query: None,
without_rowid: false,
like: None,
clone: None,
comment: None,
on_commit: None,
on_cluster: None,
primary_key: None,
order_by: None,
partition_by: None,
cluster_by: None,
clustered_by: None,
inherits: None,
strict: false,
iceberg: false,
copy_grants: false,
enable_schema_evolution: None,
change_tracking: None,
data_retention_time_in_days: None,
max_data_extension_time_in_days: None,
default_ddl_collation: None,
with_aggregation_policy: None,
with_row_access_policy: None,
with_tags: None,
base_location: None,
external_volume: None,
catalog: None,
catalog_sync: None,
storage_serialization_policy: None,
table_options: CreateTableOptions::With(with_options)
})
);
}
}
#[test]
fn parse_nested_slash_star_comment() {
let sql = r#"
select
/*
comment level 1
/*
comment level 2
*/
*/
1;
"#;
let canonical = "SELECT 1";
ms().one_statement_parses_to(sql, canonical);
}
#[test]
fn parse_create_table_with_invalid_options() {
let invalid_cases = vec![
(
"CREATE TABLE mytable (column_a INT, column_b INT, column_c INT) WITH (CLUSTERED COLUMNSTORE INDEX ORDER ())",
"Expected: identifier, found: )",
),
(
"CREATE TABLE mytable (column_a INT, column_b INT, column_c INT) WITH (CLUSTERED COLUMNSTORE)",
"invalid CLUSTERED sequence",
),
(
"CREATE TABLE mytable (column_a INT, column_b INT, column_c INT) WITH (HEAP INDEX)",
"Expected: ), found: INDEX",
),
(
"CREATE TABLE mytable (column_a INT, column_b INT, column_c INT) WITH (PARTITION (RANGE LEFT FOR VALUES (10, 11)))",
"Expected: RANGE, found: LEFT",
),
];
for (sql, expected_error) in invalid_cases {
let res = ms_and_generic().parse_sql_statements(sql);
assert_eq!(
format!("sql parser error: {expected_error}"),
res.unwrap_err().to_string()
);
}
}
#[test]
fn parse_create_table_with_identity_column() {
let with_column_options = [
(
r#"CREATE TABLE mytable (columnA INT IDENTITY NOT NULL)"#,
vec![
ColumnOptionDef {
name: None,
option: ColumnOption::Identity(IdentityPropertyKind::Identity(
IdentityProperty {
parameters: None,
order: None,
},
)),
},
ColumnOptionDef {
name: None,
option: ColumnOption::NotNull,
},
],
),
(
r#"CREATE TABLE mytable (columnA INT IDENTITY(1, 1) NOT NULL)"#,
vec![
ColumnOptionDef {
name: None,
option: ColumnOption::Identity(IdentityPropertyKind::Identity(
IdentityProperty {
parameters: Some(IdentityPropertyFormatKind::FunctionCall(
IdentityParameters {
seed: Expr::value(number("1")),
increment: Expr::value(number("1")),
},
)),
order: None,
},
)),
},
ColumnOptionDef {
name: None,
option: ColumnOption::NotNull,
},
],
),
];
for (sql, column_options) in with_column_options {
assert_eq!(
ms_and_generic().verified_stmt(sql),
Statement::CreateTable(CreateTable {
or_replace: false,
temporary: false,
external: false,
global: None,
if_not_exists: false,
transient: false,
volatile: false,
iceberg: false,
name: ObjectName::from(vec![Ident {
value: "mytable".to_string(),
quote_style: None,
span: Span::empty(),
},],),
columns: vec![ColumnDef {
name: Ident {
value: "columnA".to_string(),
quote_style: None,
span: Span::empty(),
},
data_type: Int(None,),
options: column_options,
},],
constraints: vec![],
hive_distribution: HiveDistributionStyle::NONE,
hive_formats: Some(HiveFormat {
row_format: None,
serde_properties: None,
storage: None,
location: None,
},),
file_format: None,
location: None,
query: None,
without_rowid: false,
like: None,
clone: None,
comment: None,
on_commit: None,
on_cluster: None,
primary_key: None,
order_by: None,
partition_by: None,
cluster_by: None,
clustered_by: None,
inherits: None,
strict: false,
copy_grants: false,
enable_schema_evolution: None,
change_tracking: None,
data_retention_time_in_days: None,
max_data_extension_time_in_days: None,
default_ddl_collation: None,
with_aggregation_policy: None,
with_row_access_policy: None,
with_tags: None,
base_location: None,
external_volume: None,
catalog: None,
catalog_sync: None,
storage_serialization_policy: None,
table_options: CreateTableOptions::None
}),
);
}
}
#[test]
fn parse_true_false_as_identifiers() {
assert_eq!(
ms().verified_expr("true"),
Expr::Identifier(Ident::new("true"))
);
assert_eq!(
ms().verified_expr("false"),
Expr::Identifier(Ident::new("false"))
);
}
#[test]
fn parse_mssql_set_session_value() {
ms().verified_stmt(
"SET OFFSETS SELECT, FROM, ORDER, TABLE, PROCEDURE, STATEMENT, PARAM, EXECUTE ON",
);
ms().verified_stmt("SET IDENTITY_INSERT dbo.Tool ON");
ms().verified_stmt("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED");
ms().verified_stmt("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
ms().verified_stmt("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ");
ms().verified_stmt("SET TRANSACTION ISOLATION LEVEL SNAPSHOT");
ms().verified_stmt("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
ms().verified_stmt("SET STATISTICS IO ON");
ms().verified_stmt("SET STATISTICS XML ON");
ms().verified_stmt("SET STATISTICS PROFILE ON");
ms().verified_stmt("SET STATISTICS TIME ON");
ms().verified_stmt("SET DATEFIRST 7");
ms().verified_stmt("SET DATEFIRST @xxx");
ms().verified_stmt("SET DATEFIRST @@xxx");
ms().verified_stmt("SET DATEFORMAT dmy");
ms().verified_stmt("SET DATEFORMAT @datevar");
ms().verified_stmt("SET DATEFORMAT @@datevar");
ms().verified_stmt("SET DEADLOCK_PRIORITY 'LOW'");
ms().verified_stmt("SET DEADLOCK_PRIORITY LOW");
ms().verified_stmt("SET DEADLOCK_PRIORITY 8");
ms().verified_stmt("SET DEADLOCK_PRIORITY -8");
ms().verified_stmt("SET DEADLOCK_PRIORITY @xxx");
ms().verified_stmt("SET DEADLOCK_PRIORITY @@xxx");
ms().verified_stmt("SET LOCK_TIMEOUT 1800");
ms().verified_stmt("SET CONCAT_NULL_YIELDS_NULL ON");
ms().verified_stmt("SET CURSOR_CLOSE_ON_COMMIT ON");
ms().verified_stmt("SET FIPS_FLAGGER 'level'");
ms().verified_stmt("SET FIPS_FLAGGER OFF");
ms().verified_stmt("SET LANGUAGE Italian");
ms().verified_stmt("SET QUOTED_IDENTIFIER ON");
ms().verified_stmt("SET ARITHABORT ON");
ms().verified_stmt("SET ARITHIGNORE OFF");
ms().verified_stmt("SET FMTONLY ON");
ms().verified_stmt("SET NOCOUNT OFF");
ms().verified_stmt("SET NOEXEC ON");
ms().verified_stmt("SET NUMERIC_ROUNDABORT ON");
ms().verified_stmt("SET QUERY_GOVERNOR_COST_LIMIT 11");
ms().verified_stmt("SET ROWCOUNT 4");
ms().verified_stmt("SET ROWCOUNT @xxx");
ms().verified_stmt("SET ROWCOUNT @@xxx");
ms().verified_stmt("SET TEXTSIZE 11");
ms().verified_stmt("SET ANSI_DEFAULTS ON");
ms().verified_stmt("SET ANSI_NULL_DFLT_OFF ON");
ms().verified_stmt("SET ANSI_NULL_DFLT_ON ON");
ms().verified_stmt("SET ANSI_NULLS ON");
ms().verified_stmt("SET ANSI_PADDING ON");
ms().verified_stmt("SET ANSI_WARNINGS ON");
ms().verified_stmt("SET FORCEPLAN ON");
ms().verified_stmt("SET SHOWPLAN_ALL ON");
ms().verified_stmt("SET SHOWPLAN_TEXT ON");
ms().verified_stmt("SET SHOWPLAN_XML ON");
ms().verified_stmt("SET IMPLICIT_TRANSACTIONS ON");
ms().verified_stmt("SET REMOTE_PROC_TRANSACTIONS ON");
ms().verified_stmt("SET XACT_ABORT ON");
ms().verified_stmt("SET ANSI_NULLS, ANSI_PADDING ON");
}
#[test]
fn parse_mssql_if_else() {
// Simple statements and blocks
ms().verified_stmt("IF 1 = 1 SELECT '1'; ELSE SELECT '2';");
ms().verified_stmt("IF 1 = 1 BEGIN SET @A = 1; END ELSE SET @A = 2;");
ms().verified_stmt(
"IF DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday') SELECT 'Weekend'; ELSE SELECT 'Weekday';"
);
ms().verified_stmt(
"IF (SELECT COUNT(*) FROM a.b WHERE c LIKE 'x%') > 1 SELECT 'yes'; ELSE SELECT 'No';",
);
// Multiple statements
let stmts = ms()
.parse_sql_statements("DECLARE @A INT; IF 1=1 BEGIN SET @A = 1 END ELSE SET @A = 2")
.unwrap();
match &stmts[..] {
[Statement::Declare { .. }, Statement::If(stmt)] => {
assert_eq!(
stmt.to_string(),
"IF 1 = 1 BEGIN SET @A = 1; END ELSE SET @A = 2;"
);
}
_ => panic!("Unexpected statements: {stmts:?}"),
}
}
#[test]
fn test_mssql_if_else_span() {
let sql = "IF 1 = 1 SELECT '1' ELSE SELECT '2'";
let mut parser = Parser::new(&MsSqlDialect {}).try_with_sql(sql).unwrap();
assert_eq!(
parser.parse_statement().unwrap().span(),
Span::new(Location::new(1, 1), Location::new(1, sql.len() as u64 + 1))
);
}
#[test]
fn test_mssql_if_else_multiline_span() {
let sql_line1 = "IF 1 = 1";
let sql_line2 = "SELECT '1'";
let sql_line3 = "ELSE SELECT '2'";
let sql = [sql_line1, sql_line2, sql_line3].join("\n");
let mut parser = Parser::new(&MsSqlDialect {}).try_with_sql(&sql).unwrap();
assert_eq!(
parser.parse_statement().unwrap().span(),
Span::new(
Location::new(1, 1),
Location::new(3, sql_line3.len() as u64 + 1)
)
);
}
#[test]
fn test_mssql_if_statements_span() {
// Simple statements
let mut sql = "IF 1 = 1 SELECT '1' ELSE SELECT '2'";
let mut parser = Parser::new(&MsSqlDialect {}).try_with_sql(sql).unwrap();
match parser.parse_statement().unwrap() {
Statement::If(IfStatement {
if_block,
else_block: Some(else_block),
..
}) => {
assert_eq!(
if_block.span(),
Span::new(Location::new(1, 1), Location::new(1, 20))
);
assert_eq!(
else_block.span(),
Span::new(Location::new(1, 21), Location::new(1, 36))
);
}
stmt => panic!("Unexpected statement: {stmt:?}"),
}
// Blocks
sql = "IF 1 = 1 BEGIN SET @A = 1; END ELSE BEGIN SET @A = 2 END";
parser = Parser::new(&MsSqlDialect {}).try_with_sql(sql).unwrap();
match parser.parse_statement().unwrap() {
Statement::If(IfStatement {
if_block,
else_block: Some(else_block),
..
}) => {
assert_eq!(
if_block.span(),
Span::new(Location::new(1, 1), Location::new(1, 31))
);
assert_eq!(
else_block.span(),
Span::new(Location::new(1, 32), Location::new(1, 57))
);
}
stmt => panic!("Unexpected statement: {stmt:?}"),
}
}
#[test]
fn parse_mssql_varbinary_max_length() {
let sql = "CREATE TABLE example (var_binary_col VARBINARY(MAX))";
match ms_and_generic().verified_stmt(sql) {
Statement::CreateTable(CreateTable { name, columns, .. }) => {
assert_eq!(
name,
ObjectName::from(vec![Ident {
value: "example".to_string(),
quote_style: None,
span: Span::empty(),
}])
);
assert_eq!(
columns,
vec![ColumnDef {
name: Ident::new("var_binary_col"),
data_type: Varbinary(Some(BinaryLength::Max)),
options: vec![]
},],
);
}
_ => unreachable!(),
}
let sql = "CREATE TABLE example (var_binary_col VARBINARY(50))";
match ms_and_generic().verified_stmt(sql) {
Statement::CreateTable(CreateTable { name, columns, .. }) => {
assert_eq!(
name,
ObjectName::from(vec![Ident {
value: "example".to_string(),
quote_style: None,
span: Span::empty(),
}])
);
assert_eq!(
columns,
vec![ColumnDef {
name: Ident::new("var_binary_col"),
data_type: Varbinary(Some(BinaryLength::IntegerLength { length: 50 })),
options: vec![]
},],
);
}
_ => unreachable!(),
}
}
#[test]
fn parse_mssql_table_identifier_with_default_schema() {
ms().verified_stmt("SELECT * FROM mydatabase..MyTable");
}
fn ms() -> TestedDialects {
TestedDialects::new(vec![Box::new(MsSqlDialect {})])
}
fn ms_and_generic() -> TestedDialects {
TestedDialects::new(vec![Box::new(MsSqlDialect {}), Box::new(GenericDialect {})])
}
#[test]
fn parse_mssql_merge_with_output() {
let stmt = "MERGE dso.products AS t \
USING dsi.products AS \
s ON s.ProductID = t.ProductID \
WHEN MATCHED AND \
NOT (t.ProductName = s.ProductName OR (ISNULL(t.ProductName, s.ProductName) IS NULL)) \
THEN UPDATE SET t.ProductName = s.ProductName \
WHEN NOT MATCHED BY TARGET \
THEN INSERT (ProductID, ProductName) \
VALUES (s.ProductID, s.ProductName) \
WHEN NOT MATCHED BY SOURCE THEN DELETE \
OUTPUT $action, deleted.ProductID INTO dsi.temp_products";
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;";
let drop_stmt = ms().one_statement_parses_to(sql_drop_trigger, "");
assert_eq!(
drop_stmt,
Statement::DropTrigger {
if_exists: false,
trigger_name: ObjectName::from(vec![Ident::new("emp_stamp")]),
table_name: None,
option: None,
}
);
}
#[test]
fn parse_print() {
let print_string_literal = "PRINT 'Hello, world!'";
let print_stmt = ms().verified_stmt(print_string_literal);
assert_eq!(
print_stmt,
Statement::Print(PrintStatement {
message: Box::new(Expr::Value(
(Value::SingleQuotedString("Hello, world!".to_string())).with_empty_span()
)),
})
);
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");
}