summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2021-04-01 17:20:31 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2021-04-01 18:59:41 -0400
commit20c0f774e5517514da811bc446812baa6b1f32f1 (patch)
tree31130474fc5aabcd7143ac5b0ba585c25700169a /lib
parentdc5ade010da55c1158ee7294c9e882cc52cd6e01 (diff)
downloadsqlalchemy-20c0f774e5517514da811bc446812baa6b1f32f1.tar.gz
Default caching to opt-out for 3rd party dialects
Added a new flag to the :class:`_engine.Dialect` class called :attr:`_engine.Dialect.supports_statement_cache`. This flag now needs to be present directly on a dialect class in order for SQLAlchemy's :ref:`query cache <sql_caching>` to take effect for that dialect. The rationale is based on discovered issues such as :ticket:`6173` revealing that dialects which hardcode literal values from the compiled statement, often the numerical parameters used for LIMIT / OFFSET, will not be compatible with caching until these dialects are revised to use the parameters present in the statement only. For third party dialects where this flag is not applied, the SQL logging will show the message "dialect does not support caching", indicating the dialect should seek to apply this flag once they have verified that no per-statement literal values are being rendered within the compilation phase. Fixes: #6184 Change-Id: I6fd5b5d94200458d4cb0e14f2f556dbc25e27e22
Diffstat (limited to 'lib')
-rw-r--r--lib/sqlalchemy/dialects/firebird/base.py1
-rw-r--r--lib/sqlalchemy/dialects/firebird/fdb.py2
-rw-r--r--lib/sqlalchemy/dialects/firebird/kinterbasdb.py1
-rw-r--r--lib/sqlalchemy/dialects/mssql/base.py1
-rw-r--r--lib/sqlalchemy/dialects/mssql/mxodbc.py1
-rw-r--r--lib/sqlalchemy/dialects/mssql/pymssql.py1
-rw-r--r--lib/sqlalchemy/dialects/mssql/pyodbc.py1
-rw-r--r--lib/sqlalchemy/dialects/mysql/aiomysql.py1
-rw-r--r--lib/sqlalchemy/dialects/mysql/base.py2
-rw-r--r--lib/sqlalchemy/dialects/mysql/cymysql.py1
-rw-r--r--lib/sqlalchemy/dialects/mysql/mariadb.py3
-rw-r--r--lib/sqlalchemy/dialects/mysql/mariadbconnector.py1
-rw-r--r--lib/sqlalchemy/dialects/mysql/mysqlconnector.py1
-rw-r--r--lib/sqlalchemy/dialects/mysql/mysqldb.py1
-rw-r--r--lib/sqlalchemy/dialects/mysql/oursql.py1
-rw-r--r--lib/sqlalchemy/dialects/mysql/pymysql.py1
-rw-r--r--lib/sqlalchemy/dialects/mysql/pyodbc.py1
-rw-r--r--lib/sqlalchemy/dialects/oracle/base.py5
-rw-r--r--lib/sqlalchemy/dialects/oracle/cx_oracle.py1
-rw-r--r--lib/sqlalchemy/dialects/postgresql/asyncpg.py1
-rw-r--r--lib/sqlalchemy/dialects/postgresql/base.py1
-rw-r--r--lib/sqlalchemy/dialects/postgresql/pg8000.py1
-rw-r--r--lib/sqlalchemy/dialects/postgresql/psycopg2.py3
-rw-r--r--lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py1
-rw-r--r--lib/sqlalchemy/dialects/postgresql/pygresql.py1
-rw-r--r--lib/sqlalchemy/dialects/postgresql/pypostgresql.py1
-rw-r--r--lib/sqlalchemy/dialects/sqlite/aiosqlite.py1
-rw-r--r--lib/sqlalchemy/dialects/sqlite/base.py1
-rw-r--r--lib/sqlalchemy/dialects/sqlite/pysqlcipher.py1
-rw-r--r--lib/sqlalchemy/dialects/sqlite/pysqlite.py1
-rw-r--r--lib/sqlalchemy/dialects/sybase/base.py1
-rw-r--r--lib/sqlalchemy/dialects/sybase/mxodbc.py1
-rw-r--r--lib/sqlalchemy/dialects/sybase/pyodbc.py1
-rw-r--r--lib/sqlalchemy/dialects/sybase/pysybase.py2
-rw-r--r--lib/sqlalchemy/engine/default.py18
-rw-r--r--lib/sqlalchemy/engine/interfaces.py18
-rw-r--r--lib/sqlalchemy/sql/elements.py42
-rw-r--r--lib/sqlalchemy/sql/selectable.py8
38 files changed, 114 insertions, 17 deletions
diff --git a/lib/sqlalchemy/dialects/firebird/base.py b/lib/sqlalchemy/dialects/firebird/base.py
index 7fc914f1b..fcf0c31d3 100644
--- a/lib/sqlalchemy/dialects/firebird/base.py
+++ b/lib/sqlalchemy/dialects/firebird/base.py
@@ -623,6 +623,7 @@ class FBDialect(default.DefaultDialect):
"""Firebird dialect"""
name = "firebird"
+ supports_statement_cache = True
max_identifier_length = 31
diff --git a/lib/sqlalchemy/dialects/firebird/fdb.py b/lib/sqlalchemy/dialects/firebird/fdb.py
index 14954b073..18ed65f45 100644
--- a/lib/sqlalchemy/dialects/firebird/fdb.py
+++ b/lib/sqlalchemy/dialects/firebird/fdb.py
@@ -66,6 +66,8 @@ from ... import util
class FBDialect_fdb(FBDialect_kinterbasdb):
+ supports_statement_cache = True
+
def __init__(self, enable_rowcount=True, retaining=False, **kwargs):
super(FBDialect_fdb, self).__init__(
enable_rowcount=enable_rowcount, retaining=retaining, **kwargs
diff --git a/lib/sqlalchemy/dialects/firebird/kinterbasdb.py b/lib/sqlalchemy/dialects/firebird/kinterbasdb.py
index 4c937e0de..7c91db639 100644
--- a/lib/sqlalchemy/dialects/firebird/kinterbasdb.py
+++ b/lib/sqlalchemy/dialects/firebird/kinterbasdb.py
@@ -78,6 +78,7 @@ class FBExecutionContext_kinterbasdb(FBExecutionContext):
class FBDialect_kinterbasdb(FBDialect):
driver = "kinterbasdb"
+ supports_statement_cache = True
supports_sane_rowcount = False
supports_sane_multi_rowcount = False
execution_ctx_cls = FBExecutionContext_kinterbasdb
diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py
index bfa1cf08c..1fef42c53 100644
--- a/lib/sqlalchemy/dialects/mssql/base.py
+++ b/lib/sqlalchemy/dialects/mssql/base.py
@@ -2576,6 +2576,7 @@ def _schema_elements(schema):
class MSDialect(default.DefaultDialect):
# will assume it's at least mssql2005
name = "mssql"
+ supports_statement_cache = True
supports_default_values = True
supports_empty_insert = False
execution_ctx_cls = MSExecutionContext
diff --git a/lib/sqlalchemy/dialects/mssql/mxodbc.py b/lib/sqlalchemy/dialects/mssql/mxodbc.py
index da4e45f07..637b04869 100644
--- a/lib/sqlalchemy/dialects/mssql/mxodbc.py
+++ b/lib/sqlalchemy/dialects/mssql/mxodbc.py
@@ -126,6 +126,7 @@ class MSDialect_mxodbc(MxODBCConnector, MSDialect):
# this is only needed if "native ODBC" mode is used,
# which is now disabled by default.
# statement_compiler = MSSQLStrictCompiler
+ supports_statement_cache = True
execution_ctx_cls = MSExecutionContext_mxodbc
diff --git a/lib/sqlalchemy/dialects/mssql/pymssql.py b/lib/sqlalchemy/dialects/mssql/pymssql.py
index 5110badb9..4cc6c4696 100644
--- a/lib/sqlalchemy/dialects/mssql/pymssql.py
+++ b/lib/sqlalchemy/dialects/mssql/pymssql.py
@@ -65,6 +65,7 @@ class MSIdentifierPreparer_pymssql(MSIdentifierPreparer):
class MSDialect_pymssql(MSDialect):
+ supports_statement_cache = True
supports_native_decimal = True
driver = "pymssql"
diff --git a/lib/sqlalchemy/dialects/mssql/pyodbc.py b/lib/sqlalchemy/dialects/mssql/pyodbc.py
index 6b2fffc4e..99ab37228 100644
--- a/lib/sqlalchemy/dialects/mssql/pyodbc.py
+++ b/lib/sqlalchemy/dialects/mssql/pyodbc.py
@@ -431,6 +431,7 @@ class MSExecutionContext_pyodbc(MSExecutionContext):
class MSDialect_pyodbc(PyODBCConnector, MSDialect):
+ supports_statement_cache = True
# mssql still has problems with this on Linux
supports_sane_rowcount_returning = False
diff --git a/lib/sqlalchemy/dialects/mysql/aiomysql.py b/lib/sqlalchemy/dialects/mysql/aiomysql.py
index c8c7c0f97..6c77e7525 100644
--- a/lib/sqlalchemy/dialects/mysql/aiomysql.py
+++ b/lib/sqlalchemy/dialects/mysql/aiomysql.py
@@ -266,6 +266,7 @@ class AsyncAdapt_aiomysql_dbapi:
class MySQLDialect_aiomysql(MySQLDialect_pymysql):
driver = "aiomysql"
+ supports_statement_cache = True
supports_server_side_cursors = True
_sscursor = AsyncAdapt_aiomysql_ss_cursor
diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py
index b3c338bad..986ed8757 100644
--- a/lib/sqlalchemy/dialects/mysql/base.py
+++ b/lib/sqlalchemy/dialects/mysql/base.py
@@ -2511,6 +2511,8 @@ class MySQLDialect(default.DefaultDialect):
"""
name = "mysql"
+ supports_statement_cache = True
+
supports_alter = True
# MySQL has no true "boolean" type; we
diff --git a/lib/sqlalchemy/dialects/mysql/cymysql.py b/lib/sqlalchemy/dialects/mysql/cymysql.py
index 0d7ba5594..ec9fd6edd 100644
--- a/lib/sqlalchemy/dialects/mysql/cymysql.py
+++ b/lib/sqlalchemy/dialects/mysql/cymysql.py
@@ -43,6 +43,7 @@ class _cymysqlBIT(BIT):
class MySQLDialect_cymysql(MySQLDialect_mysqldb):
driver = "cymysql"
+ supports_statement_cache = True
description_encoding = None
supports_sane_rowcount = True
diff --git a/lib/sqlalchemy/dialects/mysql/mariadb.py b/lib/sqlalchemy/dialects/mysql/mariadb.py
index 0dbb579e8..8ebde462b 100644
--- a/lib/sqlalchemy/dialects/mysql/mariadb.py
+++ b/lib/sqlalchemy/dialects/mysql/mariadb.py
@@ -3,6 +3,7 @@ from .base import MySQLDialect
class MariaDBDialect(MySQLDialect):
is_mariadb = True
+ supports_statement_cache = True
name = "mariadb"
@@ -18,5 +19,5 @@ def loader(driver):
MariaDBDialect,
driver_cls,
),
- {},
+ {"supports_statement_cache": True},
)
diff --git a/lib/sqlalchemy/dialects/mysql/mariadbconnector.py b/lib/sqlalchemy/dialects/mysql/mariadbconnector.py
index ddc11f6e6..6e3a24950 100644
--- a/lib/sqlalchemy/dialects/mysql/mariadbconnector.py
+++ b/lib/sqlalchemy/dialects/mysql/mariadbconnector.py
@@ -57,6 +57,7 @@ class MySQLIdentifierPreparer_mariadbconnector(MySQLIdentifierPreparer):
class MySQLDialect_mariadbconnector(MySQLDialect):
driver = "mariadbconnector"
+ supports_statement_cache = True
# set this to True at the module level to prevent the driver from running
# against a backend that server detects as MySQL. currently this appears to
diff --git a/lib/sqlalchemy/dialects/mysql/mysqlconnector.py b/lib/sqlalchemy/dialects/mysql/mysqlconnector.py
index 5ed675b13..80f20688b 100644
--- a/lib/sqlalchemy/dialects/mysql/mysqlconnector.py
+++ b/lib/sqlalchemy/dialects/mysql/mysqlconnector.py
@@ -85,6 +85,7 @@ class _myconnpyBIT(BIT):
class MySQLDialect_mysqlconnector(MySQLDialect):
driver = "mysqlconnector"
+ supports_statement_cache = True
supports_unicode_binds = True
diff --git a/lib/sqlalchemy/dialects/mysql/mysqldb.py b/lib/sqlalchemy/dialects/mysql/mysqldb.py
index 0318b5077..274f3eea4 100644
--- a/lib/sqlalchemy/dialects/mysql/mysqldb.py
+++ b/lib/sqlalchemy/dialects/mysql/mysqldb.py
@@ -77,6 +77,7 @@ class MySQLIdentifierPreparer_mysqldb(MySQLIdentifierPreparer):
class MySQLDialect_mysqldb(MySQLDialect):
driver = "mysqldb"
+ supports_statement_cache = True
supports_unicode_statements = True
supports_sane_rowcount = True
supports_sane_multi_rowcount = True
diff --git a/lib/sqlalchemy/dialects/mysql/oursql.py b/lib/sqlalchemy/dialects/mysql/oursql.py
index 5c8c7b7c2..06a6115b4 100644
--- a/lib/sqlalchemy/dialects/mysql/oursql.py
+++ b/lib/sqlalchemy/dialects/mysql/oursql.py
@@ -55,6 +55,7 @@ class MySQLExecutionContext_oursql(MySQLExecutionContext):
class MySQLDialect_oursql(MySQLDialect):
driver = "oursql"
+ supports_statement_cache = True
if util.py2k:
supports_unicode_binds = True
diff --git a/lib/sqlalchemy/dialects/mysql/pymysql.py b/lib/sqlalchemy/dialects/mysql/pymysql.py
index 0c321f854..09b5abffe 100644
--- a/lib/sqlalchemy/dialects/mysql/pymysql.py
+++ b/lib/sqlalchemy/dialects/mysql/pymysql.py
@@ -35,6 +35,7 @@ from ...util import py3k
class MySQLDialect_pymysql(MySQLDialect_mysqldb):
driver = "pymysql"
+ supports_statement_cache = True
description_encoding = None
diff --git a/lib/sqlalchemy/dialects/mysql/pyodbc.py b/lib/sqlalchemy/dialects/mysql/pyodbc.py
index 048586b59..7bc9ff14f 100644
--- a/lib/sqlalchemy/dialects/mysql/pyodbc.py
+++ b/lib/sqlalchemy/dialects/mysql/pyodbc.py
@@ -72,6 +72,7 @@ class MySQLExecutionContext_pyodbc(MySQLExecutionContext):
class MySQLDialect_pyodbc(PyODBCConnector, MySQLDialect):
+ supports_statement_cache = True
colspecs = util.update_copy(MySQLDialect.colspecs, {Time: _pyodbcTIME})
supports_unicode_statements = True
execution_ctx_cls = MySQLExecutionContext_pyodbc
diff --git a/lib/sqlalchemy/dialects/oracle/base.py b/lib/sqlalchemy/dialects/oracle/base.py
index 46fcbbbe1..11ad61675 100644
--- a/lib/sqlalchemy/dialects/oracle/base.py
+++ b/lib/sqlalchemy/dialects/oracle/base.py
@@ -1093,10 +1093,10 @@ class OracleCompiler(compiler.SQLCompiler):
offset_clause = select._offset_clause
if select._simple_int_clause(limit_clause):
- limit_clause = limit_clause._render_literal_execute()
+ limit_clause = limit_clause.render_literal_execute()
if select._simple_int_clause(offset_clause):
- offset_clause = offset_clause._render_literal_execute()
+ offset_clause = offset_clause.render_literal_execute()
# currently using form at:
# https://blogs.oracle.com/oraclemagazine/\
@@ -1434,6 +1434,7 @@ class OracleExecutionContext(default.DefaultExecutionContext):
class OracleDialect(default.DefaultDialect):
name = "oracle"
+ supports_statement_cache = True
supports_alter = True
supports_unicode_statements = False
supports_unicode_binds = False
diff --git a/lib/sqlalchemy/dialects/oracle/cx_oracle.py b/lib/sqlalchemy/dialects/oracle/cx_oracle.py
index 38b92117b..87c817066 100644
--- a/lib/sqlalchemy/dialects/oracle/cx_oracle.py
+++ b/lib/sqlalchemy/dialects/oracle/cx_oracle.py
@@ -797,6 +797,7 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
class OracleDialect_cx_oracle(OracleDialect):
+ supports_statement_cache = True
execution_ctx_cls = OracleExecutionContext_cx_oracle
statement_compiler = OracleCompiler_cx_oracle
diff --git a/lib/sqlalchemy/dialects/postgresql/asyncpg.py b/lib/sqlalchemy/dialects/postgresql/asyncpg.py
index 4580421f6..8cd5bee41 100644
--- a/lib/sqlalchemy/dialects/postgresql/asyncpg.py
+++ b/lib/sqlalchemy/dialects/postgresql/asyncpg.py
@@ -850,6 +850,7 @@ _pg_types = {
class PGDialect_asyncpg(PGDialect):
driver = "asyncpg"
+ supports_statement_cache = True
supports_unicode_statements = True
supports_server_side_cursors = True
diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py
index 97eb07bdb..2c4e316b3 100644
--- a/lib/sqlalchemy/dialects/postgresql/base.py
+++ b/lib/sqlalchemy/dialects/postgresql/base.py
@@ -3062,6 +3062,7 @@ class PGDeferrableConnectionCharacteristic(
class PGDialect(default.DefaultDialect):
name = "postgresql"
+ supports_statement_cache = True
supports_alter = True
max_identifier_length = 63
supports_sane_rowcount = True
diff --git a/lib/sqlalchemy/dialects/postgresql/pg8000.py b/lib/sqlalchemy/dialects/postgresql/pg8000.py
index 43df6edea..eaf6ccbb8 100644
--- a/lib/sqlalchemy/dialects/postgresql/pg8000.py
+++ b/lib/sqlalchemy/dialects/postgresql/pg8000.py
@@ -252,6 +252,7 @@ class PGIdentifierPreparer_pg8000(PGIdentifierPreparer):
class PGDialect_pg8000(PGDialect):
driver = "pg8000"
+ supports_statement_cache = True
supports_unicode_statements = True
diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py
index 16f9ecefa..c2b679022 100644
--- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py
+++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py
@@ -634,6 +634,9 @@ EXECUTEMANY_VALUES_PLUS_BATCH = util.symbol(
class PGDialect_psycopg2(PGDialect):
driver = "psycopg2"
+
+ supports_statement_cache = True
+
if util.py2k:
# turn off supports_unicode_statements for Python 2. psycopg2 supports
# unicode statements in Py2K. But! it does not support unicode *bound
diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py b/lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py
index a449f9e65..780244be9 100644
--- a/lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py
+++ b/lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py
@@ -28,6 +28,7 @@ from .psycopg2 import PGDialect_psycopg2
class PGDialect_psycopg2cffi(PGDialect_psycopg2):
driver = "psycopg2cffi"
supports_unicode_statements = True
+ supports_statement_cache = True
# psycopg2cffi's first release is 2.5.0, but reports
# __version__ as 2.4.4. Subsequent releases seem to have
diff --git a/lib/sqlalchemy/dialects/postgresql/pygresql.py b/lib/sqlalchemy/dialects/postgresql/pygresql.py
index 64dd7262d..718bbf78f 100644
--- a/lib/sqlalchemy/dialects/postgresql/pygresql.py
+++ b/lib/sqlalchemy/dialects/postgresql/pygresql.py
@@ -193,6 +193,7 @@ class _PGIdentifierPreparer(PGIdentifierPreparer):
class PGDialect_pygresql(PGDialect):
driver = "pygresql"
+ supports_statement_cache = True
statement_compiler = _PGCompiler
preparer = _PGIdentifierPreparer
diff --git a/lib/sqlalchemy/dialects/postgresql/pypostgresql.py b/lib/sqlalchemy/dialects/postgresql/pypostgresql.py
index 6e4db217d..7d4783867 100644
--- a/lib/sqlalchemy/dialects/postgresql/pypostgresql.py
+++ b/lib/sqlalchemy/dialects/postgresql/pypostgresql.py
@@ -52,6 +52,7 @@ class PGExecutionContext_pypostgresql(PGExecutionContext):
class PGDialect_pypostgresql(PGDialect):
driver = "pypostgresql"
+ supports_statement_cache = True
supports_unicode_statements = True
supports_unicode_binds = True
description_encoding = None
diff --git a/lib/sqlalchemy/dialects/sqlite/aiosqlite.py b/lib/sqlalchemy/dialects/sqlite/aiosqlite.py
index e4b7d1d52..1d09a619d 100644
--- a/lib/sqlalchemy/dialects/sqlite/aiosqlite.py
+++ b/lib/sqlalchemy/dialects/sqlite/aiosqlite.py
@@ -299,6 +299,7 @@ class SQLiteExecutionContext_aiosqlite(SQLiteExecutionContext):
class SQLiteDialect_aiosqlite(SQLiteDialect_pysqlite):
driver = "aiosqlite"
+ supports_statement_cache = True
is_async = True
diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py
index 691ca642d..83c2a8ea7 100644
--- a/lib/sqlalchemy/dialects/sqlite/base.py
+++ b/lib/sqlalchemy/dialects/sqlite/base.py
@@ -1796,6 +1796,7 @@ class SQLiteDialect(default.DefaultDialect):
supports_cast = True
supports_multivalues_insert = True
tuple_in_values = True
+ supports_statement_cache = True
default_paramstyle = "qmark"
execution_ctx_cls = SQLiteExecutionContext
diff --git a/lib/sqlalchemy/dialects/sqlite/pysqlcipher.py b/lib/sqlalchemy/dialects/sqlite/pysqlcipher.py
index 8f0f46acb..ff02d4dee 100644
--- a/lib/sqlalchemy/dialects/sqlite/pysqlcipher.py
+++ b/lib/sqlalchemy/dialects/sqlite/pysqlcipher.py
@@ -98,6 +98,7 @@ from ... import util
class SQLiteDialect_pysqlcipher(SQLiteDialect_pysqlite):
driver = "pysqlcipher"
+ supports_statement_cache = True
pragmas = ("kdf_iter", "cipher", "cipher_page_size", "cipher_use_hmac")
diff --git a/lib/sqlalchemy/dialects/sqlite/pysqlite.py b/lib/sqlalchemy/dialects/sqlite/pysqlite.py
index c940faf38..0b091e73b 100644
--- a/lib/sqlalchemy/dialects/sqlite/pysqlite.py
+++ b/lib/sqlalchemy/dialects/sqlite/pysqlite.py
@@ -443,6 +443,7 @@ class _SQLite_pysqliteDate(DATE):
class SQLiteDialect_pysqlite(SQLiteDialect):
default_paramstyle = "qmark"
+ supports_statement_cache = True
colspecs = util.update_copy(
SQLiteDialect.colspecs,
diff --git a/lib/sqlalchemy/dialects/sybase/base.py b/lib/sqlalchemy/dialects/sybase/base.py
index 49243be78..fd5e5b3b6 100644
--- a/lib/sqlalchemy/dialects/sybase/base.py
+++ b/lib/sqlalchemy/dialects/sybase/base.py
@@ -632,6 +632,7 @@ class SybaseDialect(default.DefaultDialect):
supports_unicode_statements = False
supports_sane_rowcount = False
supports_sane_multi_rowcount = False
+ supports_statement_cache = True
supports_native_boolean = False
supports_unicode_binds = False
diff --git a/lib/sqlalchemy/dialects/sybase/mxodbc.py b/lib/sqlalchemy/dialects/sybase/mxodbc.py
index 6b2f07c54..7002217c0 100644
--- a/lib/sqlalchemy/dialects/sybase/mxodbc.py
+++ b/lib/sqlalchemy/dialects/sybase/mxodbc.py
@@ -28,6 +28,7 @@ class SybaseExecutionContext_mxodbc(SybaseExecutionContext):
class SybaseDialect_mxodbc(MxODBCConnector, SybaseDialect):
execution_ctx_cls = SybaseExecutionContext_mxodbc
+ supports_statement_cache = True
dialect = SybaseDialect_mxodbc
diff --git a/lib/sqlalchemy/dialects/sybase/pyodbc.py b/lib/sqlalchemy/dialects/sybase/pyodbc.py
index bbd6d968a..bbaaa7e02 100644
--- a/lib/sqlalchemy/dialects/sybase/pyodbc.py
+++ b/lib/sqlalchemy/dialects/sybase/pyodbc.py
@@ -77,6 +77,7 @@ class SybaseExecutionContext_pyodbc(SybaseExecutionContext):
class SybaseDialect_pyodbc(PyODBCConnector, SybaseDialect):
execution_ctx_cls = SybaseExecutionContext_pyodbc
+ supports_statement_cache = True
colspecs = {sqltypes.Numeric: _SybNumeric_pyodbc}
diff --git a/lib/sqlalchemy/dialects/sybase/pysybase.py b/lib/sqlalchemy/dialects/sybase/pysybase.py
index d6d2f2ed2..0c5557e99 100644
--- a/lib/sqlalchemy/dialects/sybase/pysybase.py
+++ b/lib/sqlalchemy/dialects/sybase/pysybase.py
@@ -62,6 +62,8 @@ class SybaseDialect_pysybase(SybaseDialect):
execution_ctx_cls = SybaseExecutionContext_pysybase
statement_compiler = SybaseSQLCompiler_pysybase
+ supports_statement_cache = True
+
colspecs = {sqltypes.Numeric: _SybNumeric, sqltypes.Float: sqltypes.Float}
@classmethod
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py
index 7391e7b01..5193a0273 100644
--- a/lib/sqlalchemy/engine/default.py
+++ b/lib/sqlalchemy/engine/default.py
@@ -44,6 +44,7 @@ CACHE_HIT = util.symbol("CACHE_HIT")
CACHE_MISS = util.symbol("CACHE_MISS")
CACHING_DISABLED = util.symbol("CACHING_DISABLED")
NO_CACHE_KEY = util.symbol("NO_CACHE_KEY")
+NO_DIALECT_SUPPORT = util.symbol("NO_DIALECT_SUPPORT")
class DefaultDialect(interfaces.Dialect):
@@ -57,6 +58,7 @@ class DefaultDialect(interfaces.Dialect):
supports_comments = False
inline_comments = False
use_setinputsizes = False
+ supports_statement_cache = True
# the first value we'd get for an autoincrement
# column.
@@ -215,6 +217,7 @@ class DefaultDialect(interfaces.Dialect):
CACHE_MISS = CACHE_MISS
CACHING_DISABLED = CACHING_DISABLED
NO_CACHE_KEY = NO_CACHE_KEY
+ NO_DIALECT_SUPPORT = NO_DIALECT_SUPPORT
@util.deprecated_params(
convert_unicode=(
@@ -320,6 +323,13 @@ class DefaultDialect(interfaces.Dialect):
self._decoder = processors.to_unicode_processor_factory(self.encoding)
@util.memoized_property
+ def _supports_statement_cache(self):
+ return (
+ self.__class__.__dict__.get("supports_statement_cache", False)
+ is True
+ )
+
+ @util.memoized_property
def _type_memos(self):
return weakref.WeakKeyDictionary()
@@ -771,6 +781,8 @@ class StrCompileDialect(DefaultDialect):
type_compiler = compiler.StrSQLTypeCompiler
preparer = compiler.IdentifierPreparer
+ supports_statement_cache = True
+
supports_sequences = True
sequences_optional = True
preexecute_autoincrement_sequences = False
@@ -1137,6 +1149,12 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
return "generated in %.5fs" % (now - self.compiled._gen_time,)
elif ch is CACHING_DISABLED:
return "caching disabled %.5fs" % (now - self.compiled._gen_time,)
+ elif ch is NO_DIALECT_SUPPORT:
+ return "dialect %s+%s does not support caching %.5fs" % (
+ self.dialect.name,
+ self.dialect.driver,
+ now - self.compiled._gen_time,
+ )
else:
return "unknown"
diff --git a/lib/sqlalchemy/engine/interfaces.py b/lib/sqlalchemy/engine/interfaces.py
index 24e0e5b0d..5e6cc524e 100644
--- a/lib/sqlalchemy/engine/interfaces.py
+++ b/lib/sqlalchemy/engine/interfaces.py
@@ -152,6 +152,24 @@ class Dialect(object):
_has_events = False
+ supports_statement_cache = True
+ """indicates if this dialect supports caching.
+
+ All dialects that are compatible with statement caching should set this
+ flag to True directly on each dialect class and subclass that supports
+ it. SQLAlchemy tests that this flag is locally present on each dialect
+ subclass before it will use statement caching. This is to provide
+ safety for legacy or new dialects that are not yet fully tested to be
+ compliant with SQL statement caching.
+
+ .. versionadded:: 1.4.5
+
+ .. seealso::
+
+ :ref:`engine_thirdparty_caching`
+
+ """
+
def create_connect_args(self, url):
"""Build DB-API compatible connection arguments.
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index b64427d51..7a27690b8 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -514,7 +514,7 @@ class ClauseElement(
schema_translate_map=None,
**kw
):
- if compiled_cache is not None:
+ if compiled_cache is not None and dialect._supports_statement_cache:
elem_cache_key = self._generate_cache_key()
else:
elem_cache_key = None
@@ -553,11 +553,13 @@ class ClauseElement(
schema_translate_map=schema_translate_map,
**kw
)
- cache_hit = (
- dialect.CACHING_DISABLED
- if compiled_cache is None
- else dialect.NO_CACHE_KEY
- )
+
+ if not dialect._supports_statement_cache:
+ cache_hit = dialect.NO_DIALECT_SUPPORT
+ elif compiled_cache is None:
+ cache_hit = dialect.CACHING_DISABLED
+ else:
+ cache_hit = dialect.NO_CACHE_KEY
return compiled_sql, extracted_params, cache_hit
@@ -1429,6 +1431,34 @@ class BindParameter(roles.InElementRole, ColumnElement):
else:
return self.value
+ def render_literal_execute(self):
+ """Produce a copy of this bound parameter that will enable the
+ :paramref:`_sql.BindParameter.literal_execute` flag.
+
+ The :paramref:`_sql.BindParameter.literal_execute` flag will
+ have the effect of the parameter rendered in the compiled SQL
+ string using ``[POSTCOMPILE]`` form, which is a special form that
+ is converted to be a rendering of the literal value of the parameter
+ at SQL execution time. The rationale is to support caching
+ of SQL statement strings that can embed per-statement literal values,
+ such as LIMIT and OFFSET parameters, in the final SQL string that
+ is passed to the DBAPI. Dialects in particular may want to use
+ this method within custom compilation schemes.
+
+ .. versionadded:: 1.4.5
+
+ .. seealso::
+
+ :ref:`engine_thirdparty_caching`
+
+ """
+ return self.__class__(
+ self.key,
+ self.value,
+ type_=self.type,
+ literal_execute=True,
+ )
+
def _with_binary_element_type(self, type_):
c = ClauseElement._clone(self)
c.type = type_
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index 189deec41..a2e5780f8 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -69,14 +69,6 @@ class _OffsetLimitParam(BindParameter):
def _limit_offset_value(self):
return self.effective_value
- def _render_literal_execute(self):
- return _OffsetLimitParam(
- self.key,
- self.value,
- type_=self.type,
- literal_execute=True,
- )
-
@util.deprecated(
"1.4",