diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index a72cb8add..6b7ba12d9 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -3826,7 +3826,7 @@ pub enum Statement {
         or_alter: bool,
         name: ObjectName,
         params: Option<Vec<ProcedureParam>>,
-        body: Vec<Statement>,
+        body: ConditionalStatements,
     },
     /// ```sql
     /// CREATE MACRO
@@ -4705,11 +4705,8 @@ impl fmt::Display for Statement {
                         write!(f, " ({})", display_comma_separated(p))?;
                     }
                 }
-                write!(
-                    f,
-                    " AS BEGIN {body} END",
-                    body = display_separated(body, "; ")
-                )
+
+                write!(f, " AS {body}")
             }
             Statement::CreateMacro {
                 or_replace,
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index b754a04f1..70679f38e 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -958,8 +958,14 @@ pub trait Dialect: Debug + Any {
     /// 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.
+    ///
+    /// When the dialect supports statements without semicolon delimiter, actual keywords aren't parsed as aliases.
     fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, _parser: &mut Parser) -> bool {
-        explicit || !keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw)
+        if self.supports_statements_without_semicolon_delimiter() {
+            kw == &Keyword::NoKeyword
+        } else {
+            explicit || !keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw)
+        }
     }
 
     /// Returns true if this dialect supports querying historical table data
@@ -1021,6 +1027,11 @@ pub trait Dialect: Debug + Any {
     fn supports_set_names(&self) -> bool {
         false
     }
+
+    /// Returns true if the dialect supports parsing statements without a semicolon delimiter.
+    fn supports_statements_without_semicolon_delimiter(&self) -> bool {
+        false
+    }
 }
 
 /// This represents the operators for which precedence must be defined
diff --git a/src/dialect/mssql.rs b/src/dialect/mssql.rs
index 647e82a2a..4acf745bf 100644
--- a/src/dialect/mssql.rs
+++ b/src/dialect/mssql.rs
@@ -63,7 +63,7 @@ impl Dialect for MsSqlDialect {
     }
 
     fn supports_connect_by(&self) -> bool {
-        true
+        false
     }
 
     fn supports_eq_alias_assignment(&self) -> bool {
@@ -119,6 +119,10 @@ impl Dialect for MsSqlDialect {
         true
     }
 
+    fn supports_statements_without_semicolon_delimiter(&self) -> bool {
+        true
+    }
+
     fn is_column_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
         !keywords::RESERVED_FOR_COLUMN_ALIAS.contains(kw) && !RESERVED_FOR_COLUMN_ALIAS.contains(kw)
     }
@@ -271,6 +275,9 @@ impl MsSqlDialect {
     ) -> Result<Vec<Statement>, ParserError> {
         let mut stmts = Vec::new();
         loop {
+            while let Token::SemiColon = parser.peek_token_ref().token {
+                parser.advance_token();
+            }
             if let Token::EOF = parser.peek_token_ref().token {
                 break;
             }
diff --git a/src/keywords.rs b/src/keywords.rs
index ddb786650..622b6812e 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -1062,6 +1062,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
     Keyword::ANTI,
     Keyword::SEMI,
     Keyword::RETURNING,
+    Keyword::RETURN,
     Keyword::ASOF,
     Keyword::MATCH_CONDITION,
     // for MSSQL-specific OUTER APPLY (seems reserved in most dialects)
@@ -1087,6 +1088,11 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
     Keyword::TABLESAMPLE,
     Keyword::FROM,
     Keyword::OPEN,
+    Keyword::INSERT,
+    Keyword::UPDATE,
+    Keyword::DELETE,
+    Keyword::EXEC,
+    Keyword::EXECUTE,
 ];
 
 /// Can't be used as a column alias, so that `SELECT <expr> alias`
@@ -1115,6 +1121,7 @@ pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
     Keyword::CLUSTER,
     Keyword::DISTRIBUTE,
     Keyword::RETURNING,
+    Keyword::RETURN,
     // Reserved only as a column alias in the `SELECT` clause
     Keyword::FROM,
     Keyword::INTO,
@@ -1129,6 +1136,7 @@ pub const RESERVED_FOR_TABLE_FACTOR: &[Keyword] = &[
     Keyword::LIMIT,
     Keyword::HAVING,
     Keyword::WHERE,
+    Keyword::RETURN,
 ];
 
 /// Global list of reserved keywords that cannot be parsed as identifiers
@@ -1139,4 +1147,5 @@ pub const RESERVED_FOR_IDENTIFIER: &[Keyword] = &[
     Keyword::INTERVAL,
     Keyword::STRUCT,
     Keyword::TRIM,
+    Keyword::RETURN,
 ];
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index fc6f44376..664b70c98 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -222,6 +222,9 @@ pub struct ParserOptions {
     /// Controls how literal values are unescaped. See
     /// [`Tokenizer::with_unescape`] for more details.
     pub unescape: bool,
+    /// Determines if the parser requires a semicolon at the end of every statement.
+    /// (Default: true)
+    pub require_semicolon_statement_delimiter: bool,
 }
 
 impl Default for ParserOptions {
@@ -229,6 +232,7 @@ impl Default for ParserOptions {
         Self {
             trailing_commas: false,
             unescape: true,
+            require_semicolon_statement_delimiter: true,
         }
     }
 }
@@ -261,6 +265,22 @@ impl ParserOptions {
         self.unescape = unescape;
         self
     }
+
+    /// Set if semicolon statement delimiters are required.
+    ///
+    /// If this option is `true`, the following SQL will not parse. If the option is `false`, the SQL will parse.
+    ///
+    /// ```sql
+    /// SELECT 1
+    /// SELECT 2
+    /// ```
+    pub fn with_require_semicolon_statement_delimiter(
+        mut self,
+        require_semicolon_statement_delimiter: bool,
+    ) -> Self {
+        self.require_semicolon_statement_delimiter = require_semicolon_statement_delimiter;
+        self
+    }
 }
 
 #[derive(Copy, Clone)]
@@ -351,7 +371,11 @@ impl<'a> Parser<'a> {
             state: ParserState::Normal,
             dialect,
             recursion_counter: RecursionCounter::new(DEFAULT_REMAINING_DEPTH),
-            options: ParserOptions::new().with_trailing_commas(dialect.supports_trailing_commas()),
+            options: ParserOptions::new()
+                .with_trailing_commas(dialect.supports_trailing_commas())
+                .with_require_semicolon_statement_delimiter(
+                    !dialect.supports_statements_without_semicolon_delimiter(),
+                ),
         }
     }
 
