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