Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions core/wren/src/wren/connector/trino.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,20 @@ def _import_trino():
) from e


def _coerce_limit(limit: int | None) -> int | None:
"""Validate and coerce a user-supplied ``limit`` to a non-negative ``int``.

``int(limit)`` rejects strings like ``"5 OR 1=1"`` so the value can be
safely interpolated into SQL. Negative limits are also rejected.
"""
if limit is None:
return None
coerced = int(limit)
if coerced < 0:
raise ValueError(f"limit must be non-negative, got {coerced}")
return coerced
Comment on lines +459 to +470

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject fractional negative limits before coercion.

int(-0.5) becomes 0, so the check at Line 468 accepts an originally negative limit and executes LIMIT 0. Validate the original numeric value before truncation, or reject non-integral numeric inputs; add a regression test for limit=-0.5.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/wren/src/wren/connector/trino.py` around lines 459 - 470, Update
_coerce_limit to reject negative fractional numeric inputs before int coercion,
while preserving acceptance of valid non-negative integral limits and existing
rejection of negative integers. Add a regression test confirming limit=-0.5
raises ValueError rather than becoming LIMIT 0.



class TrinoConnector(ConnectorABC):
"""Native trino DB-API connector that bypasses ibis-project."""

Expand All @@ -482,6 +496,7 @@ def __init__(self, connection_info):
self._closed = False

def query(self, sql: str, limit: int | None = None) -> pa.Table:
limit = _coerce_limit(limit)
trino = _import_trino()

if limit is not None:
Expand Down
45 changes: 45 additions & 0 deletions core/wren/tests/unit/test_trino_coerce_limit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Trino query must coerce LIMIT before SQL interpolation."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest

from wren.connector import trino as trino_mod


def _connector():
c = object.__new__(trino_mod.TrinoConnector)
c.connection = MagicMock()
c._closed = False
return c


def test_reject_negative_limit():
c = _connector()
with pytest.raises(ValueError, match="non-negative"):
c.query("SELECT 1", limit=-1)


def test_reject_injection_string():
c = _connector()
with pytest.raises(ValueError):
c.query("SELECT 1", limit="1; DROP TABLE t")


def test_numeric_string_limit_interpolated():
c = _connector()
cursor = MagicMock()
c.connection.cursor.return_value = cursor
with patch.object(trino_mod, "_import_trino") as imp, patch.object(
trino_mod, "_build_trino_arrow_table", return_value="tbl"
):
trino_pkg = MagicMock()
trino_pkg.exceptions.TrinoQueryError = type("TrinoQueryError", (Exception,), {})
imp.return_value = trino_pkg
out = c.query("SELECT 1", limit="4")
assert out == "tbl"
executed = cursor.execute.call_args[0][0]
assert "LIMIT 4" in executed
assert "DROP" not in executed
Loading