{"record":{"id":"a1ae5c389cf88be1","repo":"pola-rs/polars","slug":"unable-to-determine-metadata-from-query-result-s","errorCode":null,"errorMessage":"Unable to determine metadata from query result; {self.result!r}","messagePattern":"Unable to determine metadata from query result; (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"py-polars/src/polars/io/database/_executor.py","lineNumber":301,"sourceCode":"            msg = (\n                \"Cannot set `iter_batches` without also setting a non-zero `batch_size`\"\n            )\n            raise ValueError(msg)\n\n        if is_async := isinstance(original_result := self.result, Coroutine):\n            self.result = _run_async(self.result)\n        try:\n            if hasattr(self.result, \"fetchall\"):\n                if is_alchemy := (self.driver_name == \"sqlalchemy\"):\n                    if hasattr(self.result, \"cursor\"):\n                        cursor_desc = [\n                            (d[0], d[1:]) for d in self.result.cursor.description\n                        ]\n                    elif hasattr(self.result, \"_metadata\"):\n                        cursor_desc = [(k, None) for k in self.result._metadata.keys]\n                    else:\n                        msg = f\"Unable to determine metadata from query result; {self.result!r}\"\n                        raise ValueError(msg)\n\n                elif hasattr(self.result, \"description\"):\n                    cursor_desc = [(d[0], d[1:]) for d in self.result.description]\n                else:\n                    cursor_desc = []\n\n                schema_overrides = self._inject_type_overrides(\n                    description=cursor_desc,\n                    schema_overrides=(schema_overrides or {}),\n                )\n                result_columns = [nm for nm, _ in cursor_desc]\n                frames = (\n                    DataFrame(\n                        data=rows,\n                        schema=result_columns or None,\n                        schema_overrides=schema_overrides,\n                        infer_schema_length=infer_schema_length,\n                        orient=\"row\",","sourceCodeStart":283,"sourceCodeEnd":319,"githubUrl":"https://github.com/pola-rs/polars/blob/df599052daf96e7a9cc30a3b0c6bd25d6947e3c0/py-polars/src/polars/io/database/_executor.py#L283-L319","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass the executed result explicitly: result = conn.execute(text(query)); pl.read_database(result, connection=...)","Upgrade SQLAlchemy to 2.x so results expose the expected metadata attributes","For a connection string, use pl.read_database_uri(query, uri) instead of read_database with a connection object"],"exampleFix":"# before\ndf = pl.read_database('SELECT * FROM t', connection=engine)\n\n# after\nwith engine.connect() as conn:\n    df = pl.read_database('SELECT * FROM t', connection=conn)\n# or hand polars the URI directly\ndf = pl.read_database_uri('SELECT * FROM t', 'postgresql://user:pw@host/db')","handlingStrategy":"type-guard","validationCode":"def sqlalchemy_result_ok(result) -> bool:\n    return hasattr(result, 'cursor') or hasattr(result, '_metadata')\n\nresult = conn.execute(sqlalchemy_text(query))\nassert sqlalchemy_result_ok(result), 'pass an executed SQLAlchemy result'\ndf = pl.read_database(result, connection=conn)","typeGuard":"def has_sqlalchemy_metadata(obj: object) -> bool:\n    \"\"\"True when polars can introspect this SQLAlchemy result's columns.\"\"\"\n    return hasattr(obj, 'fetchall') and (\n        hasattr(obj, 'cursor') or hasattr(obj, '_metadata')\n    )","tryCatchPattern":"try:\n    df = pl.read_database(result, connection=conn)\nexcept ValueError as err:\n    if 'Unable to determine metadata' in str(err):\n        raise TypeError(\n            'pass the result of conn.execute(), not the engine/connection'\n        ) from err\n    raise","preventionTips":["Pass conn.execute(text(sql)) results, never the Engine or a bare Connection, to read_database","Prefer pl.read_database_uri for plain connection strings - it bypasses this path entirely","Keep SQLAlchemy >= 2.x so result objects expose the expected metadata attributes"],"tags":["polars","database","sqlalchemy","metadata","valueerror"],"backgroundTag":null,"analyzedSha":"df599052daf96e7a9cc30a3b0c6bd25d6947e3c0","analyzedAt":"2026-08-16T12:10:03.978Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}