mirror of
https://github.com/FirebirdSQL/firebird-qa.git
synced 2025-01-22 21:43:06 +01:00
282 lines
9.8 KiB
Python
282 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.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
|