Summary
The library declares PEP 249 (DB-API 2.0) compliance with apilevel = "2.0", but has significant gaps in cursor behavior that break interop with tools expecting standard DB-API behavior.
Current Problems
1. cursor.description returns None
core/cursor.py has:
@property
def description(self) -> Optional[Sequence[ColumnDescription]]:
pass # Returns None
PEP 249 requires this to return a sequence of 7-item tuples (name, type_code, display_size, internal_size, precision, scale, null_ok) after any query execution. The server already returns this metadata in QueryMetaData.columns — it just needs to be mapped.
2. fetchone() returns a dict, not a row
Currently fetchone() returns the raw server response dict ({'data': [...], 'metadata': {...}, ...}). PEP 249 specifies it should return a single row as a sequence (tuple), or None if no more rows.
Users must write:
result = cursor.fetchone()
row = result['data'][0] # Have to dig into the dict
Instead of the expected:
row = cursor.fetchone() # Should return (value1, value2, ...) directly
3. fetchall() and fetchmany() have the same issue
These return the raw response dict rather than a list of row tuples.
4. cursor.rowcount inconsistency
The rowcount property should be updated after each execute/fetch but may not be consistent across all code paths.
Proposed Changes
-
Populate cursor.description from QueryMetaData.columns after query execution:
- Map
ColumnMetaData.name -> name
- Map
ColumnMetaData.type -> type_code
- Map
ColumnMetaData.display_size -> display_size
- Map
ColumnMetaData.precision -> precision
- Map
ColumnMetaData.scale -> scale
- Map
ColumnMetaData.nullable -> null_ok
-
Fix fetchone() to return a single row tuple (or a Row object that supports both index and key access), or None
-
Fix fetchall() to return a list of row tuples
-
Fix fetchmany() to return a list of row tuples up to arraysize
-
Apply the same fixes to AsyncCursor
-
Ensure rowcount is correctly updated after execute, executemany, and fetch operations
Files to Modify
mapepire_python/core/cursor.py — Main cursor fixes
mapepire_python/asyncio/cursor.py — Async cursor fixes
mapepire_python/data_types.py — Potentially add a Row class
Acceptance Criteria
Summary
The library declares PEP 249 (DB-API 2.0) compliance with
apilevel = "2.0", but has significant gaps in cursor behavior that break interop with tools expecting standard DB-API behavior.Current Problems
1.
cursor.descriptionreturnsNonecore/cursor.pyhas:PEP 249 requires this to return a sequence of 7-item tuples
(name, type_code, display_size, internal_size, precision, scale, null_ok)after any query execution. The server already returns this metadata inQueryMetaData.columns— it just needs to be mapped.2.
fetchone()returns a dict, not a rowCurrently
fetchone()returns the raw server response dict ({'data': [...], 'metadata': {...}, ...}). PEP 249 specifies it should return a single row as a sequence (tuple), orNoneif no more rows.Users must write:
Instead of the expected:
3.
fetchall()andfetchmany()have the same issueThese return the raw response dict rather than a list of row tuples.
4.
cursor.rowcountinconsistencyThe
rowcountproperty should be updated after each execute/fetch but may not be consistent across all code paths.Proposed Changes
Populate
cursor.descriptionfromQueryMetaData.columnsafter query execution:ColumnMetaData.name->nameColumnMetaData.type->type_codeColumnMetaData.display_size->display_sizeColumnMetaData.precision->precisionColumnMetaData.scale->scaleColumnMetaData.nullable->null_okFix
fetchone()to return a single row tuple (or aRowobject that supports both index and key access), orNoneFix
fetchall()to return a list of row tuplesFix
fetchmany()to return a list of row tuples up toarraysizeApply the same fixes to
AsyncCursorEnsure
rowcountis correctly updated after execute, executemany, and fetch operationsFiles to Modify
mapepire_python/core/cursor.py— Main cursor fixesmapepire_python/asyncio/cursor.py— Async cursor fixesmapepire_python/data_types.py— Potentially add aRowclassAcceptance Criteria
cursor.descriptionreturns column metadata after query executioncursor.fetchone()returns a single row (tuple or Row object) orNonecursor.fetchall()returns a list of rowscursor.fetchmany(n)returns up tonrowscursor.rowcountis accurate after execute/fetchAsyncCursorcursor.descriptioncolumn metadata mapping