summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/changelog_14.rst61
-rw-r--r--doc/build/changelog/unreleased_14/8770.rst23
-rw-r--r--doc/build/changelog/unreleased_14/8790.rst10
-rw-r--r--doc/build/changelog/unreleased_14/8793.rst9
-rw-r--r--doc/build/changelog/unreleased_14/8800.rst8
-rw-r--r--doc/build/changelog/unreleased_14/mypy_fixes.rst9
-rw-r--r--doc/build/changelog/unreleased_20/8776.rst7
-rw-r--r--doc/build/tutorial/dbapi_transactions.rst2
-rw-r--r--lib/sqlalchemy/orm/query.py7
-rw-r--r--lib/sqlalchemy/orm/relationships.py1
-rw-r--r--lib/sqlalchemy/sql/compiler.py22
-rw-r--r--lib/sqlalchemy/sql/selectable.py7
-rw-r--r--lib/sqlalchemy/sql/sqltypes.py7
-rw-r--r--lib/sqlalchemy/sql/util.py6
-rw-r--r--lib/sqlalchemy/testing/requirements.py4
-rw-r--r--lib/sqlalchemy/util/__init__.py1
-rw-r--r--lib/sqlalchemy/util/compat.py1
-rw-r--r--test/dialect/postgresql/test_types.py5
-rw-r--r--test/orm/test_cache_key.py6
-rw-r--r--test/sql/test_compiler.py46
-rw-r--r--test/sql/test_types.py8
-rw-r--r--test/sql/test_values.py3
22 files changed, 177 insertions, 76 deletions
diff --git a/doc/build/changelog/changelog_14.rst b/doc/build/changelog/changelog_14.rst
index f7aa344e8..b33f4c45b 100644
--- a/doc/build/changelog/changelog_14.rst
+++ b/doc/build/changelog/changelog_14.rst
@@ -14,10 +14,69 @@ This document details individual issue-level changes made throughout
.. changelog::
- :version: 1.4.44
+ :version: 1.4.45
:include_notes_from: unreleased_14
.. changelog::
+ :version: 1.4.44
+ :released: November 12, 2022
+
+ .. change::
+ :tags: bug, sql
+ :tickets: 8790
+
+ Fixed critical memory issue identified in cache key generation, where for
+ very large and complex ORM statements that make use of lots of ORM aliases
+ with subqueries, cache key generation could produce excessively large keys
+ that were orders of magnitude bigger than the statement itself. Much thanks
+ to Rollo Konig Brock for their very patient, long term help in finally
+ identifying this issue.
+
+ .. change::
+ :tags: bug, postgresql, mssql
+ :tickets: 8770
+
+ For the PostgreSQL and SQL Server dialects only, adjusted the compiler so
+ that when rendering column expressions in the RETURNING clause, the "non
+ anon" label that's used in SELECT statements is suggested for SQL
+ expression elements that generate a label; the primary example is a SQL
+ function that may be emitting as part of the column's type, where the label
+ name should match the column's name by default. This restores a not-well
+ defined behavior that had changed in version 1.4.21 due to :ticket:`6718`,
+ :ticket:`6710`. The Oracle dialect has a different RETURNING implementation
+ and was not affected by this issue. Version 2.0 features an across the
+ board change for its widely expanded support of RETURNING on other
+ backends.
+
+
+ .. change::
+ :tags: bug, oracle
+
+ Fixed issue in the Oracle dialect where an INSERT statement that used
+ ``insert(some_table).values(...).returning(some_table)`` against a full
+ :class:`.Table` object at once would fail to execute, raising an exception.
+
+ .. change::
+ :tags: bug, tests
+ :tickets: 8793
+
+ Fixed issue where the ``--disable-asyncio`` parameter to the test suite
+ would fail to not actually run greenlet tests and would also not prevent
+ the suite from using a "wrapping" greenlet for the whole suite. This
+ parameter now ensures that no greenlet or asyncio use will occur within the
+ entire run when set.
+
+ .. change::
+ :tags: bug, tests
+
+ Adjusted the test suite which tests the Mypy plugin to accommodate for
+ changes in Mypy 0.990 regarding how it handles message output, which affect
+ how sys.path is interpreted when determining if notes and errors should be
+ printed for particular files. The change broke the test suite as the files
+ within the test directory itself no longer produced messaging when run
+ under the mypy API.
+
+.. changelog::
:version: 1.4.43
:released: November 4, 2022
diff --git a/doc/build/changelog/unreleased_14/8770.rst b/doc/build/changelog/unreleased_14/8770.rst
deleted file mode 100644
index 8968b0361..000000000
--- a/doc/build/changelog/unreleased_14/8770.rst
+++ /dev/null
@@ -1,23 +0,0 @@
-.. change::
- :tags: bug, postgresql, mssql
- :tickets: 8770
-
- For the PostgreSQL and SQL Server dialects only, adjusted the compiler so
- that when rendering column expressions in the RETURNING clause, the "non
- anon" label that's used in SELECT statements is suggested for SQL
- expression elements that generate a label; the primary example is a SQL
- function that may be emitting as part of the column's type, where the label
- name should match the column's name by default. This restores a not-well
- defined behavior that had changed in version 1.4.21 due to :ticket:`6718`,
- :ticket:`6710`. The Oracle dialect has a different RETURNING implementation
- and was not affected by this issue. Version 2.0 features an across the
- board change for its widely expanded support of RETURNING on other
- backends.
-
-
-.. change::
- :tags: bug, oracle
-
- Fixed issue in the Oracle dialect where an INSERT statement that used
- ``insert(some_table).values(...).returning(some_table)`` against a full
- :class:`.Table` object at once would fail to execute, raising an exception.
diff --git a/doc/build/changelog/unreleased_14/8790.rst b/doc/build/changelog/unreleased_14/8790.rst
deleted file mode 100644
index a3214801c..000000000
--- a/doc/build/changelog/unreleased_14/8790.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-.. change::
- :tags: bug, sql
- :tickets: 8790
-
- Fixed critical memory issue identified in cache key generation, where for
- very large and complex ORM statements that make use of lots of ORM aliases
- with subqueries, cache key generation could produce excessively large keys
- that were orders of magnitude bigger than the statement itself. Much thanks
- to Rollo Konig Brock for their very patient, long term help in finally
- identifying this issue.
diff --git a/doc/build/changelog/unreleased_14/8793.rst b/doc/build/changelog/unreleased_14/8793.rst
deleted file mode 100644
index 36f1003cc..000000000
--- a/doc/build/changelog/unreleased_14/8793.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-.. change::
- :tags: bug, tests
- :tickets: 8793
-
- Fixed issue where the ``--disable-asyncio`` parameter to the test suite
- would fail to not actually run greenlet tests and would also not prevent
- the suite from using a "wrapping" greenlet for the whole suite. This
- parameter now ensures that no greenlet or asyncio use will occur within the
- entire run when set.
diff --git a/doc/build/changelog/unreleased_14/8800.rst b/doc/build/changelog/unreleased_14/8800.rst
new file mode 100644
index 000000000..8a42975df
--- /dev/null
+++ b/doc/build/changelog/unreleased_14/8800.rst
@@ -0,0 +1,8 @@
+.. change::
+ :tags: usecase, sql
+ :tickets: 8800
+
+ An informative re-raise is now thrown in the case where any "literal
+ bindparam" render operation fails, indicating the value itself and
+ the datatype in use, to assist in debugging when literal params
+ are being rendered in a statement.
diff --git a/doc/build/changelog/unreleased_14/mypy_fixes.rst b/doc/build/changelog/unreleased_14/mypy_fixes.rst
deleted file mode 100644
index 32e4f1465..000000000
--- a/doc/build/changelog/unreleased_14/mypy_fixes.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-.. change::
- :tags: bug, tests
-
- Adjusted the test suite which tests the Mypy plugin to accommodate for
- changes in Mypy 0.990 regarding how it handles message output, which affect
- how sys.path is interpreted when determining if notes and errors should be
- printed for particular files. The change broke the test suite as the files
- within the test directory itself no longer produced messaging when run
- under the mypy API.
diff --git a/doc/build/changelog/unreleased_20/8776.rst b/doc/build/changelog/unreleased_20/8776.rst
new file mode 100644
index 000000000..fa4156eae
--- /dev/null
+++ b/doc/build/changelog/unreleased_20/8776.rst
@@ -0,0 +1,7 @@
+.. change::
+ :tags: bug, orm
+ :tickets: 8776
+
+ Fixed issue where passing a callbale function returning an iterable
+ of column elements to :paramref:`_orm.relationship.order_by` was
+ flagged as an error in type checkers.
diff --git a/doc/build/tutorial/dbapi_transactions.rst b/doc/build/tutorial/dbapi_transactions.rst
index 00178936b..d40aaf5b8 100644
--- a/doc/build/tutorial/dbapi_transactions.rst
+++ b/doc/build/tutorial/dbapi_transactions.rst
@@ -479,7 +479,7 @@ the block with a "commit as you go" commit.
.. tip:: The :class:`_orm.Session` doesn't actually hold onto the
:class:`_engine.Connection` object after it ends the transaction. It
gets a new :class:`_engine.Connection` from the :class:`_engine.Engine`
- when executing SQL against the database is next needed.
+ the next time it needs to execute SQL against the database.
The :class:`_orm.Session` obviously has a lot more tricks up its sleeve
than that, however understanding that it has a :meth:`_orm.Session.execute`
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 9ac6d07da..2d97754f4 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -1963,9 +1963,10 @@ class Query(
q = session.query(Entity).order_by(Entity.id, Entity.name)
- All existing ORDER BY criteria may be cancelled by passing
- ``None`` by itself. New ORDER BY criteria may then be added by
- invoking :meth:`_orm.Query.order_by` again, e.g.::
+ Calling this method multiple times is equivalent to calling it once
+ with all the clauses concatenated. All existing ORDER BY criteria may
+ be cancelled by passing ``None`` by itself. New ORDER BY criteria may
+ then be added by invoking :meth:`_orm.Query.order_by` again, e.g.::
# will erase all ORDER BY and ORDER BY new_col alone
q = q.order_by(None).order_by(new_col)
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index e0922a538..986093e02 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -164,6 +164,7 @@ _ORMOrderByArgument = Union[
Literal[False],
str,
_ColumnExpressionArgument[Any],
+ Callable[[], Iterable[ColumnElement[Any]]],
Iterable[Union[str, _ColumnExpressionArgument[Any]]],
]
_ORMBackrefArgument = Union[str, Tuple[str, Dict[str, Any]]]
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 97397e9cf..9a00afc91 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -63,6 +63,7 @@ from . import roles
from . import schema
from . import selectable
from . import sqltypes
+from . import util as sql_util
from ._typing import is_column_element
from ._typing import is_dml
from .base import _from_objects
@@ -1530,7 +1531,8 @@ class SQLCompiler(Compiled):
replacement_expressions[
escaped_name
] = self.render_literal_bindparam(
- parameter, render_literal_value=value
+ parameter,
+ render_literal_value=value,
)
continue
@@ -3154,10 +3156,22 @@ class SQLCompiler(Compiled):
processor = type_._cached_literal_processor(self.dialect)
if processor:
- return processor(value)
+ try:
+ return processor(value)
+ except Exception as e:
+ raise exc.CompileError(
+ f"Could not render literal value "
+ f'"{sql_util._repr_single_value(value)}" '
+ f"with datatype "
+ f"{type_}; see parent stack trace for "
+ "more detail."
+ ) from e
+
else:
- raise NotImplementedError(
- "Don't know how to literal-quote value %r" % value
+ raise exc.CompileError(
+ f"No literal value renderer is available for literal value "
+ f'"{sql_util._repr_single_value(value)}" '
+ f"with datatype {type_}"
)
def _truncate_bindparam(self, bindparam):
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index 3d3aea3f2..08d01c883 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -4096,9 +4096,10 @@ class GenerativeSelect(SelectBase, Generative):
stmt = select(table).order_by(table.c.id, table.c.name)
- All existing ORDER BY criteria may be cancelled by passing
- ``None`` by itself. New ORDER BY criteria may then be added by
- invoking :meth:`_sql.Select.order_by` again, e.g.::
+ Calling this method multiple times is equivalent to calling it once
+ with all the clauses concatenated. All existing ORDER BY criteria may
+ be cancelled by passing ``None`` by itself. New ORDER BY criteria may
+ then be added by invoking :meth:`_orm.Query.order_by` again, e.g.::
# will erase all ORDER BY and ORDER BY new_col alone
stmt = stmt.order_by(None).order_by(new_col)
diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py
index 414ff03c3..b98a16b6f 100644
--- a/lib/sqlalchemy/sql/sqltypes.py
+++ b/lib/sqlalchemy/sql/sqltypes.py
@@ -3381,12 +3381,7 @@ class NullType(TypeEngine[None]):
_isnull = True
def literal_processor(self, dialect):
- def process(value):
- raise exc.CompileError(
- "Don't know how to render literal SQL value: %r" % (value,)
- )
-
- return process
+ return None
class Comparator(TypeEngine.Comparator[_T]):
__slots__ = ()
diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py
index 623a3f896..ec8ea757f 100644
--- a/lib/sqlalchemy/sql/util.py
+++ b/lib/sqlalchemy/sql/util.py
@@ -581,6 +581,12 @@ class _repr_base:
return rep
+def _repr_single_value(value):
+ rp = _repr_base()
+ rp.max_chars = 300
+ return rp.trunc(value)
+
+
class _repr_row(_repr_base):
"""Provide a string view of a row."""
diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py
index 38d962fef..3c63e9362 100644
--- a/lib/sqlalchemy/testing/requirements.py
+++ b/lib/sqlalchemy/testing/requirements.py
@@ -1439,6 +1439,10 @@ class SuiteRequirements(Requirements):
)
@property
+ def is64bit(self):
+ return exclusions.only_if(lambda: util.is64bit, "64bit required")
+
+ @property
def patch_library(self):
def check_lib():
try:
diff --git a/lib/sqlalchemy/util/__init__.py b/lib/sqlalchemy/util/__init__.py
index bb4642a4f..e82cfd769 100644
--- a/lib/sqlalchemy/util/__init__.py
+++ b/lib/sqlalchemy/util/__init__.py
@@ -58,6 +58,7 @@ from .compat import decode_backslashreplace as decode_backslashreplace
from .compat import dottedgetter as dottedgetter
from .compat import has_refcount_gc as has_refcount_gc
from .compat import inspect_getfullargspec as inspect_getfullargspec
+from .compat import is64bit as is64bit
from .compat import local_dataclass_fields as local_dataclass_fields
from .compat import osx as osx
from .compat import py310 as py310
diff --git a/lib/sqlalchemy/util/compat.py b/lib/sqlalchemy/util/compat.py
index 2899b4258..24f9bcf10 100644
--- a/lib/sqlalchemy/util/compat.py
+++ b/lib/sqlalchemy/util/compat.py
@@ -41,6 +41,7 @@ cpython = platform.python_implementation() == "CPython"
win32 = sys.platform.startswith("win")
osx = sys.platform.startswith("darwin")
arm = "aarch" in platform.machine().lower()
+is64bit = platform.architecture()[0] == "64bit"
has_refcount_gc = bool(cpython)
diff --git a/test/dialect/postgresql/test_types.py b/test/dialect/postgresql/test_types.py
index 83cea8f15..39e7d7317 100644
--- a/test/dialect/postgresql/test_types.py
+++ b/test/dialect/postgresql/test_types.py
@@ -1528,8 +1528,9 @@ class ArrayTest(AssertsCompiledSQL, fixtures.TestBase):
return "MYTYPE"
with expect_raises_message(
- NotImplementedError,
- r"Don't know how to literal-quote value \[1, 2, 3\]",
+ exc.CompileError,
+ r"No literal value renderer is available for literal "
+ r"value \"\[1, 2, 3\]\" with datatype ARRAY",
):
self.assert_compile(
select(literal([1, 2, 3], ARRAY(MyType()))),
diff --git a/test/orm/test_cache_key.py b/test/orm/test_cache_key.py
index 3106a71ad..a0bf8b598 100644
--- a/test/orm/test_cache_key.py
+++ b/test/orm/test_cache_key.py
@@ -1092,7 +1092,11 @@ class EmbeddedSubqTest(_RemoveListeners, DeclarativeMappedTest):
"concrete": True,
}
- @testing.combinations("tuples", "memory", argnames="assert_on")
+ Base.registry.configure()
+
+ @testing.combinations(
+ "tuples", ("memory", testing.requires.is64bit), argnames="assert_on"
+ )
def test_cache_key_gen(self, assert_on):
Employee = self.classes.Employee
diff --git a/test/sql/test_compiler.py b/test/sql/test_compiler.py
index 97b1b9124..4eea11795 100644
--- a/test/sql/test_compiler.py
+++ b/test/sql/test_compiler.py
@@ -98,6 +98,7 @@ from sqlalchemy.testing import is_true
from sqlalchemy.testing import mock
from sqlalchemy.testing import ne_
from sqlalchemy.testing.schema import pep435_enum
+from sqlalchemy.types import UserDefinedType
table1 = table(
"mytable",
@@ -4609,6 +4610,51 @@ class BindParameterTest(AssertsCompiledSQL, fixtures.TestBase):
"OR mytable.myid = :myid_2 OR mytable.myid = :myid_3",
)
+ @testing.combinations("plain", "expanding", argnames="exprtype")
+ def test_literal_bind_typeerror(self, exprtype):
+ """test #8800"""
+
+ if exprtype == "expanding":
+ stmt = select(table1).where(
+ table1.c.myid.in_([("tuple",), ("tuple",)])
+ )
+ elif exprtype == "plain":
+ stmt = select(table1).where(table1.c.myid == ("tuple",))
+ else:
+ assert False
+
+ with expect_raises_message(
+ exc.CompileError,
+ r"Could not render literal value \"\(\'tuple\',\)\" "
+ r"with datatype INTEGER; see parent "
+ r"stack trace for more detail.",
+ ):
+ stmt.compile(compile_kwargs={"literal_binds": True})
+
+ @testing.combinations("plain", "expanding", argnames="exprtype")
+ def test_literal_bind_dont_know_how_to_quote(self, exprtype):
+ """test #8800"""
+
+ class MyType(UserDefinedType):
+ def get_col_spec(self, **kw):
+ return "MYTYPE"
+
+ col = column("x", MyType())
+
+ if exprtype == "expanding":
+ stmt = select(table1).where(col.in_([("tuple",), ("tuple",)]))
+ elif exprtype == "plain":
+ stmt = select(table1).where(col == ("tuple",))
+ else:
+ assert False
+
+ with expect_raises_message(
+ exc.CompileError,
+ r"No literal value renderer is available for literal "
+ r"value \"\('tuple',\)\" with datatype MYTYPE",
+ ):
+ stmt.compile(compile_kwargs={"literal_binds": True})
+
@testing.fixture
def ansi_compiler_fixture(self):
dialect = default.DefaultDialect()
diff --git a/test/sql/test_types.py b/test/sql/test_types.py
index a608d0040..3b1df3498 100644
--- a/test/sql/test_types.py
+++ b/test/sql/test_types.py
@@ -3125,8 +3125,9 @@ class ArrayTest(AssertsCompiledSQL, fixtures.TestBase):
return "MYTYPE"
with expect_raises_message(
- NotImplementedError,
- r"Don't know how to literal-quote value \[1, 2, 3\]",
+ exc.CompileError,
+ r"No literal value renderer is available for literal value "
+ r"\"\[1, 2, 3\]\" with datatype ARRAY",
):
self.assert_compile(
select(literal([1, 2, 3], ARRAY(MyType()))),
@@ -3629,7 +3630,8 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
def test_compile_err_formatting(self):
with expect_raises_message(
exc.CompileError,
- r"Don't know how to render literal SQL value: \(1, 2, 3\)",
+ r"No literal value renderer is available for literal "
+ r"value \"\(1, 2, 3\)\" with datatype NULL",
):
func.foo((1, 2, 3)).compile(compile_kwargs={"literal_binds": True})
diff --git a/test/sql/test_values.py b/test/sql/test_values.py
index d14de9aee..b943c4701 100644
--- a/test/sql/test_values.py
+++ b/test/sql/test_values.py
@@ -277,7 +277,8 @@ class ValuesTest(fixtures.TablesTest, AssertsCompiledSQL):
with expect_raises_message(
exc.CompileError,
- "Don't know how to render literal SQL value: 'textA'",
+ r"No literal value renderer is available for literal "
+ r"value \"'textA'\" with datatype NULL",
):
str(stmt)