mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
feat(firebird): keep routine and trigger blocks together
This commit is contained in:
@@ -980,6 +980,62 @@ SELECT 2 FROM DUMMY;`;
|
||||
it("still splits SAP HANA ALTER PROCEDURE metadata commands", () => {
|
||||
expect(rangeSqlTexts(splitSqlStatementRanges("ALTER PROCEDURE PUBLIC.P RECOMPILE; SELECT 1 FROM DUMMY;", "saphana"))).toEqual(["ALTER PROCEDURE PUBLIC.P RECOMPILE", "SELECT 1 FROM DUMMY"]);
|
||||
});
|
||||
|
||||
it("keeps Firebird CREATE OR ALTER TRIGGER blocks together (#12407)", () => {
|
||||
const trigger = `CREATE OR ALTER TRIGGER "WL_FYD_TP_AU" FOR "WL_FYD" ACTIVE
|
||||
AFTER UPDATE
|
||||
as
|
||||
begin
|
||||
/* Trigger text */
|
||||
IF ( COALESCE(NEW.QSTPS,0) <> COALESCE(OLD.QSTPS,0) ) THEN
|
||||
BEGIN
|
||||
UPDATE WL_FYD SET ZT='完成' WHERE ID= NEW.ID;
|
||||
END
|
||||
end;`;
|
||||
const sql = `${trigger}\nSELECT 2 FROM RDB$DATABASE;`;
|
||||
|
||||
expect(rangeSqlTexts(splitSqlStatementRanges(sql, "firebird"))).toEqual([trigger, "SELECT 2 FROM RDB$DATABASE"]);
|
||||
});
|
||||
|
||||
it("keeps Firebird procedure with local DECLARE statements together (#12407)", () => {
|
||||
const procedure = `CREATE OR ALTER PROCEDURE SP_GET_USER (USER_ID INT)
|
||||
RETURNS (USERNAME VARCHAR(50), TOTAL_AMOUNT NUMERIC(18, 2))
|
||||
AS
|
||||
DECLARE VARIABLE V_TEMP NUMERIC(18, 2);
|
||||
DECLARE VARIABLE V_STATUS INT;
|
||||
BEGIN
|
||||
SELECT FIRST 1 NAME FROM USERS WHERE ID = :USER_ID INTO :USERNAME;
|
||||
V_TEMP = 100.50;
|
||||
TOTAL_AMOUNT = :V_TEMP;
|
||||
SUSPEND;
|
||||
END;`;
|
||||
const sql = `${procedure}\nSELECT 1 FROM RDB$DATABASE;`;
|
||||
|
||||
expect(rangeSqlTexts(splitSqlStatementRanges(sql, "firebird"))).toEqual([procedure, "SELECT 1 FROM RDB$DATABASE"]);
|
||||
});
|
||||
|
||||
it("keeps Firebird EXECUTE BLOCK anonymous blocks together (#12407)", () => {
|
||||
const block = `EXECUTE BLOCK
|
||||
RETURNS (OUT_ID INT)
|
||||
AS
|
||||
DECLARE VARIABLE I INT;
|
||||
BEGIN
|
||||
I = 0;
|
||||
WHILE (I < 10) DO
|
||||
BEGIN
|
||||
I = I + 1;
|
||||
END
|
||||
OUT_ID = I;
|
||||
SUSPEND;
|
||||
END;`;
|
||||
const sql = `${block}\nSELECT 1 FROM RDB$DATABASE;`;
|
||||
|
||||
expect(rangeSqlTexts(splitSqlStatementRanges(sql, "firebird"))).toEqual([block, "SELECT 1 FROM RDB$DATABASE"]);
|
||||
});
|
||||
|
||||
it("still splits Firebird ALTER TRIGGER metadata commands (#12407)", () => {
|
||||
expect(rangeSqlTexts(splitSqlStatementRanges("ALTER TRIGGER TR_USERS INACTIVE; SELECT 1 FROM RDB$DATABASE;", "firebird"))).toEqual(["ALTER TRIGGER TR_USERS INACTIVE", "SELECT 1 FROM RDB$DATABASE"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("statementRangeAtCursor", () => {
|
||||
@@ -1791,6 +1847,23 @@ SET t.no = s.no`;
|
||||
|
||||
expect(range?.sql.trim()).toBe(sapHanaDoBlockFixture.slice(0, sapHanaDoBlockFixture.indexOf("\nSELECT 2")));
|
||||
});
|
||||
|
||||
it("returns the full Firebird trigger definition for cursors inside the body (#12407)", () => {
|
||||
const trigger = `CREATE OR ALTER TRIGGER "WL_FYD_TP_AU" FOR "WL_FYD" ACTIVE
|
||||
AFTER UPDATE
|
||||
as
|
||||
begin
|
||||
/* Trigger text */
|
||||
IF ( COALESCE(NEW.QSTPS,0) <> COALESCE(OLD.QSTPS,0) ) THEN
|
||||
BEGIN
|
||||
UPDATE WL_FYD SET ZT='完成' WHERE ID= NEW.ID;
|
||||
END
|
||||
end;`;
|
||||
const sql = `${trigger}\nSELECT 2 FROM RDB$DATABASE;`;
|
||||
const range = statementRangeAtCursor(sql, indexOf(sql, "UPDATE WL_FYD"), "firebird");
|
||||
|
||||
expect(range?.sql.trim()).toBe(trigger);
|
||||
});
|
||||
});
|
||||
|
||||
describe("executableStatementRanges", () => {
|
||||
@@ -1903,6 +1976,37 @@ DISTRIBUTED BY HASH(product_id) BUCKETS 1`;
|
||||
expect(rangeSqlTexts(executableStatementRanges(sapHanaDoBlockFixture, "saphana"))).toEqual([sapHanaDoBlockFixture.slice(0, sapHanaDoBlockFixture.indexOf("\nSELECT 2")), "SELECT 2 FROM DUMMY"]);
|
||||
});
|
||||
|
||||
it("does not expose run targets for statements inside a Firebird trigger block (#12407)", () => {
|
||||
const trigger = `CREATE OR ALTER TRIGGER "WL_FYD_TP_AU" FOR "WL_FYD" ACTIVE
|
||||
AFTER UPDATE
|
||||
as
|
||||
begin
|
||||
/* Trigger text */
|
||||
IF ( COALESCE(NEW.QSTPS,0) <> COALESCE(OLD.QSTPS,0) ) THEN
|
||||
BEGIN
|
||||
UPDATE WL_FYD SET ZT='完成' WHERE ID= NEW.ID;
|
||||
END
|
||||
end;`;
|
||||
const sql = `${trigger}\nSELECT 2 FROM RDB$DATABASE;`;
|
||||
|
||||
expect(rangeSqlTexts(executableStatementRanges(sql, "firebird"))).toEqual([trigger, "SELECT 2 FROM RDB$DATABASE"]);
|
||||
});
|
||||
|
||||
it("keeps Firebird trigger without trailing semicolon as one executable range (#12407)", () => {
|
||||
const trigger = `CREATE OR ALTER TRIGGER "WL_FYD_TP_AU" FOR "WL_FYD" ACTIVE
|
||||
AFTER UPDATE
|
||||
as
|
||||
begin
|
||||
/* Trigger text */
|
||||
IF ( COALESCE(NEW.QSTPS,0) <> COALESCE(OLD.QSTPS,0) ) THEN
|
||||
BEGIN
|
||||
UPDATE WL_FYD SET ZT='完成' WHERE ID= NEW.ID;
|
||||
END
|
||||
end`;
|
||||
|
||||
expect(rangeSqlTexts(executableStatementRanges(trigger, "firebird"))).toEqual([trigger]);
|
||||
});
|
||||
|
||||
it("returns executable SQL Server batches without GO delimiter lines", () => {
|
||||
expect(rangeSqlTexts(executableStatementRanges("SELECT 1\nGO\nSELECT 2;", "sqlserver"))).toEqual(["SELECT 1", "SELECT 2"]);
|
||||
});
|
||||
|
||||
@@ -267,6 +267,7 @@ const DATABASE_SOFT_STATEMENT_KEYWORDS: Partial<Record<DatabaseType, readonly st
|
||||
dameng: ["FLASHBACK", "LOCK", "PURGE"],
|
||||
gaussdb: ["DO", "LOCK"],
|
||||
"oceanbase-oracle": ["FLASHBACK", "LOCK", "PURGE"],
|
||||
firebird: ["RECREATE"],
|
||||
redis: [],
|
||||
mongodb: [],
|
||||
elasticsearch: [],
|
||||
@@ -672,7 +673,8 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
|
||||
const resolvedOraclePlSqlStatementEnd = oraclePlSqlStatementEnd;
|
||||
const isOraclePlSql = typeof resolvedOraclePlSqlStatementEnd === "number";
|
||||
const isSapHanaScriptBlock = isSapHanaScriptBlockDatabase(databaseType) && statementStart !== -1 && startsWithSapHanaScriptBlock(sql.slice(statementStart, i));
|
||||
if (isOraclePlSql || isSapHanaScriptBlock) {
|
||||
const isFirebirdRoutineBlock = isFirebirdRoutineBlockDatabase(databaseType) && statementStart !== -1 && startsWithFirebirdRoutineBlock(sql.slice(statementStart, i));
|
||||
if (isOraclePlSql || isSapHanaScriptBlock || isFirebirdRoutineBlock) {
|
||||
markContent(i);
|
||||
if (isOraclePlSql && i + 1 < resolvedOraclePlSqlStatementEnd) {
|
||||
i += 1;
|
||||
@@ -682,6 +684,10 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if (isFirebirdRoutineBlock && !firebirdRoutineBlockIsComplete(sql.slice(statementStart, i + 1))) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
flush(i + 1);
|
||||
} else {
|
||||
flush();
|
||||
@@ -807,6 +813,7 @@ function rangeForCursorInSoftRanges(sql: string, ranges: RawStatement[], pos: nu
|
||||
function splitStatementRangeAtSoftStarts(sql: string, statement: RawStatement, databaseType?: DatabaseType, parameterOptions?: SqlParameterOptions): RawStatement[] {
|
||||
if (keepsOracleStyleBlockTogether(statement.sql, databaseType, parameterOptions)) return [statement];
|
||||
if (isSapHanaScriptBlockStatement(statement.sql, databaseType)) return [statement];
|
||||
if (isFirebirdRoutineBlockStatement(statement.sql, databaseType)) return [statement];
|
||||
// Routine bodies contain top-level-looking SET/INSERT/SELECT lines that are not independent statements.
|
||||
if (isMysqlRoutineBlockDatabase(databaseType) && startsWithMysqlRoutineBlock(statement.sql, parameterOptions)) return [statement];
|
||||
// SQL Server control-flow batches use line-oriented BEGIN/EXEC tokens inside one IF/ELSE statement.
|
||||
@@ -1921,6 +1928,14 @@ function isSapHanaScriptBlockStatement(sql: string, databaseType?: DatabaseType)
|
||||
return isSapHanaScriptBlockDatabase(databaseType) && startsWithSapHanaScriptBlock(sql);
|
||||
}
|
||||
|
||||
function isFirebirdRoutineBlockDatabase(databaseType?: DatabaseType): boolean {
|
||||
return databaseType === "firebird";
|
||||
}
|
||||
|
||||
function isFirebirdRoutineBlockStatement(sql: string, databaseType?: DatabaseType): boolean {
|
||||
return isFirebirdRoutineBlockDatabase(databaseType) && startsWithFirebirdRoutineBlock(sql);
|
||||
}
|
||||
|
||||
function isMysqlRoutineBlockDatabase(databaseType?: DatabaseType): boolean {
|
||||
return !!databaseType && MYSQL_ROUTINE_BLOCK_DATABASES.has(databaseType);
|
||||
}
|
||||
@@ -2327,6 +2342,66 @@ function sapHanaScriptBlockIsComplete(sql: string): boolean {
|
||||
return sawBegin && blockDepth === 0 && caseDepth === 0 && tokens[tokens.length - 1]?.kind === "semicolon";
|
||||
}
|
||||
|
||||
const FIREBIRD_ROUTINE_OBJECT_TYPES = new Set(["PROCEDURE", "FUNCTION", "TRIGGER"]);
|
||||
|
||||
function startsWithFirebirdRoutineBlock(sql: string): boolean {
|
||||
const words = oraclePlSqlWords(sql);
|
||||
if (words.length === 0) return false;
|
||||
const first = words[0];
|
||||
if (first === "EXECUTE" && words[1] === "BLOCK") return true;
|
||||
if (first !== "CREATE" && first !== "RECREATE" && first !== "ALTER") return false;
|
||||
let index = 1;
|
||||
if (words[index] === "OR" && (words[index + 1] === "ALTER" || words[index + 1] === "REPLACE")) {
|
||||
index += 2;
|
||||
}
|
||||
const objectType = words[index];
|
||||
if (objectType === "PACKAGE") {
|
||||
if (words[index + 1] !== "BODY") return false;
|
||||
} else if (!objectType || !FIREBIRD_ROUTINE_OBJECT_TYPES.has(objectType)) {
|
||||
return false;
|
||||
}
|
||||
if (first === "ALTER") {
|
||||
// Firebird accepts `ALTER TRIGGER name ACTIVE/INACTIVE;`. Only treat ALTER
|
||||
// as a routine block when it defines a body via AS or BEGIN.
|
||||
return words.includes("AS") || words.includes("BEGIN");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function firebirdRoutineBlockIsComplete(sql: string): boolean {
|
||||
if (!startsWithFirebirdRoutineBlock(sql)) return false;
|
||||
|
||||
const tokens = oraclePlSqlTokens(sql);
|
||||
let blockDepth = 0;
|
||||
let caseDepth = 0;
|
||||
let sawBegin = false;
|
||||
|
||||
for (let index = 0; index < tokens.length; index += 1) {
|
||||
const token = tokens[index];
|
||||
if (token.kind !== "word") continue;
|
||||
|
||||
if (token.value === "BEGIN") {
|
||||
if (previousAdjacentWord(sql, tokens, index) === "END") continue;
|
||||
blockDepth += 1;
|
||||
sawBegin = true;
|
||||
continue;
|
||||
}
|
||||
if (token.value === "CASE") {
|
||||
if (previousAdjacentWord(sql, tokens, index) !== "END") caseDepth += 1;
|
||||
continue;
|
||||
}
|
||||
if (token.value === "END") {
|
||||
if (caseDepth > 0) {
|
||||
caseDepth -= 1;
|
||||
} else if (blockDepth > 0) {
|
||||
blockDepth -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sawBegin && blockDepth === 0 && caseDepth === 0 && tokens[tokens.length - 1]?.kind === "semicolon";
|
||||
}
|
||||
|
||||
function oraclePlSqlBlockEnd(sql: string): number | null {
|
||||
const tokens = oraclePlSqlTokens(sql);
|
||||
const words = oraclePlSqlWordValues(tokens);
|
||||
|
||||
@@ -205,6 +205,7 @@ struct SqlDialectProfile {
|
||||
supports_dollar_quoted_strings: bool,
|
||||
supports_postgres_dollar_quoted_routines: bool,
|
||||
supports_hana_script_blocks: bool,
|
||||
supports_firebird_routine_blocks: bool,
|
||||
supports_go_batch_separator: bool,
|
||||
keeps_sqlserver_module_batch_at_cursor: bool,
|
||||
preserves_tdsql_leading_directives: bool,
|
||||
@@ -229,6 +230,7 @@ impl Default for SqlDialectProfile {
|
||||
supports_dollar_quoted_strings: true,
|
||||
supports_postgres_dollar_quoted_routines: false,
|
||||
supports_hana_script_blocks: false,
|
||||
supports_firebird_routine_blocks: false,
|
||||
supports_go_batch_separator: false,
|
||||
keeps_sqlserver_module_batch_at_cursor: false,
|
||||
preserves_tdsql_leading_directives: false,
|
||||
@@ -293,6 +295,10 @@ impl SqlDialectProfile {
|
||||
return Self::sap_hana();
|
||||
}
|
||||
|
||||
if matches!(db_type, DatabaseType::Firebird) {
|
||||
return Self::firebird();
|
||||
}
|
||||
|
||||
if matches!(db_type, DatabaseType::DuckDb) {
|
||||
return Self {
|
||||
supports_backslash_escaped_quotes: false,
|
||||
@@ -393,6 +399,10 @@ impl SqlDialectProfile {
|
||||
Self { supports_hana_script_blocks: true, ..Self::default() }
|
||||
}
|
||||
|
||||
fn firebird() -> Self {
|
||||
Self { supports_backslash_escaped_quotes: false, supports_firebird_routine_blocks: true, ..Self::default() }
|
||||
}
|
||||
|
||||
fn is_mysql_compatible_database(db_type: DatabaseType) -> bool {
|
||||
matches!(
|
||||
db_type,
|
||||
@@ -927,6 +937,13 @@ impl SqlStatementSplitter {
|
||||
if hana_script_block_is_complete(&self.buffer) {
|
||||
self.push_current_statement(&mut statements);
|
||||
}
|
||||
} else if self.options.profile.supports_firebird_routine_blocks
|
||||
&& starts_with_firebird_routine_block(&self.buffer)
|
||||
{
|
||||
self.buffer.push(ch);
|
||||
if firebird_routine_block_is_complete(&self.buffer) {
|
||||
self.push_current_statement(&mut statements);
|
||||
}
|
||||
} else {
|
||||
self.push_current_statement(&mut statements);
|
||||
}
|
||||
@@ -1314,6 +1331,9 @@ fn split_statement_range_at_blank_lines(
|
||||
if options.profile.supports_hana_script_blocks && starts_with_hana_script_block(&statement.text) {
|
||||
return vec![statement.clone()];
|
||||
}
|
||||
if options.profile.supports_firebird_routine_blocks && starts_with_firebird_routine_block(&statement.text) {
|
||||
return vec![statement.clone()];
|
||||
}
|
||||
|
||||
let mut ranges = Vec::new();
|
||||
let mut scanner = SqlScanner::with_profile(options.profile);
|
||||
@@ -1366,6 +1386,11 @@ fn starts_with_soft_statement_keyword(sql: &str, options: SqlParsingOptions) ->
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if options.profile.supports_firebird_routine_blocks
|
||||
&& starts_with_executable_sql_keyword_with_options(sql, &["RECREATE"], options)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
starts_with_executable_sql_keyword_with_options(
|
||||
sql,
|
||||
&[
|
||||
@@ -1571,6 +1596,14 @@ fn split_sql_statement_ranges_with_options(sql: &str, options: SqlParsingOptions
|
||||
continue;
|
||||
}
|
||||
push_statement_range(&mut ranges, sql, start, i + ch.len_utf8(), options);
|
||||
} else if options.profile.supports_firebird_routine_blocks
|
||||
&& starts_with_firebird_routine_block(&sql[start..i])
|
||||
{
|
||||
if !firebird_routine_block_is_complete(&sql[start..i + ch.len_utf8()]) {
|
||||
i += ch.len_utf8();
|
||||
continue;
|
||||
}
|
||||
push_statement_range(&mut ranges, sql, start, i + ch.len_utf8(), options);
|
||||
} else {
|
||||
push_statement_range(&mut ranges, sql, start, i, options);
|
||||
}
|
||||
@@ -3104,6 +3137,97 @@ impl HanaScriptBlock {
|
||||
}
|
||||
}
|
||||
|
||||
fn starts_with_firebird_routine_block(sql: &str) -> bool {
|
||||
FirebirdRoutineBlock::parse(sql).starts_block()
|
||||
}
|
||||
|
||||
fn firebird_routine_block_is_complete(sql: &str) -> bool {
|
||||
FirebirdRoutineBlock::parse(sql).is_complete()
|
||||
}
|
||||
|
||||
struct FirebirdRoutineBlock {
|
||||
tokens: Vec<OraclePlSqlToken>,
|
||||
}
|
||||
|
||||
impl FirebirdRoutineBlock {
|
||||
fn parse(sql: &str) -> Self {
|
||||
Self { tokens: oracle_plsql_tokens(sql) }
|
||||
}
|
||||
|
||||
fn starts_block(&self) -> bool {
|
||||
let Some(first) = self.tokens.first().and_then(OraclePlSqlToken::as_word) else {
|
||||
return false;
|
||||
};
|
||||
if first == "EXECUTE" {
|
||||
return self.tokens.get(1).is_some_and(|token| token.is_word("BLOCK"));
|
||||
}
|
||||
if !matches!(first, "CREATE" | "RECREATE" | "ALTER") {
|
||||
return false;
|
||||
}
|
||||
let mut index = 1;
|
||||
if self.tokens.get(index).is_some_and(|token| token.is_word("OR"))
|
||||
&& self.tokens.get(index + 1).is_some_and(|token| token.is_any_word(&["ALTER", "REPLACE"]))
|
||||
{
|
||||
index += 2;
|
||||
}
|
||||
let Some(object_type) = self.tokens.get(index).and_then(OraclePlSqlToken::as_word) else {
|
||||
return false;
|
||||
};
|
||||
if object_type == "PACKAGE" {
|
||||
if !self.tokens.get(index + 1).is_some_and(|token| token.is_word("BODY")) {
|
||||
return false;
|
||||
}
|
||||
} else if !matches!(object_type, "PROCEDURE" | "FUNCTION" | "TRIGGER") {
|
||||
return false;
|
||||
}
|
||||
if first == "ALTER" {
|
||||
// Firebird accepts `ALTER TRIGGER name ACTIVE/INACTIVE;`. Only treat ALTER
|
||||
// as a routine block when it defines a body via AS or BEGIN.
|
||||
return self.tokens.iter().any(|token| token.is_any_word(&["AS", "BEGIN"]));
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn is_complete(&self) -> bool {
|
||||
if !self.starts_block() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut block_depth = 0usize;
|
||||
let mut case_depth = 0usize;
|
||||
let mut saw_begin = false;
|
||||
|
||||
for (index, token) in self.tokens.iter().enumerate() {
|
||||
if token.is_word("BEGIN") {
|
||||
if previous_adjacent_word(&self.tokens, index).is_some_and(|previous| previous == "END") {
|
||||
continue;
|
||||
}
|
||||
block_depth += 1;
|
||||
saw_begin = true;
|
||||
continue;
|
||||
}
|
||||
if token.is_word("CASE") {
|
||||
if previous_adjacent_word(&self.tokens, index).is_none_or(|previous| previous != "END") {
|
||||
case_depth += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if token.is_word("END") {
|
||||
if case_depth > 0 {
|
||||
case_depth -= 1;
|
||||
} else {
|
||||
block_depth = block_depth.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
saw_begin
|
||||
&& block_depth == 0
|
||||
&& case_depth == 0
|
||||
&& self.tokens.last().is_some_and(OraclePlSqlToken::is_semicolon)
|
||||
}
|
||||
}
|
||||
|
||||
struct OraclePlSqlBlock {
|
||||
tokens: Vec<OraclePlSqlToken>,
|
||||
}
|
||||
@@ -5032,6 +5156,7 @@ SELECT 2;";
|
||||
assert!(!default.supports_oracle_plsql_blocks);
|
||||
assert!(!default.supports_oracle_style_routine_bodies);
|
||||
assert!(!default.supports_hana_script_blocks);
|
||||
assert!(!default.supports_firebird_routine_blocks);
|
||||
assert!(!default.supports_slash_line_block_delimiter);
|
||||
assert!(!default.supports_go_batch_separator);
|
||||
assert!(!default.keeps_sqlserver_module_batch_at_cursor);
|
||||
@@ -5108,6 +5233,11 @@ SELECT 2;";
|
||||
let sap_hana = SqlDialectProfile::for_database_type(DatabaseType::SapHana);
|
||||
assert_eq!(sap_hana, SqlDialectProfile::sap_hana());
|
||||
assert!(sap_hana.supports_hana_script_blocks);
|
||||
|
||||
let firebird = SqlDialectProfile::for_database_type(DatabaseType::Firebird);
|
||||
assert_eq!(firebird, SqlDialectProfile::firebird());
|
||||
assert!(firebird.supports_firebird_routine_blocks);
|
||||
assert!(!firebird.supports_backslash_escaped_quotes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -5187,6 +5317,134 @@ SELECT 2 FROM DUMMY;";
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn firebird_split_keeps_trigger_block_together() {
|
||||
let sql = "\
|
||||
CREATE OR ALTER TRIGGER \"WL_FYD_TP_AU\" FOR \"WL_FYD\" ACTIVE
|
||||
AFTER UPDATE
|
||||
as
|
||||
begin
|
||||
/* Trigger text */
|
||||
IF ( COALESCE(NEW.QSTPS,0) <> COALESCE(OLD.QSTPS,0) ) THEN
|
||||
BEGIN
|
||||
UPDATE WL_FYD SET ZT='完成' WHERE ID= NEW.ID;
|
||||
END
|
||||
end;
|
||||
SELECT 2 FROM RDB$DATABASE;";
|
||||
|
||||
assert_eq!(
|
||||
split_sql_statements_for_database(sql, DatabaseType::Firebird),
|
||||
vec![
|
||||
"CREATE OR ALTER TRIGGER \"WL_FYD_TP_AU\" FOR \"WL_FYD\" ACTIVE\nAFTER UPDATE\nas\nbegin\n /* Trigger text */\n IF ( COALESCE(NEW.QSTPS,0) <> COALESCE(OLD.QSTPS,0) ) THEN\n BEGIN\n UPDATE WL_FYD SET ZT='完成' WHERE ID= NEW.ID;\n END\nend;",
|
||||
"SELECT 2 FROM RDB$DATABASE"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn firebird_split_keeps_procedure_with_declarations_together() {
|
||||
let sql = "\
|
||||
CREATE OR ALTER PROCEDURE SP_GET_USER (USER_ID INT)
|
||||
RETURNS (USERNAME VARCHAR(50), TOTAL_AMOUNT NUMERIC(18, 2))
|
||||
AS
|
||||
DECLARE VARIABLE V_TEMP NUMERIC(18, 2);
|
||||
DECLARE VARIABLE V_STATUS INT;
|
||||
BEGIN
|
||||
SELECT FIRST 1 NAME FROM USERS WHERE ID = :USER_ID INTO :USERNAME;
|
||||
V_TEMP = 100.50;
|
||||
TOTAL_AMOUNT = :V_TEMP;
|
||||
SUSPEND;
|
||||
END;
|
||||
SELECT 1 FROM RDB$DATABASE;";
|
||||
|
||||
assert_eq!(
|
||||
split_sql_statements_for_database(sql, DatabaseType::Firebird),
|
||||
vec![
|
||||
"CREATE OR ALTER PROCEDURE SP_GET_USER (USER_ID INT)\nRETURNS (USERNAME VARCHAR(50), TOTAL_AMOUNT NUMERIC(18, 2))\nAS\nDECLARE VARIABLE V_TEMP NUMERIC(18, 2);\nDECLARE VARIABLE V_STATUS INT;\nBEGIN\n SELECT FIRST 1 NAME FROM USERS WHERE ID = :USER_ID INTO :USERNAME;\n V_TEMP = 100.50;\n TOTAL_AMOUNT = :V_TEMP;\n SUSPEND;\nEND;",
|
||||
"SELECT 1 FROM RDB$DATABASE"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn firebird_split_keeps_execute_block_together() {
|
||||
let sql = "\
|
||||
EXECUTE BLOCK
|
||||
RETURNS (OUT_ID INT)
|
||||
AS
|
||||
DECLARE VARIABLE I INT;
|
||||
BEGIN
|
||||
I = 0;
|
||||
WHILE (I < 10) DO
|
||||
BEGIN
|
||||
I = I + 1;
|
||||
END
|
||||
OUT_ID = I;
|
||||
SUSPEND;
|
||||
END;
|
||||
SELECT 1 FROM RDB$DATABASE;";
|
||||
|
||||
assert_eq!(
|
||||
split_sql_statements_for_database(sql, DatabaseType::Firebird),
|
||||
vec![
|
||||
"EXECUTE BLOCK\nRETURNS (OUT_ID INT)\nAS\nDECLARE VARIABLE I INT;\nBEGIN\n I = 0;\n WHILE (I < 10) DO\n BEGIN\n I = I + 1;\n END\n OUT_ID = I;\n SUSPEND;\nEND;",
|
||||
"SELECT 1 FROM RDB$DATABASE"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn firebird_split_keeps_routine_with_case_expression_together() {
|
||||
let sql = "\
|
||||
CREATE PROCEDURE P_CASE
|
||||
AS
|
||||
BEGIN
|
||||
UPDATE T SET FLAG = CASE WHEN X > 0 THEN 1 ELSE 0 END;
|
||||
END;
|
||||
SELECT 1 FROM RDB$DATABASE;";
|
||||
|
||||
assert_eq!(
|
||||
split_sql_statements_for_database(sql, DatabaseType::Firebird),
|
||||
vec![
|
||||
"CREATE PROCEDURE P_CASE\nAS\nBEGIN\n UPDATE T SET FLAG = CASE WHEN X > 0 THEN 1 ELSE 0 END;\nEND;",
|
||||
"SELECT 1 FROM RDB$DATABASE"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn firebird_plain_alter_trigger_still_flushes_at_semicolon() {
|
||||
assert_eq!(
|
||||
split_sql_statements_for_database(
|
||||
"ALTER TRIGGER TR_USERS INACTIVE; SELECT 1 FROM RDB$DATABASE;",
|
||||
DatabaseType::Firebird
|
||||
),
|
||||
vec!["ALTER TRIGGER TR_USERS INACTIVE", "SELECT 1 FROM RDB$DATABASE"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn firebird_cursor_statement_returns_whole_trigger() {
|
||||
let sql = "\
|
||||
CREATE OR ALTER TRIGGER \"WL_FYD_TP_AU\" FOR \"WL_FYD\" ACTIVE
|
||||
AFTER UPDATE
|
||||
as
|
||||
begin
|
||||
/* Trigger text */
|
||||
IF ( COALESCE(NEW.QSTPS,0) <> COALESCE(OLD.QSTPS,0) ) THEN
|
||||
BEGIN
|
||||
UPDATE WL_FYD SET ZT='完成' WHERE ID= NEW.ID;
|
||||
END
|
||||
end;
|
||||
SELECT 2 FROM RDB$DATABASE;";
|
||||
let cursor = sql.find("UPDATE WL_FYD").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
find_statement_at_cursor_for_database(sql, cursor, DatabaseType::Firebird),
|
||||
"CREATE OR ALTER TRIGGER \"WL_FYD_TP_AU\" FOR \"WL_FYD\" ACTIVE\nAFTER UPDATE\nas\nbegin\n /* Trigger text */\n IF ( COALESCE(NEW.QSTPS,0) <> COALESCE(OLD.QSTPS,0) ) THEN\n BEGIN\n UPDATE WL_FYD SET ZT='完成' WHERE ID= NEW.ID;\n END\nend;"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oracle_split_keeps_anonymous_block_with_local_routines_together() {
|
||||
let cases = [
|
||||
|
||||
Reference in New Issue
Block a user