pola-rs/polars · error · ValueError

Unable to determine metadata from query result; {self.result

Error message

Unable to determine metadata from query result; {self.result!r}

What it means

On the row-wise path, when the result object reports driver_name 'sqlalchemy', polars needs column metadata: it looks for result.cursor.description or result._metadata. A SQLAlchemy result that has fetchall but neither attribute cannot be introspected, so polars raises ValueError with the repr of the result object so you can see what was actually passed. This is a shape mismatch - you handed read_database something that is result-like but not a standard execute() result.

Source

Thrown at py-polars/src/polars/io/database/_executor.py:301

            msg = (
                "Cannot set `iter_batches` without also setting a non-zero `batch_size`"
            )
            raise ValueError(msg)

        if is_async := isinstance(original_result := self.result, Coroutine):
            self.result = _run_async(self.result)
        try:
            if hasattr(self.result, "fetchall"):
                if is_alchemy := (self.driver_name == "sqlalchemy"):
                    if hasattr(self.result, "cursor"):
                        cursor_desc = [
                            (d[0], d[1:]) for d in self.result.cursor.description
                        ]
                    elif hasattr(self.result, "_metadata"):
                        cursor_desc = [(k, None) for k in self.result._metadata.keys]
                    else:
                        msg = f"Unable to determine metadata from query result; {self.result!r}"
                        raise ValueError(msg)

                elif hasattr(self.result, "description"):
                    cursor_desc = [(d[0], d[1:]) for d in self.result.description]
                else:
                    cursor_desc = []

                schema_overrides = self._inject_type_overrides(
                    description=cursor_desc,
                    schema_overrides=(schema_overrides or {}),
                )
                result_columns = [nm for nm, _ in cursor_desc]
                frames = (
                    DataFrame(
                        data=rows,
                        schema=result_columns or None,
                        schema_overrides=schema_overrides,
                        infer_schema_length=infer_schema_length,
                        orient="row",

View on GitHub (pinned to df599052da)

Solutions

  1. Pass the executed result explicitly: result = conn.execute(text(query)); pl.read_database(result, connection=...)
  2. Upgrade SQLAlchemy to 2.x so results expose the expected metadata attributes
  3. For a connection string, use pl.read_database_uri(query, uri) instead of read_database with a connection object

Example fix

# before
df = pl.read_database('SELECT * FROM t', connection=engine)

# after
with engine.connect() as conn:
    df = pl.read_database('SELECT * FROM t', connection=conn)
# or hand polars the URI directly
df = pl.read_database_uri('SELECT * FROM t', 'postgresql://user:pw@host/db')
Defensive patterns

Strategy: type-guard

Validate before calling

def sqlalchemy_result_ok(result) -> bool:
    return hasattr(result, 'cursor') or hasattr(result, '_metadata')

result = conn.execute(sqlalchemy_text(query))
assert sqlalchemy_result_ok(result), 'pass an executed SQLAlchemy result'
df = pl.read_database(result, connection=conn)

Type guard

def has_sqlalchemy_metadata(obj: object) -> bool:
    """True when polars can introspect this SQLAlchemy result's columns."""
    return hasattr(obj, 'fetchall') and (
        hasattr(obj, 'cursor') or hasattr(obj, '_metadata')
    )

Try / catch

try:
    df = pl.read_database(result, connection=conn)
except ValueError as err:
    if 'Unable to determine metadata' in str(err):
        raise TypeError(
            'pass the result of conn.execute(), not the engine/connection'
        ) from err
    raise

Prevention

When it happens

Trigger: pl.read_database(query, connection=sqlalchemy_engine) (an Engine, not a result); passing a Connection where polars expected the result of conn.execute(...); exotic/legacy SQLAlchemy result types or third-party wrappers lacking cursor and _metadata; a partially-consumed or closed result whose attributes were released.

Common situations: Passing engine.connect() or the engine itself instead of the executed statement's result; older code written against read_database_uri semantics; mocking SQLAlchemy objects in tests without the expected attributes.

Related errors


AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16). Data as JSON: /api/errors/a1ae5c389cf88be1. Report an issue: GitHub.