6
0
mirror of https://github.com/FirebirdSQL/firebird-qa.git synced 2025-01-22 13:33:07 +01:00

Added/Updated tests\bugs\core_1725_test.py: fully re-implemented - see notes.

This commit is contained in:
pavel-zotov 2024-10-28 14:21:51 +03:00
parent df802f3bfc
commit 38bdbb14ef

View File

@ -5,17 +5,57 @@ ID: issue-2149
ISSUE: 2149
TITLE: Unable to restore a database with inactive indices if any SP/trigger contains an explicit plan
DESCRIPTION:
We create table and indices for it.
Then we create trigger for this table, view, procedure, function and package - and all of them have DDL
which explicitly uses 'TEST ORDER <index>' in execution plan.
Such database then backed up and restored with command switch '-i(nactive)'.
Restore is logged and we check that this log does not contain 'gbak:error' message (and eventually completes OK).
Restored database must contain all created DB objects, i.e. we must have ability to explicitly specify them in SQL.
Table trigger (containing explicit PLAN clause in its DDL) also must exist and remain active.
Before this bug was fixed:
1) log of restore contained:
gbak: ERROR:Error while parsing function FN_WORKER's BLR
gbak: ERROR: index TEST_X cannot be used in the specified plan
2) restored database had NO indices that were explicitly specified in any DDL and any attempt to use appropriate
DB object failed with SQLSTATE = 42S02/39000/42000 ('Table/Procedure/Function} unknown').
JIRA: CORE-1725
FBTEST: bugs.core_1725
NOTES:
[28.10.2024] pzotov
1. Test fully re-implemented.
We do NOT extract metadata before and after restore (in order to compare it):
in FB 6.x 'gbak -i' leads to 'create INACTIVE index ...' statements in generated SQL
(see https://github.com/FirebirdSQL/firebird/issues/8091 - "Ability to create an inactive index").
Comparison of metadata that was before and after restore has no much sense.
Rather, we have to check SQL/DML that attempt to use DB object which DDL contain
explicitly specified execution plan.
All such actions must raise error related to invalid BLR, but *not* error about missing DB object.
BTW: it looks strange that such messages contain "-there is no index TEST_X for table TEST".
Such index definitely DOES exist but it is inactive.
2. Bug existed up to 17-jan-2019.
It was fixed by commits related to other issues, namely:
3.x: a74130019af89012cc1e04ba18bbc9c4a69e1a5d // 17.01.2019
4.x: fea7c61d9741dc142fa020bf3aa93af7e52e2002 // 17.01.2019
5.x: fea7c61d9741dc142fa020bf3aa93af7e52e2002 // 18.01.2019
("Attempted to fix CORE-2440, CORE-5118 and CORE-5900 together (expression indices contain NULL keys after restore).")
Checked on:
6.0.0.511-c4bc943; 5.0.2.1547-1e08f5e; 4.0.0.1384-fea7c61 (17-jan-2019, just after fix); 3.0.13.33793-3e62713
"""
import locale
import re
from collections import defaultdict
from difflib import unified_diff
from pathlib import Path
import pytest
from firebird.qa import *
from firebird.driver import SrvRestoreFlag, SrvRepairFlag
from io import BytesIO
from difflib import unified_diff
substitutions_1 = [('[ \t]+', ' ')]
init_script = """
set bail on;
@ -28,7 +68,6 @@ create or alter function fn_init returns int as begin end;
create or alter function fn_main returns int as begin end;
create or alter function fn_worker returns int as begin end;
create table test(id int primary key, x int, y int);
create index test_x on test(x);
create descending index test_y on test(y);
@ -155,8 +194,7 @@ begin
end
^
create or alter trigger trg_attach active on connect position 0 as
create or alter trigger test_bi for test active before insert position 0 as
declare c int;
begin
if ( rdb$get_context('USER_SESSION','INITIAL_DDL') is null ) then
@ -177,41 +215,180 @@ set term ;^
commit;
"""
db = db_factory(init=init_script)
substitutions = [('[ \t]+', ' '), ('(-)?invalid request BLR at offset \\d+', 'invalid request BLR at offset')]
act = python_act('db')
db = db_factory(init = init_script)
act = python_act('db', substitutions = substitutions)
tmp_fbk= temp_file('tmp_core_1725.fbk')
tmp_fdb = temp_file('tmp_core_1725.fdb')
@pytest.mark.version('>=3.0.6')
def test_1(act: Action):
# Extract metadata from initial DB
act.isql(switches=['-nod', '-x'])
meta_1 = act.stdout
def test_1(act: Action, tmp_fbk: Path, tmp_fdb: Path, capsys):
outcomes_map = defaultdict(str)
act.gbak(switches=['-b', act.db.dsn, str(tmp_fbk)])
# restore _WITHOUT_ building indices:
act.gbak(switches=['-rep', '-i', '-v', str(tmp_fbk), str(tmp_fdb) ], combine_output = True, io_enc = locale.getpreferredencoding())
watching_patterns = [re.compile(x, re.IGNORECASE) for x in (r'gbak:\s?ERROR(:)?\s?', r'gbak:finis.*\s+going home', r'gbak:adjust.*\s+flags')]
for line in act.clean_stdout.splitlines():
for p in watching_patterns:
if p.search(line):
outcomes_map['restore_log'] += line+'\n'
act.reset()
# backup + restore _WITHOUT_ building indices:
backup = BytesIO()
with act.connect_server() as srv:
srv.database.local_backup(database=act.db.db_path, backup_stream=backup)
backup.seek(0)
srv.database.local_restore(backup_stream=backup, database=act.db.db_path,
flags=SrvRestoreFlag.DEACTIVATE_IDX | SrvRestoreFlag.REPLACE)
# Get FB log before validation, run validation and get FB log after it:
log_before = act.get_firebird_log()
srv.database.repair(database=act.db.db_path, flags=SrvRepairFlag.CORRUPTION_CHECK)
#act.gfix(switches=['-v', '-full', act.db.dsn])
log_after = act.get_firebird_log()
# Extract metadata from restored DB
act.isql(switches=['-nod', '-x'])
meta_2 = act.stdout
###########################################################################
check_metadata = """
set list on;
set count on;
select ri.rdb$index_name, ri.rdb$index_inactive from rdb$indices ri where ri.rdb$relation_name = upper('test') and ri.rdb$index_name starting with upper('test');
select p.rdb$package_name, p.rdb$procedure_name as sp_name, p.rdb$valid_blr as sp_valid_blr
from rdb$procedures p
where p.rdb$system_flag is distinct from 1
order by p.rdb$package_name, p.rdb$procedure_name
;
select f.rdb$package_name, f.rdb$function_name as fn_name, f.rdb$valid_blr as fn_valid_blr
from rdb$functions f
where f.rdb$system_flag is distinct from 1
order by f.rdb$package_name, f.rdb$function_name
;
select rt.rdb$trigger_name, rt.rdb$trigger_inactive, rt.rdb$valid_blr as tg_valid_blr
from rdb$triggers rt
where
rt.rdb$system_flag is distinct from 1 and
rt.rdb$relation_name = upper('test')
;
set count off;
"""
act.isql(switches=['-nod', '-q', str(tmp_fdb)], input = check_metadata, credentials = True, charset = 'utf8', connect_db = False, combine_output = True, io_enc = locale.getpreferredencoding())
for line in act.clean_stdout.splitlines():
outcomes_map['check_metadata'] += line+'\n'
act.reset()
###########################################################################
check_avail_db_objects = """
set list on;
set echo on;
select * from v_worker;
execute procedure sp_main;
select fn_main() from rdb$database;
execute procedure pg_test.pg_sp_worker;
select pg_test.pg_fn_worker() from rdb$database;
insert into test(id, x, y) values(-1, -1, -1) returning id, x, y;
"""
act.isql(switches=['-nod', '-q', str(tmp_fdb)], input = check_avail_db_objects, credentials = True, charset = 'utf8', connect_db = False, combine_output = True, io_enc = locale.getpreferredencoding())
for line in act.clean_stdout.splitlines():
outcomes_map['check_avail_db_objects'] += line+'\n'
act.reset()
for k,v in outcomes_map.items():
print(k)
for p in v.splitlines():
print(p)
print('')
###########################################################################
act.expected_stdout = """
restore_log
gbak:finishing, closing, and going home
gbak:adjusting the ONLINE and FORCED WRITES flags
check_metadata
RDB$INDEX_NAME TEST_X
RDB$INDEX_INACTIVE 1
RDB$INDEX_NAME TEST_Y
RDB$INDEX_INACTIVE 1
Records affected: 2
RDB$PACKAGE_NAME <null>
SP_NAME SP_INIT
SP_VALID_BLR 1
RDB$PACKAGE_NAME <null>
SP_NAME SP_MAIN
SP_VALID_BLR 1
RDB$PACKAGE_NAME <null>
SP_NAME SP_WORKER
SP_VALID_BLR 1
RDB$PACKAGE_NAME PG_TEST
SP_NAME PG_SP_WORKER
SP_VALID_BLR 1
Records affected: 4
RDB$PACKAGE_NAME <null>
FN_NAME FN_INIT
FN_VALID_BLR 1
RDB$PACKAGE_NAME <null>
FN_NAME FN_MAIN
FN_VALID_BLR 1
RDB$PACKAGE_NAME <null>
FN_NAME FN_WORKER
FN_VALID_BLR 1
RDB$PACKAGE_NAME PG_TEST
FN_NAME PG_FN_WORKER
FN_VALID_BLR 1
Records affected: 4
RDB$TRIGGER_NAME TEST_BI
RDB$TRIGGER_INACTIVE 0
TG_VALID_BLR 1
Records affected: 1
check_avail_db_objects
select * from v_worker;
Statement failed, SQLSTATE = 42000
invalid request BLR at offset 35
-there is no index TEST_Y for table TEST
execute procedure sp_main;
Statement failed, SQLSTATE = 2F000
Error while parsing procedure SP_MAIN's BLR
-Error while parsing procedure SP_WORKER's BLR
-invalid request BLR at offset 66
-there is no index TEST_X for table TEST
select fn_main() from rdb$database;
Statement failed, SQLSTATE = 2F000
Error while parsing function FN_MAIN's BLR
-Error while parsing function FN_WORKER's BLR
-invalid request BLR at offset 72
-there is no index TEST_X for table TEST
execute procedure pg_test.pg_sp_worker;
Statement failed, SQLSTATE = 2F000
Error while parsing procedure PG_TEST.PG_SP_WORKER's BLR
-invalid request BLR at offset 66
-there is no index TEST_X for table TEST
select pg_test.pg_fn_worker() from rdb$database;
Statement failed, SQLSTATE = 2F000
Error while parsing function PG_TEST.PG_FN_WORKER's BLR
-invalid request BLR at offset 72
-there is no index TEST_X for table TEST
insert into test(id, x, y) values(-1, -1, -1) returning id, x, y;
Statement failed, SQLSTATE = 42000
invalid request BLR at offset
-there is no index TEST_X for table TEST
"""
act.stdout = capsys.readouterr().out
assert act.clean_stdout == act.clean_expected_stdout
act.reset()
# Restore with indices. This is necessary to drop the database safely otherwise connect
# to drop will fail in test treadown as connect trigger referes to index tat was not activated
with act.connect_server() as srv:
backup.seek(0)
srv.database.local_restore(backup_stream=backup, database=act.db.db_path,
flags=SrvRestoreFlag.REPLACE)
#
diff_meta = ''.join(unified_diff(meta_1.splitlines(), meta_2.splitlines()))
diff_log = [line for line in unified_diff(log_before, log_after) if line.startswith('+') and 'Validation finished:' in line]
# Checks
assert diff_meta == ''
assert diff_log == ['+\tValidation finished: 0 errors, 0 warnings, 0 fixed\n']