Skip to content

Commit 1e73869

Browse files
Support error reporting APIs in ibm_db_dbi: conn_errormsg, conn_error, stmt_errormsg, stmt_error, get_sqlcode (ibmdb#1025)
* Support error reporting APIs in ibm_db_dbi: conn_errormsg, conn_error, stmt_errormsg, stmt_error, get_sqlcode --------- Signed-off-by: Balram Choudhary <bchoudhary@rocketsoftware.com>
1 parent a7421ca commit 1e73869

File tree

5 files changed

+271
-0
lines changed

5 files changed

+271
-0
lines changed

INSTALL.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,12 @@ else:
383383
print('ODBC Test end')
384384
```
385385
386+
#### Troubleshooting
387+
388+
**Important:** When working with `ibm_db` or `ibm_db_dbi` on z/OS environments such as USS and BPXBATCH, always use the provided APIs — `conn_errormsg()`, `conn_error()`, `stmt_errormsg()`, `stmt_error()`, and `get_sqlcode()` — to retrieve error messages, SQLSTATE and SQLCODE values for connection or statement failures.
389+
Avoid using direct `print()` statements or relying on raw exception output, as this may result in missing or unreadable error messages, or `UnicodeDecodeError` due to encoding limitations on these platforms.
390+
These APIs ensure consistent and reliable error handling across all supported environments.
391+
386392
## <a name="m1chip"></a> 3. ibm_db installation on MacOS M1/M2 Chip System (arm64 architecture)
387393
**Important:
388394
> ibm_db@3.2.5 onwards supports native installation on MacOS ARM64(M* Chip/Apple Silicon Chip) system using clidriver/dsdriver version 12.1.0.

ibm_db_dbi.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,87 @@ def _get_exception(inst):
492492
return NotSupportedError(message)
493493
return DatabaseError(message)
494494

495+
def conn_errormsg(connection=None):
496+
"""
497+
Module-level wrapper for ibm_db.conn_errormsg().
498+
499+
When no connection handle is passed, returns the last global connection error message.
500+
When a valid IBM_DBConnection handle is passed, returns the error for that connection.
501+
502+
Args:
503+
connection: Optional IBM_DBConnection handle (default None).
504+
505+
Returns:
506+
str: SQLCODE and error message describing the last connection error,
507+
or empty string if no error.
508+
"""
509+
LogMsg(INFO, "entry conn_errormsg()")
510+
if connection is not None:
511+
LogMsg(DEBUG, f"Getting connection error message for connection handle: {connection}")
512+
err_msg = ibm_db.conn_errormsg(connection)
513+
else:
514+
LogMsg(DEBUG, "Getting global last connection error message (no handle passed)")
515+
err_msg = ibm_db.conn_errormsg()
516+
LogMsg(DEBUG, f"conn_errormsg result: {err_msg!r}")
517+
LogMsg(INFO, "exit conn_errormsg()")
518+
return err_msg
519+
520+
def conn_error(connection=None):
521+
"""
522+
Module-level wrapper for ibm_db.conn_error().
523+
524+
When no connection handle is passed, returns the last global SQLSTATE for a connection error.
525+
When a valid IBM_DBConnection handle is passed, returns the SQLSTATE for that connection.
526+
527+
Args:
528+
connection: Optional IBM_DBConnection handle (default None).
529+
530+
Returns:
531+
str: SQLSTATE string representing the reason the last connection operation failed,
532+
or an empty string if no error occurred.
533+
"""
534+
LogMsg(INFO, "entry conn_error()")
535+
if connection is not None:
536+
LogMsg(DEBUG, f"Getting connection SQLSTATE for connection handle: {connection}")
537+
sqlstate = ibm_db.conn_error(connection)
538+
else:
539+
LogMsg(DEBUG, "Getting global last connection SQLSTATE (no handle passed)")
540+
sqlstate = ibm_db.conn_error()
541+
LogMsg(DEBUG, f"conn_error result: {sqlstate!r}")
542+
LogMsg(INFO, "exit conn_error()")
543+
return sqlstate
544+
545+
546+
def get_sqlcode(handle=None):
547+
"""
548+
Retrieve the SQLCODE of the last operation performed.
549+
550+
When no handle is passed, returns the SQLCODE for the last global operation.
551+
552+
When a valid IBM_DBConnection or IBM_DBStatement handle is passed, returns
553+
the SQLCODE for the last operation using that resource.
554+
555+
Args:
556+
handle (optional): IBM_DBConnection or IBM_DBStatement handle.
557+
558+
Returns:
559+
str: SQLCODE string representing the last error code,
560+
or empty string if no error occurred.
561+
"""
562+
LogMsg(INFO, "entry get_sqlcode()")
563+
564+
if handle is not None:
565+
LogMsg(DEBUG, f"Getting SQLCODE for handle: {handle}")
566+
sqlcode = ibm_db.get_sqlcode(handle)
567+
else:
568+
LogMsg(DEBUG, "Getting global last SQLCODE (no handle passed)")
569+
sqlcode = ibm_db.get_sqlcode()
570+
571+
LogMsg(DEBUG, f"get_sqlcode result: {sqlcode}")
572+
LogMsg(INFO, "exit get_sqlcode()")
573+
574+
return sqlcode
575+
495576

496577
def _retrieve_current_schema(dsn):
497578
"""This method retrieve the value of ODBC keyword CURRENTSCHEMA from DSN
@@ -1657,6 +1738,51 @@ def _get_last_identity_val(self):
16571738

16581739
last_identity_val = property(_get_last_identity_val, None, None, "")
16591740

1741+
def stmt_errormsg(self):
1742+
"""
1743+
Retrieve the SQLCODE and error message for the last operation performed
1744+
using this cursor's statement handle.
1745+
1746+
If `self.stmt_handler` is set (not None), returns the error specific to that statement.
1747+
Otherwise, returns the last global error message.
1748+
1749+
Returns:
1750+
str: SQLCODE and error message describing why the last operation failed,
1751+
or an empty string if no error occurred.
1752+
"""
1753+
LogMsg(INFO, "entry stmt_errormsg()")
1754+
if self.stmt_handler is not None:
1755+
LogMsg(DEBUG, f"Getting statement error message for stmt_handler: {self.stmt_handler}")
1756+
err_msg = ibm_db.stmt_errormsg(self.stmt_handler)
1757+
else:
1758+
LogMsg(DEBUG, "Getting global last statement error message (no stmt_handler set)")
1759+
err_msg = ibm_db.stmt_errormsg()
1760+
LogMsg(DEBUG, f"stmt_errormsg result: {err_msg!r}")
1761+
LogMsg(INFO, "exit stmt_errormsg()")
1762+
return err_msg
1763+
1764+
def stmt_error(self):
1765+
"""
1766+
Retrieve the SQLSTATE code for the last operation performed using this cursor's statement handle.
1767+
1768+
If `self.stmt_handler` is set (not None), returns the SQLSTATE specific to that statement.
1769+
Otherwise, returns the last global SQLSTATE from ibm_db.stmt_error().
1770+
1771+
Returns:
1772+
str: SQLSTATE code representing the reason the last operation failed,
1773+
or an empty string if there was no error.
1774+
"""
1775+
LogMsg(INFO, "entry stmt_error()")
1776+
if self.stmt_handler is not None:
1777+
LogMsg(DEBUG, f"Getting statement SQLSTATE for stmt_handler: {self.stmt_handler}")
1778+
sqlstate = ibm_db.stmt_error(self.stmt_handler)
1779+
else:
1780+
LogMsg(DEBUG, "Getting global last statement SQLSTATE (no stmt_handler set)")
1781+
sqlstate = ibm_db.stmt_error()
1782+
LogMsg(DEBUG, f"stmt_error result: {sqlstate!r}")
1783+
LogMsg(INFO, "exit stmt_error()")
1784+
return sqlstate
1785+
16601786
def execute(self, operation, parameters=None):
16611787
"""
16621788
This method can be used to prepare and execute an SQL
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#
2+
# Licensed Materials - Property of IBM
3+
#
4+
# (c) Copyright IBM Corp. 2025
5+
#
6+
7+
from __future__ import print_function
8+
import sys
9+
import unittest
10+
import ibm_db
11+
import ibm_db_dbi
12+
import config
13+
from testfunctions import IbmDbTestFunctions
14+
15+
16+
class IbmDbTestCase(unittest.TestCase):
17+
18+
def test_316_stmtErrormsg_stmtError_DBI(self):
19+
obj = IbmDbTestFunctions()
20+
obj.assert_expect(self.run_test_316)
21+
22+
def run_test_316(self):
23+
conn = ibm_db_dbi.connect(config.database, config.user, config.password)
24+
cursor = conn.cursor()
25+
26+
try:
27+
cursor.execute("INVALID SQL")
28+
except Exception:
29+
print(cursor.stmt_errormsg())
30+
print(cursor.stmt_error())
31+
32+
cursor.close()
33+
conn.close()
34+
35+
#__END__
36+
#__LUW_EXPECTED__
37+
#[IBM][CLI Driver][DB2/LINUXX8664] SQL0104N An unexpected token "END-OF-STATEMENT" was found following "INVALID SQL". Expected tokens may include: "JOIN <joined_table>". SQLSTATE=42601 SQLCODE=-104
38+
#42601
39+
#__ZOS_EXPECTED__
40+
#[IBM][CLI Driver][DB2/LINUXX8664] SQL0104N An unexpected token "END-OF-STATEMENT" was found following "INVALID SQL". Expected tokens may include: "JOIN <joined_table>". SQLSTATE=42601 SQLCODE=-104
41+
#42601
42+
#__SYSTEMI_EXPECTED__
43+
#[IBM][CLI Driver][AS] SQL0104N An unexpected token "END-OF-STATEMENT" was found following "INVALID SQL". Expected tokens may include: "JOIN <joined_table>". SQLSTATE=42601 SQLCODE=-104
44+
#42601
45+
#__IDS_EXPECTED__
46+
#[IBM][CLI Driver][IDS/LINUXX8664] SQL0104N An unexpected token "END-OF-STATEMENT" was found following "INVALID SQL". Expected tokens may include: "JOIN <joined_table>". SQLSTATE=42601 SQLCODE=-104
47+
#42601
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#
2+
# Licensed Materials - Property of IBM
3+
#
4+
# (c) Copyright IBM Corp. 2025
5+
#
6+
7+
from __future__ import print_function
8+
import sys
9+
import unittest
10+
import ibm_db
11+
import ibm_db_dbi
12+
import config
13+
from testfunctions import IbmDbTestFunctions
14+
15+
16+
class IbmDbTestCase(unittest.TestCase):
17+
18+
def test_317_connErrormsg_connError_DBI(self):
19+
obj = IbmDbTestFunctions()
20+
obj.assert_expect(self.run_test_317)
21+
22+
def run_test_317(self):
23+
try:
24+
# Intentionally wrong credentials to trigger connection failure
25+
conn = ibm_db_dbi.connect(config.database, "wrongUser", "wrongPassword")
26+
print(conn) # Should not get here
27+
except Exception:
28+
print(ibm_db_dbi.conn_errormsg())
29+
print(ibm_db_dbi.conn_error())
30+
31+
#__END__
32+
#__LUW_EXPECTED__
33+
#[IBM][CLI Driver] SQL30082N Security processing failed with reason "24" ("USERNAME AND/OR PASSWORD INVALID"). SQLSTATE=08001 SQLCODE=-30082
34+
#08001
35+
#__ZOS_EXPECTED__
36+
#[IBM][CLI Driver] SQL30082N Security processing failed with reason "24" ("USERNAME AND/OR PASSWORD INVALID"). SQLSTATE=08001 SQLCODE=-30082
37+
#08001
38+
#__SYSTEMI_EXPECTED__
39+
#[IBM][CLI Driver] SQL30082N Security processing failed with reason "24" ("USERNAME AND/OR PASSWORD INVALID"). SQLSTATE=08001 SQLCODE=-30082
40+
#08001
41+
#__IDS_EXPECTED__
42+
#[IBM][CLI Driver] SQL30082N Security processing failed with reason "24" ("USERNAME AND/OR PASSWORD INVALID"). SQLSTATE=08001 SQLCODE=-30082
43+
#08001
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#
2+
# Licensed Materials - Property of IBM
3+
#
4+
# (c) Copyright IBM Corp. 2025
5+
#
6+
7+
from __future__ import print_function
8+
import sys
9+
import unittest
10+
import ibm_db
11+
import ibm_db_dbi
12+
import config
13+
from testfunctions import IbmDbTestFunctions
14+
15+
16+
class IbmDbTestCase(unittest.TestCase):
17+
18+
def test_318_get_sqlcode_DBI(self):
19+
obj = IbmDbTestFunctions()
20+
obj.assert_expect(self.run_test_318)
21+
22+
def run_test_318(self):
23+
try:
24+
# Intentionally wrong credentials to trigger connection failure
25+
conn = ibm_db_dbi.connect(config.database, "wrongUser", "wrongPassword")
26+
print(conn) # Should not get here
27+
except Exception:
28+
print(ibm_db_dbi.get_sqlcode())
29+
30+
try:
31+
conn1 = ibm_db_dbi.connect(config.database, config.user, config.password)
32+
cursor = conn1.cursor()
33+
cursor.execute("create table index_test(id int, data VARCHAR(50)")
34+
except Exception:
35+
print(ibm_db_dbi.get_sqlcode())
36+
37+
#__END__
38+
#__LUW_EXPECTED__
39+
#SQLCODE=-30082
40+
#SQLCODE=-104
41+
#__ZOS_EXPECTED__
42+
#SQLCODE=-30082
43+
#SQLCODE=-104
44+
#__SYSTEMI_EXPECTED__
45+
#SQLCODE=-30082
46+
#SQLCODE=-104
47+
#__IDS_EXPECTED__
48+
#SQLCODE=-30082
49+
#SQLCODE=-104

0 commit comments

Comments
 (0)