diff --git a/tests/bugs/gh_8290_test.py b/tests/bugs/gh_8290_test.py new file mode 100644 index 00000000..517622dc --- /dev/null +++ b/tests/bugs/gh_8290_test.py @@ -0,0 +1,154 @@ +#coding:utf-8 + +""" +ID: issue-8290 +ISSUE: https://github.com/FirebirdSQL/firebird/issues/8290 +TITLE: "Unique scan" is incorrectly reported in the explained plan for unique index and IS NULL predicate +DESCRIPTION: + Test creates a table and checks several cases related to issue: asc/desc, computed-by and partial indices. + For each case we ask engine to show explained plan. Every case must have 'Range Scan (full match)'. +NOTES: + [25.10.2024] pzotov + Confirmed problem on 6.0.0.485, 5.0.2.1519. + Checked on 6.0.0.502-d2f4cf6, 5.0.2.1542-ab50e20 (intermediate builds). +""" + +import pytest +from firebird.qa import * + +init_sql = """ + recreate table test(id int generated by default as identity, x int, y int, z int); + insert into test(x, y, z) select null, null, null from rdb$types, rdb$types rows 1000; + commit; + create unique index test_x_asc on test(x); + create unique descending index test_y_desc on test(y); + create unique index test_x_plus_y on test computed by (x+y); + + create unique index test_z_partial on test(z) where mod(id,2) = 0; + create unique index test_x_minus_y_partial on test computed by (x-y) where mod(id,3) <= 1; + commit; +""" +db = db_factory(init = init_sql) + +act = python_act('db') + +#----------------------------------------------------------- + +def replace_leading(source, char="."): + stripped = source.lstrip() + return char * (len(source) - len(stripped)) + stripped + +#----------------------------------------------------------- + +@pytest.mark.version('>=5.0.2') +def test_1(act: Action, capsys): + + qry_map = { + 0 : 'select count(*) from test where x is null' + ,1 : 'select count(*) from test where y is null' + ,2 : 'select count(*) from test where x+y is null' + ,3 : 'select count(*) from test where z is null and mod(id,2) = 0' + ,4 : 'select count(*) from test where x-y is null and mod(id,3) <= 1' + ,5 : 'select count(*) from test where x is not distinct from null' + ,6 : 'select count(*) from test where y is not distinct from null' + ,7 : 'select count(*) from test where x+y is not distinct from null' + ,8 : 'select count(*) from test where z is not distinct from null and mod(id,2) = 0' + ,9 : 'select count(*) from test where x-y is not distinct from null and mod(id,3) <= 1' + } + + with act.db.connect() as con: + cur = con.cursor() + for k,v in qry_map.items(): + ps = cur.prepare(v) + # Print explained plan with padding eash line by dots in order to see indentations: + print(v) + print( '\n'.join([replace_leading(s) for s in ps.detailed_plan.split('\n')]) ) + print('') + + + expected_out = f""" + {qry_map[0]} + Select Expression + ....-> Aggregate + ........-> Filter + ............-> Table "TEST" Access By ID + ................-> Bitmap + ....................-> Index "TEST_X_ASC" Range Scan (full match) + + {qry_map[1]} + Select Expression + ....-> Aggregate + ........-> Filter + ............-> Table "TEST" Access By ID + ................-> Bitmap + ....................-> Index "TEST_Y_DESC" Range Scan (full match) + + {qry_map[2]} + Select Expression + ....-> Aggregate + ........-> Filter + ............-> Table "TEST" Access By ID + ................-> Bitmap + ....................-> Index "TEST_X_PLUS_Y" Range Scan (full match) + + {qry_map[3]} + Select Expression + ....-> Aggregate + ........-> Filter + ............-> Table "TEST" Access By ID + ................-> Bitmap + ....................-> Index "TEST_Z_PARTIAL" Range Scan (full match) + + {qry_map[4]} + Select Expression + ....-> Aggregate + ........-> Filter + ............-> Table "TEST" Access By ID + ................-> Bitmap + ....................-> Index "TEST_X_MINUS_Y_PARTIAL" Range Scan (full match) + + {qry_map[5]} + Select Expression + ....-> Aggregate + ........-> Filter + ............-> Table "TEST" Access By ID + ................-> Bitmap + ....................-> Index "TEST_X_ASC" Range Scan (full match) + + {qry_map[6]} + Select Expression + ....-> Aggregate + ........-> Filter + ............-> Table "TEST" Access By ID + ................-> Bitmap + ....................-> Index "TEST_Y_DESC" Range Scan (full match) + + {qry_map[7]} + Select Expression + ....-> Aggregate + ........-> Filter + ............-> Table "TEST" Access By ID + ................-> Bitmap + ....................-> Index "TEST_X_PLUS_Y" Range Scan (full match) + + {qry_map[8]} + Select Expression + ....-> Aggregate + ........-> Filter + ............-> Table "TEST" Access By ID + ................-> Bitmap + ....................-> Index "TEST_Z_PARTIAL" Range Scan (full match) + + {qry_map[9]} + Select Expression + ....-> Aggregate + ........-> Filter + ............-> Table "TEST" Access By ID + ................-> Bitmap + ....................-> Index "TEST_X_MINUS_Y_PARTIAL" Range Scan (full match) + """ + + act.expected_stdout = expected_out + + act.stdout = capsys.readouterr().out + assert act.clean_stdout == act.clean_expected_stdout