]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Oracle COLUMN_VALUE is a column name, not a keyword
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 7 Dec 2022 20:54:59 +0000 (15:54 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 7 Dec 2022 22:28:49 +0000 (17:28 -0500)
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.

Fixes: #8945
Change-Id: Ia04bbdc68168e78b67a74bb3834a63f5d5000627

doc/build/changelog/unreleased_14/8827.rst
doc/build/changelog/unreleased_14/8945.rst [new file with mode: 0644]
doc/build/tutorial/data_select.rst
lib/sqlalchemy/dialects/oracle/base.py
test/dialect/oracle/test_compiler.py
test/dialect/oracle/test_dialect.py

index d71a30db597d396a6bc91db4d2db3e97080dc9ad..b389ff771bbef3b88099b082944c5d4ed66b47b0 100644 (file)
@@ -3,8 +3,11 @@
     :tickets: 8827
     :versions: 2.0.0b4
 
-    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.
+
diff --git a/doc/build/changelog/unreleased_14/8945.rst b/doc/build/changelog/unreleased_14/8945.rst
new file mode 100644 (file)
index 0000000..e1b4bd6
--- /dev/null
@@ -0,0 +1,8 @@
+.. 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.
index 64b9cef8aef21ec74c08497618aec03673dc0d68..20617198804b1e455a61fb21b0ee4fe7c51357e0 100644 (file)
@@ -1753,7 +1753,7 @@ it is usable for custom SQL functions::
     >>> 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
 
 
index 41b9ac43d460abfe856122951734ce3f53054129..dc2b011afeb95e11a4afe0f111ba05bc379a60bd 100644 (file)
@@ -859,7 +859,7 @@ class OracleCompiler(compiler.SQLCompiler):
 
     def visit_table_valued_column(self, element, **kw):
         text = super().visit_table_valued_column(element, **kw)
-        text = "COLUMN_VALUE " + text
+        text = text + ".COLUMN_VALUE"
         return text
 
     def default_from(self):
index 255efdb3c034b087b3dc0cd502beeb21395527ff..603d54e4b64fac6423d3d5b92f99b523db7184a4 100644 (file)
@@ -1796,19 +1796,41 @@ class TableValuedFunctionTest(fixtures.TestBase, testing.AssertsCompiledSQL):
         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)
index 4370992e820e1f5ca11a94b942bee0bc5264be15..38f4b176b8374bcb296cd8a50a5abf6d6e179354 100644 (file)
@@ -1,3 +1,4 @@
+import itertools
 from multiprocessing import get_context
 import re
 from unittest import mock
@@ -1113,7 +1114,7 @@ class TableValuedTest(fixtures.TestBase):
         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
@@ -1123,7 +1124,7 @@ BEGIN
 
    FOR indx IN 1 .. count_in
    LOOP
-      l_strings (indx) := 'some string';
+      l_strings (indx) := string_in;
    END LOOP;
 
    RETURN l_strings;
@@ -1173,7 +1174,8 @@ END;
     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()
@@ -1184,7 +1186,7 @@ END;
         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()
@@ -1192,7 +1194,7 @@ END;
         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)
 
@@ -1207,6 +1209,15 @@ END;
         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"