mirror of
https://github.com/FirebirdSQL/firebird-qa.git
synced 2025-01-22 21:43:06 +01:00
210 lines
7.4 KiB
Python
210 lines
7.4 KiB
Python
#coding:utf-8
|
|
|
|
"""
|
|
ID: issue-4310
|
|
ISSUE: 4310
|
|
TITLE: DELETE FROM MON$STATEMENTS does not interrupt a longish fetch
|
|
DESCRIPTION:
|
|
JIRA: CORE-3977
|
|
FBTEST: bugs.core_3977
|
|
NOTES:
|
|
[12.12.2023] pzotov
|
|
1. Re-implemented using code that does not operate with execute statement on external data source:
|
|
we use asynchronous launch of ISQL with redirecting data to OS null device and check only STDERR
|
|
content after deletion record in MON$STATEMENTS (content of ISQL log which did 'longish fetch'
|
|
*must* contain text about interrupting of query, its SQLSTATE = HY008).
|
|
Removed substitutions.
|
|
|
|
2. Removed 'naive method' for waiting until ISQL process started its work (used 'time.sleep(...)').
|
|
Intead, we have to use loop which does query to mon$ statements and looks there for record which
|
|
mon$sql_text contains some KNOWN phrase - see 'HEAVY_TAG'. See function check_mon_for_pid_appearance()
|
|
|
|
Checked on 3.0.12.33725, 4.0.5.3040, 5.0.0.1294, 6.0.0.172
|
|
"""
|
|
|
|
import os
|
|
import subprocess
|
|
from datetime import datetime as dt
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from firebird.qa import *
|
|
from firebird.driver import tpb, Isolation, TraLockResolution, DatabaseError
|
|
|
|
MAX_WAIT_FOR_ISQL_PID_APPEARS_MS = 10000
|
|
HEAVY_TAG = '/* HEAVY_TAG */'
|
|
|
|
init_script = """
|
|
create sequence g;
|
|
commit;
|
|
"""
|
|
|
|
db = db_factory(init=init_script)
|
|
act = python_act('db')
|
|
|
|
heavy_sql = temp_file('work_script.sql')
|
|
heavy_log = temp_file('work_script.log')
|
|
|
|
#---------------------------------------------------------
|
|
|
|
def check_mon_for_pid_appearance(act: Action, p_async_launched: subprocess.CompletedProcess, HEAVY_TAG: str, MAX_WAIT_FOR_ISQL_PID_APPEARS_MS: int):
|
|
|
|
chk_mon_sql = """
|
|
select 1
|
|
from mon$attachments a
|
|
join mon$statements s
|
|
using (mon$attachment_id)
|
|
where
|
|
a.mon$attachment_id <> current_connection
|
|
and a.mon$remote_pid = ?
|
|
and s.mon$sql_text containing ?
|
|
"""
|
|
|
|
found_in_mon_tables = False
|
|
with act.db.connect() as con_watcher:
|
|
|
|
custom_tpb = tpb(isolation = Isolation.SNAPSHOT, lock_timeout = -1)
|
|
tx_watcher = con_watcher.transaction_manager(custom_tpb)
|
|
cur_watcher = tx_watcher.cursor()
|
|
|
|
ps = cur_watcher.prepare(chk_mon_sql)
|
|
|
|
i = 0
|
|
da = dt.now()
|
|
while True:
|
|
cur_watcher.execute(ps, (p_async_launched.pid, HEAVY_TAG,) )
|
|
mon_result = -1
|
|
for r in cur_watcher:
|
|
mon_result = r[0]
|
|
|
|
tx_watcher.commit()
|
|
db = dt.now()
|
|
diff_ms = (db-da).seconds*1000 + (db-da).microseconds//1000
|
|
if mon_result == 1:
|
|
found_in_mon_tables = True
|
|
break
|
|
elif diff_ms > MAX_WAIT_FOR_ISQL_PID_APPEARS_MS:
|
|
break
|
|
|
|
time.sleep(0.1)
|
|
|
|
ps.free()
|
|
|
|
assert found_in_mon_tables, f'Could not find attachment in mon$ tables for {MAX_WAIT_FOR_ISQL_PID_APPEARS_MS} ms.'
|
|
|
|
#---------------------------------------------------------
|
|
|
|
@pytest.mark.version('>=3')
|
|
def test_1(act: Action, heavy_sql: Path, heavy_log: Path, capsys):
|
|
longish_fetch = f"""
|
|
out {os.devnull};
|
|
set heading off;
|
|
select {HEAVY_TAG} 'SEQ_VALUE_' || gen_id(g,1) from rdb$types,rdb$types,rdb$types,rdb$types,rdb$types;
|
|
out;
|
|
"""
|
|
heavy_sql.write_text(longish_fetch)
|
|
|
|
with open(heavy_log, 'w') as f:
|
|
# Starting ISQL in separate process with doing 'heavy query'
|
|
p_work_sql = subprocess.Popen( [ act.vars['isql'], '-i', str(heavy_sql),
|
|
'-user', act.db.user,
|
|
'-password', act.db.password,
|
|
act.db.dsn
|
|
],
|
|
stderr = f
|
|
)
|
|
|
|
# Wait for ISQL appear in MON$ tables:
|
|
######################################
|
|
check_mon_for_pid_appearance(act, p_work_sql, HEAVY_TAG, MAX_WAIT_FOR_ISQL_PID_APPEARS_MS)
|
|
|
|
# Run 2nd isql and issue there DELETE FROM MON$ATATSMENTS command. First ISQL process should be terminated for short time.
|
|
drop_sql = f"""
|
|
set bail on;
|
|
set list on;
|
|
set count on;
|
|
commit;
|
|
set term ^;
|
|
execute block returns(kill_sttm_outcome varchar(255)) as
|
|
declare v_heavy_conn type of column mon$statements.mon$attachment_id;
|
|
declare v_heavy_pid type of column mon$attachments.mon$remote_pid;
|
|
begin
|
|
delete from mon$statements
|
|
where
|
|
mon$attachment_id != current_connection
|
|
and mon$sql_text containing '{HEAVY_TAG}'
|
|
returning mon$attachment_id into v_heavy_conn
|
|
;
|
|
|
|
select mon$remote_pid
|
|
from mon$attachments a
|
|
where mon$attachment_id = :v_heavy_conn
|
|
into v_heavy_pid;
|
|
if (v_heavy_pid = {p_work_sql.pid}) then
|
|
kill_sttm_outcome = 'OK';
|
|
else
|
|
kill_sttm_outcome = 'UNEXPECTED: v_heavy_conn=' || coalesce(v_heavy_conn, '[null]') || ', v_heavy_pid=' || coalesce(v_heavy_pid, '[null]') || ', p_work_sql.pid=' || {p_work_sql.pid}
|
|
;
|
|
suspend;
|
|
end
|
|
^
|
|
set term ;^
|
|
exit;
|
|
"""
|
|
|
|
try:
|
|
act.isql(switches=[], input=drop_sql, combine_output = True)
|
|
delete_from_mon_sttm_log = act.clean_string(act.stdout)
|
|
##################################
|
|
# Result: <heavy_log> must contain:
|
|
# Statement failed, SQLSTATE = HY008
|
|
# operation was cancelled
|
|
finally:
|
|
p_work_sql.terminate()
|
|
|
|
# Run checking query: what is resuling value of sequence 'g' ?
|
|
# (it must be > 0 and < total number of records to be handled).
|
|
check_sql = """
|
|
--set echo on;
|
|
set list on;
|
|
set count on;
|
|
select iif( current_gen > 0 and current_gen < total_rows,
|
|
'OK: query was interrupted in the middle point.',
|
|
'WRONG! Query to be interrupted '
|
|
|| iif(current_gen <= 0, 'did not start.', 'already gone, current_gen = '||current_gen )
|
|
) as result_msg
|
|
from (
|
|
select gen_id(g,0) as current_gen, c.n * c.n * c.n * c.n * c.n as total_rows
|
|
from (select (select count(*) from rdb$types) as n from rdb$database) c
|
|
);
|
|
"""
|
|
act.isql(switches=[], input=check_sql, combine_output = True)
|
|
|
|
with open(heavy_log, 'r') as f:
|
|
for line in f:
|
|
if not 'line' in line:
|
|
print('LONGISH FETCH LOG:',' '.join(line.upper().split()))
|
|
|
|
for line in delete_from_mon_sttm_log.splitlines():
|
|
print('DEL FROM MON$STTM: ', ' '.join(line.upper().split()))
|
|
|
|
for line in act.clean_string(act.stdout).splitlines():
|
|
print('CHECK RESULTS LOG: ', ' '.join(line.upper().split()))
|
|
|
|
|
|
expected_stdout = """
|
|
LONGISH FETCH LOG: STATEMENT FAILED, SQLSTATE = HY008
|
|
LONGISH FETCH LOG: OPERATION WAS CANCELLED
|
|
|
|
DEL FROM MON$STTM: KILL_STTM_OUTCOME OK
|
|
DEL FROM MON$STTM: RECORDS AFFECTED: 1
|
|
|
|
CHECK RESULTS LOG: RESULT_MSG OK: QUERY WAS INTERRUPTED IN THE MIDDLE POINT.
|
|
CHECK RESULTS LOG: RECORDS AFFECTED: 1
|
|
"""
|
|
|
|
act.expected_stdout = expected_stdout
|
|
act.stdout = capsys.readouterr().out
|
|
assert act.clean_stdout == act.clean_expected_stdout
|