diff --git a/_duckdb-stubs/__init__.pyi b/_duckdb-stubs/__init__.pyi index 8770483f..c1ac1ef0 100644 --- a/_duckdb-stubs/__init__.pyi +++ b/_duckdb-stubs/__init__.pyi @@ -1,7 +1,7 @@ import os import pathlib import typing -from typing_extensions import Self +from typing_extensions import LiteralString, Self from ._expression import Expression from ._enums import ( CSVLineTerminator, @@ -863,7 +863,7 @@ def ConstantExpression(value: PythonLiteral) -> Expression: ... def DefaultExpression() -> Expression: ... def FunctionExpression(function_name: str, *args: IntoExpr) -> Expression: ... def LambdaExpression(lhs: IntoExprColumn | tuple[IntoExprColumn, ...], rhs: IntoExpr) -> Expression: ... -def SQLExpression(expression: str) -> Expression: ... +def SQLExpression(expression: LiteralString) -> Expression: ... def StarExpression(*, exclude: Iterable[IntoExprColumn] | None = None) -> Expression: ... def aggregate( df: pandas.DataFrame, diff --git a/duckdb/experimental/spark/sql/functions.py b/duckdb/experimental/spark/sql/functions.py index 71ff8c59..164b0051 100644 --- a/duckdb/experimental/spark/sql/functions.py +++ b/duckdb/experimental/spark/sql/functions.py @@ -1,6 +1,6 @@ import warnings from collections.abc import Callable -from typing import TYPE_CHECKING, Any, Optional, Union, overload +from typing import TYPE_CHECKING, Any, LiteralString, Optional, Union, overload from duckdb import ( CaseExpression, @@ -6176,7 +6176,7 @@ def instr(str: "ColumnOrName", substr: str) -> Column: return _invoke_function("instr", _to_column_expr(str), ConstantExpression(substr)) -def expr(str: str) -> Column: +def expr(str: LiteralString) -> Column: """Parses the expression string into the column that it represents. .. versionadded:: 1.5.0 @@ -6186,7 +6186,7 @@ def expr(str: str) -> Column: Parameters ---------- - str : str + str : LiteralString expression defined in string. Returns: diff --git a/tests/fast/test_sql_expression.py b/tests/fast/test_sql_expression.py index f3cf41ca..e7eaf71d 100644 --- a/tests/fast/test_sql_expression.py +++ b/tests/fast/test_sql_expression.py @@ -49,6 +49,11 @@ def test_sql_expression_basic(self, duckdb_cursor): rel = duckdb_cursor.sql("SELECT 1").select(expr) assert rel.fetchall() == [("hello world",)] + # The annotation widens this to plain str, so passing it where a + # LiteralString is expected must give a typechecking error. + dangerous: str = "'untrusted input'" + SQLExpression("'this is ' || " + dangerous) # type: ignore[arg-type] + def test_sql_expression_with_columns(self, duckdb_cursor): # Create a test table duckdb_cursor.execute(