@@ -7848,3 +7848,301 @@ async def test_comments_with_execute_multi(self):
7848
7848
i += 1
7849
7849
except IndexError :
7850
7850
self .fail (f"Got more results than expected for query { op } " )
7851
+
7852
+
7853
+ class BugOra21390859 (tests .MySQLConnectorTests ):
7854
+ """BUG#21390859: STATEMENTS GET OUT OF SYNCH WITH RESULT SETS
7855
+
7856
+ When including callproc (calling stored procedures) statements as part of a
7857
+ multi-query string while executing `cursor.execute(multi_stmt_query, multi=True)`
7858
+ the statements get out of sync with the result, that's to say, the statement-result
7859
+ mapping isn't correct, especially when a procedure produces multiple results.
7860
+
7861
+ This issue is fixed as part of BUG#35710145, however, no relevant tests for this
7862
+ use case were added during the fix of BUG#35710145.
7863
+
7864
+ With this patch, we extend the unit tests to cover the scenario where multi-result
7865
+ `callproc` statements are included in a multi-statement query.
7866
+
7867
+ Acronyms
7868
+ rop: read operation
7869
+ wop: write operation
7870
+ 1: it means the gotten result set isn't empty
7871
+ 0: it means the gotten result set is empty
7872
+ """
7873
+
7874
+ table_name = "BugOra21390859"
7875
+
7876
+ use_case , use_case_exp = {}, {}
7877
+
7878
+ use_case [
7879
+ 1
7880
+ ] = f"""
7881
+ -- rop wrop proc_milti_read proc_multi_insert;
7882
+ -- 1 0 1 1 1 0 0 ;
7883
+ SELECT 76;
7884
+ INSERT INTO { table_name } (city, country_id) VALUES ('Ciudad de Mexico', '38');
7885
+ call { table_name } _multi_read(2);
7886
+ call { table_name } _multi_insert();
7887
+ """
7888
+ use_case_exp [1 ] = [
7889
+ ("SELECT 76" , [(76 ,)]),
7890
+ (
7891
+ f"INSERT INTO { table_name } (city, country_id) VALUES ('Ciudad de Mexico', '38')" ,
7892
+ [],
7893
+ ),
7894
+ (f"call { table_name } _multi_read(2)" , [(2 ,)]),
7895
+ (f"call { table_name } _multi_read(2)" , [(3 ,)]),
7896
+ (f"call { table_name } _multi_read(2)" , [("bar" ,)]),
7897
+ (f"call { table_name } _multi_read(2)" , []),
7898
+ (f"call { table_name } _multi_insert()" , []),
7899
+ ]
7900
+
7901
+ use_case [
7902
+ 2
7903
+ ] = f"""
7904
+ SELECT 'foo';
7905
+ INSERT INTO { table_name } (city, country_id) VALUES ('Ciudad de Mexico', '38');
7906
+ call { table_name } _multi_read(4);
7907
+ # rop wrop proc_milti_read;
7908
+ -- 1 0 1 1 1 0 ;
7909
+ """
7910
+ use_case_exp [2 ] = [
7911
+ ("SELECT 'foo'" , [("foo" ,)]),
7912
+ (
7913
+ f"INSERT INTO { table_name } (city, country_id) VALUES ('Ciudad de Mexico', '38')" ,
7914
+ [],
7915
+ ),
7916
+ (f"call { table_name } _multi_read(4)" , [(4 ,)]),
7917
+ (f"call { table_name } _multi_read(4)" , [(5 ,)]),
7918
+ (f"call { table_name } _multi_read(4)" , [("bar" ,)]),
7919
+ (f"call { table_name } _multi_read(4)" , []),
7920
+ ]
7921
+
7922
+ use_case [3 ] = use_case [1 ] + use_case [2 ]
7923
+ use_case_exp [3 ] = use_case_exp [1 ] + use_case_exp [2 ]
7924
+
7925
+ use_case [4 ] = use_case [2 ] + use_case [1 ]
7926
+ use_case_exp [4 ] = use_case_exp [2 ] + use_case_exp [1 ]
7927
+
7928
+ use_case [
7929
+ 5
7930
+ ] = f"""
7931
+ -- one;
7932
+ call { table_name } _single_read(1);
7933
+ -- two;
7934
+ call { table_name } _single_read(2);
7935
+ -- three;
7936
+ call { table_name } _single_read(3);
7937
+ """
7938
+ use_case_exp [5 ] = [
7939
+ (f"call { table_name } _single_read(1)" , [(1 ,)]),
7940
+ (f"call { table_name } _single_read(1)" , []),
7941
+ (f"call { table_name } _single_read(2)" , [(2 ,)]),
7942
+ (f"call { table_name } _single_read(2)" , []),
7943
+ (f"call { table_name } _single_read(3)" , [(3 ,)]),
7944
+ (f"call { table_name } _single_read(3)" , []),
7945
+ ]
7946
+
7947
+ # same as 6, but ending the query with a EOL comment
7948
+ use_case [6 ] = use_case [5 ] + "\n -- end;"
7949
+ use_case_exp [6 ] = use_case_exp [5 ][:]
7950
+
7951
+ use_case [
7952
+ 7
7953
+ ] = f"""
7954
+ SELECT 'Hello MySQL';
7955
+ INSERT INTO { table_name } (city, country_id) VALUES ('Colima', '84');
7956
+ SELECT 10; -- ten;
7957
+ # testing
7958
+ -- comment;
7959
+ INSERT INTO { table_name } (city, country_id) VALUES ('Tabasco', '0'); # added tabasco;
7960
+ -- comment;
7961
+ -- comment;
7962
+ -- comment;
7963
+ -- two;
7964
+ call { table_name } _read_and_insert();
7965
+ -- three;
7966
+ call { table_name } _multi_insert();
7967
+ -- two;
7968
+ """
7969
+ use_case_exp [7 ] = [
7970
+ (f"SELECT 'Hello MySQL'" , [("Hello MySQL" ,)]),
7971
+ (
7972
+ f"INSERT INTO { table_name } (city, country_id) VALUES ('Colima', '84')" ,
7973
+ [],
7974
+ ),
7975
+ (f"SELECT 10" , [(10 ,)]),
7976
+ (
7977
+ f"INSERT INTO { table_name } (city, country_id) VALUES ('Tabasco', '0')" ,
7978
+ [],
7979
+ ),
7980
+ (f"call { table_name } _read_and_insert()" , [("Oracle" ,)]),
7981
+ (f"call { table_name } _read_and_insert()" , [("MySQL" ,)]),
7982
+ (f"call { table_name } _read_and_insert()" , []),
7983
+ (f"call { table_name } _multi_insert()" , []),
7984
+ ]
7985
+
7986
+ use_case [8 ] = use_case [6 ] + use_case [7 ]
7987
+ use_case_exp [8 ] = use_case_exp [6 ] + use_case_exp [7 ]
7988
+
7989
+ use_case [9 ] = use_case [1 ] + use_case [5 ]
7990
+ use_case_exp [9 ] = use_case_exp [1 ] + use_case_exp [5 ]
7991
+
7992
+ use_case [10 ] = use_case [3 ] + use_case [6 ]
7993
+ use_case_exp [10 ] = use_case_exp [3 ] + use_case_exp [6 ]
7994
+
7995
+ use_case [
7996
+ 11
7997
+ ] = f"""
7998
+ INSERT INTO { table_name } (city, country_id) VALUES ('Sonora', '33');
7999
+ -- callproc invokes procedures internally;
8000
+ CALL { table_name } _callproc();
8001
+ """
8002
+ use_case_exp [11 ] = [
8003
+ (
8004
+ f"INSERT INTO { table_name } (city, country_id) VALUES ('Sonora', '33')" ,
8005
+ [],
8006
+ ),
8007
+ (f"CALL { table_name } _callproc()" , [(1 ,)]),
8008
+ (f"CALL { table_name } _callproc()" , [(2 ,)]),
8009
+ (f"CALL { table_name } _callproc()" , [("bar" ,)]),
8010
+ (f"CALL { table_name } _callproc()" , []),
8011
+ ]
8012
+
8013
+ use_case [
8014
+ 12
8015
+ ] = f"""
8016
+ INSERT INTO { table_name } (city, country_id) VALUES ('Tamaulipas', '82');
8017
+ INSERT INTO { table_name } (city, country_id) VALUES ('Chihuahua', '3');
8018
+ -- write operations;
8019
+ SELECT ':D';
8020
+ """
8021
+ use_case_exp [12 ] = [
8022
+ (
8023
+ f"INSERT INTO { table_name } (city, country_id) VALUES ('Tamaulipas', '82')" ,
8024
+ [],
8025
+ ),
8026
+ (
8027
+ f"INSERT INTO { table_name } (city, country_id) VALUES ('Chihuahua', '3')" ,
8028
+ [],
8029
+ ),
8030
+ ("SELECT ':D'" , [(":D" ,)]),
8031
+ ]
8032
+
8033
+ use_case [13 ] = use_case [12 ] + use_case [11 ]
8034
+ use_case_exp [13 ] = use_case_exp [12 ] + use_case_exp [11 ]
8035
+
8036
+ def setUp (self ):
8037
+ with connection .MySQLConnection (** tests .get_mysql_config ()) as cnx :
8038
+ cnx .cmd_query (f"DROP PROCEDURE IF EXISTS { self .table_name } _multi_read" )
8039
+ cnx .cmd_query (f"DROP PROCEDURE IF EXISTS { self .table_name } _multi_insert" )
8040
+ cnx .cmd_query (f"DROP PROCEDURE IF EXISTS { self .table_name } _single_read" )
8041
+ cnx .cmd_query (f"DROP PROCEDURE IF EXISTS { self .table_name } _read_and_insert" )
8042
+ cnx .cmd_query (f"DROP PROCEDURE IF EXISTS { self .table_name } _callproc" )
8043
+ cnx .cmd_query (f"DROP TABLE IF EXISTS { self .table_name } " )
8044
+ cnx .cmd_query (
8045
+ f"""
8046
+ CREATE PROCEDURE { self .table_name } _single_read(val integer)
8047
+ BEGIN
8048
+ SELECT val;
8049
+ END;
8050
+ """
8051
+ )
8052
+ cnx .cmd_query (
8053
+ f"""
8054
+ CREATE PROCEDURE { self .table_name } _multi_read(val integer)
8055
+ BEGIN
8056
+ SELECT val;
8057
+ SELECT val + 1;
8058
+ SELECT 'bar';
8059
+ END;
8060
+ """
8061
+ )
8062
+ cnx .cmd_query (
8063
+ f"""
8064
+ CREATE PROCEDURE { self .table_name } _multi_insert()
8065
+ BEGIN
8066
+ INSERT INTO { self .table_name } (city, country_id) VALUES ('Chiapas', '33');
8067
+ INSERT INTO { self .table_name } (city, country_id) VALUES ('Yucatan', '28');
8068
+ INSERT INTO { self .table_name } (city, country_id) VALUES ('Oaxaca', '13');
8069
+ END;
8070
+ """
8071
+ )
8072
+ cnx .cmd_query (
8073
+ f"""
8074
+ CREATE PROCEDURE { self .table_name } _read_and_insert()
8075
+ BEGIN
8076
+ INSERT INTO { self .table_name } (city, country_id) VALUES ('CCC', '33');
8077
+ SELECT 'Oracle';
8078
+ INSERT INTO { self .table_name } (city, country_id) VALUES ('AAA', '44');
8079
+ INSERT INTO { self .table_name } (city, country_id) VALUES ('BBB', '99');
8080
+ SELECT 'MySQL';
8081
+ END;
8082
+ """
8083
+ )
8084
+ cnx .cmd_query (
8085
+ f"""
8086
+ CREATE PROCEDURE { self .table_name } _callproc()
8087
+ BEGIN
8088
+ CALL { self .table_name } _multi_read(1);
8089
+ CALL { self .table_name } _multi_insert();
8090
+ END;
8091
+ """
8092
+ )
8093
+ cnx .cmd_query (
8094
+ f"CREATE TABLE { self .table_name } (id INT AUTO_INCREMENT PRIMARY KEY, city VARCHAR(20),country_id INT)"
8095
+ )
8096
+
8097
+ def tearDown (self ) -> None :
8098
+ with connection .MySQLConnection (** tests .get_mysql_config ()) as cnx :
8099
+ cnx .cmd_query (f"DROP PROCEDURE IF EXISTS { self .table_name } _multi_read" )
8100
+ cnx .cmd_query (f"DROP PROCEDURE IF EXISTS { self .table_name } _multi_insert" )
8101
+ cnx .cmd_query (f"DROP PROCEDURE IF EXISTS { self .table_name } _single_read" )
8102
+ cnx .cmd_query (f"DROP PROCEDURE IF EXISTS { self .table_name } _read_and_insert" )
8103
+ cnx .cmd_query (f"DROP PROCEDURE IF EXISTS { self .table_name } _callproc" )
8104
+ cnx .cmd_query (f"DROP TABLE IF EXISTS { self .table_name } " )
8105
+
8106
+ @foreach_cnx ()
8107
+ def test_multi_stmts_with_callproc_out_of_sync (self ):
8108
+ with self .cnx .cursor () as cur :
8109
+ for i in self .use_case :
8110
+ for result , (stmt_exp , fetch_exp ) in zip (
8111
+ cur .execute (self .use_case [i ], multi = True ), self .use_case_exp [i ]
8112
+ ):
8113
+ fetch = result .fetchall ()
8114
+ self .assertEqual (result .statement , stmt_exp )
8115
+ self .assertListEqual (fetch , fetch_exp )
8116
+
8117
+
8118
+ class BugOra21390859_async (tests .MySQLConnectorAioTestCase ):
8119
+ """BUG#21390859: STATEMENTS GET OUT OF SYNCH WITH RESULT SETS
8120
+
8121
+ For a description see `test_bugs.BugOra21390859`.
8122
+ """
8123
+
8124
+ def setUp (self ) -> None :
8125
+ self .bug_21390859 = BugOra21390859 ()
8126
+ self .bug_21390859 .setUp ()
8127
+
8128
+ def tearDown (self ) -> None :
8129
+ self .bug_21390859 .tearDown ()
8130
+
8131
+ @foreach_cnx_aio ()
8132
+ async def test_multi_stmts_with_callproc_out_of_sync (self ):
8133
+ async with await self .cnx .cursor () as cur :
8134
+ for i in self .bug_21390859 .use_case :
8135
+ exp_list = self .bug_21390859 .use_case_exp [i ]
8136
+ j = 0
8137
+ try :
8138
+ async for result in cur .executemulti (self .bug_21390859 .use_case [i ]):
8139
+ stmt_exp , fetch_exp = exp_list [j ]
8140
+ res = await result .fetchall ()
8141
+ self .assertEqual (result .statement , stmt_exp )
8142
+ self .assertListEqual (res , fetch_exp )
8143
+ j += 1
8144
+ except IndexError :
8145
+ self .fail (
8146
+ "Got more results than expected "
8147
+ f"for query { self .bug_21390859 .use_case [i ]} "
8148
+ )
0 commit comments