summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2021-11-22 10:59:06 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2021-11-22 11:26:33 -0500
commitd3a4e96196cd47858de072ae589c6554088edc24 (patch)
tree7ef233ee6f106e1e70a1aed5166f09f4d5960196 /lib
parent0b95f0055be252b13e99b0a944466f60b5e367ff (diff)
downloadsqlalchemy-d3a4e96196cd47858de072ae589c6554088edc24.tar.gz
Support lightweight compiler column elements w/ slots
the _CompileLabel class included ``__slots__`` but these weren't used as the superclasses included slots. Create a ``__slots__`` superclass for ``ClauseElement``, creating a new class of compilable SQL elements that don't include heavier features like caching, annotations and cloning, which are meant to be used only in an ad-hoc compiler fashion. Create new ``CompilerColumnElement`` from that which serves in column-oriented contexts, but similarly does not include any expression operator support as it is intended to be used only to generate a string. Apply this to both ``_CompileLabel`` as well as PostgreSQL ``_ColonCast``, which does not actually subclass ``ColumnElement`` as this class has memoized attributes that aren't worth changing, and does not include SQL operator capabilities as these are not needed for these compiler-only objects. this allows us to more inexpensively add new ad-hoc labels / casts etc. at compile time, as we will be seeking to expand out the typecasts that are needed for PostgreSQL dialects in a subsequent patch. Change-Id: I52973ae3295cb6e2eb0d7adc816c678a626643ed
Diffstat (limited to 'lib')
-rw-r--r--lib/sqlalchemy/dialects/postgresql/base.py3
-rw-r--r--lib/sqlalchemy/sql/annotation.py5
-rw-r--r--lib/sqlalchemy/sql/compiler.py4
-rw-r--r--lib/sqlalchemy/sql/elements.py194
-rw-r--r--lib/sqlalchemy/sql/roles.py48
-rw-r--r--lib/sqlalchemy/sql/traversals.py2
-rw-r--r--lib/sqlalchemy/sql/visitors.py2
7 files changed, 164 insertions, 94 deletions
diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py
index b4c57e694..583d9c263 100644
--- a/lib/sqlalchemy/dialects/postgresql/base.py
+++ b/lib/sqlalchemy/dialects/postgresql/base.py
@@ -2041,8 +2041,9 @@ class ENUM(sqltypes.NativeForEmulated, sqltypes.Enum):
self.drop(bind=bind, checkfirst=checkfirst)
-class _ColonCast(elements.Cast):
+class _ColonCast(elements.CompilerColumnElement):
__visit_name__ = "colon_cast"
+ __slots__ = ("type", "clause", "typeclause")
def __init__(self, expression, type_):
self.type = type_
diff --git a/lib/sqlalchemy/sql/annotation.py b/lib/sqlalchemy/sql/annotation.py
index 88f045fe6..519a3103b 100644
--- a/lib/sqlalchemy/sql/annotation.py
+++ b/lib/sqlalchemy/sql/annotation.py
@@ -21,6 +21,8 @@ EMPTY_ANNOTATIONS = util.immutabledict()
class SupportsAnnotations:
+ __slots__ = ()
+
_annotations = EMPTY_ANNOTATIONS
@util.memoized_property
@@ -44,6 +46,7 @@ class SupportsAnnotations:
class SupportsCloneAnnotations(SupportsAnnotations):
+ __slots__ = ()
_clone_annotations_traverse_internals = [
("_annotations", InternalTraversal.dp_annotations_key)
@@ -92,6 +95,8 @@ class SupportsCloneAnnotations(SupportsAnnotations):
class SupportsWrappingAnnotations(SupportsAnnotations):
+ __slots__ = ()
+
def _annotate(self, values):
"""return a copy of this ClauseElement with annotations
updated by the given dictionary.
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 482afb42f..29aa57faa 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -525,12 +525,12 @@ class TypeCompiler(util.with_metaclass(util.EnsureKWArgType, object)):
# this was a Visitable, but to allow accurate detection of
# column elements this is actually a column element
-class _CompileLabel(elements.ColumnElement):
+class _CompileLabel(elements.CompilerColumnElement):
"""lightweight label object which acts as an expression.Label."""
__visit_name__ = "label"
- __slots__ = "element", "name"
+ __slots__ = "element", "name", "_alt_names"
def __init__(self, col, name, alt_names=()):
self.element = col
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index 6893d99ee..ca65f2112 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -170,13 +170,103 @@ def not_(clause):
return operators.inv(coercions.expect(roles.ExpressionElementRole, clause))
+class CompilerElement(Traversible):
+ """base class for SQL elements that can be compiled to produce a
+ SQL string.
+
+ .. versionadded:: 2.0
+
+ """
+
+ __slots__ = ()
+ __visit_name__ = "compiler_element"
+
+ supports_execution = False
+
+ stringify_dialect = "default"
+
+ @util.preload_module("sqlalchemy.engine.default")
+ @util.preload_module("sqlalchemy.engine.url")
+ def compile(self, bind=None, dialect=None, **kw):
+ """Compile this SQL expression.
+
+ The return value is a :class:`~.Compiled` object.
+ Calling ``str()`` or ``unicode()`` on the returned value will yield a
+ string representation of the result. The
+ :class:`~.Compiled` object also can return a
+ dictionary of bind parameter names and values
+ using the ``params`` accessor.
+
+ :param bind: An ``Engine`` or ``Connection`` from which a
+ ``Compiled`` will be acquired. This argument takes precedence over
+ this :class:`_expression.ClauseElement`'s bound engine, if any.
+
+ :param column_keys: Used for INSERT and UPDATE statements, a list of
+ column names which should be present in the VALUES clause of the
+ compiled statement. If ``None``, all columns from the target table
+ object are rendered.
+
+ :param dialect: A ``Dialect`` instance from which a ``Compiled``
+ will be acquired. This argument takes precedence over the `bind`
+ argument as well as this :class:`_expression.ClauseElement`
+ 's bound engine,
+ if any.
+
+ :param compile_kwargs: optional dictionary of additional parameters
+ that will be passed through to the compiler within all "visit"
+ methods. This allows any custom flag to be passed through to
+ a custom compilation construct, for example. It is also used
+ for the case of passing the ``literal_binds`` flag through::
+
+ from sqlalchemy.sql import table, column, select
+
+ t = table('t', column('x'))
+
+ s = select(t).where(t.c.x == 5)
+
+ print(s.compile(compile_kwargs={"literal_binds": True}))
+
+ .. versionadded:: 0.9.0
+
+ .. seealso::
+
+ :ref:`faq_sql_expression_string`
+
+ """
+
+ if not dialect:
+ if bind:
+ dialect = bind.dialect
+ elif self.bind:
+ dialect = self.bind.dialect
+ else:
+ if self.stringify_dialect == "default":
+ default = util.preloaded.engine_default
+ dialect = default.StrCompileDialect()
+ else:
+ url = util.preloaded.engine_url
+ dialect = url.URL.create(
+ self.stringify_dialect
+ ).get_dialect()()
+
+ return self._compiler(dialect, **kw)
+
+ def _compiler(self, dialect, **kw):
+ """Return a compiler appropriate for this ClauseElement, given a
+ Dialect."""
+
+ return dialect.statement_compiler(dialect, self, **kw)
+
+ def __str__(self):
+ return str(self.compile())
+
+
@inspection._self_inspects
class ClauseElement(
- roles.SQLRole,
SupportsWrappingAnnotations,
MemoizedHasCacheKey,
HasCopyInternals,
- Traversible,
+ CompilerElement,
):
"""Base class for elements of a programmatically constructed SQL
expression.
@@ -191,10 +281,6 @@ class ClauseElement(
"""
- supports_execution = False
-
- stringify_dialect = "default"
-
_from_objects = []
bind = None
description = None
@@ -423,72 +509,6 @@ class ClauseElement(
return self
- @util.preload_module("sqlalchemy.engine.default")
- @util.preload_module("sqlalchemy.engine.url")
- def compile(self, bind=None, dialect=None, **kw):
- """Compile this SQL expression.
-
- The return value is a :class:`~.Compiled` object.
- Calling ``str()`` or ``unicode()`` on the returned value will yield a
- string representation of the result. The
- :class:`~.Compiled` object also can return a
- dictionary of bind parameter names and values
- using the ``params`` accessor.
-
- :param bind: An ``Engine`` or ``Connection`` from which a
- ``Compiled`` will be acquired. This argument takes precedence over
- this :class:`_expression.ClauseElement`'s bound engine, if any.
-
- :param column_keys: Used for INSERT and UPDATE statements, a list of
- column names which should be present in the VALUES clause of the
- compiled statement. If ``None``, all columns from the target table
- object are rendered.
-
- :param dialect: A ``Dialect`` instance from which a ``Compiled``
- will be acquired. This argument takes precedence over the `bind`
- argument as well as this :class:`_expression.ClauseElement`
- 's bound engine,
- if any.
-
- :param compile_kwargs: optional dictionary of additional parameters
- that will be passed through to the compiler within all "visit"
- methods. This allows any custom flag to be passed through to
- a custom compilation construct, for example. It is also used
- for the case of passing the ``literal_binds`` flag through::
-
- from sqlalchemy.sql import table, column, select
-
- t = table('t', column('x'))
-
- s = select(t).where(t.c.x == 5)
-
- print(s.compile(compile_kwargs={"literal_binds": True}))
-
- .. versionadded:: 0.9.0
-
- .. seealso::
-
- :ref:`faq_sql_expression_string`
-
- """
-
- if not dialect:
- if bind:
- dialect = bind.dialect
- elif self.bind:
- dialect = self.bind.dialect
- else:
- if self.stringify_dialect == "default":
- default = util.preloaded.engine_default
- dialect = default.StrCompileDialect()
- else:
- url = util.preloaded.engine_url
- dialect = url.URL.create(
- self.stringify_dialect
- ).get_dialect()()
-
- return self._compiler(dialect, **kw)
-
def _compile_w_cache(
self,
dialect,
@@ -547,20 +567,6 @@ class ClauseElement(
return compiled_sql, extracted_params, cache_hit
- def _compiler(self, dialect, **kw):
- """Return a compiler appropriate for this ClauseElement, given a
- Dialect."""
-
- return dialect.statement_compiler(dialect, self, **kw)
-
- def __str__(self):
- if util.py3k:
- return str(self.compile())
- else:
- return unicode(self.compile()).encode( # noqa
- "ascii", "backslashreplace"
- ) # noqa
-
def __invert__(self):
# undocumented element currently used by the ORM for
# relationship.contains()
@@ -592,6 +598,21 @@ class ClauseElement(
)
+class CompilerColumnElement(
+ roles.DMLColumnRole,
+ roles.DDLConstraintColumnRole,
+ roles.ColumnsClauseRole,
+ CompilerElement,
+):
+ """A compiler-only column element used for ad-hoc string compilations.
+
+ .. versionadded:: 2.0
+
+ """
+
+ __slots__ = ()
+
+
class ColumnElement(
roles.ColumnArgumentOrKeyRole,
roles.StatementOptionRole,
@@ -684,6 +705,7 @@ class ColumnElement(
"""
__visit_name__ = "column_element"
+
primary_key = False
foreign_keys = []
_proxies = ()
diff --git a/lib/sqlalchemy/sql/roles.py b/lib/sqlalchemy/sql/roles.py
index 4e009aa26..c4eedd4a4 100644
--- a/lib/sqlalchemy/sql/roles.py
+++ b/lib/sqlalchemy/sql/roles.py
@@ -19,48 +19,60 @@ class SQLRole:
"""
+ __slots__ = ()
allows_lambda = False
uses_inspection = False
class UsesInspection:
+ __slots__ = ()
_post_inspect = None
uses_inspection = True
class AllowsLambdaRole:
+ __slots__ = ()
allows_lambda = True
class HasCacheKeyRole(SQLRole):
+ __slots__ = ()
_role_name = "Cacheable Core or ORM object"
class LiteralValueRole(SQLRole):
+ __slots__ = ()
_role_name = "Literal Python value"
class ColumnArgumentRole(SQLRole):
+ __slots__ = ()
_role_name = "Column expression"
class ColumnArgumentOrKeyRole(ColumnArgumentRole):
+ __slots__ = ()
_role_name = "Column expression or string key"
class StrAsPlainColumnRole(ColumnArgumentRole):
+ __slots__ = ()
_role_name = "Column expression or string key"
class ColumnListRole(SQLRole):
"""Elements suitable for forming comma separated lists of expressions."""
+ __slots__ = ()
+
class TruncatedLabelRole(SQLRole):
+ __slots__ = ()
_role_name = "String SQL identifier"
class ColumnsClauseRole(AllowsLambdaRole, UsesInspection, ColumnListRole):
+ __slots__ = ()
_role_name = "Column expression or FROM clause"
@property
@@ -69,14 +81,17 @@ class ColumnsClauseRole(AllowsLambdaRole, UsesInspection, ColumnListRole):
class LimitOffsetRole(SQLRole):
+ __slots__ = ()
_role_name = "LIMIT / OFFSET expression"
class ByOfRole(ColumnListRole):
+ __slots__ = ()
_role_name = "GROUP BY / OF / etc. expression"
class GroupByRole(AllowsLambdaRole, UsesInspection, ByOfRole):
+ __slots__ = ()
# note there's a special case right now where you can pass a whole
# ORM entity to group_by() and it splits out. we may not want to keep
# this around
@@ -85,48 +100,57 @@ class GroupByRole(AllowsLambdaRole, UsesInspection, ByOfRole):
class OrderByRole(AllowsLambdaRole, ByOfRole):
+ __slots__ = ()
_role_name = "ORDER BY expression"
class StructuralRole(SQLRole):
- pass
+ __slots__ = ()
class StatementOptionRole(StructuralRole):
+ __slots__ = ()
_role_name = "statement sub-expression element"
class OnClauseRole(AllowsLambdaRole, StructuralRole):
+ __slots__ = ()
_role_name = "SQL expression for ON clause"
class WhereHavingRole(OnClauseRole):
+ __slots__ = ()
_role_name = "SQL expression for WHERE/HAVING role"
class ExpressionElementRole(SQLRole):
+ __slots__ = ()
_role_name = "SQL expression element"
class ConstExprRole(ExpressionElementRole):
+ __slots__ = ()
_role_name = "Constant True/False/None expression"
class LabeledColumnExprRole(ExpressionElementRole):
- pass
+ __slots__ = ()
class BinaryElementRole(ExpressionElementRole):
+ __slots__ = ()
_role_name = "SQL expression element or literal value"
class InElementRole(SQLRole):
+ __slots__ = ()
_role_name = (
"IN expression list, SELECT construct, or bound parameter object"
)
class JoinTargetRole(AllowsLambdaRole, UsesInspection, StructuralRole):
+ __slots__ = ()
_role_name = (
"Join target, typically a FROM expression, or ORM "
"relationship attribute"
@@ -134,6 +158,7 @@ class JoinTargetRole(AllowsLambdaRole, UsesInspection, StructuralRole):
class FromClauseRole(ColumnsClauseRole, JoinTargetRole):
+ __slots__ = ()
_role_name = "FROM expression, such as a Table or alias() object"
_is_subquery = False
@@ -144,6 +169,7 @@ class FromClauseRole(ColumnsClauseRole, JoinTargetRole):
class StrictFromClauseRole(FromClauseRole):
+ __slots__ = ()
# does not allow text() or select() objects
@property
@@ -152,6 +178,7 @@ class StrictFromClauseRole(FromClauseRole):
class AnonymizedFromClauseRole(StrictFromClauseRole):
+ __slots__ = ()
# calls .alias() as a post processor
def _anonymous_fromclause(self, name=None, flat=False):
@@ -159,6 +186,7 @@ class AnonymizedFromClauseRole(StrictFromClauseRole):
class ReturnsRowsRole(SQLRole):
+ __slots__ = ()
_role_name = (
"Row returning expression such as a SELECT, a FROM clause, or an "
"INSERT/UPDATE/DELETE with RETURNING"
@@ -166,12 +194,14 @@ class ReturnsRowsRole(SQLRole):
class StatementRole(SQLRole):
+ __slots__ = ()
_role_name = "Executable SQL or text() construct"
_propagate_attrs = util.immutabledict()
class SelectStatementRole(StatementRole, ReturnsRowsRole):
+ __slots__ = ()
_role_name = "SELECT construct or equivalent text() construct"
def subquery(self):
@@ -182,16 +212,18 @@ class SelectStatementRole(StatementRole, ReturnsRowsRole):
class HasCTERole(ReturnsRowsRole):
- pass
+ __slots__ = ()
class IsCTERole(SQLRole):
+ __slots__ = ()
_role_name = "CTE object"
class CompoundElementRole(AllowsLambdaRole, SQLRole):
"""SELECT statements inside a CompoundSelect, e.g. UNION, EXTRACT, etc."""
+ __slots__ = ()
_role_name = (
"SELECT construct for inclusion in a UNION or other set construct"
)
@@ -199,36 +231,42 @@ class CompoundElementRole(AllowsLambdaRole, SQLRole):
# TODO: are we using this?
class DMLRole(StatementRole):
- pass
+ __slots__ = ()
class DMLTableRole(FromClauseRole):
+ __slots__ = ()
_role_name = "subject table for an INSERT, UPDATE or DELETE"
class DMLColumnRole(SQLRole):
+ __slots__ = ()
_role_name = "SET/VALUES column expression or string key"
class DMLSelectRole(SQLRole):
"""A SELECT statement embedded in DML, typically INSERT from SELECT"""
+ __slots__ = ()
_role_name = "SELECT statement or equivalent textual object"
class DDLRole(StatementRole):
- pass
+ __slots__ = ()
class DDLExpressionRole(StructuralRole):
+ __slots__ = ()
_role_name = "SQL expression element for DDL constraint"
class DDLConstraintColumnRole(SQLRole):
+ __slots__ = ()
_role_name = "String column name or column expression for DDL constraint"
class DDLReferredColumnRole(DDLConstraintColumnRole):
+ __slots__ = ()
_role_name = (
"String column name or Column object for DDL foreign key constraint"
)
diff --git a/lib/sqlalchemy/sql/traversals.py b/lib/sqlalchemy/sql/traversals.py
index 6acd794aa..7973b535f 100644
--- a/lib/sqlalchemy/sql/traversals.py
+++ b/lib/sqlalchemy/sql/traversals.py
@@ -714,6 +714,8 @@ _cache_key_traversal_visitor = _CacheKey()
class HasCopyInternals:
+ __slots__ = ()
+
def _clone(self, **kw):
raise NotImplementedError()
diff --git a/lib/sqlalchemy/sql/visitors.py b/lib/sqlalchemy/sql/visitors.py
index 82cb7a253..deb92b081 100644
--- a/lib/sqlalchemy/sql/visitors.py
+++ b/lib/sqlalchemy/sql/visitors.py
@@ -120,6 +120,8 @@ class Traversible(util.with_metaclass(TraversibleType)):
"""
+ __slots__ = ()
+
def __class_getitem__(cls, key):
# allow generic classes in py3.9+
return cls