2 @message |
assert
checked_mode: table, STDLOG: Records affected: 2
checked_mode: table, STDLOG: ID X
checked_mode: table, STDLOG:
checked_mode: table, STDLOG: -5 5
checked_mode: table, STDLOG: -4 4
checked_mode: table, STDLOG: -3 3
checked_mode: table, STDLOG: -2 2
checked_mode: table, STDLOG: -1 1
checked_mode: table, STDLOG: 0 0
checked_mode: table, STDLOG: 1 1
checked_mode: table, STDLOG: Records affected: 7
checked_mode: table, STDLOG: OLD_ID OP SNAP_NO_RANK
checked_mode: table, STDLOG:
- checked_mode: table, STDLOG: 0 UPD 1
+ checked_mode: table, STDLOG: 0UPD 1
- checked_mode: table, STDLOG: 1 UPD 1
+ checked_mode: table, STDLOG: 1UPD 1
- checked_mode: table, STDLOG: 0 UPD 2
+ checked_mode: table, STDLOG: 0UPD 2
- checked_mode: table, STDLOG: 1 UPD 2
+ checked_mode: table, STDLOG: 1UPD 2
- checked_mode: table, STDLOG: 0 UPD 3
+ checked_mode: table, STDLOG: 0UPD 3
- checked_mode: table, STDLOG: 1 UPD 3
+ checked_mode: table, STDLOG: 1UPD 3
- checked_mode: table, STDLOG: 0 UPD 4
+ checked_mode: table, STDLOG: 0UPD 4
- checked_mode: table, STDLOG: 1 UPD 4
+ checked_mode: table, STDLOG: 1UPD 4
- checked_mode: table, STDLOG: 0 UPD 5
+ checked_mode: table, STDLOG: 0UPD 5
- checked_mode: table, STDLOG: 1 UPD 5
+ checked_mode: table, STDLOG: 1UPD 5
checked_mode: table, STDLOG: Records affected: 10
checked_mode: table, STDERR: Statement failed, SQLSTATE 21000
checked_mode: table, STDERR: Multiple source records cannot match the same target during MERGE
checked_mode: table, STDERR:
LOG DETAILS:
2025-07-02 06:07:28.943
2025-07-02 06:07:28.951 act = <firebird.qa.plugin.Action object at [hex]>
2025-07-02 06:07:28.959 fn_worker_sql = PosixPath('/var/tmp/qa_2024/test_12475/tmp_worker.sql')
2025-07-02 06:07:28.967 fn_worker_log = PosixPath('/var/tmp/qa_2024/test_12475/tmp_worker.log')
2025-07-02 06:07:28.975 fn_worker_err = PosixPath('/var/tmp/qa_2024/test_12475/tmp_worker.err')
2025-07-02 06:07:28.982 capsys = <_pytest.capture.CaptureFixture object at [hex]>
2025-07-02 06:07:28.993
2025-07-02 06:07:29.003 @pytest.mark.trace
2025-07-02 06:07:29.011 @pytest.mark.version('>=4.0.2')
2025-07-02 06:07:29.018 def test_1(act: Action, fn_worker_sql: Path, fn_worker_log: Path, fn_worker_err: Path, capsys):
2025-07-02 06:07:29.025 sql_init = (act.files_dir / 'read-consist-sttm-restart-DDL.sql').read_text()
2025-07-02 06:07:29.031
2025-07-02 06:07:29.038 for checked_mode in('table', 'view'):
2025-07-02 06:07:29.051 target_obj = 'test' if checked_mode == 'table' else 'v_test'
2025-07-02 06:07:29.061
2025-07-02 06:07:29.073 SQL_TO_BE_RESTARTED = f"""
2025-07-02 06:07:29.084 merge /* {SQL_TAG_THAT_WE_WAITING_FOR} */ into {target_obj} t
2025-07-02 06:07:29.093 using (
2025-07-02 06:07:29.100 select s.id, s.x from {target_obj} as s
2025-07-02 06:07:29.106 where s.id <= 1
2025-07-02 06:07:29.112 order by s.id DESC -- added only 05-jun-2024; thanks to Vlad.
2025-07-02 06:07:29.118 ) s
2025-07-02 06:07:29.129 on abs(t.id) = abs(s.id)
2025-07-02 06:07:29.139 when matched then
2025-07-02 06:07:29.148 update set t.x = s.id * 100
2025-07-02 06:07:29.161 """
2025-07-02 06:07:29.170
2025-07-02 06:07:29.177 sql_addi = f'''
2025-07-02 06:07:29.183 set term ^;
2025-07-02 06:07:29.190 execute block as
2025-07-02 06:07:29.199 begin
2025-07-02 06:07:29.209 rdb$set_context('USER_SESSION', 'WHO', 'INIT_DATA');
2025-07-02 06:07:29.216 end
2025-07-02 06:07:29.222 ^
2025-07-02 06:07:29.228 set term ;^
2025-07-02 06:07:29.233
2025-07-02 06:07:29.240 -- INITIAL DATA: add rows with ID = 0...6
2025-07-02 06:07:29.246 -- #############
2025-07-02 06:07:29.254 insert into {target_obj}(id, x)
2025-07-02 06:07:29.262 select row_number()over()-1, row_number()over()-1
2025-07-02 06:07:29.272 from rdb$types rows 6;
2025-07-02 06:07:29.280
2025-07-02 06:07:29.288 commit;
2025-07-02 06:07:29.295 '''
2025-07-02 06:07:29.302
2025-07-02 06:07:29.307 act.isql(switches=['-q'], input = ''.join( (sql_init, sql_addi) ) )
2025-07-02 06:07:29.312 # ::: NOTE ::: We have to immediately quit if any error raised in prepare phase.
2025-07-02 06:07:29.318 # See also letter from dimitr, 01-feb-2022 14:46
2025-07-02 06:07:29.326 assert act.stderr == ''
2025-07-02 06:07:29.336 act.reset()
2025-07-02 06:07:29.344
2025-07-02 06:07:29.350 trace_cfg_items = [
2025-07-02 06:07:29.356 'time_threshold = 0',
2025-07-02 06:07:29.362 'log_errors = true',
2025-07-02 06:07:29.373 'log_statement_start = true',
2025-07-02 06:07:29.386 'log_statement_finish = true',
2025-07-02 06:07:29.396 ]
2025-07-02 06:07:29.406
2025-07-02 06:07:29.417 with act.trace(db_events = trace_cfg_items, encoding=locale.getpreferredencoding()):
2025-07-02 06:07:29.432
2025-07-02 06:07:29.445 with act.db.connect() as con_lock_1, act.db.connect() as con_lock_2, act.db.connect() as con_monitoring:
2025-07-02 06:07:29.459
2025-07-02 06:07:29.470 tpb_monitoring = tpb(isolation=Isolation.READ_COMMITTED_RECORD_VERSION, lock_timeout=0)
2025-07-02 06:07:29.478 tx_monitoring = con_monitoring.transaction_manager(tpb_monitoring)
2025-07-02 06:07:29.489 cur_monitoring = tx_monitoring.cursor()
2025-07-02 06:07:29.497
2025-07-02 06:07:29.505 cur_lock_1 = con_lock_1.cursor()
2025-07-02 06:07:29.519 cur_lock_2 = con_lock_2.cursor()
2025-07-02 06:07:29.531
2025-07-02 06:07:29.541 for i,c in enumerate((con_lock_1,con_lock_2)):
2025-07-02 06:07:29.556 sttm = f"execute block as begin rdb$set_context('USER_SESSION', 'WHO', 'LOCKER #{i+1}'); end"
2025-07-02 06:07:29.566 c.execute_immediate(sttm)
2025-07-02 06:07:29.573
2025-07-02 06:07:29.581
2025-07-02 06:07:29.588 #########################
2025-07-02 06:07:29.596 ### L O C K E R - 1 ###
2025-07-02 06:07:29.603 #########################
2025-07-02 06:07:29.609
2025-07-02 06:07:29.621 con_lock_1.execute_immediate( f'update {target_obj} set id=id where id = 0' )
2025-07-02 06:07:29.634
2025-07-02 06:07:29.648 worker_sql = f'''
2025-07-02 06:07:29.658 set list on;
2025-07-02 06:07:29.667 set autoddl off;
2025-07-02 06:07:29.679 set term ^;
2025-07-02 06:07:29.685 execute block as
2025-07-02 06:07:29.697 begin
2025-07-02 06:07:29.710 rdb$set_context('USER_SESSION','WHO', 'WORKER');
2025-07-02 06:07:29.719 end
2025-07-02 06:07:29.727 ^
2025-07-02 06:07:29.737 set term ;^
2025-07-02 06:07:29.748 commit;
2025-07-02 06:07:29.757 SET KEEP_TRAN_PARAMS ON;
2025-07-02 06:07:29.764 set transaction read committed read consistency;
2025-07-02 06:07:29.770 set list off;
2025-07-02 06:07:29.778 set wng off;
2025-07-02 06:07:29.791
2025-07-02 06:07:29.801 set count on;
2025-07-02 06:07:29.814 -- THIS MUST HANG:
2025-07-02 06:07:29.826 {SQL_TO_BE_RESTARTED};
2025-07-02 06:07:29.837
2025-07-02 06:07:29.848 -- check results:
2025-07-02 06:07:29.859 -- ###############
2025-07-02 06:07:29.869 select id,x from {target_obj} order by id;
2025-07-02 06:07:29.880
2025-07-02 06:07:29.894 select v.old_id, v.op, v.snap_no_rank
2025-07-02 06:07:29.906 from v_worker_log v
2025-07-02 06:07:29.915 where v.op = 'upd';
2025-07-02 06:07:29.923
2025-07-02 06:07:29.936
2025-07-02 06:07:29.945 --set width who 10;
2025-07-02 06:07:29.957 -- DO NOT check this! Values can differ here from one run to another!
2025-07-02 06:07:29.968 -- select id, trn, who, old_id, new_id, op, rec_vers, global_cn, snap_no from tlog_done order by id;
2025-07-02 06:07:29.975 rollback;
2025-07-02 06:07:29.981
2025-07-02 06:07:29.987 '''
2025-07-02 06:07:29.993
2025-07-02 06:07:30.000 fn_worker_sql.write_text(worker_sql)
2025-07-02 06:07:30.007
2025-07-02 06:07:30.016 with fn_worker_log.open(mode='w') as hang_out, fn_worker_err.open(mode='w') as hang_err:
2025-07-02 06:07:30.023
2025-07-02 06:07:30.031 ############################################################################
2025-07-02 06:07:30.038 ### L A U N C H W O R K E R U S I N G I S Q L, A S Y N C. ###
2025-07-02 06:07:30.044 ############################################################################
2025-07-02 06:07:30.050 p_worker = subprocess.Popen([act.vars['isql'], '-i', str(fn_worker_sql),
2025-07-02 06:07:30.056 '-user', act.db.user,
2025-07-02 06:07:30.062 '-password', act.db.password,
2025-07-02 06:07:30.070 '-pag', '999999',
2025-07-02 06:07:30.078 act.db.dsn
2025-07-02 06:07:30.084 ],
2025-07-02 06:07:30.090 stdout = hang_out,
2025-07-02 06:07:30.097 stderr = hang_err
2025-07-02 06:07:30.104 )
2025-07-02 06:07:30.110
2025-07-02 06:07:30.118 # NB: when ISQL will establish attach, first record that it must lock is ID = 0 -- see above SQL_TO_BE_RESTARTED
2025-07-02 06:07:30.125 # We must to ensure that this (worker) attachment has been really created and LOCKS this record:
2025-07-02 06:07:30.131 #
2025-07-02 06:07:30.136 #wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id = 0', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-02 06:07:30.147
2025-07-02 06:07:30.157 sttm = f'update {target_obj} set id = ? where abs( id ) = ?'
2025-07-02 06:07:30.164
2025-07-02 06:07:30.170 #########################
2025-07-02 06:07:30.175 ### L O C K E R - 2 ###
2025-07-02 06:07:30.181 #########################
2025-07-02 06:07:30.191 cur_lock_2.execute( sttm, ( -5, 5, ) )
2025-07-02 06:07:30.198 con_lock_2.commit()
2025-07-02 06:07:30.206 cur_lock_2.execute( sttm, ( -5, 5, ) )
2025-07-02 06:07:30.214
2025-07-02 06:07:30.225 #########################
2025-07-02 06:07:30.232 ### L O C K E R - 1 ###
2025-07-02 06:07:30.239 #########################
2025-07-02 06:07:30.247 con_lock_1.commit() # releases record with ID = 0 ==> now it can be locked by worker.
2025-07-02 06:07:30.254 # We must to ensure that this (worker) attachment has been really created and LOCKS this record:
2025-07-02 06:07:30.263 #
2025-07-02 06:07:30.274 wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=0', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-02 06:07:30.284
2025-07-02 06:07:30.291
2025-07-02 06:07:30.298 cur_lock_1.execute( sttm, ( -4, 4, ) )
2025-07-02 06:07:30.303 con_lock_1.commit()
2025-07-02 06:07:30.308 cur_lock_1.execute( sttm, ( -4, 4, ) )
2025-07-02 06:07:30.313
2025-07-02 06:07:30.325 #########################
2025-07-02 06:07:30.335 ### L O C K E R - 2 ###
2025-07-02 06:07:30.351 #########################
2025-07-02 06:07:30.360 con_lock_2.commit() # releases record with ID = -5 ==> now it can be locked by worker.
2025-07-02 06:07:30.369 # We must to ensure that this (worker) attachment has been really created and LOCKS this record:
2025-07-02 06:07:30.376 #
2025-07-02 06:07:30.384 wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=-5', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-02 06:07:30.391
2025-07-02 06:07:30.399 cur_lock_2.execute( sttm, ( -3, 3, ) )
2025-07-02 06:07:30.405 con_lock_2.commit()
2025-07-02 06:07:30.411 cur_lock_2.execute( sttm, ( -3, 3, ) )
2025-07-02 06:07:30.417
2025-07-02 06:07:30.428 #########################
2025-07-02 06:07:30.443 ### L O C K E R - 1 ###
2025-07-02 06:07:30.455 #########################
2025-07-02 06:07:30.468 con_lock_1.commit()
2025-07-02 06:07:30.481 # We must to ensure that this (worker) attachment has been really created and LOCKS this record:
2025-07-02 06:07:30.490 #
2025-07-02 06:07:30.498 wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=-4', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-02 06:07:30.509
2025-07-02 06:07:30.519 cur_lock_1.execute( sttm, ( -2, 2, ) )
2025-07-02 06:07:30.527 con_lock_1.commit()
2025-07-02 06:07:30.535 cur_lock_1.execute( sttm, ( -2, 2, ) )
2025-07-02 06:07:30.542
2025-07-02 06:07:30.553 #########################
2025-07-02 06:07:30.561 ### L O C K E R - 2 ###
2025-07-02 06:07:30.571 #########################
2025-07-02 06:07:30.583 con_lock_2.commit()
2025-07-02 06:07:30.593 # We must to ensure that this (worker) attachment has been really created and LOCKS this record:
2025-07-02 06:07:30.603 #
2025-07-02 06:07:30.615 wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=-3', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-02 06:07:30.627
2025-07-02 06:07:30.637
2025-07-02 06:07:30.645 cur_lock_2.execute( f'insert into {target_obj}(id,x) values(?, ?)', ( -1, 1, ) )
2025-07-02 06:07:30.655 con_lock_2.commit()
2025-07-02 06:07:30.661 cur_lock_2.execute( f'update {target_obj} set id = id where id = ?', ( -1, ) )
2025-07-02 06:07:30.667
2025-07-02 06:07:30.673 #########################
2025-07-02 06:07:30.679 ### L O C K E R - 1 ###
2025-07-02 06:07:30.684 #########################
2025-07-02 06:07:30.689 con_lock_1.commit()
2025-07-02 06:07:30.695 # We must to ensure that this (worker) attachment has been really created and LOCKS this record:
2025-07-02 06:07:30.700 #
2025-07-02 06:07:30.710 wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=-2', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-02 06:07:30.719
2025-07-02 06:07:30.727 #########################
2025-07-02 06:07:30.741 ### L O C K E R - 2 ###
2025-07-02 06:07:30.752 #########################
2025-07-02 06:07:30.764 con_lock_2.commit() # At this point merge can complete its job but it must FAIL because of multiple matches for abs(t.id) = abs(s.id), i.e. when ID = -1 and 1
2025-07-02 06:07:30.777
2025-07-02 06:07:30.788 # Here we wait for ISQL complete its mission:
2025-07-02 06:07:30.800 p_worker.wait()
2025-07-02 06:07:30.812
2025-07-02 06:07:30.823 # < with act.db.connect
2025-07-02 06:07:30.834
2025-07-02 06:07:30.843 for g in (fn_worker_log, fn_worker_err):
2025-07-02 06:07:30.851 with g.open() as f:
2025-07-02 06:07:30.856 for line in f:
2025-07-02 06:07:30.864 if line.strip():
2025-07-02 06:07:30.877 print(f'checked_mode: {checked_mode}, {"STDLOG" if g == fn_worker_log else "STDERR"}: {line}')
2025-07-02 06:07:30.887
2025-07-02 06:07:30.896 expected_stdout_worker = f"""
2025-07-02 06:07:30.903 checked_mode: {checked_mode}, STDLOG: Records affected: 2
2025-07-02 06:07:30.911 checked_mode: {checked_mode}, STDLOG: ID X
2025-07-02 06:07:30.919 checked_mode: {checked_mode}, STDLOG: ======= =======
2025-07-02 06:07:30.928 checked_mode: {checked_mode}, STDLOG: -5 5
2025-07-02 06:07:30.939 checked_mode: {checked_mode}, STDLOG: -4 4
2025-07-02 06:07:30.948 checked_mode: {checked_mode}, STDLOG: -3 3
2025-07-02 06:07:30.956 checked_mode: {checked_mode}, STDLOG: -2 2
2025-07-02 06:07:30.964 checked_mode: {checked_mode}, STDLOG: -1 1
2025-07-02 06:07:30.971 checked_mode: {checked_mode}, STDLOG: 0 0
2025-07-02 06:07:30.978 checked_mode: {checked_mode}, STDLOG: 1 1
2025-07-02 06:07:30.988 checked_mode: {checked_mode}, STDLOG: Records affected: 7
2025-07-02 06:07:31.000 checked_mode: {checked_mode}, STDLOG: OLD_ID OP SNAP_NO_RANK
2025-07-02 06:07:31.009 checked_mode: {checked_mode}, STDLOG: ======= ====== =====================
2025-07-02 06:07:31.018 checked_mode: {checked_mode}, STDLOG: 0 UPD 1
2025-07-02 06:07:31.032 checked_mode: {checked_mode}, STDLOG: 1 UPD 1
2025-07-02 06:07:31.042 checked_mode: {checked_mode}, STDLOG: 0 UPD 2
2025-07-02 06:07:31.051 checked_mode: {checked_mode}, STDLOG: 1 UPD 2
2025-07-02 06:07:31.060 checked_mode: {checked_mode}, STDLOG: 0 UPD 3
2025-07-02 06:07:31.068 checked_mode: {checked_mode}, STDLOG: 1 UPD 3
2025-07-02 06:07:31.082 checked_mode: {checked_mode}, STDLOG: 0 UPD 4
2025-07-02 06:07:31.094 checked_mode: {checked_mode}, STDLOG: 1 UPD 4
2025-07-02 06:07:31.103 checked_mode: {checked_mode}, STDLOG: 0 UPD 5
2025-07-02 06:07:31.111 checked_mode: {checked_mode}, STDLOG: 1 UPD 5
2025-07-02 06:07:31.118 checked_mode: {checked_mode}, STDLOG: Records affected: 10
2025-07-02 06:07:31.127 checked_mode: {checked_mode}, STDERR: Statement failed, SQLSTATE = 21000
2025-07-02 06:07:31.136 checked_mode: {checked_mode}, STDERR: Multiple source records cannot match the same target during MERGE
2025-07-02 06:07:31.144 checked_mode: {checked_mode}, STDERR:
2025-07-02 06:07:31.150 """
2025-07-02 06:07:31.156
2025-07-02 06:07:31.162 act.expected_stdout = expected_stdout_worker
2025-07-02 06:07:31.168 act.stdout = capsys.readouterr().out
2025-07-02 06:07:31.175 > assert act.clean_stdout == act.clean_expected_stdout
2025-07-02 06:07:31.189 E assert
2025-07-02 06:07:31.199 E checked_mode: table, STDLOG: Records affected: 2
2025-07-02 06:07:31.207 E checked_mode: table, STDLOG: ID X
2025-07-02 06:07:31.213 E checked_mode: table, STDLOG:
2025-07-02 06:07:31.220 E checked_mode: table, STDLOG: -5 5
2025-07-02 06:07:31.226 E checked_mode: table, STDLOG: -4 4
2025-07-02 06:07:31.231 E checked_mode: table, STDLOG: -3 3
2025-07-02 06:07:31.238 E checked_mode: table, STDLOG: -2 2
2025-07-02 06:07:31.244 E checked_mode: table, STDLOG: -1 1
2025-07-02 06:07:31.251 E checked_mode: table, STDLOG: 0 0
2025-07-02 06:07:31.259 E checked_mode: table, STDLOG: 1 1
2025-07-02 06:07:31.271 E checked_mode: table, STDLOG: Records affected: 7
2025-07-02 06:07:31.279 E checked_mode: table, STDLOG: OLD_ID OP SNAP_NO_RANK
2025-07-02 06:07:31.286 E checked_mode: table, STDLOG:
2025-07-02 06:07:31.291 E - checked_mode: table, STDLOG: 0 UPD 1
2025-07-02 06:07:31.303 E + checked_mode: table, STDLOG: 0UPD 1
2025-07-02 06:07:31.311 E - checked_mode: table, STDLOG: 1 UPD 1
2025-07-02 06:07:31.331 E + checked_mode: table, STDLOG: 1UPD 1
2025-07-02 06:07:31.339 E - checked_mode: table, STDLOG: 0 UPD 2
2025-07-02 06:07:31.352 E + checked_mode: table, STDLOG: 0UPD 2
2025-07-02 06:07:31.358 E - checked_mode: table, STDLOG: 1 UPD 2
2025-07-02 06:07:31.384 E + checked_mode: table, STDLOG: 1UPD 2
2025-07-02 06:07:31.399 E - checked_mode: table, STDLOG: 0 UPD 3
2025-07-02 06:07:31.423 E + checked_mode: table, STDLOG: 0UPD 3
2025-07-02 06:07:31.430 E - checked_mode: table, STDLOG: 1 UPD 3
2025-07-02 06:07:31.443 E + checked_mode: table, STDLOG: 1UPD 3
2025-07-02 06:07:31.456 E - checked_mode: table, STDLOG: 0 UPD 4
2025-07-02 06:07:31.472 E + checked_mode: table, STDLOG: 0UPD 4
2025-07-02 06:07:31.479 E - checked_mode: table, STDLOG: 1 UPD 4
2025-07-02 06:07:31.498 E + checked_mode: table, STDLOG: 1UPD 4
2025-07-02 06:07:31.504 E - checked_mode: table, STDLOG: 0 UPD 5
2025-07-02 06:07:31.515 E + checked_mode: table, STDLOG: 0UPD 5
2025-07-02 06:07:31.521 E - checked_mode: table, STDLOG: 1 UPD 5
2025-07-02 06:07:31.539 E + checked_mode: table, STDLOG: 1UPD 5
2025-07-02 06:07:31.552 E checked_mode: table, STDLOG: Records affected: 10
2025-07-02 06:07:31.565 E checked_mode: table, STDERR: Statement failed, SQLSTATE 21000
2025-07-02 06:07:31.575 E checked_mode: table, STDERR: Multiple source records cannot match the same target during MERGE
2025-07-02 06:07:31.586 E checked_mode: table, STDERR:
2025-07-02 06:07:31.599
2025-07-02 06:07:31.612 tests/functional/transactions/test_read_consist_sttm_merge_deny_multiple_matches.py:421: AssertionError
2025-07-02 06:07:31.623 ---------------------------- Captured stdout setup -----------------------------
2025-07-02 06:07:31.632 Creating db: localhost:/var/tmp/qa_2024/test_12475/test.fdb [page_size=None, sql_dialect=None, charset='NONE', user=SYSDBA, password=masterkey]
|
3 #text |
act = <firebird.qa.plugin.Action pytest object at [hex]>
fn_worker_sql = PosixPath('/var/tmp/qa_2024/test_12475/tmp_worker.sql')
fn_worker_log = PosixPath('/var/tmp/qa_2024/test_12475/tmp_worker.log')
fn_worker_err = PosixPath('/var/tmp/qa_2024/test_12475/tmp_worker.err')
capsys = <_pytest.capture.CaptureFixture pytest object at [hex]>
@pytest.mark.trace
@pytest.mark.version('>=4.0.2')
def test_1(act: Action, fn_worker_sql: Path, fn_worker_log: Path, fn_worker_err: Path, capsys):
sql_init = (act.files_dir / 'read-consist-sttm-restart-DDL.sql').read_text()
for checked_mode in('table', 'view'):
target_obj = 'test' if checked_mode == 'table' else 'v_test'
SQL_TO_BE_RESTARTED = f"""
merge /* {SQL_TAG_THAT_WE_WAITING_FOR} */ into {target_obj} t
using (
select s.id, s.x from {target_obj} as s
where s.id <= 1
order by s.id DESC -- added only 05-jun-2024; thanks to Vlad.
) s
on abs(t.id) = abs(s.id)
when matched then
update set t.x = s.id * 100
"""
sql_addi = f'''
set term ^;
execute block as
begin
rdb$set_context('USER_SESSION', 'WHO', 'INIT_DATA');
end
^
set term ;^
-- INITIAL DATA: add rows with ID = 0...6
-- #############
insert into {target_obj}(id, x)
select row_number()over()-1, row_number()over()-1
from rdb$types rows 6;
commit;
'''
act.isql(switches=['-q'], input = ''.join( (sql_init, sql_addi) ) )
# ::: NOTE ::: We have to immediately quit if any error raised in prepare phase.
# See also letter from dimitr, 01-feb-2022 14:46
assert act.stderr == ''
act.reset()
trace_cfg_items = [
'time_threshold = 0',
'log_errors = true',
'log_statement_start = true',
'log_statement_finish = true',
]
with act.trace(db_events = trace_cfg_items, encoding=locale.getpreferredencoding()):
with act.db.connect() as con_lock_1, act.db.connect() as con_lock_2, act.db.connect() as con_monitoring:
tpb_monitoring = tpb(isolation=Isolation.READ_COMMITTED_RECORD_VERSION, lock_timeout=0)
tx_monitoring = con_monitoring.transaction_manager(tpb_monitoring)
cur_monitoring = tx_monitoring.cursor()
cur_lock_1 = con_lock_1.cursor()
cur_lock_2 = con_lock_2.cursor()
for i,c in enumerate((con_lock_1,con_lock_2)):
sttm = f"execute block as begin rdb$set_context('USER_SESSION', 'WHO', 'LOCKER #{i+1}'); end"
c.execute_immediate(sttm)
#########################
### L O C K E R - 1 ###
#########################
con_lock_1.execute_immediate( f'update {target_obj} set id=id where id = 0' )
worker_sql = f'''
set list on;
set autoddl off;
set term ^;
execute block as
begin
rdb$set_context('USER_SESSION','WHO', 'WORKER');
end
^
set term ;^
commit;
SET KEEP_TRAN_PARAMS ON;
set transaction read committed read consistency;
set list off;
set wng off;
set count on;
-- THIS MUST HANG:
{SQL_TO_BE_RESTARTED};
-- check results:
-- ###############
select id,x from {target_obj} order by id;
select v.old_id, v.op, v.snap_no_rank
from v_worker_log v
where v.op = 'upd';
--set width who 10;
-- DO NOT check this! Values can differ here from one run to another!
-- select id, trn, who, old_id, new_id, op, rec_vers, global_cn, snap_no from tlog_done order by id;
rollback;
'''
fn_worker_sql.write_text(worker_sql)
with fn_worker_log.open(mode='w') as hang_out, fn_worker_err.open(mode='w') as hang_err:
############################################################################
### L A U N C H W O R K E R U S I N G I S Q L, A S Y N C. ###
############################################################################
p_worker = subprocess.Popen([act.vars['isql'], '-i', str(fn_worker_sql),
'-user', act.db.user,
'-password', act.db.password,
'-pag', '999999',
act.db.dsn
],
stdout = hang_out,
stderr = hang_err
)
# NB: when ISQL will establish attach, first record that it must lock is ID = 0 -- see above SQL_TO_BE_RESTARTED
# We must to ensure that this (worker) attachment has been really created and LOCKS this record:
#
#wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id = 0', SQL_TAG_THAT_WE_WAITING_FOR)
sttm = f'update {target_obj} set id = ? where abs( id ) = ?'
#########################
### L O C K E R - 2 ###
#########################
cur_lock_2.execute( sttm, ( -5, 5, ) )
con_lock_2.commit()
cur_lock_2.execute( sttm, ( -5, 5, ) )
#########################
### L O C K E R - 1 ###
#########################
con_lock_1.commit() # releases record with ID = 0 ==> now it can be locked by worker.
# We must to ensure that this (worker) attachment has been really created and LOCKS this record:
#
wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=0', SQL_TAG_THAT_WE_WAITING_FOR)
cur_lock_1.execute( sttm, ( -4, 4, ) )
con_lock_1.commit()
cur_lock_1.execute( sttm, ( -4, 4, ) )
#########################
### L O C K E R - 2 ###
#########################
con_lock_2.commit() # releases record with ID = -5 ==> now it can be locked by worker.
# We must to ensure that this (worker) attachment has been really created and LOCKS this record:
#
wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=-5', SQL_TAG_THAT_WE_WAITING_FOR)
cur_lock_2.execute( sttm, ( -3, 3, ) )
con_lock_2.commit()
cur_lock_2.execute( sttm, ( -3, 3, ) )
#########################
### L O C K E R - 1 ###
#########################
con_lock_1.commit()
# We must to ensure that this (worker) attachment has been really created and LOCKS this record:
#
wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=-4', SQL_TAG_THAT_WE_WAITING_FOR)
cur_lock_1.execute( sttm, ( -2, 2, ) )
con_lock_1.commit()
cur_lock_1.execute( sttm, ( -2, 2, ) )
#########################
### L O C K E R - 2 ###
#########################
con_lock_2.commit()
# We must to ensure that this (worker) attachment has been really created and LOCKS this record:
#
wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=-3', SQL_TAG_THAT_WE_WAITING_FOR)
cur_lock_2.execute( f'insert into {target_obj}(id,x) values(?, ?)', ( -1, 1, ) )
con_lock_2.commit()
cur_lock_2.execute( f'update {target_obj} set id = id where id = ?', ( -1, ) )
#########################
### L O C K E R - 1 ###
#########################
con_lock_1.commit()
# We must to ensure that this (worker) attachment has been really created and LOCKS this record:
#
wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=-2', SQL_TAG_THAT_WE_WAITING_FOR)
#########################
### L O C K E R - 2 ###
#########################
con_lock_2.commit() # At this point merge can complete its job but it must FAIL because of multiple matches for abs(t.id) = abs(s.id), i.e. when ID = -1 and 1
# Here we wait for ISQL complete its mission:
p_worker.wait()
# < with act.db.connect
for g in (fn_worker_log, fn_worker_err):
with g.open() as f:
for line in f:
if line.strip():
print(f'checked_mode: {checked_mode}, {"STDLOG" if g == fn_worker_log else "STDERR"}: {line}')
expected_stdout_worker = f"""
checked_mode: {checked_mode}, STDLOG: Records affected: 2
checked_mode: {checked_mode}, STDLOG: ID X
checked_mode: {checked_mode}, STDLOG: ======= =======
checked_mode: {checked_mode}, STDLOG: -5 5
checked_mode: {checked_mode}, STDLOG: -4 4
checked_mode: {checked_mode}, STDLOG: -3 3
checked_mode: {checked_mode}, STDLOG: -2 2
checked_mode: {checked_mode}, STDLOG: -1 1
checked_mode: {checked_mode}, STDLOG: 0 0
checked_mode: {checked_mode}, STDLOG: 1 1
checked_mode: {checked_mode}, STDLOG: Records affected: 7
checked_mode: {checked_mode}, STDLOG: OLD_ID OP SNAP_NO_RANK
checked_mode: {checked_mode}, STDLOG: ======= ====== =====================
checked_mode: {checked_mode}, STDLOG: 0 UPD 1
checked_mode: {checked_mode}, STDLOG: 1 UPD 1
checked_mode: {checked_mode}, STDLOG: 0 UPD 2
checked_mode: {checked_mode}, STDLOG: 1 UPD 2
checked_mode: {checked_mode}, STDLOG: 0 UPD 3
checked_mode: {checked_mode}, STDLOG: 1 UPD 3
checked_mode: {checked_mode}, STDLOG: 0 UPD 4
checked_mode: {checked_mode}, STDLOG: 1 UPD 4
checked_mode: {checked_mode}, STDLOG: 0 UPD 5
checked_mode: {checked_mode}, STDLOG: 1 UPD 5
checked_mode: {checked_mode}, STDLOG: Records affected: 10
checked_mode: {checked_mode}, STDERR: Statement failed, SQLSTATE = 21000
checked_mode: {checked_mode}, STDERR: Multiple source records cannot match the same target during MERGE
checked_mode: {checked_mode}, STDERR:
"""
act.expected_stdout = expected_stdout_worker
act.stdout = capsys.readouterr().out
> assert act.clean_stdout == act.clean_expected_stdout
E assert
E checked_mode: table, STDLOG: Records affected: 2
E checked_mode: table, STDLOG: ID X
E checked_mode: table, STDLOG:
E checked_mode: table, STDLOG: -5 5
E checked_mode: table, STDLOG: -4 4
E checked_mode: table, STDLOG: -3 3
E checked_mode: table, STDLOG: -2 2
E checked_mode: table, STDLOG: -1 1
E checked_mode: table, STDLOG: 0 0
E checked_mode: table, STDLOG: 1 1
E checked_mode: table, STDLOG: Records affected: 7
E checked_mode: table, STDLOG: OLD_ID OP SNAP_NO_RANK
E checked_mode: table, STDLOG:
E - checked_mode: table, STDLOG: 0 UPD 1
E + checked_mode: table, STDLOG: 0UPD 1
E - checked_mode: table, STDLOG: 1 UPD 1
E + checked_mode: table, STDLOG: 1UPD 1
E - checked_mode: table, STDLOG: 0 UPD 2
E + checked_mode: table, STDLOG: 0UPD 2
E - checked_mode: table, STDLOG: 1 UPD 2
E + checked_mode: table, STDLOG: 1UPD 2
E - checked_mode: table, STDLOG: 0 UPD 3
E + checked_mode: table, STDLOG: 0UPD 3
E - checked_mode: table, STDLOG: 1 UPD 3
E + checked_mode: table, STDLOG: 1UPD 3
E - checked_mode: table, STDLOG: 0 UPD 4
E + checked_mode: table, STDLOG: 0UPD 4
E - checked_mode: table, STDLOG: 1 UPD 4
E + checked_mode: table, STDLOG: 1UPD 4
E - checked_mode: table, STDLOG: 0 UPD 5
E + checked_mode: table, STDLOG: 0UPD 5
E - checked_mode: table, STDLOG: 1 UPD 5
E + checked_mode: table, STDLOG: 1UPD 5
E checked_mode: table, STDLOG: Records affected: 10
E checked_mode: table, STDERR: Statement failed, SQLSTATE 21000
E checked_mode: table, STDERR: Multiple source records cannot match the same target during MERGE
E checked_mode: table, STDERR:
tests/functional/transactions/test_read_consist_sttm_merge_deny_multiple_matches.py:421: AssertionError
|