@@ -470,10 +494,10 @@ impl<'a> Parser<'a> {
             match self.peek_token().token {
                 Token::EOF => break,
 
-                // end of statement
-                Token::Word(word) => {
-                    if expecting_statement_delimiter && word.keyword == Keyword::END {
-                        break;
+                // don't expect a semicolon statement delimiter after a newline when not otherwise required
+                Token::Whitespace(Whitespace::Newline) => {
+                    if !self.options.require_semicolon_statement_delimiter {
+                        expecting_statement_delimiter = false;
                     }
                 }
                 _ => {}
@@ -485,7 +509,7 @@ impl<'a> Parser<'a> {
 
             let statement = self.parse_statement()?;
             stmts.push(statement);
-            expecting_statement_delimiter = true;
+            expecting_statement_delimiter = self.options.require_semicolon_statement_delimiter;
         }
         Ok(stmts)
     }
@@ -4496,6 +4520,18 @@ impl<'a> Parser<'a> {
             return Ok(vec![]);
         }
 
+        if end_token == Token::SemiColon
+            && self
+                .dialect
+                .supports_statements_without_semicolon_delimiter()
+        {
+            if let Token::Word(ref kw) = self.peek_token().token {
+                if kw.keyword != Keyword::NoKeyword {
+                    return Ok(vec![]);
+                }
+            }
+        }
+
         if self.options.trailing_commas && self.peek_tokens() == [Token::Comma, end_token] {
             let _ = self.consume_token(&Token::Comma);
             return Ok(vec![]);
@@ -4513,6 +4549,9 @@ impl<'a> Parser<'a> {
     ) -> Result<Vec<Statement>, ParserError> {
         let mut values = vec![];
         loop {
+            // ignore empty statements (between successive statement delimiters)
+            while self.consume_token(&Token::SemiColon) {}
+
             match &self.peek_nth_token_ref(0).token {
                 Token::EOF => break,
                 Token::Word(w) => {
@@ -4524,7 +4563,13 @@ impl<'a> Parser<'a> {
             }
 
             values.push(self.parse_statement()?);
-            self.expect_token(&Token::SemiColon)?;
+
+            if self.options.require_semicolon_statement_delimiter {
+                self.expect_token(&Token::SemiColon)?;
+            }
+
+            // ignore empty statements (between successive statement delimiters)
+            while self.consume_token(&Token::SemiColon) {}
         }
         Ok(values)
     }
@@ -15505,14 +15550,14 @@ impl<'a> Parser<'a> {
         let name = self.parse_object_name(false)?;
         let params = self.parse_optional_procedure_parameters()?;
         self.expect_keyword_is(Keyword::AS)?;
-        self.expect_keyword_is(Keyword::BEGIN)?;
-        let statements = self.parse_statements()?;
-        self.expect_keyword_is(Keyword::END)?;
+
+        let body = self.parse_conditional_statements(&[Keyword::END])?;
+
         Ok(Statement::CreateProcedure {
             name,
             or_alter,
             params,
-            body: statements,
+            body,
         })
     }
 
@@ -15639,7 +15684,28 @@ impl<'a> Parser<'a> {
 
     /// Parse [Statement::Return]
     fn parse_return(&mut self) -> Result<Statement, ParserError> {
-        match self.maybe_parse(|p| p.parse_expr())? {
+        let rs = self.maybe_parse(|p| {
+            let expr = p.parse_expr()?;
+
+            match &expr {
+                Expr::Value(_)
+                | Expr::Function(_)
+                | Expr::UnaryOp { .. }
+                | Expr::BinaryOp { .. }
+                | Expr::Case { .. }
+                | Expr::Cast { .. }
+                | Expr::Convert { .. }
+                | Expr::Subquery(_) => Ok(expr),
+                // todo: how to retstrict to variables?
+                Expr::Identifier(id) if id.value.starts_with('@') => Ok(expr),
+                _ => parser_err!(
+                    "Non-returnable expression found following RETURN",
+                    p.peek_token().span.start
+                ),
+            }
+        })?;
+
+        match rs {
             Some(expr) => Ok(Statement::Return(ReturnStatement {
                 value: Some(ReturnStatementValue::Expr(expr)),
             })),
diff --git a/src/test_utils.rs b/src/test_utils.rs
index 3c22fa911..79e3168e7 100644
--- a/src/test_utils.rs
+++ b/src/test_utils.rs
@@ -186,6 +186,37 @@ impl TestedDialects {
         statements
     }
 
+    /// The same as [`statements_parse_to`] but it will strip semicolons from the SQL text.
+    pub fn statements_without_semicolons_parse_to(
+        &self,
+        sql: &str,
+        canonical: &str,
+    ) -> Vec<Statement> {
+        let sql_without_semicolons = sql
+            .replace("; ", " ")
+            .replace(" ;", " ")
+            .replace(";\n", "\n")
+            .replace("\n;", "\n")
+            .replace(";", " ");
+        let statements = self
+            .parse_sql_statements(&sql_without_semicolons)
+            .expect(&sql_without_semicolons);
+        if !canonical.is_empty() && sql != canonical {
+            assert_eq!(self.parse_sql_statements(canonical).unwrap(), statements);
+        } else {
+            assert_eq!(
+                sql,
+                statements
+                    .iter()
+                    // note: account for format_statement_list manually inserted semicolons
+                    .map(|s| s.to_string().trim_end_matches(";").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 {
@@ -313,6 +344,43 @@ where
     all_dialects_where(|d| !except(d))
 }
 
+/// Returns all dialects that don't support statements without semicolon delimiters.
+/// (i.e. dialects that require semicolon delimiters.)
+pub fn all_dialects_requiring_semicolon_statement_delimiter() -> TestedDialects {
+    let tested_dialects =
+        all_dialects_except(|d| d.supports_statements_without_semicolon_delimiter());
+    assert_ne!(tested_dialects.dialects.len(), 0);
+    tested_dialects
+}
+
+/// Returns all dialects that do support statements without semicolon delimiters.
+/// (i.e. dialects not requiring semicolon delimiters.)
+pub fn all_dialects_not_requiring_semicolon_statement_delimiter() -> TestedDialects {
+    let tested_dialects =
+        all_dialects_where(|d| d.supports_statements_without_semicolon_delimiter());
+    assert_ne!(tested_dialects.dialects.len(), 0);
+    tested_dialects
+}
+
+/// Asserts an error for `parse_sql_statements`:
+/// - "end of statement" for dialects that require semicolon delimiters
+/// - "an SQL statement" for dialects that don't require semicolon delimiters.
+pub fn assert_err_parse_statements(sql: &str, found: &str) {
+    assert_eq!(
+        ParserError::ParserError(format!("Expected: end of statement, found: {}", found)),
+        all_dialects_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(sql)
+            .unwrap_err()
+    );
+
+    assert_eq!(
+        ParserError::ParserError(format!("Expected: an SQL statement, found: {}", found)),
+        all_dialects_not_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(sql)
+            .unwrap_err()
+    );
+}
+
 pub fn assert_eq_vec<T: ToString>(expected: &[&str], actual: &[T]) {
     assert_eq!(
         expected,
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 7a8b8bdaa..b062846ad 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -54,6 +54,9 @@ use sqlparser::ast::DateTimeField::Seconds;
 use sqlparser::ast::Expr::{Identifier, UnaryOp};
 use sqlparser::ast::Value::Number;
 use sqlparser::test_utils::all_dialects_except;
+use sqlparser::test_utils::all_dialects_not_requiring_semicolon_statement_delimiter;
+use sqlparser::test_utils::all_dialects_requiring_semicolon_statement_delimiter;
+use sqlparser::test_utils::assert_err_parse_statements;
 
 #[test]
 fn parse_numeric_literal_underscore() {
@@ -271,20 +274,39 @@ fn parse_insert_default_values() {
         "INSERT INTO test_table DEFAULT VALUES (some_column)";
     assert_eq!(
         ParserError::ParserError("Expected: end of statement, found: (".to_string()),
-        parse_sql_statements(insert_with_default_values_and_hive_after_columns).unwrap_err()
+        all_dialects_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(insert_with_default_values_and_hive_after_columns)
+            .unwrap_err()
     );
-
-    let insert_with_default_values_and_hive_partition =
-        "INSERT INTO test_table DEFAULT VALUES PARTITION (some_column)";
     assert_eq!(
-        ParserError::ParserError("Expected: end of statement, found: PARTITION".to_string()),
-        parse_sql_statements(insert_with_default_values_and_hive_partition).unwrap_err()
+        ParserError::ParserError(
+            "Expected: SELECT, VALUES, or a subquery in the query body, found: some_column"
+                .to_string()
+        ),
+        all_dialects_not_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(insert_with_default_values_and_hive_after_columns)
+            .unwrap_err()
+    );
+
+    assert_err_parse_statements(
+        "INSERT INTO test_table DEFAULT VALUES PARTITION (some_column)",
+        "PARTITION",
     );
 
     let insert_with_default_values_and_values_list = "INSERT INTO test_table DEFAULT VALUES (1)";
     assert_eq!(
         ParserError::ParserError("Expected: end of statement, found: (".to_string()),
-        parse_sql_statements(insert_with_default_values_and_values_list).unwrap_err()
+        all_dialects_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(insert_with_default_values_and_values_list)
+            .unwrap_err()
+    );
+    assert_eq!(
+        ParserError::ParserError(
+            "Expected: SELECT, VALUES, or a subquery in the query body, found: 1".to_string()
+        ),
+        all_dialects_not_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(insert_with_default_values_and_values_list)
+            .unwrap_err()
     );
 }
 
@@ -413,11 +435,7 @@ fn parse_update() {
     );
 
     let sql = "UPDATE t SET a = 1 extrabadstuff";
-    let res = parse_sql_statements(sql);
-    assert_eq!(
-        ParserError::ParserError("Expected: end of statement, found: extrabadstuff".to_string()),
-        res.unwrap_err()
-    );
+    assert_err_parse_statements(sql, "extrabadstuff");
 }
 
 #[test]
@@ -648,6 +666,45 @@ fn parse_select_with_table_alias() {
     );
 }
 
+#[test]
+fn parse_select_with_table_alias_keyword() {
+    // note: DECLARE isn't included in RESERVED_FOR_TABLE_ALIAS
+    let table_alias_non_reserved_keyword = "SELECT a FROM lineitem DECLARE";
+    let statements = all_dialects_requiring_semicolon_statement_delimiter()
+        .parse_sql_statements(table_alias_non_reserved_keyword)
+        .unwrap();
+    assert_eq!(1, statements.len());
+    assert_eq!(
+        ParserError::ParserError("Expected: identifier, found: EOF".to_string()),
+        all_dialects_not_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(table_alias_non_reserved_keyword)
+            .unwrap_err()
+    );
+
+    let table_alias_quoted_keyword = "SELECT a FROM lineitem \"DECLARE\"";
+    let statements = all_dialects()
+        .parse_sql_statements(table_alias_quoted_keyword)
+        .unwrap();
+    assert_eq!(1, statements.len());
+}
+
+#[test]
+fn parse_consecutive_queries() {
+    let select_then_exec = "SELECT * FROM deleted; EXECUTE my_sp 'some', 'params'";
+    let _ = all_dialects()
+        .parse_sql_statements(select_then_exec)
+        .unwrap();
+    let _ = all_dialects_not_requiring_semicolon_statement_delimiter()
+        .statements_without_semicolons_parse_to(select_then_exec, "");
+
+    let select_then_update = "SELECT 1 FROM x; UPDATE y SET z = 1";
+    let _ = all_dialects()
+        .parse_sql_statements(select_then_update)
+        .unwrap();
+    let _ = all_dialects_not_requiring_semicolon_statement_delimiter()
+        .statements_without_semicolons_parse_to(select_then_update, "");
+}
+
 #[test]
 fn parse_analyze() {
     verified_stmt("ANALYZE TABLE test_table");
@@ -914,9 +971,18 @@ fn parse_limit() {
 
 #[test]
 fn parse_invalid_limit_by() {
-    all_dialects()
-        .parse_sql_statements("SELECT * FROM user BY name")
-        .expect_err("BY without LIMIT");
+    assert_eq!(
+        ParserError::ParserError("Expected: end of statement, found: name".to_string()),
+        all_dialects_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements("SELECT * FROM user BY name")
+            .unwrap_err()
+    );
+    assert_eq!(
+        ParserError::ParserError("Expected: an SQL statement, found: BY".to_string()),
+        all_dialects_not_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements("SELECT * FROM user BY name")
+            .unwrap_err()
+    );
 }
 
 #[test]
@@ -1086,11 +1152,7 @@ fn parse_select_into() {
 
     // Do not allow aliases here
     let sql = "SELECT * INTO table0 asdf FROM table1";
-    let result = parse_sql_statements(sql);
-    assert_eq!(
-        ParserError::ParserError("Expected: end of statement, found: asdf".to_string()),
-        result.unwrap_err()
-    )
+    assert_err_parse_statements(sql, "asdf");
 }
 
 #[test]
@@ -1126,11 +1188,7 @@ fn parse_select_wildcard() {
     );
 
     let sql = "SELECT * + * FROM foo;";
-    let result = parse_sql_statements(sql);
-    assert_eq!(
-        ParserError::ParserError("Expected: end of statement, found: +".to_string()),
-        result.unwrap_err(),
-    );
+    assert_err_parse_statements(sql, "+");
 }
 
 #[test]
@@ -1336,11 +1394,7 @@ fn parse_not() {
 
 #[test]
 fn parse_invalid_infix_not() {
-    let res = parse_sql_statements("SELECT c FROM t WHERE c NOT (");
-    assert_eq!(
-        ParserError::ParserError("Expected: end of statement, found: NOT".to_string()),
-        res.unwrap_err(),
-    );
+    assert_err_parse_statements("SELECT c FROM t WHERE c NOT (", "NOT");
 }
 
 #[test]
@@ -4718,10 +4772,7 @@ fn parse_rename_table() {
         _ => unreachable!(),
     };
 
-    assert_eq!(
-        parse_sql_statements("RENAME TABLE old_table TO new_table a").unwrap_err(),
-        ParserError::ParserError("Expected: end of statement, found: a".to_string())
-    );
+    assert_err_parse_statements("RENAME TABLE old_table TO new_table a", "a");
 
     assert_eq!(
         parse_sql_statements("RENAME TABLE1 old_table TO new_table a").unwrap_err(),
@@ -5059,11 +5110,7 @@ fn parse_alter_table_drop_constraint() {
         }
     }
 
-    let res = parse_sql_statements("ALTER TABLE tab DROP CONSTRAINT is_active TEXT");
-    assert_eq!(
-        ParserError::ParserError("Expected: end of statement, found: TEXT".to_string()),
-        res.unwrap_err()
-    );
+    assert_err_parse_statements("ALTER TABLE tab DROP CONSTRAINT is_active TEXT", "TEXT");
 }
 
 #[test]
@@ -5261,10 +5308,13 @@ fn parse_explain_query_plan() {
     // missing PLAN keyword should return error
     assert_eq!(
         ParserError::ParserError("Expected: end of statement, found: SELECT".to_string()),
-        all_dialects()
+        all_dialects_requiring_semicolon_statement_delimiter()
             .parse_sql_statements("EXPLAIN QUERY SELECT sqrt(id) FROM foo")
             .unwrap_err()
     );
+    assert!(all_dialects_not_requiring_semicolon_statement_delimiter()
+        .parse_sql_statements("EXPLAIN QUERY SELECT sqrt(id) FROM foo")
+        .is_ok());
 }
 
 #[test]
@@ -5973,16 +6023,22 @@ fn parse_interval_all() {
         expr_from_projection(only(&select.projection)),
     );
 
-    let result = parse_sql_statements("SELECT INTERVAL '1' SECOND TO SECOND");
-    assert_eq!(
-        ParserError::ParserError("Expected: end of statement, found: SECOND".to_string()),
-        result.unwrap_err(),
-    );
+    assert_err_parse_statements("SELECT INTERVAL '1' SECOND TO SECOND", "SECOND");
 
-    let result = parse_sql_statements("SELECT INTERVAL '10' HOUR (1) TO HOUR (2)");
+    let incorrect_hour_interval = "SELECT INTERVAL '10' HOUR (1) TO HOUR (2)";
     assert_eq!(
         ParserError::ParserError("Expected: end of statement, found: (".to_string()),
-        result.unwrap_err(),
+        all_dialects_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(incorrect_hour_interval)
+            .unwrap_err(),
+    );
+    assert_eq!(
+        ParserError::ParserError(
+            "Expected: SELECT, VALUES, or a subquery in the query body, found: 2".to_string()
+        ),
+        all_dialects_not_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(incorrect_hour_interval)
+            .unwrap_err(),
     );
 
     verified_only_select("SELECT INTERVAL '1' YEAR");
@@ -7575,11 +7631,17 @@ fn parse_multiple_statements() {
         // Check that extra semicolon at the end is stripped by normalization:
         one_statement_parses_to(&(sql1.to_owned() + ";"), sql1);
         // Check that forgetting the semicolon results in an error:
-        let res = parse_sql_statements(&(sql1.to_owned() + " " + sql2_kw + sql2_rest));
+        // (if configured as required by the dialect)
+        let sql = sql1.to_owned() + " " + sql2_kw + sql2_rest;
         assert_eq!(
             ParserError::ParserError("Expected: end of statement, found: ".to_string() + sql2_kw),
-            res.unwrap_err()
+            all_dialects_requiring_semicolon_statement_delimiter()
+                .parse_sql_statements(&sql)
+                .unwrap_err()
         );
+        assert!(all_dialects_not_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(&sql)
+            .is_ok());
     }
     test_with("SELECT foo", "SELECT", " bar");
     // ensure that SELECT/WITH is not parsed as a table or column alias if ';'
@@ -8242,11 +8304,7 @@ fn parse_drop_view() {
 
 #[test]
 fn parse_invalid_subquery_without_parens() {
-    let res = parse_sql_statements("SELECT SELECT 1 FROM bar WHERE 1=1 FROM baz");
-    assert_eq!(
-        ParserError::ParserError("Expected: end of statement, found: 1".to_string()),
-        res.unwrap_err()
-    );
+    assert_err_parse_statements("SELECT SELECT 1 FROM bar WHERE 1=1 FROM baz", "1");
 }
 
 #[test]
@@ -8472,10 +8530,17 @@ fn lateral_derived() {
     chk(true);
 
     let sql = "SELECT * FROM LATERAL UNNEST ([10,20,30]) as numbers WITH OFFSET;";
-    let res = parse_sql_statements(sql);
     assert_eq!(
         ParserError::ParserError("Expected: end of statement, found: WITH".to_string()),
-        res.unwrap_err()
+        all_dialects_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(sql)
+            .unwrap_err()
+    );
+    assert_eq!(
+        ParserError::ParserError("Expected: AS, found: ;".to_string()),
+        all_dialects_not_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(sql)
+            .unwrap_err()
     );
 
     let sql = "SELECT * FROM a LEFT JOIN LATERAL (b CROSS JOIN c)";
@@ -8604,11 +8669,7 @@ fn parse_start_transaction() {
         res.unwrap_err()
     );
 
-    let res = dialects.parse_sql_statements("START TRANSACTION BAD");
-    assert_eq!(
-        ParserError::ParserError("Expected: end of statement, found: BAD".to_string()),
-        res.unwrap_err()
-    );
+    assert_err_parse_statements("START TRANSACTION BAD", "BAD");
 
     let res = dialects.parse_sql_statements("START TRANSACTION READ ONLY,");
     assert_eq!(
@@ -9935,23 +9996,9 @@ fn parse_offset_and_limit() {
     verified_stmt("SELECT foo FROM bar OFFSET 2");
 
     // Can't repeat OFFSET / LIMIT
-    let res = parse_sql_statements("SELECT foo FROM bar OFFSET 2 OFFSET 2");
-    assert_eq!(
-        ParserError::ParserError("Expected: end of statement, found: OFFSET".to_string()),
-        res.unwrap_err()
-    );
-
-    let res = parse_sql_statements("SELECT foo FROM bar LIMIT 2 LIMIT 2");
-    assert_eq!(
-        ParserError::ParserError("Expected: end of statement, found: LIMIT".to_string()),
-        res.unwrap_err()
-    );
-
-    let res = parse_sql_statements("SELECT foo FROM bar OFFSET 2 LIMIT 2 OFFSET 2");
-    assert_eq!(
-        ParserError::ParserError("Expected: end of statement, found: OFFSET".to_string()),
-        res.unwrap_err()
-    );
+    assert_err_parse_statements("SELECT foo FROM bar OFFSET 2 OFFSET 2", "OFFSET");
+    assert_err_parse_statements("SELECT foo FROM bar LIMIT 2 LIMIT 2", "LIMIT");
+    assert_err_parse_statements("SELECT foo FROM bar OFFSET 2 LIMIT 2 OFFSET 2", "OFFSET");
 }
 
 #[test]
@@ -10419,11 +10466,7 @@ fn parse_uncache_table() {
         }
     );
 
-    let res = parse_sql_statements("UNCACHE TABLE 'table_name' foo");
-    assert_eq!(
-        ParserError::ParserError("Expected: end of statement, found: foo".to_string()),
-        res.unwrap_err()
-    );
+    assert_err_parse_statements("UNCACHE TABLE 'table_name' foo", "foo");
 
     let res = parse_sql_statements("UNCACHE 'table_name' foo");
     assert_eq!(
@@ -10816,7 +10859,9 @@ fn parse_select_table_with_index_hints() {
 
     // Test that dialects that don't support table hints will keep parsing the USE as table alias
     let sql = "SELECT * FROM T USE LIMIT 1";
-    let unsupported_dialects = all_dialects_where(|d| !d.supports_table_hints());
+    let unsupported_dialects = all_dialects_where(|d| {
+        !d.supports_table_hints() && !d.supports_statements_without_semicolon_delimiter()
+    });
     let select = unsupported_dialects
         .verified_only_select_with_canonical(sql, "SELECT * FROM T AS USE LIMIT 1");
     assert_eq!(
@@ -12909,13 +12954,7 @@ fn test_drop_policy() {
         "sql parser error: Expected: ON, found: EOF"
     );
     // Wrong option name
-    assert_eq!(
-        all_dialects()
-            .parse_sql_statements("DROP POLICY my_policy ON my_table WRONG")
-            .unwrap_err()
-            .to_string(),
-        "sql parser error: Expected: end of statement, found: WRONG"
-    );
+    assert_err_parse_statements("DROP POLICY my_policy ON my_table WRONG", "WRONG");
 }
 
 #[test]
@@ -12956,18 +12995,27 @@ fn test_alter_policy() {
     verified_stmt("ALTER POLICY my_policy ON my_table");
 
     // mixing RENAME and APPLY expressions
+    let sql = "ALTER POLICY old_policy ON my_table TO public RENAME TO new_policy";
     assert_eq!(
-        parse_sql_statements("ALTER POLICY old_policy ON my_table TO public RENAME TO new_policy")
+        all_dialects_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(sql)
             .unwrap_err()
             .to_string(),
-        "sql parser error: Expected: end of statement, found: RENAME"
+        "sql parser error: Expected: end of statement, found: RENAME".to_string(),
     );
     assert_eq!(
-        parse_sql_statements("ALTER POLICY old_policy ON my_table RENAME TO new_policy TO public")
+        all_dialects_not_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(sql)
             .unwrap_err()
             .to_string(),
-        "sql parser error: Expected: end of statement, found: TO"
+        "sql parser error: Expected: KEYWORD `TABLE` after RENAME, found: TO".to_string(),
+    );
+
+    assert_err_parse_statements(
+        "ALTER POLICY old_policy ON my_table RENAME TO new_policy TO public",
+        "TO",
     );
+
     // missing TO in RENAME TO
     assert_eq!(
         parse_sql_statements("ALTER POLICY old_policy ON my_table RENAME")
@@ -13165,14 +13213,9 @@ fn test_alter_connector() {
     }
 
     // Wrong option name
-    assert_eq!(
-        dialects
-            .parse_sql_statements(
-                "ALTER CONNECTOR my_connector SET WRONG 'jdbc:mysql://localhost:3306/mydb'"
-            )
-            .unwrap_err()
-            .to_string(),
-        "sql parser error: Expected: end of statement, found: WRONG"
+    assert_err_parse_statements(
+        "ALTER CONNECTOR my_connector SET WRONG 'jdbc:mysql://localhost:3306/mydb'",
+        "WRONG",
     );
 }
 
@@ -13916,12 +13959,25 @@ fn parse_create_table_select() {
         r#"CREATE TABLE foo (baz INT, name STRING) AS SELECT bar, oth_name FROM test.table_a"#;
     let _ = dialects.one_statement_parses_to(sql_2, expected);
 
-    let dialects = all_dialects_where(|d| !d.supports_create_table_select());
+    let err_dialects = all_dialects_where(|d| {
+        !d.supports_create_table_select() && !d.supports_statements_without_semicolon_delimiter()
+    });
+    let multi_statement_dialects = all_dialects_where(|d| {
+        !d.supports_create_table_select() && d.supports_statements_without_semicolon_delimiter()
+    });
     for sql in [sql_1, sql_2] {
         assert_eq!(
-            dialects.parse_sql_statements(sql).unwrap_err(),
+            err_dialects.parse_sql_statements(sql).unwrap_err(),
             ParserError::ParserError("Expected: end of statement, found: SELECT".to_string())
         );
+
+        assert_eq!(
+            multi_statement_dialects
+                .parse_sql_statements(sql)
+                .unwrap()
+                .len(),
+            2
+        );
     }
 }
 
@@ -14210,7 +14266,17 @@ fn parse_update_from_before_select() {
     "UPDATE t1 FROM (SELECT name, id FROM t1 GROUP BY id) AS t2 SET name = t2.name FROM (SELECT name from t2) AS t2";
     assert_eq!(
         ParserError::ParserError("Expected: end of statement, found: FROM".to_string()),
-        parse_sql_statements(query).unwrap_err()
+        all_dialects_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(query)
+            .unwrap_err()
+    );
+    assert_eq!(
+        ParserError::ParserError(
+            "Expected: SELECT, VALUES, or a subquery in the query body, found: FROM".to_string()
+        ),
+        all_dialects_not_requiring_semicolon_statement_delimiter()
+            .parse_sql_statements(query)
+            .unwrap_err()
     );
 }
 #[test]
@@ -15179,6 +15245,34 @@ fn parse_return() {
     assert_eq!(stmt, Statement::Return(ReturnStatement { value: None }));
 
     let _ = all_dialects().verified_stmt("RETURN 1");
+    let _ = all_dialects().verified_stmt("RETURN -1");
+    let _ = all_dialects_where(|d| d.is_identifier_start('@')).verified_stmt("RETURN @my_var");
+    let _ = all_dialects().verified_stmt("RETURN CAST(1 AS INT)");
+    let _ = all_dialects().verified_stmt("RETURN dbo.my_func()");
+    let _ = all_dialects().verified_stmt("RETURN (SELECT 1)");
+    let _ = all_dialects().verified_stmt("RETURN CASE 1 WHEN 1 THEN 2 END");
+
+    let _ = all_dialects_where(|d| {
+        d.is::<GenericDialect>()
+            || d.is::<PostgreSqlDialect>()
+            || d.is::<MySqlDialect>()
+            || d.is::<BigQueryDialect>()
+            || d.is::<SQLiteDialect>()
+            || d.is::<DuckDbDialect>()
+            || d.is::<DatabricksDialect>()
+            || d.is::<ClickHouseDialect>()
+    })
+    .verified_stmt("RETURN CONVERT(1, INT)");
+
+    let _ = all_dialects_except(|d| {
+        d.is::<GenericDialect>()
+            || d.is::<AnsiDialect>()
+            || d.is::<RedshiftSqlDialect>()
+            || d.is::<MsSqlDialect>()
+            || d.is::<HiveDialect>()
+            || d.is::<SnowflakeDialect>()
+    })
+    .verified_stmt("RETURN CONVERT(1, INT)");
 }
 
 #[test]
diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs
index 9ff55198f..2fd070c75 100644
--- a/tests/sqlparser_mssql.rs
+++ b/tests/sqlparser_mssql.rs
@@ -32,7 +32,10 @@ use sqlparser::ast::DeclareAssignment::MsSqlAssignment;
 use sqlparser::ast::Value::SingleQuotedString;
 use sqlparser::ast::*;
 use sqlparser::dialect::{GenericDialect, MsSqlDialect};
-use sqlparser::parser::{Parser, ParserError};
+use sqlparser::parser::{Parser, ParserError, ParserOptions};
+
+#[cfg(test)]
+use pretty_assertions::assert_eq;
 
 #[test]
 fn parse_mssql_identifiers() {
@@ -100,48 +103,52 @@ 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: 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,
-                })))
-            }))],
+            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 {
@@ -174,19 +181,24 @@ fn parse_create_procedure() {
 
 #[test]
 fn parse_mssql_create_procedure() {
-    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 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",
+        "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");
+    // early return
+    let _ = ms().verified_stmt(
+        "CREATE PROCEDURE [foo] AS BEGIN IF 1 = 0 RETURN;; DECLARE @x INT; RETURN @x; END",
+    );
 }
 
 #[test]
@@ -237,6 +249,7 @@ fn parse_create_function() {
             remote_connection: None,
         }),
     );
+    let _ = ms().statements_without_semicolons_parse_to(return_expression_function, "");
 
     let multi_statement_function = "\
         CREATE FUNCTION some_scalar_udf(@foo INT, @bar VARCHAR(256)) \
@@ -248,6 +261,7 @@ fn parse_create_function() {
         END\
     ";
     let _ = ms().verified_stmt(multi_statement_function);
+    let _ = ms().statements_without_semicolons_parse_to(multi_statement_function, "");
 
     let create_function_with_conditional = "\
         CREATE FUNCTION some_scalar_udf() \
@@ -262,6 +276,7 @@ fn parse_create_function() {
         END\
     ";
     let _ = ms().verified_stmt(create_function_with_conditional);
+    let _ = ms().statements_without_semicolons_parse_to(create_function_with_conditional, "");
 
     let create_or_alter_function = "\
         CREATE OR ALTER FUNCTION some_scalar_udf(@foo INT, @bar VARCHAR(256)) \
@@ -273,6 +288,7 @@ fn parse_create_function() {
         END\
     ";
     let _ = ms().verified_stmt(create_or_alter_function);
+    let _ = ms().statements_without_semicolons_parse_to(create_or_alter_function, "");
 
     let create_function_with_return_expression = "\
         CREATE FUNCTION some_scalar_udf(@foo INT, @bar VARCHAR(256)) \
@@ -283,6 +299,7 @@ fn parse_create_function() {
         END\
     ";
     let _ = ms().verified_stmt(create_function_with_return_expression);
+    let _ = ms().statements_without_semicolons_parse_to(create_function_with_return_expression, "");
 }
 
 #[test]
@@ -1429,6 +1446,7 @@ fn test_mssql_cursor() {
         DEALLOCATE Employee_Cursor\
     ";
     let _ = ms().statements_parse_to(full_cursor_usage, "");
+    let _ = ms().statements_without_semicolons_parse_to(full_cursor_usage, "");
 }
 
 #[test]
@@ -2043,7 +2061,7 @@ fn parse_mssql_if_else() {
 
     // Multiple statements
     let stmts = ms()
-        .parse_sql_statements("DECLARE @A INT; IF 1=1 BEGIN SET @A = 1 END ELSE SET @A = 2")
+        .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)] => {
@@ -2058,11 +2076,11 @@ fn parse_mssql_if_else() {
 
 #[test]
 fn test_mssql_if_else_span() {
-    let sql = "IF 1 = 1 SELECT '1' ELSE SELECT '2'";
+    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))
+        Span::new(Location::new(1, 1), Location::new(1, sql.len() as u64))
     );
 }
 
@@ -2085,7 +2103,7 @@ fn test_mssql_if_else_multiline_span() {
 #[test]
 fn test_mssql_if_statements_span() {
     // Simple statements
-    let mut sql = "IF 1 = 1 SELECT '1' ELSE SELECT '2'";
+    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 {
@@ -2099,14 +2117,15 @@ fn test_mssql_if_statements_span() {
             );
             assert_eq!(
                 else_block.span(),
-                Span::new(Location::new(1, 21), Location::new(1, 36))
+                Span::new(Location::new(1, 22), Location::new(1, 37))
             );
         }
         stmt => panic!("Unexpected statement: {:?}", stmt),
     }
+    let _ = ms().statements_without_semicolons_parse_to(sql, "");
 
     // Blocks
-    sql = "IF 1 = 1 BEGIN SET @A = 1; END ELSE BEGIN SET @A = 2 END";
+    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 {
@@ -2120,11 +2139,12 @@ fn test_mssql_if_statements_span() {
             );
             assert_eq!(
                 else_block.span(),
-                Span::new(Location::new(1, 32), Location::new(1, 57))
+                Span::new(Location::new(1, 32), Location::new(1, 58))
             );
         }
         stmt => panic!("Unexpected statement: {:?}", stmt),
     }
+    let _ = ms().statements_without_semicolons_parse_to(sql, "");
 }
 
 #[test]
@@ -2271,6 +2291,7 @@ fn parse_create_trigger() {
         END\
     ";
     let _ = ms().verified_stmt(multi_statement_trigger);
+    let _ = ms().statements_without_semicolons_parse_to(multi_statement_trigger, "");
 
     let create_trigger_with_return = "\
         CREATE TRIGGER some_trigger ON some_table FOR INSERT \
@@ -2280,15 +2301,7 @@ fn parse_create_trigger() {
         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 _ = ms().statements_without_semicolons_parse_to(create_trigger_with_return, "");
 
     let create_trigger_with_conditional = "\
         CREATE TRIGGER some_trigger ON some_table FOR INSERT \
@@ -2302,6 +2315,7 @@ fn parse_create_trigger() {
         END\
     ";
     let _ = ms().verified_stmt(create_trigger_with_conditional);
+    let _ = ms().statements_without_semicolons_parse_to(create_trigger_with_conditional, "");
 }
 
 #[test]
@@ -2335,3 +2349,416 @@ fn parse_print() {
     let _ = ms().verified_stmt("PRINT N'Hello, ⛄️!'");
     let _ = ms().verified_stmt("PRINT @my_variable");
 }
+
+#[test]
+fn test_supports_statements_without_semicolon_delimiter() {
+    use sqlparser::ast::Ident;
+
+    use sqlparser::tokenizer::Location;
+
+    fn parse_n_statements(n: usize, sql: &str) -> Vec<Statement> {
+        let dialect = MsSqlDialect {};
+        let parser = Parser::new(&dialect).with_options(
+            ParserOptions::default().with_require_semicolon_statement_delimiter(false),
+        );
+        let stmts = parser
+            .try_with_sql(sql)
+            .unwrap()
+            .parse_statements()
+            .unwrap();
+        assert_eq!(stmts.len(), n);
+        stmts
+    }
+
+    let multiple_statements = "SELECT 1 SELECT 2";
+    assert_eq!(
+        parse_n_statements(2, multiple_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(
+                        (Value::Number("1".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,
+                }))),
+            })),
+            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(
+                        (Value::Number("2".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
+                }))),
+            })),
+        ]
+    );
+
+    let udf = "CREATE OR ALTER FUNCTION utc_now()
+        RETURNS SMALLDATETIME \
+        AS \
+        BEGIN \
+            RETURN GETUTCDATE()
+        END \
+    ";
+    assert_eq!(
+        parse_n_statements(1, udf)[0],
+        Statement::CreateFunction(CreateFunction {
+            or_alter: true,
+            or_replace: false,
+            temporary: false,
+            if_not_exists: false,
+            name: ObjectName::from(vec![sqlparser::ast::Ident::with_span(
+                Span::new(Location::new(1, 26), Location::new(1, 33)),
+                "utc_now"
+            )]),
+            args: Some(vec![]),
+            return_type: Some(sqlparser::ast::DataType::Custom(
+                ObjectName(vec![sqlparser::ast::ObjectNamePart::Identifier(Ident {
+                    value: "SMALLDATETIME".to_string(),
+                    quote_style: None,
+                    span: Span {
+                        start: Location::new(2, 17),
+                        end: Location::new(2, 30)
+                    },
+                })]),
+                vec![]
+            )),
+            function_body: Some(CreateFunctionBody::AsBeginEnd(BeginEndStatements {
+                begin_token: AttachedToken(TokenWithSpan {
+                    token: Token::Word(Word {
+                        value: "BEGIN".to_string(),
+                        quote_style: None,
+                        keyword: Keyword::BEGIN
+                    }),
+                    span: Span::new(Location::new(2, 47), Location::new(2, 57)),
+                }),
+                statements: vec![Statement::Return(ReturnStatement {
+                    value: Some(ReturnStatementValue::Expr(Expr::Function(Function {
+                        name: ObjectName::from(vec![Ident::new("GETUTCDATE")]),
+                        uses_odbc_syntax: false,
+                        parameters: FunctionArguments::None,
+                        args: FunctionArguments::List(FunctionArgumentList {
+                            duplicate_treatment: None,
+                            args: vec![],
+                            clauses: vec![],
+                        }),
+                        filter: None,
+                        null_treatment: None,
+                        over: None,
+                        within_group: vec![],
+                    }))),
+                })],
+                end_token: AttachedToken(TokenWithSpan {
+                    token: Token::Word(Word {
+                        value: "END".to_string(),
+                        quote_style: None,
+                        keyword: Keyword::END
+                    }),
+                    span: Span::new(Location::new(3, 9), Location::new(3, 12)),
+                })
+            })),
+            behavior: None,
+            called_on_null: None,
+            parallel: None,
+            using: None,
+            language: None,
+            determinism_specifier: None,
+            options: None,
+            remote_connection: None
+        })
+    );
+
+    let sp = "CREATE OR ALTER PROCEDURE example_sp \
+        AS \
+            IF USER_NAME() = 'X' \
+                RETURN \
+            IF 1 = 2 \
+                RETURN (SELECT 1) \
+            \
+            RETURN CONVERT(INT, 123) \
+    ";
+    assert_eq!(
+        parse_n_statements(1, sp)[0],
+        Statement::CreateProcedure {
+            or_alter: true,
+            name: ObjectName::from(vec![Ident::new("example_sp")]),
+            params: Some(vec![]),
+            body: ConditionalStatements::Sequence {
+                statements: vec![
+                    Statement::If(IfStatement {
+                        if_block: ConditionalStatementBlock {
+                            start_token: AttachedToken::empty(),
+                            condition: Some(Expr::BinaryOp {
+                                left: Box::new(Expr::Function(Function {
+                                    name: ObjectName::from(vec![Ident::new("USER_NAME")]),
+                                    uses_odbc_syntax: false,
+                                    parameters: FunctionArguments::None,
+                                    args: FunctionArguments::List(FunctionArgumentList {
+                                        duplicate_treatment: None,
+                                        args: vec![],
+                                        clauses: vec![],
+                                    }),
+                                    filter: None,
+                                    null_treatment: None,
+                                    over: None,
+                                    within_group: vec![],
+                                })),
+                                op: BinaryOperator::Eq,
+                                right: Box::new(Expr::Value(ValueWithSpan {
+                                    value: Value::SingleQuotedString("X".to_string()),
+                                    span: Span::new(Location::new(1, 58), Location::new(1, 61)),
+                                })),
+                            }),
+                            then_token: None,
+                            conditional_statements: ConditionalStatements::Sequence {
+                                statements: vec![Statement::Return(ReturnStatement {
+                                    value: None
+                                })],
+                            },
+                        },
+                        elseif_blocks: vec![],
+                        else_block: None,
+                        end_token: None,
+                    }),
+                    Statement::If(IfStatement {
+                        if_block: ConditionalStatementBlock {
+                            start_token: AttachedToken::empty(),
+                            condition: Some(Expr::BinaryOp {
+                                left: Box::new(Expr::Value(number("1").with_span(Span::new(
+                                    Location::new(1, 73),
+                                    Location::new(1, 74)
+                                )))),
+                                op: BinaryOperator::Eq,
+                                right: Box::new(Expr::Value(number("2").with_span(Span::new(
+                                    Location::new(1, 76),
+                                    Location::new(1, 77)
+                                )))),
+                            }),
+                            then_token: None,
+                            conditional_statements: ConditionalStatements::Sequence {
+                                statements: vec![Statement::Return(ReturnStatement {
+                                    value: Some(ReturnStatementValue::Expr(Expr::Subquery(
+                                        Box::new(Query {
+                                            with: 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_span(Span::new(
+                                                        Location::new(1, 93),
+                                                        Location::new(1, 94)
+                                                    )))
+                                                ),],
+                                                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![],
+                                                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![],
+                                        }),
+                                    ))),
+                                })],
+                            },
+                        },
+                        elseif_blocks: vec![],
+                        else_block: None,
+                        end_token: None,
+                    }),
+                    Statement::Return(ReturnStatement {
+                        value: Some(ReturnStatementValue::Expr(Expr::Convert {
+                            is_try: false,
+                            expr: Box::new(Expr::Value(
+                                number("123").with_span(Span::new(
+                                    Location::new(1, 89),
+                                    Location::new(1, 92)
+                                ))
+                            )),
+                            data_type: Some(DataType::Int(None)),
+                            charset: None,
+                            target_before_value: true,
+                            styles: vec![],
+                        })),
+                    }),
+                ],
+            },
+        }
+    );
+
+    let exec_then_update = "\
+        EXEC my_sp \
+        UPDATE my_table SET col = 1 \
+    ";
+    assert_eq!(
+        parse_n_statements(2, exec_then_update),
+        vec![
+            Statement::Execute {
+                name: Some(ObjectName::from(vec![Ident::new("my_sp")])),
+                parameters: vec![],
+                has_parentheses: false,
+                immediate: false,
+                into: vec![],
+                using: vec![],
+            },
+            Statement::Update {
+                table: TableWithJoins {
+                    relation: TableFactor::Table {
+                        name: ObjectName::from(vec![Ident::new("my_table")]),
+                        alias: None,
+                        with_hints: vec![],
+                        args: None,
+                        version: None,
+                        with_ordinality: false,
+                        partitions: vec![],
+                        json_path: None,
+                        sample: None,
+                        index_hints: vec![]
+                    },
+                    joins: vec![],
+                },
+                assignments: vec![Assignment {
+                    value: Expr::Value(
+                        number("1")
+                            .with_span(Span::new(Location::new(3, 16), Location::new(3, 17)))
+                    ),
+                    target: AssignmentTarget::ColumnName(ObjectName::from(vec![Ident::new("col")])),
+                },],
+                selection: None,
+                returning: None,
+                from: None,
+                or: None
+            },
+        ]
+    );
+
+    let exec_params_then_update = "\
+        EXEC my_sp 1, 2 \
+        UPDATE my_table SET col = 1 \
+    ";
+    assert_eq!(
+        parse_n_statements(2, exec_params_then_update),
+        vec![
+            Statement::Execute {
+                name: Some(ObjectName::from(vec![Ident::with_span(
+                    Span::new(Location::new(1, 6), Location::new(1, 11)),
+                    "my_sp"
+                )])),
+                parameters: vec![],
+                has_parentheses: false,
+                immediate: false,
+                into: vec![],
+                using: vec![],
+            },
+            Statement::Update {
+                table: TableWithJoins {
+                    relation: TableFactor::Table {
+                        name: ObjectName::from(vec![Ident::with_span(
+                            Span::new(Location::new(1, 24), Location::new(1, 32)),
+                            "my_table"
+                        )]),
+                        alias: None,
+                        with_hints: vec![],
+                        args: None,
+                        version: None,
+                        with_ordinality: false,
+                        partitions: vec![],
+                        json_path: None,
+                        sample: None,
+                        index_hints: vec![]
+                    },
+                    joins: vec![],
+                },
+                assignments: vec![Assignment {
+                    value: Expr::Value(
+                        number("1")
+                            .with_span(Span::new(Location::new(3, 16), Location::new(3, 17)))
+                    ),
+                    target: AssignmentTarget::ColumnName(ObjectName::from(vec![Ident::with_span(
+                        Span::new(Location::new(1, 37), Location::new(1, 40)),
+                        "col"
+                    )])),
+                },],
+                selection: None,
+                returning: None,
+                from: None,
+                or: None
+            },
+        ]
+    );
+}
diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs
index 27c60b052..6b85d43e8 100644
--- a/tests/sqlparser_mysql.rs
+++ b/tests/sqlparser_mysql.rs
@@ -1367,6 +1367,7 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
             ParserOptions {
                 trailing_commas: false,
                 unescape: false,
+                require_semicolon_statement_delimiter: true,
             }
         )
         .verified_stmt(sql),