@@ -1660,6 +1660,128 @@ func TestInvalidEmailParseStatement(t *testing.T) {
16601660 assertEqual (t , all_roles , role )
16611661 }
16621662}
1663+
1664+ // TestStoredProcedureWithNotice verifies that rows.Next() does not panic when a
1665+ // stored procedure raises one or more RAISE NOTICE messages before returning its
1666+ // result set.
1667+ //
1668+ // Root cause: Vertica sends a correct RowDescription (2 cols: a, b) for the CALL
1669+ // result, then emits NoticeResponse messages, then sends a spurious second
1670+ // RowDescription with fewer columns (a PLvSQL protocol artefact). The driver was
1671+ // blindly adopting the second RowDescription, so r.columnDefs ended up with 1
1672+ // column while the DataRow still contained 2 fields — causing an index-out-of-range
1673+ // panic in rows.Next().
1674+ //
1675+ // Fix: runSimpleStatement and collectResults now ignore any RowDescription that
1676+ // arrives after DataRows have already been buffered for the current result set.
1677+ func TestStoredProcedureWithNotice (t * testing.T ) {
1678+ connDB := openConnection (t )
1679+ defer closeConnection (t , connDB )
1680+
1681+ // Create the procedure; drop it afterward.
1682+ _ , err := connDB .ExecContext (ctx , `
1683+ CREATE OR REPLACE PROCEDURE test_notice_proc(INOUT a INT, INOUT b VARCHAR(64))
1684+ LANGUAGE PLvSQL AS $$
1685+ BEGIN
1686+ RAISE NOTICE 'Value of a: %', a;
1687+ RAISE NOTICE 'Value of b: %', b;
1688+ END;
1689+ $$` )
1690+ assertNoErr (t , err )
1691+
1692+ defer connDB .ExecContext (ctx , "DROP PROCEDURE IF EXISTS test_notice_proc(INT, VARCHAR)" )
1693+
1694+ // CALL must not panic. We wrap in a recover so that a panic becomes a
1695+ // test failure with a meaningful message rather than crashing the whole
1696+ // test binary.
1697+ func () {
1698+ defer func () {
1699+ if r := recover (); r != nil {
1700+ t .Fatalf ("rows.Next() panicked when stored procedure emitted NOTICE messages: %v" , r )
1701+ }
1702+ }()
1703+
1704+ rows , err := connDB .QueryContext (ctx , "CALL test_notice_proc(10, 'hello')" )
1705+ assertNoErr (t , err )
1706+ defer rows .Close ()
1707+
1708+ // Iterate over all result sets (NOTICE messages may produce extra sets).
1709+ for {
1710+ cols , err := rows .Columns ()
1711+ assertNoErr (t , err )
1712+
1713+ valuePtrs := make ([]interface {}, len (cols ))
1714+ values := make ([]interface {}, len (cols ))
1715+ for i := range values {
1716+ valuePtrs [i ] = & values [i ]
1717+ }
1718+
1719+ for rows .Next () {
1720+ assertNoErr (t , rows .Scan (valuePtrs ... ))
1721+ testLogger .Debug ("TestStoredProcedureWithNotice row: %v" , values )
1722+ }
1723+
1724+ if ! rows .NextResultSet () {
1725+ break
1726+ }
1727+ }
1728+ }()
1729+ }
1730+
1731+ // TestStoredProcedureWithNoticeSimpleQuery is the same scenario as
1732+ // TestStoredProcedureWithNotice but forces the simple query protocol path
1733+ // (use_prepared_statements=0) to verify correct behaviour on both code paths.
1734+ func TestStoredProcedureWithNoticeSimpleQuery (t * testing.T ) {
1735+ simpleConnStr := strings .Replace (myDBConnectString , "use_prepared_statements=1" , "use_prepared_statements=0" , 1 )
1736+ connDB , err := sql .Open ("vertica" , simpleConnStr )
1737+ assertNoErr (t , err )
1738+ assertNoErr (t , connDB .PingContext (ctx ))
1739+ defer closeConnection (t , connDB )
1740+
1741+ _ , err = connDB .ExecContext (ctx , `
1742+ CREATE OR REPLACE PROCEDURE test_notice_proc_simple(INOUT a INT, INOUT b VARCHAR(64))
1743+ LANGUAGE PLvSQL AS $$
1744+ BEGIN
1745+ RAISE NOTICE 'Value of a: %', a;
1746+ RAISE NOTICE 'Value of b: %', b;
1747+ END;
1748+ $$` )
1749+ assertNoErr (t , err )
1750+ defer connDB .ExecContext (ctx , "DROP PROCEDURE IF EXISTS test_notice_proc_simple(INT, VARCHAR)" )
1751+
1752+ func () {
1753+ defer func () {
1754+ if r := recover (); r != nil {
1755+ t .Fatalf ("rows.Next() panicked on simple query path: %v" , r )
1756+ }
1757+ }()
1758+
1759+ rows , err := connDB .QueryContext (ctx , "CALL test_notice_proc_simple(10, 'hello')" )
1760+ assertNoErr (t , err )
1761+ defer rows .Close ()
1762+
1763+ for {
1764+ cols , err := rows .Columns ()
1765+ assertNoErr (t , err )
1766+
1767+ valuePtrs := make ([]interface {}, len (cols ))
1768+ values := make ([]interface {}, len (cols ))
1769+ for i := range values {
1770+ valuePtrs [i ] = & values [i ]
1771+ }
1772+
1773+ for rows .Next () {
1774+ assertNoErr (t , rows .Scan (valuePtrs ... ))
1775+ testLogger .Debug ("TestStoredProcedureWithNoticeSimpleQuery row: %v" , values )
1776+ }
1777+
1778+ if ! rows .NextResultSet () {
1779+ break
1780+ }
1781+ }
1782+ }()
1783+ }
1784+
16631785func init () {
16641786 // One or both lines below are necessary depending on your go version
16651787 testing .Init ()
0 commit comments