3434
3535 Checked on 5.0.2.1516-92316F0 -- all ok: two hash joins instead of subquery.
3636 Thanks to dimitr for the advice on implementing the test.
37+
38+ [16.04.2025] pzotov
39+ Re-implemented in order to check FB 5.x with set 'SubQueryConversion = true' and FB 6.x w/o any changes in its config.
40+ Checked on 6.0.0.687-730aa8f, 5.0.3.1647-8993a57
3741"""
3842
3943import pytest
6165
6266db = db_factory (init = init_script )
6367
64- act = python_act ('db' , substitutions = [('record length: \\ d+' , 'record length' )])
68+ # Substitusions are needed here in order to ignore concrete numbers in explained plan parts, e.g.:
69+ # Hash Join (semi) (keys: 1, total key length: 4)
70+ # Sort (record length: 28, key length: 8)
71+ # Record Buffer (record length: 25)
72+ substitutions = [
73+ (r'Hash Join \(semi\) \(keys: \d+, total key length: \d+\)' ,'Hash Join (semi)' )
74+ ,(r'record length: \d+' , 'record length: NN' )
75+ ,(r'key length: \d+' , 'key length: NN' )
76+ ]
77+
78+ act = python_act ('db' , substitutions = substitutions )
6579
6680#-----------------------------------------------------------
6781
@@ -71,7 +85,7 @@ def replace_leading(source, char="."):
7185
7286#-----------------------------------------------------------
7387
74- @pytest .mark .version ('>=5.0.2,<6 ' )
88+ @pytest .mark .version ('>=5.0.2' )
7589def test_1 (act : Action , capsys ):
7690
7791 test_sql = """
@@ -87,77 +101,57 @@ def test_1(act: Action, capsys):
87101 );
88102 """
89103
90- for sq_conv in ( 'true' , 'false' ,):
91- srv_cfg = driver_config . register_server ( name = f'srv_cfg_aae2ae32_ { sq_conv } ' , config = '' )
92- db_cfg_name = f'db_cfg_aae2ae32_ { sq_conv } '
93- db_cfg_object = driver_config . register_database ( name = db_cfg_name )
94- db_cfg_object .server .value = srv_cfg . name
95- db_cfg_object . database . value = str ( act .db . db_path )
104+ srv_cfg = driver_config . register_server ( name = f'srv_cfg_aae2ae32' , config = '' )
105+ db_cfg_name = f'db_cfg_aae2ae32'
106+ db_cfg_object = driver_config . register_database ( name = db_cfg_name )
107+ db_cfg_object . server . value = srv_cfg . name
108+ db_cfg_object .database .value = str ( act . db . db_path )
109+ if act .is_version ( '<6' ):
96110 db_cfg_object .config .value = f"""
97- SubQueryConversion = { sq_conv }
111+ SubQueryConversion = true
98112 """
99113
100- with connect (db_cfg_name , user = act .db .user , password = act .db .password ) as con :
101- ps , rs = None , None
102- try :
103- cur = con .cursor ()
104- cur .execute ("select g.rdb$config_name, g.rdb$config_value from rdb$database r left join rdb$config g on g.rdb$config_name = 'SubQueryConversion'" )
105- for r in cur :
106- print (r [0 ],r [1 ])
107-
108- ps = cur .prepare (test_sql )
109-
110- # Print explained plan with padding eash line by dots in order to see indentations:
111- print ( '\n ' .join ([replace_leading (s ) for s in ps .detailed_plan .split ('\n ' )]) )
112-
113- # ::: NB ::: 'ps' returns data, i.e. this is SELECTABLE expression.
114- # We have to store result of cur.execute(<psInstance>) in order to
115- # close it explicitly.
116- # Otherwise AV can occur during Python garbage collection and this
117- # causes pytest to hang on its final point.
118- # Explained by hvlad, email 26.10.24 17:42
119- rs = cur .execute (ps )
120- for r in rs :
121- print (r [0 ])
122- except DatabaseError as e :
123- print (e .__str__ ())
124- print (e .gds_codes )
125- finally :
126- if rs :
127- rs .close () # <<< EXPLICITLY CLOSING CURSOR RESULTS
128- if ps :
129- ps .free ()
114+ with connect (db_cfg_name , user = act .db .user , password = act .db .password ) as con :
115+ ps , rs = None , None
116+ try :
117+ cur = con .cursor ()
118+ ps = cur .prepare (test_sql )
119+
120+ # Print explained plan with padding eash line by dots in order to see indentations:
121+ print ( '\n ' .join ([replace_leading (s ) for s in ps .detailed_plan .split ('\n ' )]) )
122+
123+ # ::: NB ::: 'ps' returns data, i.e. this is SELECTABLE expression.
124+ # We have to store result of cur.execute(<psInstance>) in order to
125+ # close it explicitly.
126+ # Otherwise AV can occur during Python garbage collection and this
127+ # causes pytest to hang on its final point.
128+ # Explained by hvlad, email 26.10.24 17:42
129+ rs = cur .execute (ps )
130+ for r in rs :
131+ print (r [0 ])
132+ except DatabaseError as e :
133+ print (e .__str__ ())
134+ print (e .gds_codes )
135+ finally :
136+ if rs :
137+ rs .close () # <<< EXPLICITLY CLOSING CURSOR RESULTS
138+ if ps :
139+ ps .free ()
130140
131141 act .expected_stdout = f"""
132- SubQueryConversion true
133142 Select Expression
134143 ....-> Aggregate
135144 ........-> Filter
136145 ............-> Hash Join (semi)
137146 ................-> Table "TEST1" as "A" Full Scan
138- ................-> Record Buffer (record length: 82111 )
147+ ................-> Record Buffer (record length: 82 )
139148 ....................-> Filter
140149 ........................-> Hash Join (semi)
141150 ............................-> Table "TEST2" as "B" Full Scan
142- ............................-> Record Buffer (record length: 57111 )
151+ ............................-> Record Buffer (record length: 57 )
143152 ................................-> Filter
144153 ....................................-> Table "TEST3" as "C" Full Scan
145154 10
146-
147- SubQueryConversion false
148- Sub-query
149- ....-> Filter
150- ........-> Filter
151- ............-> Table "TEST3" as "C" Full Scan
152- Sub-query
153- ....-> Filter
154- ........-> Filter
155- ............-> Table "TEST2" as "B" Full Scan
156- Select Expression
157- ....-> Aggregate
158- ........-> Filter
159- ............-> Table "TEST1" as "A" Full Scan
160- 10
161155 """
162156 act .stdout = capsys .readouterr ().out
163157 assert act .clean_stdout == act .clean_expected_stdout
0 commit comments