-
Notifications
You must be signed in to change notification settings - Fork 35
FIX: Cursor.describe invalid data #355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c403899
f96d14c
e135e40
38966d0
3f52dd7
38b041b
139994a
6fd823e
a7d9b8e
646ef04
8018872
67268e5
d335ed0
2dc93f3
eee5256
d289a9b
ced5fc2
0568451
257f0f6
033f7cd
63c08f5
6adeead
7b6da4e
42d3e09
31a6d13
6eb43ea
327ee42
7a487b8
839019c
466a4d5
5e67a1b
f484e8d
c0c2117
b5460dd
bced470
3d09f1a
fe2a042
9913467
c0b619e
96b3240
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -48,6 +48,103 @@ | |||||||||||||||
| MONEY_MAX: decimal.Decimal = decimal.Decimal("922337203685477.5807") | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| class SQLTypeCode: | ||||||||||||||||
| """ | ||||||||||||||||
| A dual-compatible type code that compares equal to both SQL type integers and Python types. | ||||||||||||||||
|
|
||||||||||||||||
| This class maintains backwards compatibility with code that checks | ||||||||||||||||
| `cursor.description[i][1] == str` while also supporting DB-API 2.0 | ||||||||||||||||
| compliant code that checks `cursor.description[i][1] == -9`. | ||||||||||||||||
|
|
||||||||||||||||
| Examples: | ||||||||||||||||
| >>> type_code = SQLTypeCode(-9, str) | ||||||||||||||||
| >>> type_code == str # Backwards compatible with pandas, etc. | ||||||||||||||||
| True | ||||||||||||||||
| >>> type_code == -9 # DB-API 2.0 compliant | ||||||||||||||||
| True | ||||||||||||||||
| >>> int(type_code) # Get the raw SQL type code | ||||||||||||||||
| -9 | ||||||||||||||||
| """ | ||||||||||||||||
|
|
||||||||||||||||
| # SQL type code to Python type mapping (class-level cache) | ||||||||||||||||
| _type_map = None | ||||||||||||||||
|
|
||||||||||||||||
| def __init__(self, type_code: int, python_type: type = None): | ||||||||||||||||
| self.type_code = type_code | ||||||||||||||||
| # If python_type not provided, look it up from the mapping | ||||||||||||||||
| if python_type is None: | ||||||||||||||||
| python_type = self._get_python_type(type_code) | ||||||||||||||||
| self.python_type = python_type | ||||||||||||||||
|
|
||||||||||||||||
| @classmethod | ||||||||||||||||
| def _get_type_map(cls): | ||||||||||||||||
| """Lazily build the SQL to Python type mapping.""" | ||||||||||||||||
| if cls._type_map is None: | ||||||||||||||||
| cls._type_map = { | ||||||||||||||||
| ddbc_sql_const.SQL_CHAR.value: str, | ||||||||||||||||
| ddbc_sql_const.SQL_VARCHAR.value: str, | ||||||||||||||||
| ddbc_sql_const.SQL_LONGVARCHAR.value: str, | ||||||||||||||||
| ddbc_sql_const.SQL_WCHAR.value: str, | ||||||||||||||||
| ddbc_sql_const.SQL_WVARCHAR.value: str, | ||||||||||||||||
| ddbc_sql_const.SQL_WLONGVARCHAR.value: str, | ||||||||||||||||
| ddbc_sql_const.SQL_INTEGER.value: int, | ||||||||||||||||
| ddbc_sql_const.SQL_REAL.value: float, | ||||||||||||||||
| ddbc_sql_const.SQL_FLOAT.value: float, | ||||||||||||||||
| ddbc_sql_const.SQL_DOUBLE.value: float, | ||||||||||||||||
| ddbc_sql_const.SQL_DECIMAL.value: decimal.Decimal, | ||||||||||||||||
| ddbc_sql_const.SQL_NUMERIC.value: decimal.Decimal, | ||||||||||||||||
| ddbc_sql_const.SQL_DATE.value: datetime.date, | ||||||||||||||||
| ddbc_sql_const.SQL_TIMESTAMP.value: datetime.datetime, | ||||||||||||||||
| ddbc_sql_const.SQL_TIME.value: datetime.time, | ||||||||||||||||
|
||||||||||||||||
| ddbc_sql_const.SQL_TIME.value: datetime.time, | |
| ddbc_sql_const.SQL_TIME.value: datetime.time, | |
| # ODBC 3.x date/time type codes (SQL_TYPE_DATE/TIME/TIMESTAMP/TIMESTAMP_WITH_TIMEZONE) | |
| 91: datetime.date, # SQL_TYPE_DATE | |
| 92: datetime.time, # SQL_TYPE_TIME | |
| 93: datetime.datetime, # SQL_TYPE_TIMESTAMP | |
| 95: datetime.datetime, # SQL_TYPE_TIMESTAMP_WITH_TIMEZONE |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SQLTypeCode defines hash as hash(type_code), but eq can return True when compared to objects with unrelated hash values (e.g., SQLTypeCode(-9) == str). This violates Python’s hash/eq contract and will make set/dict behavior inconsistent. Consider making SQLTypeCode unhashable (remove/set hash = None) or narrowing equality semantics so only same-kind objects compare equal.
| def __hash__(self): | |
| return hash(self.type_code) | |
| # Instances are intentionally unhashable because __eq__ allows | |
| # comparisons to both Python types and integer SQL codes, and | |
| # there is no single hash value that can be consistent with both. | |
| __hash__ = None |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SQLTypeCode is not an int, so passing cursor.description[i][1] into APIs that expect an integer SQL type (e.g., add_output_converter) will register/look up converters under a non-int key and break matching when the driver later uses raw integer SQL type codes. Consider normalizing such APIs to int(sqltype) for SQLTypeCode inputs and/or implementing index / an int-subclass approach.
Uh oh!
There was an error while loading. Please reload this page.