Skip to content

Commit bc2c4e2

Browse files
authored
Support optional semicolon between statements (#1937)
1 parent ee31b64 commit bc2c4e2

File tree

5 files changed

+59
-3
lines changed

5 files changed

+59
-3
lines changed

src/parser/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,17 @@ pub struct ParserOptions {
222222
/// Controls how literal values are unescaped. See
223223
/// [`Tokenizer::with_unescape`] for more details.
224224
pub unescape: bool,
225+
/// Controls if the parser expects a semi-colon token
226+
/// between statements. Default is `true`.
227+
pub require_semicolon_stmt_delimiter: bool,
225228
}
226229

227230
impl Default for ParserOptions {
228231
fn default() -> Self {
229232
Self {
230233
trailing_commas: false,
231234
unescape: true,
235+
require_semicolon_stmt_delimiter: true,
232236
}
233237
}
234238
}
@@ -467,6 +471,10 @@ impl<'a> Parser<'a> {
467471
expecting_statement_delimiter = false;
468472
}
469473

474+
if !self.options.require_semicolon_stmt_delimiter {
475+
expecting_statement_delimiter = false;
476+
}
477+
470478
match self.peek_token().token {
471479
Token::EOF => break,
472480

src/test_utils.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,11 @@ pub fn all_dialects() -> TestedDialects {
294294
])
295295
}
296296

297+
// Returns all available dialects with the specified parser options
298+
pub fn all_dialects_with_options(options: ParserOptions) -> TestedDialects {
299+
TestedDialects::new_with_options(all_dialects().dialects, options)
300+
}
301+
297302
/// Returns all dialects matching the given predicate.
298303
pub fn all_dialects_where<F>(predicate: F) -> TestedDialects
299304
where

tests/sqlparser_common.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ use sqlparser::parser::{Parser, ParserError, ParserOptions};
4040
use sqlparser::tokenizer::Tokenizer;
4141
use sqlparser::tokenizer::{Location, Span};
4242
use test_utils::{
43-
all_dialects, all_dialects_where, alter_table_op, assert_eq_vec, call, expr_from_projection,
44-
join, number, only, table, table_alias, table_from_name, TestedDialects,
43+
all_dialects, all_dialects_where, all_dialects_with_options, alter_table_op, assert_eq_vec,
44+
call, expr_from_projection, join, number, only, table, table_alias, table_from_name,
45+
TestedDialects,
4546
};
4647

4748
#[macro_use]
@@ -16115,3 +16116,20 @@ fn test_select_exclude() {
1611516116
ParserError::ParserError("Expected: end of statement, found: EXCLUDE".to_string())
1611616117
);
1611716118
}
16119+
16120+
#[test]
16121+
fn test_no_semicolon_required_between_statements() {
16122+
let sql = r#"
16123+
SELECT * FROM tbl1
16124+
SELECT * FROM tbl2
16125+
"#;
16126+
16127+
let dialects = all_dialects_with_options(ParserOptions {
16128+
trailing_commas: false,
16129+
unescape: true,
16130+
require_semicolon_stmt_delimiter: false,
16131+
});
16132+
let stmts = dialects.parse_sql_statements(sql).unwrap();
16133+
assert_eq!(stmts.len(), 2);
16134+
assert!(stmts.iter().all(|s| matches!(s, Statement::Query { .. })));
16135+
}

tests/sqlparser_mssql.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use sqlparser::ast::DeclareAssignment::MsSqlAssignment;
3232
use sqlparser::ast::Value::SingleQuotedString;
3333
use sqlparser::ast::*;
3434
use sqlparser::dialect::{GenericDialect, MsSqlDialect};
35-
use sqlparser::parser::{Parser, ParserError};
35+
use sqlparser::parser::{Parser, ParserError, ParserOptions};
3636

3737
#[test]
3838
fn parse_mssql_identifiers() {
@@ -2327,6 +2327,18 @@ fn ms() -> TestedDialects {
23272327
TestedDialects::new(vec![Box::new(MsSqlDialect {})])
23282328
}
23292329

2330+
// MS SQL dialect with support for optional semi-colon statement delimiters
2331+
fn tsql() -> TestedDialects {
2332+
TestedDialects::new_with_options(
2333+
vec![Box::new(MsSqlDialect {})],
2334+
ParserOptions {
2335+
trailing_commas: false,
2336+
unescape: true,
2337+
require_semicolon_stmt_delimiter: false,
2338+
},
2339+
)
2340+
}
2341+
23302342
fn ms_and_generic() -> TestedDialects {
23312343
TestedDialects::new(vec![Box::new(MsSqlDialect {}), Box::new(GenericDialect {})])
23322344
}
@@ -2483,3 +2495,15 @@ fn parse_mssql_grant() {
24832495
fn parse_mssql_deny() {
24842496
ms().verified_stmt("DENY SELECT ON my_table TO public, db_admin");
24852497
}
2498+
2499+
#[test]
2500+
fn test_tsql_no_semicolon_delimiter() {
2501+
let sql = r#"
2502+
DECLARE @X AS NVARCHAR(MAX)='x'
2503+
DECLARE @Y AS NVARCHAR(MAX)='y'
2504+
"#;
2505+
2506+
let stmts = tsql().parse_sql_statements(sql).unwrap();
2507+
assert_eq!(stmts.len(), 2);
2508+
assert!(stmts.iter().all(|s| matches!(s, Statement::Declare { .. })));
2509+
}

tests/sqlparser_mysql.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,6 +1442,7 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
14421442
ParserOptions {
14431443
trailing_commas: false,
14441444
unescape: false,
1445+
require_semicolon_stmt_delimiter: true,
14451446
}
14461447
)
14471448
.verified_stmt(sql),

0 commit comments

Comments
 (0)