:tags: bug, sql
:tickets: 8827
- Fixed a series of issues regarding positionally rendered bound parameters,
- such as those used for SQLite, asyncpg, MySQL and others. Some compiled
- forms would not maintain the order of parameters correctly, such as the
- PostgreSQL ``regexp_replace()`` function as well as within the "nesting"
- feature of the :class:`.CTE` construct first introduced in :ticket:`4123`.
+ Fixed a series of issues regarding the position and sometimes the identity
+ of rendered bound parameters, such as those used for SQLite, asyncpg,
+ MySQL, Oracle and others. Some compiled forms would not maintain the order
+ of parameters correctly, such as the PostgreSQL ``regexp_replace()``
+ function, the "nesting" feature of the :class:`.CTE` construct first
+ introduced in :ticket:`4123`, and selectable tables formed by using the
+ :meth:`.FunctionElement.column_valued` method with Oracle.
+
--- /dev/null
+.. change::
+ :tags: bug, oracle
+ :tickets: 8945
+ :versions: 2.0.0b5
+
+ Fixed issue in Oracle compiler where the syntax for
+ :meth:`.FunctionElement.column_valued` was incorrect, rendering the name
+ ``COLUMN_VALUE`` without qualifying the source table correctly.
>>> from sqlalchemy.dialects import oracle
>>> stmt = select(func.scalar_strings(5).column_valued("s"))
>>> print(stmt.compile(dialect=oracle.dialect()))
- SELECT COLUMN_VALUE s
+ SELECT s.COLUMN_VALUE
FROM TABLE (scalar_strings(:scalar_strings_1)) s
text = super(OracleCompiler, self).visit_table_valued_column(
element, **kw
)
- text = "COLUMN_VALUE " + text
+ text = text + ".COLUMN_VALUE"
return text
def default_from(self):
class DocTest(fixtures.TestBase):
__requires__ = ("python3",)
+ __only_on__ = "sqlite+pysqlite"
def _setup_logger(self):
rootlogger = logging.getLogger("sqlalchemy.engine.Engine")
stmt = select(fn.alias().column)
self.assert_compile(
stmt,
- "SELECT COLUMN_VALUE anon_1 "
+ "SELECT anon_1.COLUMN_VALUE "
"FROM TABLE (scalar_strings(:scalar_strings_1)) anon_1",
)
+ def test_scalar_alias_multi_columns(self):
+ fn1 = func.scalar_strings(5)
+ fn2 = func.scalar_strings(3)
+ stmt = select(fn1.alias().column, fn2.alias().column)
+ self.assert_compile(
+ stmt,
+ "SELECT anon_1.COLUMN_VALUE, anon_2.COLUMN_VALUE FROM TABLE "
+ "(scalar_strings(:scalar_strings_1)) anon_1, "
+ "TABLE (scalar_strings(:scalar_strings_2)) anon_2",
+ )
+
def test_column_valued(self):
fn = func.scalar_strings(5)
stmt = select(fn.column_valued())
self.assert_compile(
stmt,
- "SELECT COLUMN_VALUE anon_1 "
+ "SELECT anon_1.COLUMN_VALUE "
"FROM TABLE (scalar_strings(:scalar_strings_1)) anon_1",
)
+ def test_multi_column_valued(self):
+ fn1 = func.scalar_strings(5)
+ fn2 = func.scalar_strings(3)
+ stmt = select(fn1.column_valued(), fn2.column_valued().label("x"))
+ self.assert_compile(
+ stmt,
+ "SELECT anon_1.COLUMN_VALUE, anon_2.COLUMN_VALUE AS x FROM "
+ "TABLE (scalar_strings(:scalar_strings_1)) anon_1, "
+ "TABLE (scalar_strings(:scalar_strings_2)) anon_2",
+ )
+
def test_table_valued(self):
fn = func.three_pairs().table_valued("string1", "string2")
stmt = select(fn.c.string1, fn.c.string2)
# coding: utf-8
+import itertools
import re
from sqlalchemy import bindparam
connection.exec_driver_sql(
r"""
CREATE OR REPLACE FUNCTION scalar_strings (
- count_in IN INTEGER)
+ count_in IN INTEGER, string_in IN VARCHAR2)
RETURN strings_t
AUTHID DEFINER
IS
FOR indx IN 1 .. count_in
LOOP
- l_strings (indx) := 'some string';
+ l_strings (indx) := string_in;
END LOOP;
RETURN l_strings;
def test_scalar_strings_control(self, scalar_strings, connection):
result = (
connection.exec_driver_sql(
- "SELECT COLUMN_VALUE my_string FROM TABLE (scalar_strings (5))"
+ "SELECT COLUMN_VALUE my_string FROM TABLE "
+ "(scalar_strings (5, 'some string'))"
)
.scalars()
.all()
result = (
connection.exec_driver_sql(
"SELECT COLUMN_VALUE anon_1 "
- "FROM TABLE (scalar_strings (5)) anon_1"
+ "FROM TABLE (scalar_strings (5, 'some string')) anon_1"
)
.scalars()
.all()
eq_(result, ["some string"] * 5)
def test_scalar_strings(self, scalar_strings, connection):
- fn = func.scalar_strings(5)
+ fn = func.scalar_strings(5, "some string")
result = connection.execute(select(fn.column_valued())).scalars().all()
eq_(result, ["some string"] * 5)
result = connection.execute(select(fn.c.string1, fn.c.string2)).all()
eq_(result, [("a", "b"), ("c", "d"), ("e", "f")])
+ def test_two_independent_tables(self, scalar_strings, connection):
+ fn1 = func.scalar_strings(5, "string one").column_valued()
+ fn2 = func.scalar_strings(3, "string two").column_valued()
+ result = connection.execute(select(fn1, fn2).where(fn1 != fn2)).all()
+ eq_(
+ result,
+ list(itertools.product(["string one"] * 5, ["string two"] * 3)),
+ )
+
class OptimizedFetchLimitOffsetTest(test_select.FetchLimitOffsetTest):
__only_on__ = "oracle"