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

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions