Skip to content

Fix PEP 249 compliance: cursor.description and result types #91

@ajshedivy

Description

@ajshedivy

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

  1. 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
  2. Fix fetchone() to return a single row tuple (or a Row object that supports both index and key access), or None

  3. Fix fetchall() to return a list of row tuples

  4. Fix fetchmany() to return a list of row tuples up to arraysize

  5. Apply the same fixes to AsyncCursor

  6. 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

  • cursor.description returns column metadata after query execution
  • cursor.fetchone() returns a single row (tuple or Row object) or None
  • cursor.fetchall() returns a list of rows
  • cursor.fetchmany(n) returns up to n rows
  • cursor.rowcount is accurate after execute/fetch
  • Same behavior for AsyncCursor
  • Existing PEP 249 tests updated to validate new behavior
  • New tests for cursor.description column metadata mapping

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions