2 @message |
assert
- Changed ID: -1
+ Dynamic SQL Error
+ -SQL error code = -901
+ -invalid request handle
+ gds codes:
+ 335544569
+ 335544436
+ 335544327
LOG DETAILS:
2024-12-24 12:33:45.288
2024-12-24 12:33:45.288 act = <firebird.qa.plugin.Action object at [hex]>
2024-12-24 12:33:45.288 capsys = <_pytest.capture.CaptureFixture object at [hex]>
2024-12-24 12:33:45.288
2024-12-24 12:33:45.288 @pytest.mark.version('>=5.0.0')
2024-12-24 12:33:45.288 def test_1(act: Action, capsys):
2024-12-24 12:33:45.288
2024-12-24 12:33:45.288 srv_cfg = driver_config.register_server(name = 'test_srv_gh_8185', config = '')
2024-12-24 12:33:45.288 db_cfg_name = f'db_cfg_8185'
2024-12-24 12:33:45.288 db_cfg_object = driver_config.register_database(name = db_cfg_name)
2024-12-24 12:33:45.288 db_cfg_object.server.value = srv_cfg.name
2024-12-24 12:33:45.288 db_cfg_object.database.value = str(act.db.db_path)
2024-12-24 12:33:45.288 db_cfg_object.config.value = f"""
2024-12-24 12:33:45.288 MaxStatementCacheSize = 1M
2024-12-24 12:33:45.288 """
2024-12-24 12:33:45.288
2024-12-24 12:33:45.288 # Pre-check:
2024-12-24 12:33:45.288 with connect(db_cfg_name, user = act.db.user, password = act.db.password) as con:
2024-12-24 12:33:45.288 cur = con.cursor()
2024-12-24 12:33:45.289 cur.execute("select a.mon$remote_protocol, g.rdb$config_value from mon$attachments a left join rdb$config g on g.rdb$config_name = 'MaxStatementCacheSize' where a.mon$attachment_id = current_connection")
2024-12-24 12:33:45.289 for r in cur:
2024-12-24 12:33:45.289 conn_protocol = r[0]
2024-12-24 12:33:45.289 db_sttm_cache_size = int(r[1])
2024-12-24 12:33:45.289 assert conn_protocol is None, "Test must use LOCAL protocol."
2024-12-24 12:33:45.289 assert db_sttm_cache_size > 0, "Parameter 'MaxStatementCacheSize' (per-database) must be greater than zero for this test."
2024-12-24 12:33:45.289
2024-12-24 12:33:45.289 #---------------------------------------------
2024-12-24 12:33:45.289
2024-12-24 12:33:45.289 CURSOR_NAME = 'k1'
2024-12-24 12:33:45.289 SELECT_STTM = 'select /* ps-1*/ id, f01 from test where id > 0 for update'
2024-12-24 12:33:45.289 UPDATE_STTM = f'update /* ps-2 */ test set id = -id where current of {CURSOR_NAME} returning id'
2024-12-24 12:33:45.289
2024-12-24 12:33:45.289 update_tpb = tpb( access_mode = TraAccessMode.WRITE,
2024-12-24 12:33:45.289 isolation = Isolation.READ_COMMITTED_RECORD_VERSION,
2024-12-24 12:33:45.289 lock_timeout = 1)
2024-12-24 12:33:45.289
2024-12-24 12:33:45.289 with connect(db_cfg_name, user = act.db.user, password = act.db.password) as con:
2024-12-24 12:33:45.290
2024-12-24 12:33:45.290 tx2 = con.transaction_manager(update_tpb)
2024-12-24 12:33:45.290 tx2.begin()
2024-12-24 12:33:45.290
2024-12-24 12:33:45.290 with con.cursor() as cur1, tx2.cursor() as cur2, con.cursor() as cur3:
2024-12-24 12:33:45.290
2024-12-24 12:33:45.290 ps1, rs1, ps2, rs2, ps3, rs3 = None, None, None, None, None, None
2024-12-24 12:33:45.290 try:
2024-12-24 12:33:45.290 ps1 = cur1.prepare(SELECT_STTM) # 1. [ticket, DS] Prepare statement 1 "select ... for update"
2024-12-24 12:33:45.290 ps1._istmt.set_cursor_name(CURSOR_NAME) # 2. [ticket, DS] Set cursor name for statement 1 // ~hack.
2024-12-24 12:33:45.290
2024-12-24 12:33:45.290 # DO NOT use it because subsequent update statement will get 'deadlock / update conflict' and not able to start:
2024-12-24 12:33:45.290 #rs1 = cur1.execute(ps1)
2024-12-24 12:33:45.290 #cur1.set_cursor_name(CURSOR_NAME)
2024-12-24 12:33:45.290
2024-12-24 12:33:45.290 # DS example: "// Prepare positioned update statement"
2024-12-24 12:33:45.290 ps2 = cur2.prepare(UPDATE_STTM) # 3. [ticket, DS] Prepare statement 2 "update ... where current of <cursor name from step 2>"
2024-12-24 12:33:45.290
2024-12-24 12:33:45.290 # DS .cpp: // fetch records from cursor and print them
2024-12-24 12:33:45.290 rs1 = cur1.execute(ps1)
2024-12-24 12:33:45.291 rs1.fetchall()
2024-12-24 12:33:45.291
2024-12-24 12:33:45.291 # DS .cpp: // IStatement* stmt2 = att->prepare(&status, tra, 0, "select * from pos where a > 1 for update",
2024-12-24 12:33:45.291 ps3 = cur3.prepare(SELECT_STTM) # 4. [ticket, DS] Prepare statement 3 similar to statement 1
2024-12-24 12:33:45.291
2024-12-24 12:33:45.291 rs1.close() # 5. [ticket, DS] Release statement 1 // see hvlad recipe, 26.10.2024
2024-12-24 12:33:45.291 ps1.free()
2024-12-24 12:33:45.291
2024-12-24 12:33:45.291 # DS .cpp: updStmt->free(&status);
2024-12-24 12:33:45.291 ps2.free() # 6. [ticket, DS] Release statement 2 // see hvlad recipe, 26.10.2024
2024-12-24 12:33:45.291
2024-12-24 12:33:45.291 # DS .cpp: stmt = stmt2
2024-12-24 12:33:45.291 ps3._istmt.set_cursor_name(CURSOR_NAME) # 7. [ticket, DS] Set cursor name to statement 3 as in step 2
2024-12-24 12:33:45.291
2024-12-24 12:33:45.291 ps2 = cur2.prepare(UPDATE_STTM) # 8. [ticket, DS] Prepare statement 2 again (it will be got from cache keeping reference to statement 1)
2024-12-24 12:33:45.291
2024-12-24 12:33:45.291 rs3 = cur3.execute(ps3)
2024-12-24 12:33:45.291 rs3.fetchone() # 9. [ticket, DS] Run statement 3 and fetch one record
2024-12-24 12:33:45.291
2024-12-24 12:33:45.291 # At step 10 you can get "Invalid handle" error or a crash if you swap steps 5 and 6.
2024-12-24 12:33:45.292 rs2 = cur2.execute(ps2) # 10. [ticket, DS] Execute statement 2
2024-12-24 12:33:45.292 data2 = rs2.fetchone()
2024-12-24 12:33:45.292 print('Changed ID:', data2[0])
2024-12-24 12:33:45.292 # print(f'{rs2.rowcount=}')
2024-12-24 12:33:45.292
2024-12-24 12:33:45.292 except DatabaseError as e:
2024-12-24 12:33:45.292 print(e.__str__())
2024-12-24 12:33:45.292 print('gds codes:')
2024-12-24 12:33:45.292 for i in e.gds_codes:
2024-12-24 12:33:45.292 print(i)
2024-12-24 12:33:45.292
2024-12-24 12:33:45.292 finally:
2024-12-24 12:33:45.292 if rs1:
2024-12-24 12:33:45.292 rs1.close()
2024-12-24 12:33:45.292 if ps1:
2024-12-24 12:33:45.292 ps1.free()
2024-12-24 12:33:45.292
2024-12-24 12:33:45.292 if rs2:
2024-12-24 12:33:45.292 rs2.close()
2024-12-24 12:33:45.293 if ps2:
2024-12-24 12:33:45.293 ps2.free()
2024-12-24 12:33:45.293
2024-12-24 12:33:45.293 if rs3:
2024-12-24 12:33:45.293 rs3.close()
2024-12-24 12:33:45.293 if ps3:
2024-12-24 12:33:45.293 ps3.free()
2024-12-24 12:33:45.293
2024-12-24 12:33:45.293 #---------------------------------------------
2024-12-24 12:33:45.293
2024-12-24 12:33:45.293 act.expected_stdout = 'Changed ID: -1'
2024-12-24 12:33:45.293 act.stdout = capsys.readouterr().out
2024-12-24 12:33:45.293 > assert act.clean_stdout == act.clean_expected_stdout
2024-12-24 12:33:45.293 E assert
2024-12-24 12:33:45.293 E - Changed ID: -1
2024-12-24 12:33:45.293 E + Dynamic SQL Error
2024-12-24 12:33:45.293 E + -SQL error code = -901
2024-12-24 12:33:45.293 E + -invalid request handle
2024-12-24 12:33:45.293 E + gds codes:
2024-12-24 12:33:45.293 E + 335544569
2024-12-24 12:33:45.294 E + 335544436
2024-12-24 12:33:45.294 E + 335544327
2024-12-24 12:33:45.294
2024-12-24 12:33:45.294 tests\bugs\gh_8185_test.py:155: AssertionError
2024-12-24 12:33:45.294 ---------------------------- Captured stdout setup ----------------------------
2024-12-24 12:33:45.294 Creating db: localhost:H:\QA\temp\qa2024.tmp\fbqa\test_11675\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]>
capsys = <_pytest.capture.CaptureFixture pytest object at [hex]>
@pytest.mark.version('>=5.0.0')
def test_1(act: Action, capsys):
srv_cfg = driver_config.register_server(name = 'test_srv_gh_8185', config = '')
db_cfg_name = f'db_cfg_8185'
db_cfg_object = driver_config.register_database(name = db_cfg_name)
db_cfg_object.server.value = srv_cfg.name
db_cfg_object.database.value = str(act.db.db_path)
db_cfg_object.config.value = f"""
MaxStatementCacheSize = 1M
"""
# Pre-check:
with connect(db_cfg_name, user = act.db.user, password = act.db.password) as con:
cur = con.cursor()
cur.execute("select a.mon$remote_protocol, g.rdb$config_value from mon$attachments a left join rdb$config g on g.rdb$config_name = 'MaxStatementCacheSize' where a.mon$attachment_id = current_connection")
for r in cur:
conn_protocol = r[0]
db_sttm_cache_size = int(r[1])
assert conn_protocol is None, "Test must use LOCAL protocol."
assert db_sttm_cache_size > 0, "Parameter 'MaxStatementCacheSize' (per-database) must be greater than zero for this test."
#---------------------------------------------
CURSOR_NAME = 'k1'
SELECT_STTM = 'select /* ps-1*/ id, f01 from test where id > 0 for update'
UPDATE_STTM = f'update /* ps-2 */ test set id = -id where current of {CURSOR_NAME} returning id'
update_tpb = tpb( access_mode = TraAccessMode.WRITE,
isolation = Isolation.READ_COMMITTED_RECORD_VERSION,
lock_timeout = 1)
with connect(db_cfg_name, user = act.db.user, password = act.db.password) as con:
tx2 = con.transaction_manager(update_tpb)
tx2.begin()
with con.cursor() as cur1, tx2.cursor() as cur2, con.cursor() as cur3:
ps1, rs1, ps2, rs2, ps3, rs3 = None, None, None, None, None, None
try:
ps1 = cur1.prepare(SELECT_STTM) # 1. [ticket, DS] Prepare statement 1 "select ... for update"
ps1._istmt.set_cursor_name(CURSOR_NAME) # 2. [ticket, DS] Set cursor name for statement 1 // ~hack.
# DO NOT use it because subsequent update statement will get 'deadlock / update conflict' and not able to start:
#rs1 = cur1.execute(ps1)
#cur1.set_cursor_name(CURSOR_NAME)
# DS example: "// Prepare positioned update statement"
ps2 = cur2.prepare(UPDATE_STTM) # 3. [ticket, DS] Prepare statement 2 "update ... where current of <cursor name from step 2>"
# DS .cpp: // fetch records from cursor and print them
rs1 = cur1.execute(ps1)
rs1.fetchall()
# DS .cpp: // IStatement* stmt2 = att->prepare(&status, tra, 0, "select * from pos where a > 1 for update",
ps3 = cur3.prepare(SELECT_STTM) # 4. [ticket, DS] Prepare statement 3 similar to statement 1
rs1.close() # 5. [ticket, DS] Release statement 1 // see hvlad recipe, 26.10.2024
ps1.free()
# DS .cpp: updStmt->free(&status);
ps2.free() # 6. [ticket, DS] Release statement 2 // see hvlad recipe, 26.10.2024
# DS .cpp: stmt = stmt2
ps3._istmt.set_cursor_name(CURSOR_NAME) # 7. [ticket, DS] Set cursor name to statement 3 as in step 2
ps2 = cur2.prepare(UPDATE_STTM) # 8. [ticket, DS] Prepare statement 2 again (it will be got from cache keeping reference to statement 1)
rs3 = cur3.execute(ps3)
rs3.fetchone() # 9. [ticket, DS] Run statement 3 and fetch one record
# At step 10 you can get "Invalid handle" error or a crash if you swap steps 5 and 6.
rs2 = cur2.execute(ps2) # 10. [ticket, DS] Execute statement 2
data2 = rs2.fetchone()
print('Changed ID:', data2[0])
# print(f'{rs2.rowcount=}')
except DatabaseError as e:
print(e.__str__())
print('gds codes:')
for i in e.gds_codes:
print(i)
finally:
if rs1:
rs1.close()
if ps1:
ps1.free()
if rs2:
rs2.close()
if ps2:
ps2.free()
if rs3:
rs3.close()
if ps3:
ps3.free()
#---------------------------------------------
act.expected_stdout = 'Changed ID: -1'
act.stdout = capsys.readouterr().out
> assert act.clean_stdout == act.clean_expected_stdout
E assert
E - Changed ID: -1
E + Dynamic SQL Error
E + -SQL error code = -901
E + -invalid request handle
E + gds codes:
E + 335544569
E + 335544436
E + 335544327
tests\bugs\gh_8185_test.py:155: AssertionError
|