6
0
mirror of https://github.com/FirebirdSQL/firebird-qa.git synced 2025-01-22 21:43:06 +01:00
firebird-qa/tests/bugs/gh_7466_test.py

283 lines
9.8 KiB
Python

#coding:utf-8
"""
ID: issue-7466
ISSUE: https://github.com/FirebirdSQL/firebird/issues/7466
TITLE: Add COMPILE trace events for procedures/functions/triggers
DESCRIPTION:
Test prepares trace config with requrement to see events related to units compilation.
We create standalone procedure, standalone function and package with procedure and function.
Also, we create three type of triggers: for a table, for DB-level event ('on connect') and for any DDL statement.
Then test launches trace session and runs ISQL with appropriate script with above mentioned actions.
Finally, we parse trace log and filter only lines containing names of created PSQL units which we know.
No errors must present in the trace log. All created units must be specified in blocks related to compilation.
NOTES:
[17-aug-2023] pzotov
::: NB :::
0. This test DOES NOT check tracking of plans for queries inside those PSQL modules (i.e. strarting ticket issue,
see: https://github.com/FirebirdSQL/firebird/pull/7466#issue-1564439735 ).
SEPARATE TEST WILL BE IMPLEMENTED FOR THAT.
1. It must be noted that the term 'COMPILE' means parsing of BLR code into an execution tree, i.e. this action
occurs when unit code is loaded into metadata cache.
2. Procedures and functions are loaded into metadata cache immediatelly when they are created.
3. Triggers are loaded into metadata cache in 'deferred' way, when something occurs that causes trigger to fire.
So, DML trigger will fire when we do (for example) INSERT, DB_level trigger - when we do some action on DB level
(e.g. connect/disconnect), and similar to DDL trigger.
4. Currently there is no way to specify in the trace what EXACT type of DDL trigger fired. It is shown as "AFTER DDL".
5. Lot of system-related triggers are displayed in the trace log during creating user-defined units:
Trigger RDB$TRIGGER_26 FOR RDB$RELATION_CONSTRAINTS
Trigger RDB$TRIGGER_18 FOR RDB$INDEX_SEGMENTS (BEFORE UPDATE)
Trigger RDB$TRIGGER_8 FOR RDB$USER_PRIVILEGES (BEFORE DELETE)
etc. Test ignores them and takes in account only triggers that have been creates by "our" SQL script.
6. User-defined DDL trigger will be loaded into metadata cache MULTIPLE times (three in this test: for create view,
its altering and its dropping - although there is no re-connect between these actions). This is conisdered as bug,
see: https://github.com/FirebirdSQL/firebird/pull/7426 (currently it is not yet fixed).
Checked on 5.0.0.1164.
Thanks to dimitr for explanations.
Discussed with dimitr, letters 17.08.2023.
[06-sep-2023] pzotov
Changed expected output: DDL trigger is loaded into metadata cache only once, so we have to check only SINGLE
occurence of "Trigger TRG_DDL (AFTER DDL)" event.
See also: https://github.com/FirebirdSQL/firebird/commit/00c2d10102468d5494b413c0de295079f62a27ec
Checkec on 5.0.0.1190
"""
import locale
import re
import pytest
from firebird.qa import *
db = db_factory()
act = python_act('db')
trace = ['log_initfini = false',
'log_errors = true',
'log_procedure_compile = true',
'log_function_compile = true',
'log_trigger_compile = true',
]
allowed_patterns = [ ' ERROR AT ', 'Trigger TRG_', 'Procedure (SP_TEST|PG_TEST.PG_SP_WORKER)', 'Function (FN_TEST|PG_TEST.PG_FN_WORKER)' ]
allowed_patterns = [ re.compile(r, re.IGNORECASE) for r in allowed_patterns]
@pytest.mark.trace
@pytest.mark.version('>=5.0')
def test_1(act: Action, capsys):
test_script = f"""
set autoddl off;
recreate sequence g;
create table att_log (
msg varchar(60)
,dts timestamp default 'now'
);
recreate table ddl_log (
id integer,
ddl_event varchar(25),
sql blob sub_type text
);
recreate table test(id int primary key, x int, y int);
create index test_x on test(x);
create index test_y on test(y);
commit;
create or alter view v_init as
select count(*) as cnt from test group by x
rows 1
;
create or alter view v_worker as
select count(*) as cnt
from test
group by y
plan (TEST ORDER TEST_Y)
union all
select cnt from v_init
;
commit;
set term ^;
--##################################################################
create or alter procedure sp_test (
a_table varchar(63)
,a_field varchar(63)
) returns (
o_field_len int
) as
declare procedure sp_test_inner(a_x int) returns(o_y int) as
begin
for select y from test where x = :a_x into o_y do suspend;
end
begin
for
select f.rdb$field_length
from rdb$relation_fields rf
join rdb$fields f on rf.rdb$field_source=f.rdb$field_name
where rf.rdb$relation_name = upper(:a_table) and rf.rdb$field_name=upper(:a_field)
into o_field_len
do
suspend;
end
^
--##################################################################
create or alter function fn_test (
a_table varchar(63)
,a_field varchar(63)
) returns int
as
declare function fn_test_inner(a_x int) returns int as
begin
return ( select count(*) from test where x = :a_x );
end
begin
return (
select first 1 f.rdb$field_length
from rdb$relation_fields rf
join rdb$fields f on rf.rdb$field_source=f.rdb$field_name
where rf.rdb$relation_name = upper(:a_table) and rf.rdb$field_name=upper(:a_field)
);
end
^
--##################################################################
create or alter package pg_test as
begin
function pg_fn_worker returns int;
procedure pg_sp_worker;
end
^
recreate package body pg_test as
begin
function pg_fn_worker returns int as
declare function fn_test_inner_pg(a_x int) returns int as
begin
return ( select count(*) from test where x = :a_x );
end
begin
return (
select sum(cnt)
from (
select count(*) as cnt
from test group by x
plan (TEST ORDER TEST_X)
union all
select cnt from v_worker
)
);
end
procedure pg_sp_worker as
declare c int;
declare procedure sp_test_inner_pg(a_x int) returns(o_y int) as
begin
for select y from test where x = :a_x into o_y do suspend;
end
begin
select sum(cnt)
from (
select count(*) as cnt
from test group by x
plan (TEST ORDER TEST_X)
union all
select cnt from v_worker
)
into c
;
end
end
^
--##################################################################
-- DML trigger:
create trigger trg_test_biu for test before insert or update as
begin
if (inserting) then
new.id = coalesce(new.id, gen_id(g,1));
new.y = minvalue(new.y, new.x * new.x);
end
^
--##################################################################
-- DB level trigger:
create trigger trg_db_conn on connect
as
begin
if (current_user = 'SYSDBA') then
begin
in autonomous transaction
do
begin
insert into att_log (msg) values ( current_user || ' connected');
end
end
end
^
--##################################################################
-- DDL trigger:
create or alter trigger trg_ddl after any ddl statement
as
begin
insert into ddl_log(sql, ddl_event)
values (rdb$get_context('DDL_TRIGGER', 'SQL_TEXT'),
rdb$get_context('DDL_TRIGGER', 'DDL_EVENT') );
end
^
set term ;^
commit;
set autoddl on;
connect '{act.db.dsn}';
insert into test(x, y) select rand()*100, rand()*100 from rdb$types rows 10;
commit;
create view v_test as select * from test;
alter view v_test as select * from rdb$database;
drop view v_test;
commit;
"""
with act.trace(db_events=trace, encoding = locale.getpreferredencoding(), encoding_errors='utf8'):
act.isql(switches = ['-q'], input = test_script, combine_output = True, io_enc = locale.getpreferredencoding())
# Process trace
for line in act.trace_log:
if line.rstrip().split():
for p in allowed_patterns:
if p.search(line):
print(line.strip())
expected_stdout = f"""
Procedure SP_TEST:
Procedure PG_TEST.PG_SP_WORKER:
Function FN_TEST:
Function PG_TEST.PG_FN_WORKER:
Trigger TRG_DB_CONN (ON CONNECT):
Trigger TRG_TEST_BIU FOR TEST (BEFORE INSERT):
Trigger TRG_DDL (AFTER DDL):
"""
act.expected_stdout = expected_stdout
act.stdout = capsys.readouterr().out
assert act.clean_stdout == act.clean_expected_stdout