fix(sql): split GaussDB dollar-quoted routines

This commit is contained in:
zipg authored and GitHub committed 2026-07-28 00:51:34 +08:00
1 parent 59f9d5b2a1
commit 09f794fd97
4 files changed
+112 -4

No files matched your search

+76 -3
View File
@@ -113,6 +113,7 @@ struct SqlDialectProfile {
supports_custom_delimiter_commands: bool,
supports_mysql_routine_blocks: bool,
supports_dollar_quoted_strings: bool,
supports_postgres_dollar_quoted_routines: bool,
supports_hana_do_blocks: bool,
supports_go_batch_separator: bool,
keeps_sqlserver_module_batch_at_cursor: bool,
@@ -127,6 +128,7 @@ impl Default for SqlDialectProfile {
supports_custom_delimiter_commands: true,
supports_mysql_routine_blocks: false,
supports_dollar_quoted_strings: true,
supports_postgres_dollar_quoted_routines: false,
supports_hana_do_blocks: false,
supports_go_batch_separator: false,
keeps_sqlserver_module_batch_at_cursor: false,
@@ -136,6 +138,10 @@ impl Default for SqlDialectProfile {
impl SqlDialectProfile {
fn for_database_type(db_type: DatabaseType) -> Self {
if matches!(db_type, DatabaseType::Gaussdb) {
return Self::gaussdb();
}
if Self::is_oracle_like_database(db_type) {
return Self::oracle_like();
}
@@ -163,6 +169,10 @@ impl SqlDialectProfile {
Self { supports_oracle_plsql_blocks: true, supports_slash_line_block_delimiter: true, ..Self::default() }
}
fn gaussdb() -> Self {
Self { supports_postgres_dollar_quoted_routines: true, ..Self::oracle_like() }
}
fn sql_server() -> Self {
Self { supports_go_batch_separator: true, keeps_sqlserver_module_batch_at_cursor: true, ..Self::default() }
}
@@ -271,6 +281,7 @@ pub struct SqlStatementSplitter {
in_line_comment: bool,
in_block_comment: bool,
dollar_quote_tag: Option<String>,
postgres_dollar_quoted_routine: bool,
previous: Option<char>,
custom_delimiter: Option<String>,
options: SqlParsingOptions,
@@ -373,6 +384,11 @@ impl SqlStatementSplitter {
.flatten()
{
if self.custom_delimiter.is_none() && !self.on_delimiter_line() {
if self.options.profile.supports_postgres_dollar_quoted_routines
&& starts_with_postgres_dollar_quoted_routine_prefix(&self.buffer)
{
self.postgres_dollar_quoted_routine = true;
}
for tag_ch in tag.chars() {
self.buffer.push(tag_ch);
self.previous = Some(tag_ch);
@@ -416,6 +432,7 @@ impl SqlStatementSplitter {
self.buffer.push(ch);
}
} else if self.options.profile.supports_oracle_plsql_blocks
&& !self.postgres_dollar_quoted_routine
&& starts_with_oracle_plsql_block(&self.buffer)
{
self.buffer.push(ch);
@@ -445,6 +462,7 @@ impl SqlStatementSplitter {
statements.push(before.to_string());
}
self.buffer.clear();
self.postgres_dollar_quoted_routine = false;
self.previous = None;
i += 1;
continue;
@@ -464,6 +482,7 @@ impl SqlStatementSplitter {
}
}
self.buffer.clear();
self.postgres_dollar_quoted_routine = false;
self.previous = None;
i += 1;
continue;
@@ -515,6 +534,7 @@ impl SqlStatementSplitter {
statements.push(statement.to_string());
}
self.buffer.clear();
self.postgres_dollar_quoted_routine = false;
self.previous = None;
}
@@ -726,6 +746,7 @@ fn split_sql_statement_ranges_with_options(sql: &str, options: SqlParsingOptions
let mut in_block_comment = false;
let mut dollar_quote_tag: Option<String> = None;
let mut custom_delimiter: Option<String> = None;
let mut postgres_dollar_quoted_routine = false;
while i < sql.len() {
if let Some(tag) = &dollar_quote_tag {
@@ -779,6 +800,11 @@ fn split_sql_statement_ranges_with_options(sql: &str, options: SqlParsingOptions
options.profile.supports_dollar_quoted_strings.then(|| dollar_quote_tag_at_str(sql, i)).flatten()
{
if custom_delimiter.is_none() && !is_on_delimiter_line(sql, start, i) {
if options.profile.supports_postgres_dollar_quoted_routines
&& starts_with_postgres_dollar_quoted_routine_prefix(&sql[start..i])
{
postgres_dollar_quoted_routine = true;
}
i += tag.len();
dollar_quote_tag = Some(tag);
continue;
@@ -790,6 +816,7 @@ fn split_sql_statement_ranges_with_options(sql: &str, options: SqlParsingOptions
if options.profile.supports_slash_line_block_delimiter && line == "/" {
push_statement_range(&mut ranges, sql, start, line_start, options);
start = i + ch.len_utf8();
postgres_dollar_quoted_routine = false;
i = start;
continue;
}
@@ -802,6 +829,7 @@ fn split_sql_statement_ranges_with_options(sql: &str, options: SqlParsingOptions
}
custom_delimiter = if new_delimiter == ";" { None } else { Some(new_delimiter.to_string()) };
start = i + ch.len_utf8();
postgres_dollar_quoted_routine = false;
i = start;
continue;
}
@@ -836,8 +864,9 @@ fn split_sql_statement_ranges_with_options(sql: &str, options: SqlParsingOptions
}
push_statement_range(&mut ranges, sql, start, i, options);
} else {
let is_oracle_plsql =
options.profile.supports_oracle_plsql_blocks && starts_with_oracle_plsql_block(&sql[start..i]);
let is_oracle_plsql = options.profile.supports_oracle_plsql_blocks
&& !postgres_dollar_quoted_routine
&& starts_with_oracle_plsql_block(&sql[start..i]);
if is_oracle_plsql {
if !oracle_plsql_block_is_complete(&sql[start..i + ch.len_utf8()]) {
i += ch.len_utf8();
@@ -856,6 +885,7 @@ fn split_sql_statement_ranges_with_options(sql: &str, options: SqlParsingOptions
}
i += ch.len_utf8();
start = i;
postgres_dollar_quoted_routine = false;
}
_ => {
i += ch.len_utf8();
@@ -865,6 +895,7 @@ fn split_sql_statement_ranges_with_options(sql: &str, options: SqlParsingOptions
let end = i - delimiter.len();
push_statement_range(&mut ranges, sql, start, end, options);
start = i;
postgres_dollar_quoted_routine = false;
}
}
}
@@ -1990,6 +2021,19 @@ fn starts_with_oracle_plsql_block(sql: &str) -> bool {
OraclePlSqlBlock::parse(sql).starts_block()
}
fn starts_with_postgres_dollar_quoted_routine_prefix(sql: &str) -> bool {
let block = OraclePlSqlBlock::parse(sql);
let Some(first) = block.tokens.first() else {
return false;
};
if !first.is_word("CREATE") {
return false;
}
let tokens = OraclePlSqlBlock::skip_create_modifiers(&block.tokens[1..]);
tokens.first().is_some_and(|token| token.is_any_word(&["FUNCTION", "PROCEDURE"]))
&& block.tokens.iter().rev().find_map(OraclePlSqlToken::as_word) == Some("AS")
}
fn oracle_plsql_block_is_complete(sql: &str) -> bool {
OraclePlSqlBlock::parse(sql).is_complete()
}
@@ -3072,7 +3116,6 @@ SELECT 2;";
for db_type in [
DatabaseType::Oracle,
DatabaseType::Dameng,
DatabaseType::Gaussdb,
DatabaseType::Yashandb,
DatabaseType::Oscar,
DatabaseType::OceanbaseOracle,
@@ -3081,8 +3124,15 @@ SELECT 2;";
assert_eq!(profile, SqlDialectProfile::oracle_like());
assert!(profile.supports_oracle_plsql_blocks);
assert!(profile.supports_slash_line_block_delimiter);
assert!(!profile.supports_postgres_dollar_quoted_routines);
}
let gaussdb = SqlDialectProfile::for_database_type(DatabaseType::Gaussdb);
assert_eq!(gaussdb, SqlDialectProfile::gaussdb());
assert!(gaussdb.supports_oracle_plsql_blocks);
assert!(gaussdb.supports_slash_line_block_delimiter);
assert!(gaussdb.supports_postgres_dollar_quoted_routines);
let sql_server = SqlDialectProfile::for_database_type(DatabaseType::SqlServer);
assert_eq!(sql_server, SqlDialectProfile::sql_server());
assert!(sql_server.supports_go_batch_separator);
@@ -3148,6 +3198,29 @@ END;";
assert_eq!(split_sql_statements_for_database(sql, DatabaseType::Xugu), vec![sql.to_string()]);
}
#[test]
fn gaussdb_split_separates_dollar_quoted_function_from_following_statements() {
let sql = "\
DROP FUNCTION IF EXISTS dbx_issue_4572_tmp_md5_uuid;
CREATE OR REPLACE FUNCTION dbx_issue_4572_tmp_md5_uuid (v_str IN TEXT) RETURNS varchar(36) LANGUAGE PLPGSQL IMMUTABLE AS $function$
DECLARE
str1 TEXT;
BEGIN
str1 := md5(v_str);
RETURN CAST(str1 AS varchar(36));
END$function$;
DROP FUNCTION IF EXISTS dbx_issue_4572_tmp_missing;";
let statements = split_sql_statements_for_database(sql, DatabaseType::Gaussdb);
assert_eq!(statements.len(), 3);
assert_eq!(statements[0], "DROP FUNCTION IF EXISTS dbx_issue_4572_tmp_md5_uuid");
assert!(statements[1].starts_with("CREATE OR REPLACE FUNCTION dbx_issue_4572_tmp_md5_uuid"));
assert!(statements[1].ends_with("END$function$"));
assert_eq!(statements[2], "DROP FUNCTION IF EXISTS dbx_issue_4572_tmp_missing");
}
#[test]
fn oracle_like_split_keeps_issue_2405_anonymous_plsql_block_together() {
let sql = "\