summaryrefslogtreecommitdiff
path: root/venv/lib/python3.11/site-packages/sqlalchemy/sql
diff options
context:
space:
mode:
authorcyfraeviolae <cyfraeviolae>2024-04-03 03:10:44 -0400
committercyfraeviolae <cyfraeviolae>2024-04-03 03:10:44 -0400
commit6d7ba58f880be618ade07f8ea080fe8c4bf8a896 (patch)
treeb1c931051ffcebd2bd9d61d98d6233ffa289bbce /venv/lib/python3.11/site-packages/sqlalchemy/sql
parent4f884c9abc32990b4061a1bb6997b4b37e58ea0b (diff)
venv
Diffstat (limited to 'venv/lib/python3.11/site-packages/sqlalchemy/sql')
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__init__.py145
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/__init__.cpython-311.pycbin0 -> 6476 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_dml_constructors.cpython-311.pycbin0 -> 4303 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_elements_constructors.cpython-311.pycbin0 -> 66541 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_orm_types.cpython-311.pycbin0 -> 678 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_py_util.cpython-311.pycbin0 -> 3351 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-311.pycbin0 -> 21634 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_typing.cpython-311.pycbin0 -> 16730 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/annotation.cpython-311.pycbin0 -> 23147 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/base.cpython-311.pycbin0 -> 107198 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/cache_key.cpython-311.pycbin0 -> 39232 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/coercions.cpython-311.pycbin0 -> 53716 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/compiler.cpython-311.pycbin0 -> 286030 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/crud.cpython-311.pycbin0 -> 47495 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/ddl.cpython-311.pycbin0 -> 62979 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/default_comparator.cpython-311.pycbin0 -> 18086 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/dml.cpython-311.pycbin0 -> 77526 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/elements.cpython-311.pycbin0 -> 217761 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/events.cpython-311.pycbin0 -> 19364 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/expression.cpython-311.pycbin0 -> 7230 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/functions.cpython-311.pycbin0 -> 80876 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/lambdas.cpython-311.pycbin0 -> 59594 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/naming.cpython-311.pycbin0 -> 9203 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/operators.cpython-311.pycbin0 -> 93289 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/roles.cpython-311.pycbin0 -> 14938 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/schema.cpython-311.pycbin0 -> 255679 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/selectable.cpython-311.pycbin0 -> 272258 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/sqltypes.cpython-311.pycbin0 -> 158830 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/traversals.cpython-311.pycbin0 -> 49251 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/type_api.cpython-311.pycbin0 -> 87962 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/util.cpython-311.pycbin0 -> 59936 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/visitors.cpython-311.pycbin0 -> 38853 bytes
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/_dml_constructors.py140
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/_elements_constructors.py1840
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/_orm_types.py20
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/_py_util.py75
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/_selectable_constructors.py635
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/_typing.py457
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/annotation.py585
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/base.py2180
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/cache_key.py1057
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/coercions.py1389
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/compiler.py7811
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/crud.py1669
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/ddl.py1378
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/default_comparator.py552
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/dml.py1817
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/elements.py5405
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/events.py455
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/expression.py162
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/functions.py2052
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/lambdas.py1449
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/naming.py212
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/operators.py2573
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/roles.py323
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/schema.py6115
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/selectable.py6913
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/sqltypes.py3786
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/traversals.py1022
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/type_api.py2303
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/util.py1486
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/sql/visitors.py1165
62 files changed, 57171 insertions, 0 deletions
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__init__.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__init__.py
new file mode 100644
index 0000000..9e0d2ca
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__init__.py
@@ -0,0 +1,145 @@
+# sql/__init__.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+from typing import Any
+from typing import TYPE_CHECKING
+
+from ._typing import ColumnExpressionArgument as ColumnExpressionArgument
+from ._typing import NotNullable as NotNullable
+from ._typing import Nullable as Nullable
+from .base import Executable as Executable
+from .compiler import COLLECT_CARTESIAN_PRODUCTS as COLLECT_CARTESIAN_PRODUCTS
+from .compiler import FROM_LINTING as FROM_LINTING
+from .compiler import NO_LINTING as NO_LINTING
+from .compiler import WARN_LINTING as WARN_LINTING
+from .ddl import BaseDDLElement as BaseDDLElement
+from .ddl import DDL as DDL
+from .ddl import DDLElement as DDLElement
+from .ddl import ExecutableDDLElement as ExecutableDDLElement
+from .expression import Alias as Alias
+from .expression import alias as alias
+from .expression import all_ as all_
+from .expression import and_ as and_
+from .expression import any_ as any_
+from .expression import asc as asc
+from .expression import between as between
+from .expression import bindparam as bindparam
+from .expression import case as case
+from .expression import cast as cast
+from .expression import ClauseElement as ClauseElement
+from .expression import collate as collate
+from .expression import column as column
+from .expression import ColumnCollection as ColumnCollection
+from .expression import ColumnElement as ColumnElement
+from .expression import CompoundSelect as CompoundSelect
+from .expression import cte as cte
+from .expression import Delete as Delete
+from .expression import delete as delete
+from .expression import desc as desc
+from .expression import distinct as distinct
+from .expression import except_ as except_
+from .expression import except_all as except_all
+from .expression import exists as exists
+from .expression import extract as extract
+from .expression import false as false
+from .expression import False_ as False_
+from .expression import FromClause as FromClause
+from .expression import func as func
+from .expression import funcfilter as funcfilter
+from .expression import Insert as Insert
+from .expression import insert as insert
+from .expression import intersect as intersect
+from .expression import intersect_all as intersect_all
+from .expression import Join as Join
+from .expression import join as join
+from .expression import label as label
+from .expression import LABEL_STYLE_DEFAULT as LABEL_STYLE_DEFAULT
+from .expression import (
+ LABEL_STYLE_DISAMBIGUATE_ONLY as LABEL_STYLE_DISAMBIGUATE_ONLY,
+)
+from .expression import LABEL_STYLE_NONE as LABEL_STYLE_NONE
+from .expression import (
+ LABEL_STYLE_TABLENAME_PLUS_COL as LABEL_STYLE_TABLENAME_PLUS_COL,
+)
+from .expression import lambda_stmt as lambda_stmt
+from .expression import LambdaElement as LambdaElement
+from .expression import lateral as lateral
+from .expression import literal as literal
+from .expression import literal_column as literal_column
+from .expression import modifier as modifier
+from .expression import not_ as not_
+from .expression import null as null
+from .expression import nulls_first as nulls_first
+from .expression import nulls_last as nulls_last
+from .expression import nullsfirst as nullsfirst
+from .expression import nullslast as nullslast
+from .expression import or_ as or_
+from .expression import outerjoin as outerjoin
+from .expression import outparam as outparam
+from .expression import over as over
+from .expression import quoted_name as quoted_name
+from .expression import Select as Select
+from .expression import select as select
+from .expression import Selectable as Selectable
+from .expression import SelectLabelStyle as SelectLabelStyle
+from .expression import SQLColumnExpression as SQLColumnExpression
+from .expression import StatementLambdaElement as StatementLambdaElement
+from .expression import Subquery as Subquery
+from .expression import table as table
+from .expression import TableClause as TableClause
+from .expression import TableSample as TableSample
+from .expression import tablesample as tablesample
+from .expression import text as text
+from .expression import true as true
+from .expression import True_ as True_
+from .expression import try_cast as try_cast
+from .expression import tuple_ as tuple_
+from .expression import type_coerce as type_coerce
+from .expression import union as union
+from .expression import union_all as union_all
+from .expression import Update as Update
+from .expression import update as update
+from .expression import Values as Values
+from .expression import values as values
+from .expression import within_group as within_group
+from .visitors import ClauseVisitor as ClauseVisitor
+
+
+def __go(lcls: Any) -> None:
+ from .. import util as _sa_util
+
+ from . import base
+ from . import coercions
+ from . import elements
+ from . import lambdas
+ from . import selectable
+ from . import schema
+ from . import traversals
+ from . import type_api
+
+ if not TYPE_CHECKING:
+ base.coercions = elements.coercions = coercions
+ base.elements = elements
+ base.type_api = type_api
+ coercions.elements = elements
+ coercions.lambdas = lambdas
+ coercions.schema = schema
+ coercions.selectable = selectable
+
+ from .annotation import _prepare_annotations
+ from .annotation import Annotated
+ from .elements import AnnotatedColumnElement
+ from .elements import ClauseList
+ from .selectable import AnnotatedFromClause
+
+ _prepare_annotations(ColumnElement, AnnotatedColumnElement)
+ _prepare_annotations(FromClause, AnnotatedFromClause)
+ _prepare_annotations(ClauseList, Annotated)
+
+ _sa_util.preloaded.import_prefix("sqlalchemy.sql")
+
+
+__go(locals())
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000..16135d0
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/__init__.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_dml_constructors.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_dml_constructors.cpython-311.pyc
new file mode 100644
index 0000000..0525743
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_dml_constructors.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_elements_constructors.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_elements_constructors.cpython-311.pyc
new file mode 100644
index 0000000..493de78
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_elements_constructors.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_orm_types.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_orm_types.cpython-311.pyc
new file mode 100644
index 0000000..76e4a97
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_orm_types.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_py_util.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_py_util.cpython-311.pyc
new file mode 100644
index 0000000..ab0a578
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_py_util.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-311.pyc
new file mode 100644
index 0000000..29ea597
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_typing.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_typing.cpython-311.pyc
new file mode 100644
index 0000000..d5f60fb
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/_typing.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/annotation.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/annotation.cpython-311.pyc
new file mode 100644
index 0000000..d52f144
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/annotation.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/base.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/base.cpython-311.pyc
new file mode 100644
index 0000000..efe2b16
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/base.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/cache_key.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/cache_key.cpython-311.pyc
new file mode 100644
index 0000000..fa315ca
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/cache_key.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/coercions.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/coercions.cpython-311.pyc
new file mode 100644
index 0000000..132dce4
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/coercions.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/compiler.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/compiler.cpython-311.pyc
new file mode 100644
index 0000000..1f1a5fc
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/compiler.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/crud.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/crud.cpython-311.pyc
new file mode 100644
index 0000000..021ffb3
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/crud.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/ddl.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/ddl.cpython-311.pyc
new file mode 100644
index 0000000..3f5a4e9
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/ddl.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/default_comparator.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/default_comparator.cpython-311.pyc
new file mode 100644
index 0000000..ae92d63
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/default_comparator.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/dml.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/dml.cpython-311.pyc
new file mode 100644
index 0000000..14985ca
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/dml.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/elements.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/elements.cpython-311.pyc
new file mode 100644
index 0000000..3eda846
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/elements.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/events.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/events.cpython-311.pyc
new file mode 100644
index 0000000..6ff8a38
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/events.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/expression.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/expression.cpython-311.pyc
new file mode 100644
index 0000000..e3d2b68
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/expression.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/functions.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/functions.cpython-311.pyc
new file mode 100644
index 0000000..f03311a
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/functions.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/lambdas.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/lambdas.cpython-311.pyc
new file mode 100644
index 0000000..9eea091
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/lambdas.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/naming.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/naming.cpython-311.pyc
new file mode 100644
index 0000000..86ebcd9
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/naming.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/operators.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/operators.cpython-311.pyc
new file mode 100644
index 0000000..36ffdc3
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/operators.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/roles.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/roles.cpython-311.pyc
new file mode 100644
index 0000000..d3dab5d
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/roles.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/schema.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/schema.cpython-311.pyc
new file mode 100644
index 0000000..ca5ea38
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/schema.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/selectable.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/selectable.cpython-311.pyc
new file mode 100644
index 0000000..fea6f8f
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/selectable.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/sqltypes.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/sqltypes.cpython-311.pyc
new file mode 100644
index 0000000..1214a08
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/sqltypes.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/traversals.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/traversals.cpython-311.pyc
new file mode 100644
index 0000000..f1c5425
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/traversals.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/type_api.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/type_api.cpython-311.pyc
new file mode 100644
index 0000000..47f73cd
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/type_api.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/util.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/util.cpython-311.pyc
new file mode 100644
index 0000000..1a4cda5
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/util.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/visitors.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/visitors.cpython-311.pyc
new file mode 100644
index 0000000..63c33db
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/__pycache__/visitors.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/_dml_constructors.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/_dml_constructors.py
new file mode 100644
index 0000000..a7ead52
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/_dml_constructors.py
@@ -0,0 +1,140 @@
+# sql/_dml_constructors.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from .dml import Delete
+from .dml import Insert
+from .dml import Update
+
+if TYPE_CHECKING:
+ from ._typing import _DMLTableArgument
+
+
+def insert(table: _DMLTableArgument) -> Insert:
+ """Construct an :class:`_expression.Insert` object.
+
+ E.g.::
+
+ from sqlalchemy import insert
+
+ stmt = (
+ insert(user_table).
+ values(name='username', fullname='Full Username')
+ )
+
+ Similar functionality is available via the
+ :meth:`_expression.TableClause.insert` method on
+ :class:`_schema.Table`.
+
+ .. seealso::
+
+ :ref:`tutorial_core_insert` - in the :ref:`unified_tutorial`
+
+
+ :param table: :class:`_expression.TableClause`
+ which is the subject of the
+ insert.
+
+ :param values: collection of values to be inserted; see
+ :meth:`_expression.Insert.values`
+ for a description of allowed formats here.
+ Can be omitted entirely; a :class:`_expression.Insert` construct
+ will also dynamically render the VALUES clause at execution time
+ based on the parameters passed to :meth:`_engine.Connection.execute`.
+
+ :param inline: if True, no attempt will be made to retrieve the
+ SQL-generated default values to be provided within the statement;
+ in particular,
+ this allows SQL expressions to be rendered 'inline' within the
+ statement without the need to pre-execute them beforehand; for
+ backends that support "returning", this turns off the "implicit
+ returning" feature for the statement.
+
+ If both :paramref:`_expression.insert.values` and compile-time bind
+ parameters are present, the compile-time bind parameters override the
+ information specified within :paramref:`_expression.insert.values` on a
+ per-key basis.
+
+ The keys within :paramref:`_expression.Insert.values` can be either
+ :class:`~sqlalchemy.schema.Column` objects or their string
+ identifiers. Each key may reference one of:
+
+ * a literal data value (i.e. string, number, etc.);
+ * a Column object;
+ * a SELECT statement.
+
+ If a ``SELECT`` statement is specified which references this
+ ``INSERT`` statement's table, the statement will be correlated
+ against the ``INSERT`` statement.
+
+ .. seealso::
+
+ :ref:`tutorial_core_insert` - in the :ref:`unified_tutorial`
+
+ """
+ return Insert(table)
+
+
+def update(table: _DMLTableArgument) -> Update:
+ r"""Construct an :class:`_expression.Update` object.
+
+ E.g.::
+
+ from sqlalchemy import update
+
+ stmt = (
+ update(user_table).
+ where(user_table.c.id == 5).
+ values(name='user #5')
+ )
+
+ Similar functionality is available via the
+ :meth:`_expression.TableClause.update` method on
+ :class:`_schema.Table`.
+
+ :param table: A :class:`_schema.Table`
+ object representing the database
+ table to be updated.
+
+
+ .. seealso::
+
+ :ref:`tutorial_core_update_delete` - in the :ref:`unified_tutorial`
+
+
+ """
+ return Update(table)
+
+
+def delete(table: _DMLTableArgument) -> Delete:
+ r"""Construct :class:`_expression.Delete` object.
+
+ E.g.::
+
+ from sqlalchemy import delete
+
+ stmt = (
+ delete(user_table).
+ where(user_table.c.id == 5)
+ )
+
+ Similar functionality is available via the
+ :meth:`_expression.TableClause.delete` method on
+ :class:`_schema.Table`.
+
+ :param table: The table to delete rows from.
+
+ .. seealso::
+
+ :ref:`tutorial_core_update_delete` - in the :ref:`unified_tutorial`
+
+
+ """
+ return Delete(table)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/_elements_constructors.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/_elements_constructors.py
new file mode 100644
index 0000000..77cc2a8
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/_elements_constructors.py
@@ -0,0 +1,1840 @@
+# sql/_elements_constructors.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+from __future__ import annotations
+
+import typing
+from typing import Any
+from typing import Callable
+from typing import Mapping
+from typing import Optional
+from typing import overload
+from typing import Sequence
+from typing import Tuple as typing_Tuple
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from . import coercions
+from . import roles
+from .base import _NoArg
+from .coercions import _document_text_coercion
+from .elements import BindParameter
+from .elements import BooleanClauseList
+from .elements import Case
+from .elements import Cast
+from .elements import CollationClause
+from .elements import CollectionAggregate
+from .elements import ColumnClause
+from .elements import ColumnElement
+from .elements import Extract
+from .elements import False_
+from .elements import FunctionFilter
+from .elements import Label
+from .elements import Null
+from .elements import Over
+from .elements import TextClause
+from .elements import True_
+from .elements import TryCast
+from .elements import Tuple
+from .elements import TypeCoerce
+from .elements import UnaryExpression
+from .elements import WithinGroup
+from .functions import FunctionElement
+from ..util.typing import Literal
+
+if typing.TYPE_CHECKING:
+ from ._typing import _ByArgument
+ from ._typing import _ColumnExpressionArgument
+ from ._typing import _ColumnExpressionOrLiteralArgument
+ from ._typing import _ColumnExpressionOrStrLabelArgument
+ from ._typing import _TypeEngineArgument
+ from .elements import BinaryExpression
+ from .selectable import FromClause
+ from .type_api import TypeEngine
+
+_T = TypeVar("_T")
+
+
+def all_(expr: _ColumnExpressionArgument[_T]) -> CollectionAggregate[bool]:
+ """Produce an ALL expression.
+
+ For dialects such as that of PostgreSQL, this operator applies
+ to usage of the :class:`_types.ARRAY` datatype, for that of
+ MySQL, it may apply to a subquery. e.g.::
+
+ # renders on PostgreSQL:
+ # '5 = ALL (somearray)'
+ expr = 5 == all_(mytable.c.somearray)
+
+ # renders on MySQL:
+ # '5 = ALL (SELECT value FROM table)'
+ expr = 5 == all_(select(table.c.value))
+
+ Comparison to NULL may work using ``None``::
+
+ None == all_(mytable.c.somearray)
+
+ The any_() / all_() operators also feature a special "operand flipping"
+ behavior such that if any_() / all_() are used on the left side of a
+ comparison using a standalone operator such as ``==``, ``!=``, etc.
+ (not including operator methods such as
+ :meth:`_sql.ColumnOperators.is_`) the rendered expression is flipped::
+
+ # would render '5 = ALL (column)`
+ all_(mytable.c.column) == 5
+
+ Or with ``None``, which note will not perform
+ the usual step of rendering "IS" as is normally the case for NULL::
+
+ # would render 'NULL = ALL(somearray)'
+ all_(mytable.c.somearray) == None
+
+ .. versionchanged:: 1.4.26 repaired the use of any_() / all_()
+ comparing to NULL on the right side to be flipped to the left.
+
+ The column-level :meth:`_sql.ColumnElement.all_` method (not to be
+ confused with :class:`_types.ARRAY` level
+ :meth:`_types.ARRAY.Comparator.all`) is shorthand for
+ ``all_(col)``::
+
+ 5 == mytable.c.somearray.all_()
+
+ .. seealso::
+
+ :meth:`_sql.ColumnOperators.all_`
+
+ :func:`_expression.any_`
+
+ """
+ return CollectionAggregate._create_all(expr)
+
+
+def and_( # type: ignore[empty-body]
+ initial_clause: Union[Literal[True], _ColumnExpressionArgument[bool]],
+ *clauses: _ColumnExpressionArgument[bool],
+) -> ColumnElement[bool]:
+ r"""Produce a conjunction of expressions joined by ``AND``.
+
+ E.g.::
+
+ from sqlalchemy import and_
+
+ stmt = select(users_table).where(
+ and_(
+ users_table.c.name == 'wendy',
+ users_table.c.enrolled == True
+ )
+ )
+
+ The :func:`.and_` conjunction is also available using the
+ Python ``&`` operator (though note that compound expressions
+ need to be parenthesized in order to function with Python
+ operator precedence behavior)::
+
+ stmt = select(users_table).where(
+ (users_table.c.name == 'wendy') &
+ (users_table.c.enrolled == True)
+ )
+
+ The :func:`.and_` operation is also implicit in some cases;
+ the :meth:`_expression.Select.where`
+ method for example can be invoked multiple
+ times against a statement, which will have the effect of each
+ clause being combined using :func:`.and_`::
+
+ stmt = select(users_table).\
+ where(users_table.c.name == 'wendy').\
+ where(users_table.c.enrolled == True)
+
+ The :func:`.and_` construct must be given at least one positional
+ argument in order to be valid; a :func:`.and_` construct with no
+ arguments is ambiguous. To produce an "empty" or dynamically
+ generated :func:`.and_` expression, from a given list of expressions,
+ a "default" element of :func:`_sql.true` (or just ``True``) should be
+ specified::
+
+ from sqlalchemy import true
+ criteria = and_(true(), *expressions)
+
+ The above expression will compile to SQL as the expression ``true``
+ or ``1 = 1``, depending on backend, if no other expressions are
+ present. If expressions are present, then the :func:`_sql.true` value is
+ ignored as it does not affect the outcome of an AND expression that
+ has other elements.
+
+ .. deprecated:: 1.4 The :func:`.and_` element now requires that at
+ least one argument is passed; creating the :func:`.and_` construct
+ with no arguments is deprecated, and will emit a deprecation warning
+ while continuing to produce a blank SQL string.
+
+ .. seealso::
+
+ :func:`.or_`
+
+ """
+ ...
+
+
+if not TYPE_CHECKING:
+ # handle deprecated case which allows zero-arguments
+ def and_(*clauses): # noqa: F811
+ r"""Produce a conjunction of expressions joined by ``AND``.
+
+ E.g.::
+
+ from sqlalchemy import and_
+
+ stmt = select(users_table).where(
+ and_(
+ users_table.c.name == 'wendy',
+ users_table.c.enrolled == True
+ )
+ )
+
+ The :func:`.and_` conjunction is also available using the
+ Python ``&`` operator (though note that compound expressions
+ need to be parenthesized in order to function with Python
+ operator precedence behavior)::
+
+ stmt = select(users_table).where(
+ (users_table.c.name == 'wendy') &
+ (users_table.c.enrolled == True)
+ )
+
+ The :func:`.and_` operation is also implicit in some cases;
+ the :meth:`_expression.Select.where`
+ method for example can be invoked multiple
+ times against a statement, which will have the effect of each
+ clause being combined using :func:`.and_`::
+
+ stmt = select(users_table).\
+ where(users_table.c.name == 'wendy').\
+ where(users_table.c.enrolled == True)
+
+ The :func:`.and_` construct must be given at least one positional
+ argument in order to be valid; a :func:`.and_` construct with no
+ arguments is ambiguous. To produce an "empty" or dynamically
+ generated :func:`.and_` expression, from a given list of expressions,
+ a "default" element of :func:`_sql.true` (or just ``True``) should be
+ specified::
+
+ from sqlalchemy import true
+ criteria = and_(true(), *expressions)
+
+ The above expression will compile to SQL as the expression ``true``
+ or ``1 = 1``, depending on backend, if no other expressions are
+ present. If expressions are present, then the :func:`_sql.true` value
+ is ignored as it does not affect the outcome of an AND expression that
+ has other elements.
+
+ .. deprecated:: 1.4 The :func:`.and_` element now requires that at
+ least one argument is passed; creating the :func:`.and_` construct
+ with no arguments is deprecated, and will emit a deprecation warning
+ while continuing to produce a blank SQL string.
+
+ .. seealso::
+
+ :func:`.or_`
+
+ """
+ return BooleanClauseList.and_(*clauses)
+
+
+def any_(expr: _ColumnExpressionArgument[_T]) -> CollectionAggregate[bool]:
+ """Produce an ANY expression.
+
+ For dialects such as that of PostgreSQL, this operator applies
+ to usage of the :class:`_types.ARRAY` datatype, for that of
+ MySQL, it may apply to a subquery. e.g.::
+
+ # renders on PostgreSQL:
+ # '5 = ANY (somearray)'
+ expr = 5 == any_(mytable.c.somearray)
+
+ # renders on MySQL:
+ # '5 = ANY (SELECT value FROM table)'
+ expr = 5 == any_(select(table.c.value))
+
+ Comparison to NULL may work using ``None`` or :func:`_sql.null`::
+
+ None == any_(mytable.c.somearray)
+
+ The any_() / all_() operators also feature a special "operand flipping"
+ behavior such that if any_() / all_() are used on the left side of a
+ comparison using a standalone operator such as ``==``, ``!=``, etc.
+ (not including operator methods such as
+ :meth:`_sql.ColumnOperators.is_`) the rendered expression is flipped::
+
+ # would render '5 = ANY (column)`
+ any_(mytable.c.column) == 5
+
+ Or with ``None``, which note will not perform
+ the usual step of rendering "IS" as is normally the case for NULL::
+
+ # would render 'NULL = ANY(somearray)'
+ any_(mytable.c.somearray) == None
+
+ .. versionchanged:: 1.4.26 repaired the use of any_() / all_()
+ comparing to NULL on the right side to be flipped to the left.
+
+ The column-level :meth:`_sql.ColumnElement.any_` method (not to be
+ confused with :class:`_types.ARRAY` level
+ :meth:`_types.ARRAY.Comparator.any`) is shorthand for
+ ``any_(col)``::
+
+ 5 = mytable.c.somearray.any_()
+
+ .. seealso::
+
+ :meth:`_sql.ColumnOperators.any_`
+
+ :func:`_expression.all_`
+
+ """
+ return CollectionAggregate._create_any(expr)
+
+
+def asc(
+ column: _ColumnExpressionOrStrLabelArgument[_T],
+) -> UnaryExpression[_T]:
+ """Produce an ascending ``ORDER BY`` clause element.
+
+ e.g.::
+
+ from sqlalchemy import asc
+ stmt = select(users_table).order_by(asc(users_table.c.name))
+
+ will produce SQL as::
+
+ SELECT id, name FROM user ORDER BY name ASC
+
+ The :func:`.asc` function is a standalone version of the
+ :meth:`_expression.ColumnElement.asc`
+ method available on all SQL expressions,
+ e.g.::
+
+
+ stmt = select(users_table).order_by(users_table.c.name.asc())
+
+ :param column: A :class:`_expression.ColumnElement` (e.g.
+ scalar SQL expression)
+ with which to apply the :func:`.asc` operation.
+
+ .. seealso::
+
+ :func:`.desc`
+
+ :func:`.nulls_first`
+
+ :func:`.nulls_last`
+
+ :meth:`_expression.Select.order_by`
+
+ """
+ return UnaryExpression._create_asc(column)
+
+
+def collate(
+ expression: _ColumnExpressionArgument[str], collation: str
+) -> BinaryExpression[str]:
+ """Return the clause ``expression COLLATE collation``.
+
+ e.g.::
+
+ collate(mycolumn, 'utf8_bin')
+
+ produces::
+
+ mycolumn COLLATE utf8_bin
+
+ The collation expression is also quoted if it is a case sensitive
+ identifier, e.g. contains uppercase characters.
+
+ .. versionchanged:: 1.2 quoting is automatically applied to COLLATE
+ expressions if they are case sensitive.
+
+ """
+ return CollationClause._create_collation_expression(expression, collation)
+
+
+def between(
+ expr: _ColumnExpressionOrLiteralArgument[_T],
+ lower_bound: Any,
+ upper_bound: Any,
+ symmetric: bool = False,
+) -> BinaryExpression[bool]:
+ """Produce a ``BETWEEN`` predicate clause.
+
+ E.g.::
+
+ from sqlalchemy import between
+ stmt = select(users_table).where(between(users_table.c.id, 5, 7))
+
+ Would produce SQL resembling::
+
+ SELECT id, name FROM user WHERE id BETWEEN :id_1 AND :id_2
+
+ The :func:`.between` function is a standalone version of the
+ :meth:`_expression.ColumnElement.between` method available on all
+ SQL expressions, as in::
+
+ stmt = select(users_table).where(users_table.c.id.between(5, 7))
+
+ All arguments passed to :func:`.between`, including the left side
+ column expression, are coerced from Python scalar values if a
+ the value is not a :class:`_expression.ColumnElement` subclass.
+ For example,
+ three fixed values can be compared as in::
+
+ print(between(5, 3, 7))
+
+ Which would produce::
+
+ :param_1 BETWEEN :param_2 AND :param_3
+
+ :param expr: a column expression, typically a
+ :class:`_expression.ColumnElement`
+ instance or alternatively a Python scalar expression to be coerced
+ into a column expression, serving as the left side of the ``BETWEEN``
+ expression.
+
+ :param lower_bound: a column or Python scalar expression serving as the
+ lower bound of the right side of the ``BETWEEN`` expression.
+
+ :param upper_bound: a column or Python scalar expression serving as the
+ upper bound of the right side of the ``BETWEEN`` expression.
+
+ :param symmetric: if True, will render " BETWEEN SYMMETRIC ". Note
+ that not all databases support this syntax.
+
+ .. seealso::
+
+ :meth:`_expression.ColumnElement.between`
+
+ """
+ col_expr = coercions.expect(roles.ExpressionElementRole, expr)
+ return col_expr.between(lower_bound, upper_bound, symmetric=symmetric)
+
+
+def outparam(
+ key: str, type_: Optional[TypeEngine[_T]] = None
+) -> BindParameter[_T]:
+ """Create an 'OUT' parameter for usage in functions (stored procedures),
+ for databases which support them.
+
+ The ``outparam`` can be used like a regular function parameter.
+ The "output" value will be available from the
+ :class:`~sqlalchemy.engine.CursorResult` object via its ``out_parameters``
+ attribute, which returns a dictionary containing the values.
+
+ """
+ return BindParameter(key, None, type_=type_, unique=False, isoutparam=True)
+
+
+@overload
+def not_(clause: BinaryExpression[_T]) -> BinaryExpression[_T]: ...
+
+
+@overload
+def not_(clause: _ColumnExpressionArgument[_T]) -> ColumnElement[_T]: ...
+
+
+def not_(clause: _ColumnExpressionArgument[_T]) -> ColumnElement[_T]:
+ """Return a negation of the given clause, i.e. ``NOT(clause)``.
+
+ The ``~`` operator is also overloaded on all
+ :class:`_expression.ColumnElement` subclasses to produce the
+ same result.
+
+ """
+
+ return coercions.expect(roles.ExpressionElementRole, clause).__invert__()
+
+
+def bindparam(
+ key: Optional[str],
+ value: Any = _NoArg.NO_ARG,
+ type_: Optional[_TypeEngineArgument[_T]] = None,
+ unique: bool = False,
+ required: Union[bool, Literal[_NoArg.NO_ARG]] = _NoArg.NO_ARG,
+ quote: Optional[bool] = None,
+ callable_: Optional[Callable[[], Any]] = None,
+ expanding: bool = False,
+ isoutparam: bool = False,
+ literal_execute: bool = False,
+) -> BindParameter[_T]:
+ r"""Produce a "bound expression".
+
+ The return value is an instance of :class:`.BindParameter`; this
+ is a :class:`_expression.ColumnElement`
+ subclass which represents a so-called
+ "placeholder" value in a SQL expression, the value of which is
+ supplied at the point at which the statement in executed against a
+ database connection.
+
+ In SQLAlchemy, the :func:`.bindparam` construct has
+ the ability to carry along the actual value that will be ultimately
+ used at expression time. In this way, it serves not just as
+ a "placeholder" for eventual population, but also as a means of
+ representing so-called "unsafe" values which should not be rendered
+ directly in a SQL statement, but rather should be passed along
+ to the :term:`DBAPI` as values which need to be correctly escaped
+ and potentially handled for type-safety.
+
+ When using :func:`.bindparam` explicitly, the use case is typically
+ one of traditional deferment of parameters; the :func:`.bindparam`
+ construct accepts a name which can then be referred to at execution
+ time::
+
+ from sqlalchemy import bindparam
+
+ stmt = select(users_table).where(
+ users_table.c.name == bindparam("username")
+ )
+
+ The above statement, when rendered, will produce SQL similar to::
+
+ SELECT id, name FROM user WHERE name = :username
+
+ In order to populate the value of ``:username`` above, the value
+ would typically be applied at execution time to a method
+ like :meth:`_engine.Connection.execute`::
+
+ result = connection.execute(stmt, {"username": "wendy"})
+
+ Explicit use of :func:`.bindparam` is also common when producing
+ UPDATE or DELETE statements that are to be invoked multiple times,
+ where the WHERE criterion of the statement is to change on each
+ invocation, such as::
+
+ stmt = (
+ users_table.update()
+ .where(user_table.c.name == bindparam("username"))
+ .values(fullname=bindparam("fullname"))
+ )
+
+ connection.execute(
+ stmt,
+ [
+ {"username": "wendy", "fullname": "Wendy Smith"},
+ {"username": "jack", "fullname": "Jack Jones"},
+ ],
+ )
+
+ SQLAlchemy's Core expression system makes wide use of
+ :func:`.bindparam` in an implicit sense. It is typical that Python
+ literal values passed to virtually all SQL expression functions are
+ coerced into fixed :func:`.bindparam` constructs. For example, given
+ a comparison operation such as::
+
+ expr = users_table.c.name == 'Wendy'
+
+ The above expression will produce a :class:`.BinaryExpression`
+ construct, where the left side is the :class:`_schema.Column` object
+ representing the ``name`` column, and the right side is a
+ :class:`.BindParameter` representing the literal value::
+
+ print(repr(expr.right))
+ BindParameter('%(4327771088 name)s', 'Wendy', type_=String())
+
+ The expression above will render SQL such as::
+
+ user.name = :name_1
+
+ Where the ``:name_1`` parameter name is an anonymous name. The
+ actual string ``Wendy`` is not in the rendered string, but is carried
+ along where it is later used within statement execution. If we
+ invoke a statement like the following::
+
+ stmt = select(users_table).where(users_table.c.name == 'Wendy')
+ result = connection.execute(stmt)
+
+ We would see SQL logging output as::
+
+ SELECT "user".id, "user".name
+ FROM "user"
+ WHERE "user".name = %(name_1)s
+ {'name_1': 'Wendy'}
+
+ Above, we see that ``Wendy`` is passed as a parameter to the database,
+ while the placeholder ``:name_1`` is rendered in the appropriate form
+ for the target database, in this case the PostgreSQL database.
+
+ Similarly, :func:`.bindparam` is invoked automatically when working
+ with :term:`CRUD` statements as far as the "VALUES" portion is
+ concerned. The :func:`_expression.insert` construct produces an
+ ``INSERT`` expression which will, at statement execution time, generate
+ bound placeholders based on the arguments passed, as in::
+
+ stmt = users_table.insert()
+ result = connection.execute(stmt, {"name": "Wendy"})
+
+ The above will produce SQL output as::
+
+ INSERT INTO "user" (name) VALUES (%(name)s)
+ {'name': 'Wendy'}
+
+ The :class:`_expression.Insert` construct, at
+ compilation/execution time, rendered a single :func:`.bindparam`
+ mirroring the column name ``name`` as a result of the single ``name``
+ parameter we passed to the :meth:`_engine.Connection.execute` method.
+
+ :param key:
+ the key (e.g. the name) for this bind param.
+ Will be used in the generated
+ SQL statement for dialects that use named parameters. This
+ value may be modified when part of a compilation operation,
+ if other :class:`BindParameter` objects exist with the same
+ key, or if its length is too long and truncation is
+ required.
+
+ If omitted, an "anonymous" name is generated for the bound parameter;
+ when given a value to bind, the end result is equivalent to calling upon
+ the :func:`.literal` function with a value to bind, particularly
+ if the :paramref:`.bindparam.unique` parameter is also provided.
+
+ :param value:
+ Initial value for this bind param. Will be used at statement
+ execution time as the value for this parameter passed to the
+ DBAPI, if no other value is indicated to the statement execution
+ method for this particular parameter name. Defaults to ``None``.
+
+ :param callable\_:
+ A callable function that takes the place of "value". The function
+ will be called at statement execution time to determine the
+ ultimate value. Used for scenarios where the actual bind
+ value cannot be determined at the point at which the clause
+ construct is created, but embedded bind values are still desirable.
+
+ :param type\_:
+ A :class:`.TypeEngine` class or instance representing an optional
+ datatype for this :func:`.bindparam`. If not passed, a type
+ may be determined automatically for the bind, based on the given
+ value; for example, trivial Python types such as ``str``,
+ ``int``, ``bool``
+ may result in the :class:`.String`, :class:`.Integer` or
+ :class:`.Boolean` types being automatically selected.
+
+ The type of a :func:`.bindparam` is significant especially in that
+ the type will apply pre-processing to the value before it is
+ passed to the database. For example, a :func:`.bindparam` which
+ refers to a datetime value, and is specified as holding the
+ :class:`.DateTime` type, may apply conversion needed to the
+ value (such as stringification on SQLite) before passing the value
+ to the database.
+
+ :param unique:
+ if True, the key name of this :class:`.BindParameter` will be
+ modified if another :class:`.BindParameter` of the same name
+ already has been located within the containing
+ expression. This flag is used generally by the internals
+ when producing so-called "anonymous" bound expressions, it
+ isn't generally applicable to explicitly-named :func:`.bindparam`
+ constructs.
+
+ :param required:
+ If ``True``, a value is required at execution time. If not passed,
+ it defaults to ``True`` if neither :paramref:`.bindparam.value`
+ or :paramref:`.bindparam.callable` were passed. If either of these
+ parameters are present, then :paramref:`.bindparam.required`
+ defaults to ``False``.
+
+ :param quote:
+ True if this parameter name requires quoting and is not
+ currently known as a SQLAlchemy reserved word; this currently
+ only applies to the Oracle backend, where bound names must
+ sometimes be quoted.
+
+ :param isoutparam:
+ if True, the parameter should be treated like a stored procedure
+ "OUT" parameter. This applies to backends such as Oracle which
+ support OUT parameters.
+
+ :param expanding:
+ if True, this parameter will be treated as an "expanding" parameter
+ at execution time; the parameter value is expected to be a sequence,
+ rather than a scalar value, and the string SQL statement will
+ be transformed on a per-execution basis to accommodate the sequence
+ with a variable number of parameter slots passed to the DBAPI.
+ This is to allow statement caching to be used in conjunction with
+ an IN clause.
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.in_`
+
+ :ref:`baked_in` - with baked queries
+
+ .. note:: The "expanding" feature does not support "executemany"-
+ style parameter sets.
+
+ .. versionadded:: 1.2
+
+ .. versionchanged:: 1.3 the "expanding" bound parameter feature now
+ supports empty lists.
+
+ :param literal_execute:
+ if True, the bound parameter will be rendered in the compile phase
+ with a special "POSTCOMPILE" token, and the SQLAlchemy compiler will
+ render the final value of the parameter into the SQL statement at
+ statement execution time, omitting the value from the parameter
+ dictionary / list passed to DBAPI ``cursor.execute()``. This
+ produces a similar effect as that of using the ``literal_binds``,
+ compilation flag, however takes place as the statement is sent to
+ the DBAPI ``cursor.execute()`` method, rather than when the statement
+ is compiled. The primary use of this
+ capability is for rendering LIMIT / OFFSET clauses for database
+ drivers that can't accommodate for bound parameters in these
+ contexts, while allowing SQL constructs to be cacheable at the
+ compilation level.
+
+ .. versionadded:: 1.4 Added "post compile" bound parameters
+
+ .. seealso::
+
+ :ref:`change_4808`.
+
+ .. seealso::
+
+ :ref:`tutorial_sending_parameters` - in the
+ :ref:`unified_tutorial`
+
+
+ """
+ return BindParameter(
+ key,
+ value,
+ type_,
+ unique,
+ required,
+ quote,
+ callable_,
+ expanding,
+ isoutparam,
+ literal_execute,
+ )
+
+
+def case(
+ *whens: Union[
+ typing_Tuple[_ColumnExpressionArgument[bool], Any], Mapping[Any, Any]
+ ],
+ value: Optional[Any] = None,
+ else_: Optional[Any] = None,
+) -> Case[Any]:
+ r"""Produce a ``CASE`` expression.
+
+ The ``CASE`` construct in SQL is a conditional object that
+ acts somewhat analogously to an "if/then" construct in other
+ languages. It returns an instance of :class:`.Case`.
+
+ :func:`.case` in its usual form is passed a series of "when"
+ constructs, that is, a list of conditions and results as tuples::
+
+ from sqlalchemy import case
+
+ stmt = select(users_table).\
+ where(
+ case(
+ (users_table.c.name == 'wendy', 'W'),
+ (users_table.c.name == 'jack', 'J'),
+ else_='E'
+ )
+ )
+
+ The above statement will produce SQL resembling::
+
+ SELECT id, name FROM user
+ WHERE CASE
+ WHEN (name = :name_1) THEN :param_1
+ WHEN (name = :name_2) THEN :param_2
+ ELSE :param_3
+ END
+
+ When simple equality expressions of several values against a single
+ parent column are needed, :func:`.case` also has a "shorthand" format
+ used via the
+ :paramref:`.case.value` parameter, which is passed a column
+ expression to be compared. In this form, the :paramref:`.case.whens`
+ parameter is passed as a dictionary containing expressions to be
+ compared against keyed to result expressions. The statement below is
+ equivalent to the preceding statement::
+
+ stmt = select(users_table).\
+ where(
+ case(
+ {"wendy": "W", "jack": "J"},
+ value=users_table.c.name,
+ else_='E'
+ )
+ )
+
+ The values which are accepted as result values in
+ :paramref:`.case.whens` as well as with :paramref:`.case.else_` are
+ coerced from Python literals into :func:`.bindparam` constructs.
+ SQL expressions, e.g. :class:`_expression.ColumnElement` constructs,
+ are accepted
+ as well. To coerce a literal string expression into a constant
+ expression rendered inline, use the :func:`_expression.literal_column`
+ construct,
+ as in::
+
+ from sqlalchemy import case, literal_column
+
+ case(
+ (
+ orderline.c.qty > 100,
+ literal_column("'greaterthan100'")
+ ),
+ (
+ orderline.c.qty > 10,
+ literal_column("'greaterthan10'")
+ ),
+ else_=literal_column("'lessthan10'")
+ )
+
+ The above will render the given constants without using bound
+ parameters for the result values (but still for the comparison
+ values), as in::
+
+ CASE
+ WHEN (orderline.qty > :qty_1) THEN 'greaterthan100'
+ WHEN (orderline.qty > :qty_2) THEN 'greaterthan10'
+ ELSE 'lessthan10'
+ END
+
+ :param \*whens: The criteria to be compared against,
+ :paramref:`.case.whens` accepts two different forms, based on
+ whether or not :paramref:`.case.value` is used.
+
+ .. versionchanged:: 1.4 the :func:`_sql.case`
+ function now accepts the series of WHEN conditions positionally
+
+ In the first form, it accepts multiple 2-tuples passed as positional
+ arguments; each 2-tuple consists of ``(<sql expression>, <value>)``,
+ where the SQL expression is a boolean expression and "value" is a
+ resulting value, e.g.::
+
+ case(
+ (users_table.c.name == 'wendy', 'W'),
+ (users_table.c.name == 'jack', 'J')
+ )
+
+ In the second form, it accepts a Python dictionary of comparison
+ values mapped to a resulting value; this form requires
+ :paramref:`.case.value` to be present, and values will be compared
+ using the ``==`` operator, e.g.::
+
+ case(
+ {"wendy": "W", "jack": "J"},
+ value=users_table.c.name
+ )
+
+ :param value: An optional SQL expression which will be used as a
+ fixed "comparison point" for candidate values within a dictionary
+ passed to :paramref:`.case.whens`.
+
+ :param else\_: An optional SQL expression which will be the evaluated
+ result of the ``CASE`` construct if all expressions within
+ :paramref:`.case.whens` evaluate to false. When omitted, most
+ databases will produce a result of NULL if none of the "when"
+ expressions evaluate to true.
+
+
+ """
+ return Case(*whens, value=value, else_=else_)
+
+
+def cast(
+ expression: _ColumnExpressionOrLiteralArgument[Any],
+ type_: _TypeEngineArgument[_T],
+) -> Cast[_T]:
+ r"""Produce a ``CAST`` expression.
+
+ :func:`.cast` returns an instance of :class:`.Cast`.
+
+ E.g.::
+
+ from sqlalchemy import cast, Numeric
+
+ stmt = select(cast(product_table.c.unit_price, Numeric(10, 4)))
+
+ The above statement will produce SQL resembling::
+
+ SELECT CAST(unit_price AS NUMERIC(10, 4)) FROM product
+
+ The :func:`.cast` function performs two distinct functions when
+ used. The first is that it renders the ``CAST`` expression within
+ the resulting SQL string. The second is that it associates the given
+ type (e.g. :class:`.TypeEngine` class or instance) with the column
+ expression on the Python side, which means the expression will take
+ on the expression operator behavior associated with that type,
+ as well as the bound-value handling and result-row-handling behavior
+ of the type.
+
+ An alternative to :func:`.cast` is the :func:`.type_coerce` function.
+ This function performs the second task of associating an expression
+ with a specific type, but does not render the ``CAST`` expression
+ in SQL.
+
+ :param expression: A SQL expression, such as a
+ :class:`_expression.ColumnElement`
+ expression or a Python string which will be coerced into a bound
+ literal value.
+
+ :param type\_: A :class:`.TypeEngine` class or instance indicating
+ the type to which the ``CAST`` should apply.
+
+ .. seealso::
+
+ :ref:`tutorial_casts`
+
+ :func:`.try_cast` - an alternative to CAST that results in
+ NULLs when the cast fails, instead of raising an error.
+ Only supported by some dialects.
+
+ :func:`.type_coerce` - an alternative to CAST that coerces the type
+ on the Python side only, which is often sufficient to generate the
+ correct SQL and data coercion.
+
+
+ """
+ return Cast(expression, type_)
+
+
+def try_cast(
+ expression: _ColumnExpressionOrLiteralArgument[Any],
+ type_: _TypeEngineArgument[_T],
+) -> TryCast[_T]:
+ """Produce a ``TRY_CAST`` expression for backends which support it;
+ this is a ``CAST`` which returns NULL for un-castable conversions.
+
+ In SQLAlchemy, this construct is supported **only** by the SQL Server
+ dialect, and will raise a :class:`.CompileError` if used on other
+ included backends. However, third party backends may also support
+ this construct.
+
+ .. tip:: As :func:`_sql.try_cast` originates from the SQL Server dialect,
+ it's importable both from ``sqlalchemy.`` as well as from
+ ``sqlalchemy.dialects.mssql``.
+
+ :func:`_sql.try_cast` returns an instance of :class:`.TryCast` and
+ generally behaves similarly to the :class:`.Cast` construct;
+ at the SQL level, the difference between ``CAST`` and ``TRY_CAST``
+ is that ``TRY_CAST`` returns NULL for an un-castable expression,
+ such as attempting to cast a string ``"hi"`` to an integer value.
+
+ E.g.::
+
+ from sqlalchemy import select, try_cast, Numeric
+
+ stmt = select(
+ try_cast(product_table.c.unit_price, Numeric(10, 4))
+ )
+
+ The above would render on Microsoft SQL Server as::
+
+ SELECT TRY_CAST (product_table.unit_price AS NUMERIC(10, 4))
+ FROM product_table
+
+ .. versionadded:: 2.0.14 :func:`.try_cast` has been
+ generalized from the SQL Server dialect into a general use
+ construct that may be supported by additional dialects.
+
+ """
+ return TryCast(expression, type_)
+
+
+def column(
+ text: str,
+ type_: Optional[_TypeEngineArgument[_T]] = None,
+ is_literal: bool = False,
+ _selectable: Optional[FromClause] = None,
+) -> ColumnClause[_T]:
+ """Produce a :class:`.ColumnClause` object.
+
+ The :class:`.ColumnClause` is a lightweight analogue to the
+ :class:`_schema.Column` class. The :func:`_expression.column`
+ function can
+ be invoked with just a name alone, as in::
+
+ from sqlalchemy import column
+
+ id, name = column("id"), column("name")
+ stmt = select(id, name).select_from("user")
+
+ The above statement would produce SQL like::
+
+ SELECT id, name FROM user
+
+ Once constructed, :func:`_expression.column`
+ may be used like any other SQL
+ expression element such as within :func:`_expression.select`
+ constructs::
+
+ from sqlalchemy.sql import column
+
+ id, name = column("id"), column("name")
+ stmt = select(id, name).select_from("user")
+
+ The text handled by :func:`_expression.column`
+ is assumed to be handled
+ like the name of a database column; if the string contains mixed case,
+ special characters, or matches a known reserved word on the target
+ backend, the column expression will render using the quoting
+ behavior determined by the backend. To produce a textual SQL
+ expression that is rendered exactly without any quoting,
+ use :func:`_expression.literal_column` instead,
+ or pass ``True`` as the
+ value of :paramref:`_expression.column.is_literal`. Additionally,
+ full SQL
+ statements are best handled using the :func:`_expression.text`
+ construct.
+
+ :func:`_expression.column` can be used in a table-like
+ fashion by combining it with the :func:`.table` function
+ (which is the lightweight analogue to :class:`_schema.Table`
+ ) to produce
+ a working table construct with minimal boilerplate::
+
+ from sqlalchemy import table, column, select
+
+ user = table("user",
+ column("id"),
+ column("name"),
+ column("description"),
+ )
+
+ stmt = select(user.c.description).where(user.c.name == 'wendy')
+
+ A :func:`_expression.column` / :func:`.table`
+ construct like that illustrated
+ above can be created in an
+ ad-hoc fashion and is not associated with any
+ :class:`_schema.MetaData`, DDL, or events, unlike its
+ :class:`_schema.Table` counterpart.
+
+ :param text: the text of the element.
+
+ :param type: :class:`_types.TypeEngine` object which can associate
+ this :class:`.ColumnClause` with a type.
+
+ :param is_literal: if True, the :class:`.ColumnClause` is assumed to
+ be an exact expression that will be delivered to the output with no
+ quoting rules applied regardless of case sensitive settings. the
+ :func:`_expression.literal_column()` function essentially invokes
+ :func:`_expression.column` while passing ``is_literal=True``.
+
+ .. seealso::
+
+ :class:`_schema.Column`
+
+ :func:`_expression.literal_column`
+
+ :func:`.table`
+
+ :func:`_expression.text`
+
+ :ref:`tutorial_select_arbitrary_text`
+
+ """
+ return ColumnClause(text, type_, is_literal, _selectable)
+
+
+def desc(
+ column: _ColumnExpressionOrStrLabelArgument[_T],
+) -> UnaryExpression[_T]:
+ """Produce a descending ``ORDER BY`` clause element.
+
+ e.g.::
+
+ from sqlalchemy import desc
+
+ stmt = select(users_table).order_by(desc(users_table.c.name))
+
+ will produce SQL as::
+
+ SELECT id, name FROM user ORDER BY name DESC
+
+ The :func:`.desc` function is a standalone version of the
+ :meth:`_expression.ColumnElement.desc`
+ method available on all SQL expressions,
+ e.g.::
+
+
+ stmt = select(users_table).order_by(users_table.c.name.desc())
+
+ :param column: A :class:`_expression.ColumnElement` (e.g.
+ scalar SQL expression)
+ with which to apply the :func:`.desc` operation.
+
+ .. seealso::
+
+ :func:`.asc`
+
+ :func:`.nulls_first`
+
+ :func:`.nulls_last`
+
+ :meth:`_expression.Select.order_by`
+
+ """
+ return UnaryExpression._create_desc(column)
+
+
+def distinct(expr: _ColumnExpressionArgument[_T]) -> UnaryExpression[_T]:
+ """Produce an column-expression-level unary ``DISTINCT`` clause.
+
+ This applies the ``DISTINCT`` keyword to an individual column
+ expression, and is typically contained within an aggregate function,
+ as in::
+
+ from sqlalchemy import distinct, func
+ stmt = select(func.count(distinct(users_table.c.name)))
+
+ The above would produce an expression resembling::
+
+ SELECT COUNT(DISTINCT name) FROM user
+
+ The :func:`.distinct` function is also available as a column-level
+ method, e.g. :meth:`_expression.ColumnElement.distinct`, as in::
+
+ stmt = select(func.count(users_table.c.name.distinct()))
+
+ The :func:`.distinct` operator is different from the
+ :meth:`_expression.Select.distinct` method of
+ :class:`_expression.Select`,
+ which produces a ``SELECT`` statement
+ with ``DISTINCT`` applied to the result set as a whole,
+ e.g. a ``SELECT DISTINCT`` expression. See that method for further
+ information.
+
+ .. seealso::
+
+ :meth:`_expression.ColumnElement.distinct`
+
+ :meth:`_expression.Select.distinct`
+
+ :data:`.func`
+
+ """
+ return UnaryExpression._create_distinct(expr)
+
+
+def bitwise_not(expr: _ColumnExpressionArgument[_T]) -> UnaryExpression[_T]:
+ """Produce a unary bitwise NOT clause, typically via the ``~`` operator.
+
+ Not to be confused with boolean negation :func:`_sql.not_`.
+
+ .. versionadded:: 2.0.2
+
+ .. seealso::
+
+ :ref:`operators_bitwise`
+
+
+ """
+
+ return UnaryExpression._create_bitwise_not(expr)
+
+
+def extract(field: str, expr: _ColumnExpressionArgument[Any]) -> Extract:
+ """Return a :class:`.Extract` construct.
+
+ This is typically available as :func:`.extract`
+ as well as ``func.extract`` from the
+ :data:`.func` namespace.
+
+ :param field: The field to extract.
+
+ :param expr: A column or Python scalar expression serving as the
+ right side of the ``EXTRACT`` expression.
+
+ E.g.::
+
+ from sqlalchemy import extract
+ from sqlalchemy import table, column
+
+ logged_table = table("user",
+ column("id"),
+ column("date_created"),
+ )
+
+ stmt = select(logged_table.c.id).where(
+ extract("YEAR", logged_table.c.date_created) == 2021
+ )
+
+ In the above example, the statement is used to select ids from the
+ database where the ``YEAR`` component matches a specific value.
+
+ Similarly, one can also select an extracted component::
+
+ stmt = select(
+ extract("YEAR", logged_table.c.date_created)
+ ).where(logged_table.c.id == 1)
+
+ The implementation of ``EXTRACT`` may vary across database backends.
+ Users are reminded to consult their database documentation.
+ """
+ return Extract(field, expr)
+
+
+def false() -> False_:
+ """Return a :class:`.False_` construct.
+
+ E.g.:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import false
+ >>> print(select(t.c.x).where(false()))
+ {printsql}SELECT x FROM t WHERE false
+
+ A backend which does not support true/false constants will render as
+ an expression against 1 or 0:
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(select(t.c.x).where(false()))
+ {printsql}SELECT x FROM t WHERE 0 = 1
+
+ The :func:`.true` and :func:`.false` constants also feature
+ "short circuit" operation within an :func:`.and_` or :func:`.or_`
+ conjunction:
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(select(t.c.x).where(or_(t.c.x > 5, true())))
+ {printsql}SELECT x FROM t WHERE true{stop}
+
+ >>> print(select(t.c.x).where(and_(t.c.x > 5, false())))
+ {printsql}SELECT x FROM t WHERE false{stop}
+
+ .. seealso::
+
+ :func:`.true`
+
+ """
+
+ return False_._instance()
+
+
+def funcfilter(
+ func: FunctionElement[_T], *criterion: _ColumnExpressionArgument[bool]
+) -> FunctionFilter[_T]:
+ """Produce a :class:`.FunctionFilter` object against a function.
+
+ Used against aggregate and window functions,
+ for database backends that support the "FILTER" clause.
+
+ E.g.::
+
+ from sqlalchemy import funcfilter
+ funcfilter(func.count(1), MyClass.name == 'some name')
+
+ Would produce "COUNT(1) FILTER (WHERE myclass.name = 'some name')".
+
+ This function is also available from the :data:`~.expression.func`
+ construct itself via the :meth:`.FunctionElement.filter` method.
+
+ .. seealso::
+
+ :ref:`tutorial_functions_within_group` - in the
+ :ref:`unified_tutorial`
+
+ :meth:`.FunctionElement.filter`
+
+ """
+ return FunctionFilter(func, *criterion)
+
+
+def label(
+ name: str,
+ element: _ColumnExpressionArgument[_T],
+ type_: Optional[_TypeEngineArgument[_T]] = None,
+) -> Label[_T]:
+ """Return a :class:`Label` object for the
+ given :class:`_expression.ColumnElement`.
+
+ A label changes the name of an element in the columns clause of a
+ ``SELECT`` statement, typically via the ``AS`` SQL keyword.
+
+ This functionality is more conveniently available via the
+ :meth:`_expression.ColumnElement.label` method on
+ :class:`_expression.ColumnElement`.
+
+ :param name: label name
+
+ :param obj: a :class:`_expression.ColumnElement`.
+
+ """
+ return Label(name, element, type_)
+
+
+def null() -> Null:
+ """Return a constant :class:`.Null` construct."""
+
+ return Null._instance()
+
+
+def nulls_first(column: _ColumnExpressionArgument[_T]) -> UnaryExpression[_T]:
+ """Produce the ``NULLS FIRST`` modifier for an ``ORDER BY`` expression.
+
+ :func:`.nulls_first` is intended to modify the expression produced
+ by :func:`.asc` or :func:`.desc`, and indicates how NULL values
+ should be handled when they are encountered during ordering::
+
+
+ from sqlalchemy import desc, nulls_first
+
+ stmt = select(users_table).order_by(
+ nulls_first(desc(users_table.c.name)))
+
+ The SQL expression from the above would resemble::
+
+ SELECT id, name FROM user ORDER BY name DESC NULLS FIRST
+
+ Like :func:`.asc` and :func:`.desc`, :func:`.nulls_first` is typically
+ invoked from the column expression itself using
+ :meth:`_expression.ColumnElement.nulls_first`,
+ rather than as its standalone
+ function version, as in::
+
+ stmt = select(users_table).order_by(
+ users_table.c.name.desc().nulls_first())
+
+ .. versionchanged:: 1.4 :func:`.nulls_first` is renamed from
+ :func:`.nullsfirst` in previous releases.
+ The previous name remains available for backwards compatibility.
+
+ .. seealso::
+
+ :func:`.asc`
+
+ :func:`.desc`
+
+ :func:`.nulls_last`
+
+ :meth:`_expression.Select.order_by`
+
+ """
+ return UnaryExpression._create_nulls_first(column)
+
+
+def nulls_last(column: _ColumnExpressionArgument[_T]) -> UnaryExpression[_T]:
+ """Produce the ``NULLS LAST`` modifier for an ``ORDER BY`` expression.
+
+ :func:`.nulls_last` is intended to modify the expression produced
+ by :func:`.asc` or :func:`.desc`, and indicates how NULL values
+ should be handled when they are encountered during ordering::
+
+
+ from sqlalchemy import desc, nulls_last
+
+ stmt = select(users_table).order_by(
+ nulls_last(desc(users_table.c.name)))
+
+ The SQL expression from the above would resemble::
+
+ SELECT id, name FROM user ORDER BY name DESC NULLS LAST
+
+ Like :func:`.asc` and :func:`.desc`, :func:`.nulls_last` is typically
+ invoked from the column expression itself using
+ :meth:`_expression.ColumnElement.nulls_last`,
+ rather than as its standalone
+ function version, as in::
+
+ stmt = select(users_table).order_by(
+ users_table.c.name.desc().nulls_last())
+
+ .. versionchanged:: 1.4 :func:`.nulls_last` is renamed from
+ :func:`.nullslast` in previous releases.
+ The previous name remains available for backwards compatibility.
+
+ .. seealso::
+
+ :func:`.asc`
+
+ :func:`.desc`
+
+ :func:`.nulls_first`
+
+ :meth:`_expression.Select.order_by`
+
+ """
+ return UnaryExpression._create_nulls_last(column)
+
+
+def or_( # type: ignore[empty-body]
+ initial_clause: Union[Literal[False], _ColumnExpressionArgument[bool]],
+ *clauses: _ColumnExpressionArgument[bool],
+) -> ColumnElement[bool]:
+ """Produce a conjunction of expressions joined by ``OR``.
+
+ E.g.::
+
+ from sqlalchemy import or_
+
+ stmt = select(users_table).where(
+ or_(
+ users_table.c.name == 'wendy',
+ users_table.c.name == 'jack'
+ )
+ )
+
+ The :func:`.or_` conjunction is also available using the
+ Python ``|`` operator (though note that compound expressions
+ need to be parenthesized in order to function with Python
+ operator precedence behavior)::
+
+ stmt = select(users_table).where(
+ (users_table.c.name == 'wendy') |
+ (users_table.c.name == 'jack')
+ )
+
+ The :func:`.or_` construct must be given at least one positional
+ argument in order to be valid; a :func:`.or_` construct with no
+ arguments is ambiguous. To produce an "empty" or dynamically
+ generated :func:`.or_` expression, from a given list of expressions,
+ a "default" element of :func:`_sql.false` (or just ``False``) should be
+ specified::
+
+ from sqlalchemy import false
+ or_criteria = or_(false(), *expressions)
+
+ The above expression will compile to SQL as the expression ``false``
+ or ``0 = 1``, depending on backend, if no other expressions are
+ present. If expressions are present, then the :func:`_sql.false` value is
+ ignored as it does not affect the outcome of an OR expression which
+ has other elements.
+
+ .. deprecated:: 1.4 The :func:`.or_` element now requires that at
+ least one argument is passed; creating the :func:`.or_` construct
+ with no arguments is deprecated, and will emit a deprecation warning
+ while continuing to produce a blank SQL string.
+
+ .. seealso::
+
+ :func:`.and_`
+
+ """
+ ...
+
+
+if not TYPE_CHECKING:
+ # handle deprecated case which allows zero-arguments
+ def or_(*clauses): # noqa: F811
+ """Produce a conjunction of expressions joined by ``OR``.
+
+ E.g.::
+
+ from sqlalchemy import or_
+
+ stmt = select(users_table).where(
+ or_(
+ users_table.c.name == 'wendy',
+ users_table.c.name == 'jack'
+ )
+ )
+
+ The :func:`.or_` conjunction is also available using the
+ Python ``|`` operator (though note that compound expressions
+ need to be parenthesized in order to function with Python
+ operator precedence behavior)::
+
+ stmt = select(users_table).where(
+ (users_table.c.name == 'wendy') |
+ (users_table.c.name == 'jack')
+ )
+
+ The :func:`.or_` construct must be given at least one positional
+ argument in order to be valid; a :func:`.or_` construct with no
+ arguments is ambiguous. To produce an "empty" or dynamically
+ generated :func:`.or_` expression, from a given list of expressions,
+ a "default" element of :func:`_sql.false` (or just ``False``) should be
+ specified::
+
+ from sqlalchemy import false
+ or_criteria = or_(false(), *expressions)
+
+ The above expression will compile to SQL as the expression ``false``
+ or ``0 = 1``, depending on backend, if no other expressions are
+ present. If expressions are present, then the :func:`_sql.false` value
+ is ignored as it does not affect the outcome of an OR expression which
+ has other elements.
+
+ .. deprecated:: 1.4 The :func:`.or_` element now requires that at
+ least one argument is passed; creating the :func:`.or_` construct
+ with no arguments is deprecated, and will emit a deprecation warning
+ while continuing to produce a blank SQL string.
+
+ .. seealso::
+
+ :func:`.and_`
+
+ """
+ return BooleanClauseList.or_(*clauses)
+
+
+def over(
+ element: FunctionElement[_T],
+ partition_by: Optional[_ByArgument] = None,
+ order_by: Optional[_ByArgument] = None,
+ range_: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
+ rows: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
+) -> Over[_T]:
+ r"""Produce an :class:`.Over` object against a function.
+
+ Used against aggregate or so-called "window" functions,
+ for database backends that support window functions.
+
+ :func:`_expression.over` is usually called using
+ the :meth:`.FunctionElement.over` method, e.g.::
+
+ func.row_number().over(order_by=mytable.c.some_column)
+
+ Would produce::
+
+ ROW_NUMBER() OVER(ORDER BY some_column)
+
+ Ranges are also possible using the :paramref:`.expression.over.range_`
+ and :paramref:`.expression.over.rows` parameters. These
+ mutually-exclusive parameters each accept a 2-tuple, which contains
+ a combination of integers and None::
+
+ func.row_number().over(
+ order_by=my_table.c.some_column, range_=(None, 0))
+
+ The above would produce::
+
+ ROW_NUMBER() OVER(ORDER BY some_column
+ RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
+
+ A value of ``None`` indicates "unbounded", a
+ value of zero indicates "current row", and negative / positive
+ integers indicate "preceding" and "following":
+
+ * RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING::
+
+ func.row_number().over(order_by='x', range_=(-5, 10))
+
+ * ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW::
+
+ func.row_number().over(order_by='x', rows=(None, 0))
+
+ * RANGE BETWEEN 2 PRECEDING AND UNBOUNDED FOLLOWING::
+
+ func.row_number().over(order_by='x', range_=(-2, None))
+
+ * RANGE BETWEEN 1 FOLLOWING AND 3 FOLLOWING::
+
+ func.row_number().over(order_by='x', range_=(1, 3))
+
+ :param element: a :class:`.FunctionElement`, :class:`.WithinGroup`,
+ or other compatible construct.
+ :param partition_by: a column element or string, or a list
+ of such, that will be used as the PARTITION BY clause
+ of the OVER construct.
+ :param order_by: a column element or string, or a list
+ of such, that will be used as the ORDER BY clause
+ of the OVER construct.
+ :param range\_: optional range clause for the window. This is a
+ tuple value which can contain integer values or ``None``,
+ and will render a RANGE BETWEEN PRECEDING / FOLLOWING clause.
+
+ :param rows: optional rows clause for the window. This is a tuple
+ value which can contain integer values or None, and will render
+ a ROWS BETWEEN PRECEDING / FOLLOWING clause.
+
+ This function is also available from the :data:`~.expression.func`
+ construct itself via the :meth:`.FunctionElement.over` method.
+
+ .. seealso::
+
+ :ref:`tutorial_window_functions` - in the :ref:`unified_tutorial`
+
+ :data:`.expression.func`
+
+ :func:`_expression.within_group`
+
+ """
+ return Over(element, partition_by, order_by, range_, rows)
+
+
+@_document_text_coercion("text", ":func:`.text`", ":paramref:`.text.text`")
+def text(text: str) -> TextClause:
+ r"""Construct a new :class:`_expression.TextClause` clause,
+ representing
+ a textual SQL string directly.
+
+ E.g.::
+
+ from sqlalchemy import text
+
+ t = text("SELECT * FROM users")
+ result = connection.execute(t)
+
+ The advantages :func:`_expression.text`
+ provides over a plain string are
+ backend-neutral support for bind parameters, per-statement
+ execution options, as well as
+ bind parameter and result-column typing behavior, allowing
+ SQLAlchemy type constructs to play a role when executing
+ a statement that is specified literally. The construct can also
+ be provided with a ``.c`` collection of column elements, allowing
+ it to be embedded in other SQL expression constructs as a subquery.
+
+ Bind parameters are specified by name, using the format ``:name``.
+ E.g.::
+
+ t = text("SELECT * FROM users WHERE id=:user_id")
+ result = connection.execute(t, {"user_id": 12})
+
+ For SQL statements where a colon is required verbatim, as within
+ an inline string, use a backslash to escape::
+
+ t = text(r"SELECT * FROM users WHERE name='\:username'")
+
+ The :class:`_expression.TextClause`
+ construct includes methods which can
+ provide information about the bound parameters as well as the column
+ values which would be returned from the textual statement, assuming
+ it's an executable SELECT type of statement. The
+ :meth:`_expression.TextClause.bindparams`
+ method is used to provide bound
+ parameter detail, and :meth:`_expression.TextClause.columns`
+ method allows
+ specification of return columns including names and types::
+
+ t = text("SELECT * FROM users WHERE id=:user_id").\
+ bindparams(user_id=7).\
+ columns(id=Integer, name=String)
+
+ for id, name in connection.execute(t):
+ print(id, name)
+
+ The :func:`_expression.text` construct is used in cases when
+ a literal string SQL fragment is specified as part of a larger query,
+ such as for the WHERE clause of a SELECT statement::
+
+ s = select(users.c.id, users.c.name).where(text("id=:user_id"))
+ result = connection.execute(s, {"user_id": 12})
+
+ :func:`_expression.text` is also used for the construction
+ of a full, standalone statement using plain text.
+ As such, SQLAlchemy refers
+ to it as an :class:`.Executable` object and may be used
+ like any other statement passed to an ``.execute()`` method.
+
+ :param text:
+ the text of the SQL statement to be created. Use ``:<param>``
+ to specify bind parameters; they will be compiled to their
+ engine-specific format.
+
+ .. seealso::
+
+ :ref:`tutorial_select_arbitrary_text`
+
+ """
+ return TextClause(text)
+
+
+def true() -> True_:
+ """Return a constant :class:`.True_` construct.
+
+ E.g.:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import true
+ >>> print(select(t.c.x).where(true()))
+ {printsql}SELECT x FROM t WHERE true
+
+ A backend which does not support true/false constants will render as
+ an expression against 1 or 0:
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(select(t.c.x).where(true()))
+ {printsql}SELECT x FROM t WHERE 1 = 1
+
+ The :func:`.true` and :func:`.false` constants also feature
+ "short circuit" operation within an :func:`.and_` or :func:`.or_`
+ conjunction:
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(select(t.c.x).where(or_(t.c.x > 5, true())))
+ {printsql}SELECT x FROM t WHERE true{stop}
+
+ >>> print(select(t.c.x).where(and_(t.c.x > 5, false())))
+ {printsql}SELECT x FROM t WHERE false{stop}
+
+ .. seealso::
+
+ :func:`.false`
+
+ """
+
+ return True_._instance()
+
+
+def tuple_(
+ *clauses: _ColumnExpressionArgument[Any],
+ types: Optional[Sequence[_TypeEngineArgument[Any]]] = None,
+) -> Tuple:
+ """Return a :class:`.Tuple`.
+
+ Main usage is to produce a composite IN construct using
+ :meth:`.ColumnOperators.in_` ::
+
+ from sqlalchemy import tuple_
+
+ tuple_(table.c.col1, table.c.col2).in_(
+ [(1, 2), (5, 12), (10, 19)]
+ )
+
+ .. versionchanged:: 1.3.6 Added support for SQLite IN tuples.
+
+ .. warning::
+
+ The composite IN construct is not supported by all backends, and is
+ currently known to work on PostgreSQL, MySQL, and SQLite.
+ Unsupported backends will raise a subclass of
+ :class:`~sqlalchemy.exc.DBAPIError` when such an expression is
+ invoked.
+
+ """
+ return Tuple(*clauses, types=types)
+
+
+def type_coerce(
+ expression: _ColumnExpressionOrLiteralArgument[Any],
+ type_: _TypeEngineArgument[_T],
+) -> TypeCoerce[_T]:
+ r"""Associate a SQL expression with a particular type, without rendering
+ ``CAST``.
+
+ E.g.::
+
+ from sqlalchemy import type_coerce
+
+ stmt = select(type_coerce(log_table.date_string, StringDateTime()))
+
+ The above construct will produce a :class:`.TypeCoerce` object, which
+ does not modify the rendering in any way on the SQL side, with the
+ possible exception of a generated label if used in a columns clause
+ context:
+
+ .. sourcecode:: sql
+
+ SELECT date_string AS date_string FROM log
+
+ When result rows are fetched, the ``StringDateTime`` type processor
+ will be applied to result rows on behalf of the ``date_string`` column.
+
+ .. note:: the :func:`.type_coerce` construct does not render any
+ SQL syntax of its own, including that it does not imply
+ parenthesization. Please use :meth:`.TypeCoerce.self_group`
+ if explicit parenthesization is required.
+
+ In order to provide a named label for the expression, use
+ :meth:`_expression.ColumnElement.label`::
+
+ stmt = select(
+ type_coerce(log_table.date_string, StringDateTime()).label('date')
+ )
+
+
+ A type that features bound-value handling will also have that behavior
+ take effect when literal values or :func:`.bindparam` constructs are
+ passed to :func:`.type_coerce` as targets.
+ For example, if a type implements the
+ :meth:`.TypeEngine.bind_expression`
+ method or :meth:`.TypeEngine.bind_processor` method or equivalent,
+ these functions will take effect at statement compilation/execution
+ time when a literal value is passed, as in::
+
+ # bound-value handling of MyStringType will be applied to the
+ # literal value "some string"
+ stmt = select(type_coerce("some string", MyStringType))
+
+ When using :func:`.type_coerce` with composed expressions, note that
+ **parenthesis are not applied**. If :func:`.type_coerce` is being
+ used in an operator context where the parenthesis normally present from
+ CAST are necessary, use the :meth:`.TypeCoerce.self_group` method:
+
+ .. sourcecode:: pycon+sql
+
+ >>> some_integer = column("someint", Integer)
+ >>> some_string = column("somestr", String)
+ >>> expr = type_coerce(some_integer + 5, String) + some_string
+ >>> print(expr)
+ {printsql}someint + :someint_1 || somestr{stop}
+ >>> expr = type_coerce(some_integer + 5, String).self_group() + some_string
+ >>> print(expr)
+ {printsql}(someint + :someint_1) || somestr{stop}
+
+ :param expression: A SQL expression, such as a
+ :class:`_expression.ColumnElement`
+ expression or a Python string which will be coerced into a bound
+ literal value.
+
+ :param type\_: A :class:`.TypeEngine` class or instance indicating
+ the type to which the expression is coerced.
+
+ .. seealso::
+
+ :ref:`tutorial_casts`
+
+ :func:`.cast`
+
+ """ # noqa
+ return TypeCoerce(expression, type_)
+
+
+def within_group(
+ element: FunctionElement[_T], *order_by: _ColumnExpressionArgument[Any]
+) -> WithinGroup[_T]:
+ r"""Produce a :class:`.WithinGroup` object against a function.
+
+ Used against so-called "ordered set aggregate" and "hypothetical
+ set aggregate" functions, including :class:`.percentile_cont`,
+ :class:`.rank`, :class:`.dense_rank`, etc.
+
+ :func:`_expression.within_group` is usually called using
+ the :meth:`.FunctionElement.within_group` method, e.g.::
+
+ from sqlalchemy import within_group
+ stmt = select(
+ department.c.id,
+ func.percentile_cont(0.5).within_group(
+ department.c.salary.desc()
+ )
+ )
+
+ The above statement would produce SQL similar to
+ ``SELECT department.id, percentile_cont(0.5)
+ WITHIN GROUP (ORDER BY department.salary DESC)``.
+
+ :param element: a :class:`.FunctionElement` construct, typically
+ generated by :data:`~.expression.func`.
+ :param \*order_by: one or more column elements that will be used
+ as the ORDER BY clause of the WITHIN GROUP construct.
+
+ .. seealso::
+
+ :ref:`tutorial_functions_within_group` - in the
+ :ref:`unified_tutorial`
+
+ :data:`.expression.func`
+
+ :func:`_expression.over`
+
+ """
+ return WithinGroup(element, *order_by)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/_orm_types.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/_orm_types.py
new file mode 100644
index 0000000..bccb533
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/_orm_types.py
@@ -0,0 +1,20 @@
+# sql/_orm_types.py
+# Copyright (C) 2022-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+"""ORM types that need to present specifically for **documentation only** of
+the Executable.execution_options() method, which includes options that
+are meaningful to the ORM.
+
+"""
+
+
+from __future__ import annotations
+
+from ..util.typing import Literal
+
+SynchronizeSessionArgument = Literal[False, "auto", "evaluate", "fetch"]
+DMLStrategyArgument = Literal["bulk", "raw", "orm", "auto"]
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/_py_util.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/_py_util.py
new file mode 100644
index 0000000..df372bf
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/_py_util.py
@@ -0,0 +1,75 @@
+# sql/_py_util.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+from __future__ import annotations
+
+import typing
+from typing import Any
+from typing import Dict
+from typing import Tuple
+from typing import Union
+
+from ..util.typing import Literal
+
+if typing.TYPE_CHECKING:
+ from .cache_key import CacheConst
+
+
+class prefix_anon_map(Dict[str, str]):
+ """A map that creates new keys for missing key access.
+
+ Considers keys of the form "<ident> <name>" to produce
+ new symbols "<name>_<index>", where "index" is an incrementing integer
+ corresponding to <name>.
+
+ Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which
+ is otherwise usually used for this type of operation.
+
+ """
+
+ def __missing__(self, key: str) -> str:
+ (ident, derived) = key.split(" ", 1)
+ anonymous_counter = self.get(derived, 1)
+ self[derived] = anonymous_counter + 1 # type: ignore
+ value = f"{derived}_{anonymous_counter}"
+ self[key] = value
+ return value
+
+
+class cache_anon_map(
+ Dict[Union[int, "Literal[CacheConst.NO_CACHE]"], Union[Literal[True], str]]
+):
+ """A map that creates new keys for missing key access.
+
+ Produces an incrementing sequence given a series of unique keys.
+
+ This is similar to the compiler prefix_anon_map class although simpler.
+
+ Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which
+ is otherwise usually used for this type of operation.
+
+ """
+
+ _index = 0
+
+ def get_anon(self, object_: Any) -> Tuple[str, bool]:
+ idself = id(object_)
+ if idself in self:
+ s_val = self[idself]
+ assert s_val is not True
+ return s_val, True
+ else:
+ # inline of __missing__
+ self[idself] = id_ = str(self._index)
+ self._index += 1
+
+ return id_, False
+
+ def __missing__(self, key: int) -> str:
+ self[key] = val = str(self._index)
+ self._index += 1
+ return val
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/_selectable_constructors.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/_selectable_constructors.py
new file mode 100644
index 0000000..c2b5008
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/_selectable_constructors.py
@@ -0,0 +1,635 @@
+# sql/_selectable_constructors.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+from __future__ import annotations
+
+from typing import Any
+from typing import Optional
+from typing import overload
+from typing import Tuple
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from . import coercions
+from . import roles
+from ._typing import _ColumnsClauseArgument
+from ._typing import _no_kw
+from .elements import ColumnClause
+from .selectable import Alias
+from .selectable import CompoundSelect
+from .selectable import Exists
+from .selectable import FromClause
+from .selectable import Join
+from .selectable import Lateral
+from .selectable import LateralFromClause
+from .selectable import NamedFromClause
+from .selectable import Select
+from .selectable import TableClause
+from .selectable import TableSample
+from .selectable import Values
+
+if TYPE_CHECKING:
+ from ._typing import _FromClauseArgument
+ from ._typing import _OnClauseArgument
+ from ._typing import _SelectStatementForCompoundArgument
+ from ._typing import _T0
+ from ._typing import _T1
+ from ._typing import _T2
+ from ._typing import _T3
+ from ._typing import _T4
+ from ._typing import _T5
+ from ._typing import _T6
+ from ._typing import _T7
+ from ._typing import _T8
+ from ._typing import _T9
+ from ._typing import _TypedColumnClauseArgument as _TCCA
+ from .functions import Function
+ from .selectable import CTE
+ from .selectable import HasCTE
+ from .selectable import ScalarSelect
+ from .selectable import SelectBase
+
+
+_T = TypeVar("_T", bound=Any)
+
+
+def alias(
+ selectable: FromClause, name: Optional[str] = None, flat: bool = False
+) -> NamedFromClause:
+ """Return a named alias of the given :class:`.FromClause`.
+
+ For :class:`.Table` and :class:`.Join` objects, the return type is the
+ :class:`_expression.Alias` object. Other kinds of :class:`.NamedFromClause`
+ objects may be returned for other kinds of :class:`.FromClause` objects.
+
+ The named alias represents any :class:`_expression.FromClause` with an
+ alternate name assigned within SQL, typically using the ``AS`` clause when
+ generated, e.g. ``SELECT * FROM table AS aliasname``.
+
+ Equivalent functionality is available via the
+ :meth:`_expression.FromClause.alias`
+ method available on all :class:`_expression.FromClause` objects.
+
+ :param selectable: any :class:`_expression.FromClause` subclass,
+ such as a table, select statement, etc.
+
+ :param name: string name to be assigned as the alias.
+ If ``None``, a name will be deterministically generated at compile
+ time. Deterministic means the name is guaranteed to be unique against
+ other constructs used in the same statement, and will also be the same
+ name for each successive compilation of the same statement object.
+
+ :param flat: Will be passed through to if the given selectable
+ is an instance of :class:`_expression.Join` - see
+ :meth:`_expression.Join.alias` for details.
+
+ """
+ return Alias._factory(selectable, name=name, flat=flat)
+
+
+def cte(
+ selectable: HasCTE, name: Optional[str] = None, recursive: bool = False
+) -> CTE:
+ r"""Return a new :class:`_expression.CTE`,
+ or Common Table Expression instance.
+
+ Please see :meth:`_expression.HasCTE.cte` for detail on CTE usage.
+
+ """
+ return coercions.expect(roles.HasCTERole, selectable).cte(
+ name=name, recursive=recursive
+ )
+
+
+def except_(
+ *selects: _SelectStatementForCompoundArgument,
+) -> CompoundSelect:
+ r"""Return an ``EXCEPT`` of multiple selectables.
+
+ The returned object is an instance of
+ :class:`_expression.CompoundSelect`.
+
+ :param \*selects:
+ a list of :class:`_expression.Select` instances.
+
+ """
+ return CompoundSelect._create_except(*selects)
+
+
+def except_all(
+ *selects: _SelectStatementForCompoundArgument,
+) -> CompoundSelect:
+ r"""Return an ``EXCEPT ALL`` of multiple selectables.
+
+ The returned object is an instance of
+ :class:`_expression.CompoundSelect`.
+
+ :param \*selects:
+ a list of :class:`_expression.Select` instances.
+
+ """
+ return CompoundSelect._create_except_all(*selects)
+
+
+def exists(
+ __argument: Optional[
+ Union[_ColumnsClauseArgument[Any], SelectBase, ScalarSelect[Any]]
+ ] = None,
+) -> Exists:
+ """Construct a new :class:`_expression.Exists` construct.
+
+ The :func:`_sql.exists` can be invoked by itself to produce an
+ :class:`_sql.Exists` construct, which will accept simple WHERE
+ criteria::
+
+ exists_criteria = exists().where(table1.c.col1 == table2.c.col2)
+
+ However, for greater flexibility in constructing the SELECT, an
+ existing :class:`_sql.Select` construct may be converted to an
+ :class:`_sql.Exists`, most conveniently by making use of the
+ :meth:`_sql.SelectBase.exists` method::
+
+ exists_criteria = (
+ select(table2.c.col2).
+ where(table1.c.col1 == table2.c.col2).
+ exists()
+ )
+
+ The EXISTS criteria is then used inside of an enclosing SELECT::
+
+ stmt = select(table1.c.col1).where(exists_criteria)
+
+ The above statement will then be of the form::
+
+ SELECT col1 FROM table1 WHERE EXISTS
+ (SELECT table2.col2 FROM table2 WHERE table2.col2 = table1.col1)
+
+ .. seealso::
+
+ :ref:`tutorial_exists` - in the :term:`2.0 style` tutorial.
+
+ :meth:`_sql.SelectBase.exists` - method to transform a ``SELECT`` to an
+ ``EXISTS`` clause.
+
+ """ # noqa: E501
+
+ return Exists(__argument)
+
+
+def intersect(
+ *selects: _SelectStatementForCompoundArgument,
+) -> CompoundSelect:
+ r"""Return an ``INTERSECT`` of multiple selectables.
+
+ The returned object is an instance of
+ :class:`_expression.CompoundSelect`.
+
+ :param \*selects:
+ a list of :class:`_expression.Select` instances.
+
+ """
+ return CompoundSelect._create_intersect(*selects)
+
+
+def intersect_all(
+ *selects: _SelectStatementForCompoundArgument,
+) -> CompoundSelect:
+ r"""Return an ``INTERSECT ALL`` of multiple selectables.
+
+ The returned object is an instance of
+ :class:`_expression.CompoundSelect`.
+
+ :param \*selects:
+ a list of :class:`_expression.Select` instances.
+
+
+ """
+ return CompoundSelect._create_intersect_all(*selects)
+
+
+def join(
+ left: _FromClauseArgument,
+ right: _FromClauseArgument,
+ onclause: Optional[_OnClauseArgument] = None,
+ isouter: bool = False,
+ full: bool = False,
+) -> Join:
+ """Produce a :class:`_expression.Join` object, given two
+ :class:`_expression.FromClause`
+ expressions.
+
+ E.g.::
+
+ j = join(user_table, address_table,
+ user_table.c.id == address_table.c.user_id)
+ stmt = select(user_table).select_from(j)
+
+ would emit SQL along the lines of::
+
+ SELECT user.id, user.name FROM user
+ JOIN address ON user.id = address.user_id
+
+ Similar functionality is available given any
+ :class:`_expression.FromClause` object (e.g. such as a
+ :class:`_schema.Table`) using
+ the :meth:`_expression.FromClause.join` method.
+
+ :param left: The left side of the join.
+
+ :param right: the right side of the join; this is any
+ :class:`_expression.FromClause` object such as a
+ :class:`_schema.Table` object, and
+ may also be a selectable-compatible object such as an ORM-mapped
+ class.
+
+ :param onclause: a SQL expression representing the ON clause of the
+ join. If left at ``None``, :meth:`_expression.FromClause.join`
+ will attempt to
+ join the two tables based on a foreign key relationship.
+
+ :param isouter: if True, render a LEFT OUTER JOIN, instead of JOIN.
+
+ :param full: if True, render a FULL OUTER JOIN, instead of JOIN.
+
+ .. seealso::
+
+ :meth:`_expression.FromClause.join` - method form,
+ based on a given left side.
+
+ :class:`_expression.Join` - the type of object produced.
+
+ """
+
+ return Join(left, right, onclause, isouter, full)
+
+
+def lateral(
+ selectable: Union[SelectBase, _FromClauseArgument],
+ name: Optional[str] = None,
+) -> LateralFromClause:
+ """Return a :class:`_expression.Lateral` object.
+
+ :class:`_expression.Lateral` is an :class:`_expression.Alias`
+ subclass that represents
+ a subquery with the LATERAL keyword applied to it.
+
+ The special behavior of a LATERAL subquery is that it appears in the
+ FROM clause of an enclosing SELECT, but may correlate to other
+ FROM clauses of that SELECT. It is a special case of subquery
+ only supported by a small number of backends, currently more recent
+ PostgreSQL versions.
+
+ .. seealso::
+
+ :ref:`tutorial_lateral_correlation` - overview of usage.
+
+ """
+ return Lateral._factory(selectable, name=name)
+
+
+def outerjoin(
+ left: _FromClauseArgument,
+ right: _FromClauseArgument,
+ onclause: Optional[_OnClauseArgument] = None,
+ full: bool = False,
+) -> Join:
+ """Return an ``OUTER JOIN`` clause element.
+
+ The returned object is an instance of :class:`_expression.Join`.
+
+ Similar functionality is also available via the
+ :meth:`_expression.FromClause.outerjoin` method on any
+ :class:`_expression.FromClause`.
+
+ :param left: The left side of the join.
+
+ :param right: The right side of the join.
+
+ :param onclause: Optional criterion for the ``ON`` clause, is
+ derived from foreign key relationships established between
+ left and right otherwise.
+
+ To chain joins together, use the :meth:`_expression.FromClause.join`
+ or
+ :meth:`_expression.FromClause.outerjoin` methods on the resulting
+ :class:`_expression.Join` object.
+
+ """
+ return Join(left, right, onclause, isouter=True, full=full)
+
+
+# START OVERLOADED FUNCTIONS select Select 1-10
+
+# code within this block is **programmatically,
+# statically generated** by tools/generate_tuple_map_overloads.py
+
+
+@overload
+def select(__ent0: _TCCA[_T0]) -> Select[Tuple[_T0]]: ...
+
+
+@overload
+def select(
+ __ent0: _TCCA[_T0], __ent1: _TCCA[_T1]
+) -> Select[Tuple[_T0, _T1]]: ...
+
+
+@overload
+def select(
+ __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2]
+) -> Select[Tuple[_T0, _T1, _T2]]: ...
+
+
+@overload
+def select(
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+) -> Select[Tuple[_T0, _T1, _T2, _T3]]: ...
+
+
+@overload
+def select(
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4]]: ...
+
+
+@overload
+def select(
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]: ...
+
+
+@overload
+def select(
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ __ent6: _TCCA[_T6],
+) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]: ...
+
+
+@overload
+def select(
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ __ent6: _TCCA[_T6],
+ __ent7: _TCCA[_T7],
+) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]: ...
+
+
+@overload
+def select(
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ __ent6: _TCCA[_T6],
+ __ent7: _TCCA[_T7],
+ __ent8: _TCCA[_T8],
+) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8]]: ...
+
+
+@overload
+def select(
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ __ent6: _TCCA[_T6],
+ __ent7: _TCCA[_T7],
+ __ent8: _TCCA[_T8],
+ __ent9: _TCCA[_T9],
+) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9]]: ...
+
+
+# END OVERLOADED FUNCTIONS select
+
+
+@overload
+def select(
+ *entities: _ColumnsClauseArgument[Any], **__kw: Any
+) -> Select[Any]: ...
+
+
+def select(*entities: _ColumnsClauseArgument[Any], **__kw: Any) -> Select[Any]:
+ r"""Construct a new :class:`_expression.Select`.
+
+
+ .. versionadded:: 1.4 - The :func:`_sql.select` function now accepts
+ column arguments positionally. The top-level :func:`_sql.select`
+ function will automatically use the 1.x or 2.x style API based on
+ the incoming arguments; using :func:`_sql.select` from the
+ ``sqlalchemy.future`` module will enforce that only the 2.x style
+ constructor is used.
+
+ Similar functionality is also available via the
+ :meth:`_expression.FromClause.select` method on any
+ :class:`_expression.FromClause`.
+
+ .. seealso::
+
+ :ref:`tutorial_selecting_data` - in the :ref:`unified_tutorial`
+
+ :param \*entities:
+ Entities to SELECT from. For Core usage, this is typically a series
+ of :class:`_expression.ColumnElement` and / or
+ :class:`_expression.FromClause`
+ objects which will form the columns clause of the resulting
+ statement. For those objects that are instances of
+ :class:`_expression.FromClause` (typically :class:`_schema.Table`
+ or :class:`_expression.Alias`
+ objects), the :attr:`_expression.FromClause.c`
+ collection is extracted
+ to form a collection of :class:`_expression.ColumnElement` objects.
+
+ This parameter will also accept :class:`_expression.TextClause`
+ constructs as
+ given, as well as ORM-mapped classes.
+
+ """
+ # the keyword args are a necessary element in order for the typing
+ # to work out w/ the varargs vs. having named "keyword" arguments that
+ # aren't always present.
+ if __kw:
+ raise _no_kw()
+ return Select(*entities)
+
+
+def table(name: str, *columns: ColumnClause[Any], **kw: Any) -> TableClause:
+ """Produce a new :class:`_expression.TableClause`.
+
+ The object returned is an instance of
+ :class:`_expression.TableClause`, which
+ represents the "syntactical" portion of the schema-level
+ :class:`_schema.Table` object.
+ It may be used to construct lightweight table constructs.
+
+ :param name: Name of the table.
+
+ :param columns: A collection of :func:`_expression.column` constructs.
+
+ :param schema: The schema name for this table.
+
+ .. versionadded:: 1.3.18 :func:`_expression.table` can now
+ accept a ``schema`` argument.
+ """
+
+ return TableClause(name, *columns, **kw)
+
+
+def tablesample(
+ selectable: _FromClauseArgument,
+ sampling: Union[float, Function[Any]],
+ name: Optional[str] = None,
+ seed: Optional[roles.ExpressionElementRole[Any]] = None,
+) -> TableSample:
+ """Return a :class:`_expression.TableSample` object.
+
+ :class:`_expression.TableSample` is an :class:`_expression.Alias`
+ subclass that represents
+ a table with the TABLESAMPLE clause applied to it.
+ :func:`_expression.tablesample`
+ is also available from the :class:`_expression.FromClause`
+ class via the
+ :meth:`_expression.FromClause.tablesample` method.
+
+ The TABLESAMPLE clause allows selecting a randomly selected approximate
+ percentage of rows from a table. It supports multiple sampling methods,
+ most commonly BERNOULLI and SYSTEM.
+
+ e.g.::
+
+ from sqlalchemy import func
+
+ selectable = people.tablesample(
+ func.bernoulli(1),
+ name='alias',
+ seed=func.random())
+ stmt = select(selectable.c.people_id)
+
+ Assuming ``people`` with a column ``people_id``, the above
+ statement would render as::
+
+ SELECT alias.people_id FROM
+ people AS alias TABLESAMPLE bernoulli(:bernoulli_1)
+ REPEATABLE (random())
+
+ :param sampling: a ``float`` percentage between 0 and 100 or
+ :class:`_functions.Function`.
+
+ :param name: optional alias name
+
+ :param seed: any real-valued SQL expression. When specified, the
+ REPEATABLE sub-clause is also rendered.
+
+ """
+ return TableSample._factory(selectable, sampling, name=name, seed=seed)
+
+
+def union(
+ *selects: _SelectStatementForCompoundArgument,
+) -> CompoundSelect:
+ r"""Return a ``UNION`` of multiple selectables.
+
+ The returned object is an instance of
+ :class:`_expression.CompoundSelect`.
+
+ A similar :func:`union()` method is available on all
+ :class:`_expression.FromClause` subclasses.
+
+ :param \*selects:
+ a list of :class:`_expression.Select` instances.
+
+ :param \**kwargs:
+ available keyword arguments are the same as those of
+ :func:`select`.
+
+ """
+ return CompoundSelect._create_union(*selects)
+
+
+def union_all(
+ *selects: _SelectStatementForCompoundArgument,
+) -> CompoundSelect:
+ r"""Return a ``UNION ALL`` of multiple selectables.
+
+ The returned object is an instance of
+ :class:`_expression.CompoundSelect`.
+
+ A similar :func:`union_all()` method is available on all
+ :class:`_expression.FromClause` subclasses.
+
+ :param \*selects:
+ a list of :class:`_expression.Select` instances.
+
+ """
+ return CompoundSelect._create_union_all(*selects)
+
+
+def values(
+ *columns: ColumnClause[Any],
+ name: Optional[str] = None,
+ literal_binds: bool = False,
+) -> Values:
+ r"""Construct a :class:`_expression.Values` construct.
+
+ The column expressions and the actual data for
+ :class:`_expression.Values` are given in two separate steps. The
+ constructor receives the column expressions typically as
+ :func:`_expression.column` constructs,
+ and the data is then passed via the
+ :meth:`_expression.Values.data` method as a list,
+ which can be called multiple
+ times to add more data, e.g.::
+
+ from sqlalchemy import column
+ from sqlalchemy import values
+
+ value_expr = values(
+ column('id', Integer),
+ column('name', String),
+ name="my_values"
+ ).data(
+ [(1, 'name1'), (2, 'name2'), (3, 'name3')]
+ )
+
+ :param \*columns: column expressions, typically composed using
+ :func:`_expression.column` objects.
+
+ :param name: the name for this VALUES construct. If omitted, the
+ VALUES construct will be unnamed in a SQL expression. Different
+ backends may have different requirements here.
+
+ :param literal_binds: Defaults to False. Whether or not to render
+ the data values inline in the SQL output, rather than using bound
+ parameters.
+
+ """
+ return Values(*columns, literal_binds=literal_binds, name=name)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/_typing.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/_typing.py
new file mode 100644
index 0000000..c861bae
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/_typing.py
@@ -0,0 +1,457 @@
+# sql/_typing.py
+# Copyright (C) 2022-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+from __future__ import annotations
+
+import operator
+from typing import Any
+from typing import Callable
+from typing import Dict
+from typing import Generic
+from typing import Iterable
+from typing import Mapping
+from typing import NoReturn
+from typing import Optional
+from typing import overload
+from typing import Set
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from . import roles
+from .. import exc
+from .. import util
+from ..inspection import Inspectable
+from ..util.typing import Literal
+from ..util.typing import Protocol
+from ..util.typing import TypeAlias
+
+if TYPE_CHECKING:
+ from datetime import date
+ from datetime import datetime
+ from datetime import time
+ from datetime import timedelta
+ from decimal import Decimal
+ from uuid import UUID
+
+ from .base import Executable
+ from .compiler import Compiled
+ from .compiler import DDLCompiler
+ from .compiler import SQLCompiler
+ from .dml import UpdateBase
+ from .dml import ValuesBase
+ from .elements import ClauseElement
+ from .elements import ColumnElement
+ from .elements import KeyedColumnElement
+ from .elements import quoted_name
+ from .elements import SQLCoreOperations
+ from .elements import TextClause
+ from .lambdas import LambdaElement
+ from .roles import FromClauseRole
+ from .schema import Column
+ from .selectable import Alias
+ from .selectable import CTE
+ from .selectable import FromClause
+ from .selectable import Join
+ from .selectable import NamedFromClause
+ from .selectable import ReturnsRows
+ from .selectable import Select
+ from .selectable import Selectable
+ from .selectable import SelectBase
+ from .selectable import Subquery
+ from .selectable import TableClause
+ from .sqltypes import TableValueType
+ from .sqltypes import TupleType
+ from .type_api import TypeEngine
+ from ..engine import Dialect
+ from ..util.typing import TypeGuard
+
+_T = TypeVar("_T", bound=Any)
+_T_co = TypeVar("_T_co", bound=Any, covariant=True)
+
+
+_CE = TypeVar("_CE", bound="ColumnElement[Any]")
+
+_CLE = TypeVar("_CLE", bound="ClauseElement")
+
+
+class _HasClauseElement(Protocol, Generic[_T_co]):
+ """indicates a class that has a __clause_element__() method"""
+
+ def __clause_element__(self) -> roles.ExpressionElementRole[_T_co]: ...
+
+
+class _CoreAdapterProto(Protocol):
+ """protocol for the ClauseAdapter/ColumnAdapter.traverse() method."""
+
+ def __call__(self, obj: _CE) -> _CE: ...
+
+
+class _HasDialect(Protocol):
+ """protocol for Engine/Connection-like objects that have dialect
+ attribute.
+ """
+
+ @property
+ def dialect(self) -> Dialect: ...
+
+
+# match column types that are not ORM entities
+_NOT_ENTITY = TypeVar(
+ "_NOT_ENTITY",
+ int,
+ str,
+ bool,
+ "datetime",
+ "date",
+ "time",
+ "timedelta",
+ "UUID",
+ float,
+ "Decimal",
+)
+
+_MAYBE_ENTITY = TypeVar(
+ "_MAYBE_ENTITY",
+ roles.ColumnsClauseRole,
+ Literal["*", 1],
+ Type[Any],
+ Inspectable[_HasClauseElement[Any]],
+ _HasClauseElement[Any],
+)
+
+
+# convention:
+# XYZArgument - something that the end user is passing to a public API method
+# XYZElement - the internal representation that we use for the thing.
+# the coercions system is responsible for converting from XYZArgument to
+# XYZElement.
+
+_TextCoercedExpressionArgument = Union[
+ str,
+ "TextClause",
+ "ColumnElement[_T]",
+ _HasClauseElement[_T],
+ roles.ExpressionElementRole[_T],
+]
+
+_ColumnsClauseArgument = Union[
+ roles.TypedColumnsClauseRole[_T],
+ roles.ColumnsClauseRole,
+ "SQLCoreOperations[_T]",
+ Literal["*", 1],
+ Type[_T],
+ Inspectable[_HasClauseElement[_T]],
+ _HasClauseElement[_T],
+]
+"""open-ended SELECT columns clause argument.
+
+Includes column expressions, tables, ORM mapped entities, a few literal values.
+
+This type is used for lists of columns / entities to be returned in result
+sets; select(...), insert().returning(...), etc.
+
+
+"""
+
+_TypedColumnClauseArgument = Union[
+ roles.TypedColumnsClauseRole[_T],
+ "SQLCoreOperations[_T]",
+ Type[_T],
+]
+
+_TP = TypeVar("_TP", bound=Tuple[Any, ...])
+
+_T0 = TypeVar("_T0", bound=Any)
+_T1 = TypeVar("_T1", bound=Any)
+_T2 = TypeVar("_T2", bound=Any)
+_T3 = TypeVar("_T3", bound=Any)
+_T4 = TypeVar("_T4", bound=Any)
+_T5 = TypeVar("_T5", bound=Any)
+_T6 = TypeVar("_T6", bound=Any)
+_T7 = TypeVar("_T7", bound=Any)
+_T8 = TypeVar("_T8", bound=Any)
+_T9 = TypeVar("_T9", bound=Any)
+
+
+_ColumnExpressionArgument = Union[
+ "ColumnElement[_T]",
+ _HasClauseElement[_T],
+ "SQLCoreOperations[_T]",
+ roles.ExpressionElementRole[_T],
+ Callable[[], "ColumnElement[_T]"],
+ "LambdaElement",
+]
+"See docs in public alias ColumnExpressionArgument."
+
+ColumnExpressionArgument: TypeAlias = _ColumnExpressionArgument[_T]
+"""Narrower "column expression" argument.
+
+This type is used for all the other "column" kinds of expressions that
+typically represent a single SQL column expression, not a set of columns the
+way a table or ORM entity does.
+
+This includes ColumnElement, or ORM-mapped attributes that will have a
+``__clause_element__()`` method, it also has the ExpressionElementRole
+overall which brings in the TextClause object also.
+
+.. versionadded:: 2.0.13
+
+"""
+
+_ColumnExpressionOrLiteralArgument = Union[Any, _ColumnExpressionArgument[_T]]
+
+_ColumnExpressionOrStrLabelArgument = Union[str, _ColumnExpressionArgument[_T]]
+
+_ByArgument = Union[
+ Iterable[_ColumnExpressionOrStrLabelArgument[Any]],
+ _ColumnExpressionOrStrLabelArgument[Any],
+]
+"""Used for keyword-based ``order_by`` and ``partition_by`` parameters."""
+
+
+_InfoType = Dict[Any, Any]
+"""the .info dictionary accepted and used throughout Core /ORM"""
+
+_FromClauseArgument = Union[
+ roles.FromClauseRole,
+ Type[Any],
+ Inspectable[_HasClauseElement[Any]],
+ _HasClauseElement[Any],
+]
+"""A FROM clause, like we would send to select().select_from().
+
+Also accommodates ORM entities and related constructs.
+
+"""
+
+_JoinTargetArgument = Union[_FromClauseArgument, roles.JoinTargetRole]
+"""target for join() builds on _FromClauseArgument to include additional
+join target roles such as those which come from the ORM.
+
+"""
+
+_OnClauseArgument = Union[_ColumnExpressionArgument[Any], roles.OnClauseRole]
+"""target for an ON clause, includes additional roles such as those which
+come from the ORM.
+
+"""
+
+_SelectStatementForCompoundArgument = Union[
+ "SelectBase", roles.CompoundElementRole
+]
+"""SELECT statement acceptable by ``union()`` and other SQL set operations"""
+
+_DMLColumnArgument = Union[
+ str,
+ _HasClauseElement[Any],
+ roles.DMLColumnRole,
+ "SQLCoreOperations[Any]",
+]
+"""A DML column expression. This is a "key" inside of insert().values(),
+update().values(), and related.
+
+These are usually strings or SQL table columns.
+
+There's also edge cases like JSON expression assignment, which we would want
+the DMLColumnRole to be able to accommodate.
+
+"""
+
+_DMLKey = TypeVar("_DMLKey", bound=_DMLColumnArgument)
+_DMLColumnKeyMapping = Mapping[_DMLKey, Any]
+
+
+_DDLColumnArgument = Union[str, "Column[Any]", roles.DDLConstraintColumnRole]
+"""DDL column.
+
+used for :class:`.PrimaryKeyConstraint`, :class:`.UniqueConstraint`, etc.
+
+"""
+
+_DMLTableArgument = Union[
+ "TableClause",
+ "Join",
+ "Alias",
+ "CTE",
+ Type[Any],
+ Inspectable[_HasClauseElement[Any]],
+ _HasClauseElement[Any],
+]
+
+_PropagateAttrsType = util.immutabledict[str, Any]
+
+_TypeEngineArgument = Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"]
+
+_EquivalentColumnMap = Dict["ColumnElement[Any]", Set["ColumnElement[Any]"]]
+
+_LimitOffsetType = Union[int, _ColumnExpressionArgument[int], None]
+
+_AutoIncrementType = Union[bool, Literal["auto", "ignore_fk"]]
+
+if TYPE_CHECKING:
+
+ def is_sql_compiler(c: Compiled) -> TypeGuard[SQLCompiler]: ...
+
+ def is_ddl_compiler(c: Compiled) -> TypeGuard[DDLCompiler]: ...
+
+ def is_named_from_clause(
+ t: FromClauseRole,
+ ) -> TypeGuard[NamedFromClause]: ...
+
+ def is_column_element(
+ c: ClauseElement,
+ ) -> TypeGuard[ColumnElement[Any]]: ...
+
+ def is_keyed_column_element(
+ c: ClauseElement,
+ ) -> TypeGuard[KeyedColumnElement[Any]]: ...
+
+ def is_text_clause(c: ClauseElement) -> TypeGuard[TextClause]: ...
+
+ def is_from_clause(c: ClauseElement) -> TypeGuard[FromClause]: ...
+
+ def is_tuple_type(t: TypeEngine[Any]) -> TypeGuard[TupleType]: ...
+
+ def is_table_value_type(
+ t: TypeEngine[Any],
+ ) -> TypeGuard[TableValueType]: ...
+
+ def is_selectable(t: Any) -> TypeGuard[Selectable]: ...
+
+ def is_select_base(
+ t: Union[Executable, ReturnsRows]
+ ) -> TypeGuard[SelectBase]: ...
+
+ def is_select_statement(
+ t: Union[Executable, ReturnsRows]
+ ) -> TypeGuard[Select[Any]]: ...
+
+ def is_table(t: FromClause) -> TypeGuard[TableClause]: ...
+
+ def is_subquery(t: FromClause) -> TypeGuard[Subquery]: ...
+
+ def is_dml(c: ClauseElement) -> TypeGuard[UpdateBase]: ...
+
+else:
+ is_sql_compiler = operator.attrgetter("is_sql")
+ is_ddl_compiler = operator.attrgetter("is_ddl")
+ is_named_from_clause = operator.attrgetter("named_with_column")
+ is_column_element = operator.attrgetter("_is_column_element")
+ is_keyed_column_element = operator.attrgetter("_is_keyed_column_element")
+ is_text_clause = operator.attrgetter("_is_text_clause")
+ is_from_clause = operator.attrgetter("_is_from_clause")
+ is_tuple_type = operator.attrgetter("_is_tuple_type")
+ is_table_value_type = operator.attrgetter("_is_table_value")
+ is_selectable = operator.attrgetter("is_selectable")
+ is_select_base = operator.attrgetter("_is_select_base")
+ is_select_statement = operator.attrgetter("_is_select_statement")
+ is_table = operator.attrgetter("_is_table")
+ is_subquery = operator.attrgetter("_is_subquery")
+ is_dml = operator.attrgetter("is_dml")
+
+
+def has_schema_attr(t: FromClauseRole) -> TypeGuard[TableClause]:
+ return hasattr(t, "schema")
+
+
+def is_quoted_name(s: str) -> TypeGuard[quoted_name]:
+ return hasattr(s, "quote")
+
+
+def is_has_clause_element(s: object) -> TypeGuard[_HasClauseElement[Any]]:
+ return hasattr(s, "__clause_element__")
+
+
+def is_insert_update(c: ClauseElement) -> TypeGuard[ValuesBase]:
+ return c.is_dml and (c.is_insert or c.is_update) # type: ignore
+
+
+def _no_kw() -> exc.ArgumentError:
+ return exc.ArgumentError(
+ "Additional keyword arguments are not accepted by this "
+ "function/method. The presence of **kw is for pep-484 typing purposes"
+ )
+
+
+def _unexpected_kw(methname: str, kw: Dict[str, Any]) -> NoReturn:
+ k = list(kw)[0]
+ raise TypeError(f"{methname} got an unexpected keyword argument '{k}'")
+
+
+@overload
+def Nullable(
+ val: "SQLCoreOperations[_T]",
+) -> "SQLCoreOperations[Optional[_T]]": ...
+
+
+@overload
+def Nullable(
+ val: roles.ExpressionElementRole[_T],
+) -> roles.ExpressionElementRole[Optional[_T]]: ...
+
+
+@overload
+def Nullable(val: Type[_T]) -> Type[Optional[_T]]: ...
+
+
+def Nullable(
+ val: _TypedColumnClauseArgument[_T],
+) -> _TypedColumnClauseArgument[Optional[_T]]:
+ """Types a column or ORM class as nullable.
+
+ This can be used in select and other contexts to express that the value of
+ a column can be null, for example due to an outer join::
+
+ stmt1 = select(A, Nullable(B)).outerjoin(A.bs)
+ stmt2 = select(A.data, Nullable(B.data)).outerjoin(A.bs)
+
+ At runtime this method returns the input unchanged.
+
+ .. versionadded:: 2.0.20
+ """
+ return val
+
+
+@overload
+def NotNullable(
+ val: "SQLCoreOperations[Optional[_T]]",
+) -> "SQLCoreOperations[_T]": ...
+
+
+@overload
+def NotNullable(
+ val: roles.ExpressionElementRole[Optional[_T]],
+) -> roles.ExpressionElementRole[_T]: ...
+
+
+@overload
+def NotNullable(val: Type[Optional[_T]]) -> Type[_T]: ...
+
+
+@overload
+def NotNullable(val: Optional[Type[_T]]) -> Type[_T]: ...
+
+
+def NotNullable(
+ val: Union[_TypedColumnClauseArgument[Optional[_T]], Optional[Type[_T]]],
+) -> _TypedColumnClauseArgument[_T]:
+ """Types a column or ORM class as not nullable.
+
+ This can be used in select and other contexts to express that the value of
+ a column cannot be null, for example due to a where condition on a
+ nullable column::
+
+ stmt = select(NotNullable(A.value)).where(A.value.is_not(None))
+
+ At runtime this method returns the input unchanged.
+
+ .. versionadded:: 2.0.20
+ """
+ return val # type: ignore
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/annotation.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/annotation.py
new file mode 100644
index 0000000..db382b8
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/annotation.py
@@ -0,0 +1,585 @@
+# sql/annotation.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+"""The :class:`.Annotated` class and related routines; creates hash-equivalent
+copies of SQL constructs which contain context-specific markers and
+associations.
+
+Note that the :class:`.Annotated` concept as implemented in this module is not
+related in any way to the pep-593 concept of "Annotated".
+
+
+"""
+
+from __future__ import annotations
+
+import typing
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import Dict
+from typing import FrozenSet
+from typing import Mapping
+from typing import Optional
+from typing import overload
+from typing import Sequence
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+
+from . import operators
+from .cache_key import HasCacheKey
+from .visitors import anon_map
+from .visitors import ExternallyTraversible
+from .visitors import InternalTraversal
+from .. import util
+from ..util.typing import Literal
+from ..util.typing import Self
+
+if TYPE_CHECKING:
+ from .base import _EntityNamespace
+ from .visitors import _TraverseInternalsType
+
+_AnnotationDict = Mapping[str, Any]
+
+EMPTY_ANNOTATIONS: util.immutabledict[str, Any] = util.EMPTY_DICT
+
+
+class SupportsAnnotations(ExternallyTraversible):
+ __slots__ = ()
+
+ _annotations: util.immutabledict[str, Any] = EMPTY_ANNOTATIONS
+
+ proxy_set: util.generic_fn_descriptor[FrozenSet[Any]]
+
+ _is_immutable: bool
+
+ def _annotate(self, values: _AnnotationDict) -> Self:
+ raise NotImplementedError()
+
+ @overload
+ def _deannotate(
+ self,
+ values: Literal[None] = ...,
+ clone: bool = ...,
+ ) -> Self: ...
+
+ @overload
+ def _deannotate(
+ self,
+ values: Sequence[str] = ...,
+ clone: bool = ...,
+ ) -> SupportsAnnotations: ...
+
+ def _deannotate(
+ self,
+ values: Optional[Sequence[str]] = None,
+ clone: bool = False,
+ ) -> SupportsAnnotations:
+ raise NotImplementedError()
+
+ @util.memoized_property
+ def _annotations_cache_key(self) -> Tuple[Any, ...]:
+ anon_map_ = anon_map()
+
+ return self._gen_annotations_cache_key(anon_map_)
+
+ def _gen_annotations_cache_key(
+ self, anon_map: anon_map
+ ) -> Tuple[Any, ...]:
+ return (
+ "_annotations",
+ tuple(
+ (
+ key,
+ (
+ value._gen_cache_key(anon_map, [])
+ if isinstance(value, HasCacheKey)
+ else value
+ ),
+ )
+ for key, value in [
+ (key, self._annotations[key])
+ for key in sorted(self._annotations)
+ ]
+ ),
+ )
+
+
+class SupportsWrappingAnnotations(SupportsAnnotations):
+ __slots__ = ()
+
+ _constructor: Callable[..., SupportsWrappingAnnotations]
+
+ if TYPE_CHECKING:
+
+ @util.ro_non_memoized_property
+ def entity_namespace(self) -> _EntityNamespace: ...
+
+ def _annotate(self, values: _AnnotationDict) -> Self:
+ """return a copy of this ClauseElement with annotations
+ updated by the given dictionary.
+
+ """
+ return Annotated._as_annotated_instance(self, values) # type: ignore
+
+ def _with_annotations(self, values: _AnnotationDict) -> Self:
+ """return a copy of this ClauseElement with annotations
+ replaced by the given dictionary.
+
+ """
+ return Annotated._as_annotated_instance(self, values) # type: ignore
+
+ @overload
+ def _deannotate(
+ self,
+ values: Literal[None] = ...,
+ clone: bool = ...,
+ ) -> Self: ...
+
+ @overload
+ def _deannotate(
+ self,
+ values: Sequence[str] = ...,
+ clone: bool = ...,
+ ) -> SupportsAnnotations: ...
+
+ def _deannotate(
+ self,
+ values: Optional[Sequence[str]] = None,
+ clone: bool = False,
+ ) -> SupportsAnnotations:
+ """return a copy of this :class:`_expression.ClauseElement`
+ with annotations
+ removed.
+
+ :param values: optional tuple of individual values
+ to remove.
+
+ """
+ if clone:
+ s = self._clone()
+ return s
+ else:
+ return self
+
+
+class SupportsCloneAnnotations(SupportsWrappingAnnotations):
+ # SupportsCloneAnnotations extends from SupportsWrappingAnnotations
+ # to support the structure of having the base ClauseElement
+ # be a subclass of SupportsWrappingAnnotations. Any ClauseElement
+ # subclass that wants to extend from SupportsCloneAnnotations
+ # will inherently also be subclassing SupportsWrappingAnnotations, so
+ # make that specific here.
+
+ if not typing.TYPE_CHECKING:
+ __slots__ = ()
+
+ _clone_annotations_traverse_internals: _TraverseInternalsType = [
+ ("_annotations", InternalTraversal.dp_annotations_key)
+ ]
+
+ def _annotate(self, values: _AnnotationDict) -> Self:
+ """return a copy of this ClauseElement with annotations
+ updated by the given dictionary.
+
+ """
+ new = self._clone()
+ new._annotations = new._annotations.union(values)
+ new.__dict__.pop("_annotations_cache_key", None)
+ new.__dict__.pop("_generate_cache_key", None)
+ return new
+
+ def _with_annotations(self, values: _AnnotationDict) -> Self:
+ """return a copy of this ClauseElement with annotations
+ replaced by the given dictionary.
+
+ """
+ new = self._clone()
+ new._annotations = util.immutabledict(values)
+ new.__dict__.pop("_annotations_cache_key", None)
+ new.__dict__.pop("_generate_cache_key", None)
+ return new
+
+ @overload
+ def _deannotate(
+ self,
+ values: Literal[None] = ...,
+ clone: bool = ...,
+ ) -> Self: ...
+
+ @overload
+ def _deannotate(
+ self,
+ values: Sequence[str] = ...,
+ clone: bool = ...,
+ ) -> SupportsAnnotations: ...
+
+ def _deannotate(
+ self,
+ values: Optional[Sequence[str]] = None,
+ clone: bool = False,
+ ) -> SupportsAnnotations:
+ """return a copy of this :class:`_expression.ClauseElement`
+ with annotations
+ removed.
+
+ :param values: optional tuple of individual values
+ to remove.
+
+ """
+ if clone or self._annotations:
+ # clone is used when we are also copying
+ # the expression for a deep deannotation
+ new = self._clone()
+ new._annotations = util.immutabledict()
+ new.__dict__.pop("_annotations_cache_key", None)
+ return new
+ else:
+ return self
+
+
+class Annotated(SupportsAnnotations):
+ """clones a SupportsAnnotations and applies an 'annotations' dictionary.
+
+ Unlike regular clones, this clone also mimics __hash__() and
+ __eq__() of the original element so that it takes its place
+ in hashed collections.
+
+ A reference to the original element is maintained, for the important
+ reason of keeping its hash value current. When GC'ed, the
+ hash value may be reused, causing conflicts.
+
+ .. note:: The rationale for Annotated producing a brand new class,
+ rather than placing the functionality directly within ClauseElement,
+ is **performance**. The __hash__() method is absent on plain
+ ClauseElement which leads to significantly reduced function call
+ overhead, as the use of sets and dictionaries against ClauseElement
+ objects is prevalent, but most are not "annotated".
+
+ """
+
+ _is_column_operators = False
+
+ @classmethod
+ def _as_annotated_instance(
+ cls, element: SupportsWrappingAnnotations, values: _AnnotationDict
+ ) -> Annotated:
+ try:
+ cls = annotated_classes[element.__class__]
+ except KeyError:
+ cls = _new_annotation_type(element.__class__, cls)
+ return cls(element, values)
+
+ _annotations: util.immutabledict[str, Any]
+ __element: SupportsWrappingAnnotations
+ _hash: int
+
+ def __new__(cls: Type[Self], *args: Any) -> Self:
+ return object.__new__(cls)
+
+ def __init__(
+ self, element: SupportsWrappingAnnotations, values: _AnnotationDict
+ ):
+ self.__dict__ = element.__dict__.copy()
+ self.__dict__.pop("_annotations_cache_key", None)
+ self.__dict__.pop("_generate_cache_key", None)
+ self.__element = element
+ self._annotations = util.immutabledict(values)
+ self._hash = hash(element)
+
+ def _annotate(self, values: _AnnotationDict) -> Self:
+ _values = self._annotations.union(values)
+ new = self._with_annotations(_values)
+ return new
+
+ def _with_annotations(self, values: _AnnotationDict) -> Self:
+ clone = self.__class__.__new__(self.__class__)
+ clone.__dict__ = self.__dict__.copy()
+ clone.__dict__.pop("_annotations_cache_key", None)
+ clone.__dict__.pop("_generate_cache_key", None)
+ clone._annotations = util.immutabledict(values)
+ return clone
+
+ @overload
+ def _deannotate(
+ self,
+ values: Literal[None] = ...,
+ clone: bool = ...,
+ ) -> Self: ...
+
+ @overload
+ def _deannotate(
+ self,
+ values: Sequence[str] = ...,
+ clone: bool = ...,
+ ) -> Annotated: ...
+
+ def _deannotate(
+ self,
+ values: Optional[Sequence[str]] = None,
+ clone: bool = True,
+ ) -> SupportsAnnotations:
+ if values is None:
+ return self.__element
+ else:
+ return self._with_annotations(
+ util.immutabledict(
+ {
+ key: value
+ for key, value in self._annotations.items()
+ if key not in values
+ }
+ )
+ )
+
+ if not typing.TYPE_CHECKING:
+ # manually proxy some methods that need extra attention
+ def _compiler_dispatch(self, visitor: Any, **kw: Any) -> Any:
+ return self.__element.__class__._compiler_dispatch(
+ self, visitor, **kw
+ )
+
+ @property
+ def _constructor(self):
+ return self.__element._constructor
+
+ def _clone(self, **kw: Any) -> Self:
+ clone = self.__element._clone(**kw)
+ if clone is self.__element:
+ # detect immutable, don't change anything
+ return self
+ else:
+ # update the clone with any changes that have occurred
+ # to this object's __dict__.
+ clone.__dict__.update(self.__dict__)
+ return self.__class__(clone, self._annotations)
+
+ def __reduce__(self) -> Tuple[Type[Annotated], Tuple[Any, ...]]:
+ return self.__class__, (self.__element, self._annotations)
+
+ def __hash__(self) -> int:
+ return self._hash
+
+ def __eq__(self, other: Any) -> bool:
+ if self._is_column_operators:
+ return self.__element.__class__.__eq__(self, other)
+ else:
+ return hash(other) == hash(self)
+
+ @util.ro_non_memoized_property
+ def entity_namespace(self) -> _EntityNamespace:
+ if "entity_namespace" in self._annotations:
+ return cast(
+ SupportsWrappingAnnotations,
+ self._annotations["entity_namespace"],
+ ).entity_namespace
+ else:
+ return self.__element.entity_namespace
+
+
+# hard-generate Annotated subclasses. this technique
+# is used instead of on-the-fly types (i.e. type.__new__())
+# so that the resulting objects are pickleable; additionally, other
+# decisions can be made up front about the type of object being annotated
+# just once per class rather than per-instance.
+annotated_classes: Dict[Type[SupportsWrappingAnnotations], Type[Annotated]] = (
+ {}
+)
+
+_SA = TypeVar("_SA", bound="SupportsAnnotations")
+
+
+def _safe_annotate(to_annotate: _SA, annotations: _AnnotationDict) -> _SA:
+ try:
+ _annotate = to_annotate._annotate
+ except AttributeError:
+ # skip objects that don't actually have an `_annotate`
+ # attribute, namely QueryableAttribute inside of a join
+ # condition
+ return to_annotate
+ else:
+ return _annotate(annotations)
+
+
+def _deep_annotate(
+ element: _SA,
+ annotations: _AnnotationDict,
+ exclude: Optional[Sequence[SupportsAnnotations]] = None,
+ *,
+ detect_subquery_cols: bool = False,
+ ind_cols_on_fromclause: bool = False,
+ annotate_callable: Optional[
+ Callable[[SupportsAnnotations, _AnnotationDict], SupportsAnnotations]
+ ] = None,
+) -> _SA:
+ """Deep copy the given ClauseElement, annotating each element
+ with the given annotations dictionary.
+
+ Elements within the exclude collection will be cloned but not annotated.
+
+ """
+
+ # annotated objects hack the __hash__() method so if we want to
+ # uniquely process them we have to use id()
+
+ cloned_ids: Dict[int, SupportsAnnotations] = {}
+
+ def clone(elem: SupportsAnnotations, **kw: Any) -> SupportsAnnotations:
+ # ind_cols_on_fromclause means make sure an AnnotatedFromClause
+ # has its own .c collection independent of that which its proxying.
+ # this is used specifically by orm.LoaderCriteriaOption to break
+ # a reference cycle that it's otherwise prone to building,
+ # see test_relationship_criteria->
+ # test_loader_criteria_subquery_w_same_entity. logic here was
+ # changed for #8796 and made explicit; previously it occurred
+ # by accident
+
+ kw["detect_subquery_cols"] = detect_subquery_cols
+ id_ = id(elem)
+
+ if id_ in cloned_ids:
+ return cloned_ids[id_]
+
+ if (
+ exclude
+ and hasattr(elem, "proxy_set")
+ and elem.proxy_set.intersection(exclude)
+ ):
+ newelem = elem._clone(clone=clone, **kw)
+ elif annotations != elem._annotations:
+ if detect_subquery_cols and elem._is_immutable:
+ to_annotate = elem._clone(clone=clone, **kw)
+ else:
+ to_annotate = elem
+ if annotate_callable:
+ newelem = annotate_callable(to_annotate, annotations)
+ else:
+ newelem = _safe_annotate(to_annotate, annotations)
+ else:
+ newelem = elem
+
+ newelem._copy_internals(
+ clone=clone, ind_cols_on_fromclause=ind_cols_on_fromclause
+ )
+
+ cloned_ids[id_] = newelem
+ return newelem
+
+ if element is not None:
+ element = cast(_SA, clone(element))
+ clone = None # type: ignore # remove gc cycles
+ return element
+
+
+@overload
+def _deep_deannotate(
+ element: Literal[None], values: Optional[Sequence[str]] = None
+) -> Literal[None]: ...
+
+
+@overload
+def _deep_deannotate(
+ element: _SA, values: Optional[Sequence[str]] = None
+) -> _SA: ...
+
+
+def _deep_deannotate(
+ element: Optional[_SA], values: Optional[Sequence[str]] = None
+) -> Optional[_SA]:
+ """Deep copy the given element, removing annotations."""
+
+ cloned: Dict[Any, SupportsAnnotations] = {}
+
+ def clone(elem: SupportsAnnotations, **kw: Any) -> SupportsAnnotations:
+ key: Any
+ if values:
+ key = id(elem)
+ else:
+ key = elem
+
+ if key not in cloned:
+ newelem = elem._deannotate(values=values, clone=True)
+ newelem._copy_internals(clone=clone)
+ cloned[key] = newelem
+ return newelem
+ else:
+ return cloned[key]
+
+ if element is not None:
+ element = cast(_SA, clone(element))
+ clone = None # type: ignore # remove gc cycles
+ return element
+
+
+def _shallow_annotate(element: _SA, annotations: _AnnotationDict) -> _SA:
+ """Annotate the given ClauseElement and copy its internals so that
+ internal objects refer to the new annotated object.
+
+ Basically used to apply a "don't traverse" annotation to a
+ selectable, without digging throughout the whole
+ structure wasting time.
+ """
+ element = element._annotate(annotations)
+ element._copy_internals()
+ return element
+
+
+def _new_annotation_type(
+ cls: Type[SupportsWrappingAnnotations], base_cls: Type[Annotated]
+) -> Type[Annotated]:
+ """Generates a new class that subclasses Annotated and proxies a given
+ element type.
+
+ """
+ if issubclass(cls, Annotated):
+ return cls
+ elif cls in annotated_classes:
+ return annotated_classes[cls]
+
+ for super_ in cls.__mro__:
+ # check if an Annotated subclass more specific than
+ # the given base_cls is already registered, such
+ # as AnnotatedColumnElement.
+ if super_ in annotated_classes:
+ base_cls = annotated_classes[super_]
+ break
+
+ annotated_classes[cls] = anno_cls = cast(
+ Type[Annotated],
+ type("Annotated%s" % cls.__name__, (base_cls, cls), {}),
+ )
+ globals()["Annotated%s" % cls.__name__] = anno_cls
+
+ if "_traverse_internals" in cls.__dict__:
+ anno_cls._traverse_internals = list(cls._traverse_internals) + [
+ ("_annotations", InternalTraversal.dp_annotations_key)
+ ]
+ elif cls.__dict__.get("inherit_cache", False):
+ anno_cls._traverse_internals = list(cls._traverse_internals) + [
+ ("_annotations", InternalTraversal.dp_annotations_key)
+ ]
+
+ # some classes include this even if they have traverse_internals
+ # e.g. BindParameter, add it if present.
+ if cls.__dict__.get("inherit_cache", False):
+ anno_cls.inherit_cache = True # type: ignore
+ elif "inherit_cache" in cls.__dict__:
+ anno_cls.inherit_cache = cls.__dict__["inherit_cache"] # type: ignore
+
+ anno_cls._is_column_operators = issubclass(cls, operators.ColumnOperators)
+
+ return anno_cls
+
+
+def _prepare_annotations(
+ target_hierarchy: Type[SupportsWrappingAnnotations],
+ base_cls: Type[Annotated],
+) -> None:
+ for cls in util.walk_subclasses(target_hierarchy):
+ _new_annotation_type(cls, base_cls)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/base.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/base.py
new file mode 100644
index 0000000..5eb32e3
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/base.py
@@ -0,0 +1,2180 @@
+# sql/base.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+# mypy: allow-untyped-defs, allow-untyped-calls
+
+"""Foundational utilities common to many sql modules.
+
+"""
+
+
+from __future__ import annotations
+
+import collections
+from enum import Enum
+import itertools
+from itertools import zip_longest
+import operator
+import re
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import Dict
+from typing import FrozenSet
+from typing import Generic
+from typing import Iterable
+from typing import Iterator
+from typing import List
+from typing import Mapping
+from typing import MutableMapping
+from typing import NamedTuple
+from typing import NoReturn
+from typing import Optional
+from typing import overload
+from typing import Sequence
+from typing import Set
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from . import roles
+from . import visitors
+from .cache_key import HasCacheKey # noqa
+from .cache_key import MemoizedHasCacheKey # noqa
+from .traversals import HasCopyInternals # noqa
+from .visitors import ClauseVisitor
+from .visitors import ExtendedInternalTraversal
+from .visitors import ExternallyTraversible
+from .visitors import InternalTraversal
+from .. import event
+from .. import exc
+from .. import util
+from ..util import HasMemoized as HasMemoized
+from ..util import hybridmethod
+from ..util import typing as compat_typing
+from ..util.typing import Protocol
+from ..util.typing import Self
+from ..util.typing import TypeGuard
+
+if TYPE_CHECKING:
+ from . import coercions
+ from . import elements
+ from . import type_api
+ from ._orm_types import DMLStrategyArgument
+ from ._orm_types import SynchronizeSessionArgument
+ from ._typing import _CLE
+ from .elements import BindParameter
+ from .elements import ClauseList
+ from .elements import ColumnClause # noqa
+ from .elements import ColumnElement
+ from .elements import KeyedColumnElement
+ from .elements import NamedColumn
+ from .elements import SQLCoreOperations
+ from .elements import TextClause
+ from .schema import Column
+ from .schema import DefaultGenerator
+ from .selectable import _JoinTargetElement
+ from .selectable import _SelectIterable
+ from .selectable import FromClause
+ from ..engine import Connection
+ from ..engine import CursorResult
+ from ..engine.interfaces import _CoreMultiExecuteParams
+ from ..engine.interfaces import _ExecuteOptions
+ from ..engine.interfaces import _ImmutableExecuteOptions
+ from ..engine.interfaces import CacheStats
+ from ..engine.interfaces import Compiled
+ from ..engine.interfaces import CompiledCacheType
+ from ..engine.interfaces import CoreExecuteOptionsParameter
+ from ..engine.interfaces import Dialect
+ from ..engine.interfaces import IsolationLevel
+ from ..engine.interfaces import SchemaTranslateMapType
+ from ..event import dispatcher
+
+if not TYPE_CHECKING:
+ coercions = None # noqa
+ elements = None # noqa
+ type_api = None # noqa
+
+
+class _NoArg(Enum):
+ NO_ARG = 0
+
+ def __repr__(self):
+ return f"_NoArg.{self.name}"
+
+
+NO_ARG = _NoArg.NO_ARG
+
+
+class _NoneName(Enum):
+ NONE_NAME = 0
+ """indicate a 'deferred' name that was ultimately the value None."""
+
+
+_NONE_NAME = _NoneName.NONE_NAME
+
+_T = TypeVar("_T", bound=Any)
+
+_Fn = TypeVar("_Fn", bound=Callable[..., Any])
+
+_AmbiguousTableNameMap = MutableMapping[str, str]
+
+
+class _DefaultDescriptionTuple(NamedTuple):
+ arg: Any
+ is_scalar: Optional[bool]
+ is_callable: Optional[bool]
+ is_sentinel: Optional[bool]
+
+ @classmethod
+ def _from_column_default(
+ cls, default: Optional[DefaultGenerator]
+ ) -> _DefaultDescriptionTuple:
+ return (
+ _DefaultDescriptionTuple(
+ default.arg, # type: ignore
+ default.is_scalar,
+ default.is_callable,
+ default.is_sentinel,
+ )
+ if default
+ and (
+ default.has_arg
+ or (not default.for_update and default.is_sentinel)
+ )
+ else _DefaultDescriptionTuple(None, None, None, None)
+ )
+
+
+_never_select_column = operator.attrgetter("_omit_from_statements")
+
+
+class _EntityNamespace(Protocol):
+ def __getattr__(self, key: str) -> SQLCoreOperations[Any]: ...
+
+
+class _HasEntityNamespace(Protocol):
+ @util.ro_non_memoized_property
+ def entity_namespace(self) -> _EntityNamespace: ...
+
+
+def _is_has_entity_namespace(element: Any) -> TypeGuard[_HasEntityNamespace]:
+ return hasattr(element, "entity_namespace")
+
+
+# Remove when https://github.com/python/mypy/issues/14640 will be fixed
+_Self = TypeVar("_Self", bound=Any)
+
+
+class Immutable:
+ """mark a ClauseElement as 'immutable' when expressions are cloned.
+
+ "immutable" objects refers to the "mutability" of an object in the
+ context of SQL DQL and DML generation. Such as, in DQL, one can
+ compose a SELECT or subquery of varied forms, but one cannot modify
+ the structure of a specific table or column within DQL.
+ :class:`.Immutable` is mostly intended to follow this concept, and as
+ such the primary "immutable" objects are :class:`.ColumnClause`,
+ :class:`.Column`, :class:`.TableClause`, :class:`.Table`.
+
+ """
+
+ __slots__ = ()
+
+ _is_immutable = True
+
+ def unique_params(self, *optionaldict, **kwargs):
+ raise NotImplementedError("Immutable objects do not support copying")
+
+ def params(self, *optionaldict, **kwargs):
+ raise NotImplementedError("Immutable objects do not support copying")
+
+ def _clone(self: _Self, **kw: Any) -> _Self:
+ return self
+
+ def _copy_internals(
+ self, *, omit_attrs: Iterable[str] = (), **kw: Any
+ ) -> None:
+ pass
+
+
+class SingletonConstant(Immutable):
+ """Represent SQL constants like NULL, TRUE, FALSE"""
+
+ _is_singleton_constant = True
+
+ _singleton: SingletonConstant
+
+ def __new__(cls: _T, *arg: Any, **kw: Any) -> _T:
+ return cast(_T, cls._singleton)
+
+ @util.non_memoized_property
+ def proxy_set(self) -> FrozenSet[ColumnElement[Any]]:
+ raise NotImplementedError()
+
+ @classmethod
+ def _create_singleton(cls):
+ obj = object.__new__(cls)
+ obj.__init__() # type: ignore
+
+ # for a long time this was an empty frozenset, meaning
+ # a SingletonConstant would never be a "corresponding column" in
+ # a statement. This referred to #6259. However, in #7154 we see
+ # that we do in fact need "correspondence" to work when matching cols
+ # in result sets, so the non-correspondence was moved to a more
+ # specific level when we are actually adapting expressions for SQL
+ # render only.
+ obj.proxy_set = frozenset([obj])
+ cls._singleton = obj
+
+
+def _from_objects(
+ *elements: Union[
+ ColumnElement[Any], FromClause, TextClause, _JoinTargetElement
+ ]
+) -> Iterator[FromClause]:
+ return itertools.chain.from_iterable(
+ [element._from_objects for element in elements]
+ )
+
+
+def _select_iterables(
+ elements: Iterable[roles.ColumnsClauseRole],
+) -> _SelectIterable:
+ """expand tables into individual columns in the
+ given list of column expressions.
+
+ """
+ return itertools.chain.from_iterable(
+ [c._select_iterable for c in elements]
+ )
+
+
+_SelfGenerativeType = TypeVar("_SelfGenerativeType", bound="_GenerativeType")
+
+
+class _GenerativeType(compat_typing.Protocol):
+ def _generate(self) -> Self: ...
+
+
+def _generative(fn: _Fn) -> _Fn:
+ """non-caching _generative() decorator.
+
+ This is basically the legacy decorator that copies the object and
+ runs a method on the new copy.
+
+ """
+
+ @util.decorator
+ def _generative(
+ fn: _Fn, self: _SelfGenerativeType, *args: Any, **kw: Any
+ ) -> _SelfGenerativeType:
+ """Mark a method as generative."""
+
+ self = self._generate()
+ x = fn(self, *args, **kw)
+ assert x is self, "generative methods must return self"
+ return self
+
+ decorated = _generative(fn)
+ decorated.non_generative = fn # type: ignore
+ return decorated
+
+
+def _exclusive_against(*names: str, **kw: Any) -> Callable[[_Fn], _Fn]:
+ msgs = kw.pop("msgs", {})
+
+ defaults = kw.pop("defaults", {})
+
+ getters = [
+ (name, operator.attrgetter(name), defaults.get(name, None))
+ for name in names
+ ]
+
+ @util.decorator
+ def check(fn, *args, **kw):
+ # make pylance happy by not including "self" in the argument
+ # list
+ self = args[0]
+ args = args[1:]
+ for name, getter, default_ in getters:
+ if getter(self) is not default_:
+ msg = msgs.get(
+ name,
+ "Method %s() has already been invoked on this %s construct"
+ % (fn.__name__, self.__class__),
+ )
+ raise exc.InvalidRequestError(msg)
+ return fn(self, *args, **kw)
+
+ return check
+
+
+def _clone(element, **kw):
+ return element._clone(**kw)
+
+
+def _expand_cloned(
+ elements: Iterable[_CLE],
+) -> Iterable[_CLE]:
+ """expand the given set of ClauseElements to be the set of all 'cloned'
+ predecessors.
+
+ """
+ # TODO: cython candidate
+ return itertools.chain(*[x._cloned_set for x in elements])
+
+
+def _de_clone(
+ elements: Iterable[_CLE],
+) -> Iterable[_CLE]:
+ for x in elements:
+ while x._is_clone_of is not None:
+ x = x._is_clone_of
+ yield x
+
+
+def _cloned_intersection(a: Iterable[_CLE], b: Iterable[_CLE]) -> Set[_CLE]:
+ """return the intersection of sets a and b, counting
+ any overlap between 'cloned' predecessors.
+
+ The returned set is in terms of the entities present within 'a'.
+
+ """
+ all_overlap = set(_expand_cloned(a)).intersection(_expand_cloned(b))
+ return {elem for elem in a if all_overlap.intersection(elem._cloned_set)}
+
+
+def _cloned_difference(a: Iterable[_CLE], b: Iterable[_CLE]) -> Set[_CLE]:
+ all_overlap = set(_expand_cloned(a)).intersection(_expand_cloned(b))
+ return {
+ elem for elem in a if not all_overlap.intersection(elem._cloned_set)
+ }
+
+
+class _DialectArgView(MutableMapping[str, Any]):
+ """A dictionary view of dialect-level arguments in the form
+ <dialectname>_<argument_name>.
+
+ """
+
+ def __init__(self, obj):
+ self.obj = obj
+
+ def _key(self, key):
+ try:
+ dialect, value_key = key.split("_", 1)
+ except ValueError as err:
+ raise KeyError(key) from err
+ else:
+ return dialect, value_key
+
+ def __getitem__(self, key):
+ dialect, value_key = self._key(key)
+
+ try:
+ opt = self.obj.dialect_options[dialect]
+ except exc.NoSuchModuleError as err:
+ raise KeyError(key) from err
+ else:
+ return opt[value_key]
+
+ def __setitem__(self, key, value):
+ try:
+ dialect, value_key = self._key(key)
+ except KeyError as err:
+ raise exc.ArgumentError(
+ "Keys must be of the form <dialectname>_<argname>"
+ ) from err
+ else:
+ self.obj.dialect_options[dialect][value_key] = value
+
+ def __delitem__(self, key):
+ dialect, value_key = self._key(key)
+ del self.obj.dialect_options[dialect][value_key]
+
+ def __len__(self):
+ return sum(
+ len(args._non_defaults)
+ for args in self.obj.dialect_options.values()
+ )
+
+ def __iter__(self):
+ return (
+ "%s_%s" % (dialect_name, value_name)
+ for dialect_name in self.obj.dialect_options
+ for value_name in self.obj.dialect_options[
+ dialect_name
+ ]._non_defaults
+ )
+
+
+class _DialectArgDict(MutableMapping[str, Any]):
+ """A dictionary view of dialect-level arguments for a specific
+ dialect.
+
+ Maintains a separate collection of user-specified arguments
+ and dialect-specified default arguments.
+
+ """
+
+ def __init__(self):
+ self._non_defaults = {}
+ self._defaults = {}
+
+ def __len__(self):
+ return len(set(self._non_defaults).union(self._defaults))
+
+ def __iter__(self):
+ return iter(set(self._non_defaults).union(self._defaults))
+
+ def __getitem__(self, key):
+ if key in self._non_defaults:
+ return self._non_defaults[key]
+ else:
+ return self._defaults[key]
+
+ def __setitem__(self, key, value):
+ self._non_defaults[key] = value
+
+ def __delitem__(self, key):
+ del self._non_defaults[key]
+
+
+@util.preload_module("sqlalchemy.dialects")
+def _kw_reg_for_dialect(dialect_name):
+ dialect_cls = util.preloaded.dialects.registry.load(dialect_name)
+ if dialect_cls.construct_arguments is None:
+ return None
+ return dict(dialect_cls.construct_arguments)
+
+
+class DialectKWArgs:
+ """Establish the ability for a class to have dialect-specific arguments
+ with defaults and constructor validation.
+
+ The :class:`.DialectKWArgs` interacts with the
+ :attr:`.DefaultDialect.construct_arguments` present on a dialect.
+
+ .. seealso::
+
+ :attr:`.DefaultDialect.construct_arguments`
+
+ """
+
+ __slots__ = ()
+
+ _dialect_kwargs_traverse_internals = [
+ ("dialect_options", InternalTraversal.dp_dialect_options)
+ ]
+
+ @classmethod
+ def argument_for(cls, dialect_name, argument_name, default):
+ """Add a new kind of dialect-specific keyword argument for this class.
+
+ E.g.::
+
+ Index.argument_for("mydialect", "length", None)
+
+ some_index = Index('a', 'b', mydialect_length=5)
+
+ The :meth:`.DialectKWArgs.argument_for` method is a per-argument
+ way adding extra arguments to the
+ :attr:`.DefaultDialect.construct_arguments` dictionary. This
+ dictionary provides a list of argument names accepted by various
+ schema-level constructs on behalf of a dialect.
+
+ New dialects should typically specify this dictionary all at once as a
+ data member of the dialect class. The use case for ad-hoc addition of
+ argument names is typically for end-user code that is also using
+ a custom compilation scheme which consumes the additional arguments.
+
+ :param dialect_name: name of a dialect. The dialect must be
+ locatable, else a :class:`.NoSuchModuleError` is raised. The
+ dialect must also include an existing
+ :attr:`.DefaultDialect.construct_arguments` collection, indicating
+ that it participates in the keyword-argument validation and default
+ system, else :class:`.ArgumentError` is raised. If the dialect does
+ not include this collection, then any keyword argument can be
+ specified on behalf of this dialect already. All dialects packaged
+ within SQLAlchemy include this collection, however for third party
+ dialects, support may vary.
+
+ :param argument_name: name of the parameter.
+
+ :param default: default value of the parameter.
+
+ """
+
+ construct_arg_dictionary = DialectKWArgs._kw_registry[dialect_name]
+ if construct_arg_dictionary is None:
+ raise exc.ArgumentError(
+ "Dialect '%s' does have keyword-argument "
+ "validation and defaults enabled configured" % dialect_name
+ )
+ if cls not in construct_arg_dictionary:
+ construct_arg_dictionary[cls] = {}
+ construct_arg_dictionary[cls][argument_name] = default
+
+ @util.memoized_property
+ def dialect_kwargs(self):
+ """A collection of keyword arguments specified as dialect-specific
+ options to this construct.
+
+ The arguments are present here in their original ``<dialect>_<kwarg>``
+ format. Only arguments that were actually passed are included;
+ unlike the :attr:`.DialectKWArgs.dialect_options` collection, which
+ contains all options known by this dialect including defaults.
+
+ The collection is also writable; keys are accepted of the
+ form ``<dialect>_<kwarg>`` where the value will be assembled
+ into the list of options.
+
+ .. seealso::
+
+ :attr:`.DialectKWArgs.dialect_options` - nested dictionary form
+
+ """
+ return _DialectArgView(self)
+
+ @property
+ def kwargs(self):
+ """A synonym for :attr:`.DialectKWArgs.dialect_kwargs`."""
+ return self.dialect_kwargs
+
+ _kw_registry = util.PopulateDict(_kw_reg_for_dialect)
+
+ def _kw_reg_for_dialect_cls(self, dialect_name):
+ construct_arg_dictionary = DialectKWArgs._kw_registry[dialect_name]
+ d = _DialectArgDict()
+
+ if construct_arg_dictionary is None:
+ d._defaults.update({"*": None})
+ else:
+ for cls in reversed(self.__class__.__mro__):
+ if cls in construct_arg_dictionary:
+ d._defaults.update(construct_arg_dictionary[cls])
+ return d
+
+ @util.memoized_property
+ def dialect_options(self):
+ """A collection of keyword arguments specified as dialect-specific
+ options to this construct.
+
+ This is a two-level nested registry, keyed to ``<dialect_name>``
+ and ``<argument_name>``. For example, the ``postgresql_where``
+ argument would be locatable as::
+
+ arg = my_object.dialect_options['postgresql']['where']
+
+ .. versionadded:: 0.9.2
+
+ .. seealso::
+
+ :attr:`.DialectKWArgs.dialect_kwargs` - flat dictionary form
+
+ """
+
+ return util.PopulateDict(
+ util.portable_instancemethod(self._kw_reg_for_dialect_cls)
+ )
+
+ def _validate_dialect_kwargs(self, kwargs: Dict[str, Any]) -> None:
+ # validate remaining kwargs that they all specify DB prefixes
+
+ if not kwargs:
+ return
+
+ for k in kwargs:
+ m = re.match("^(.+?)_(.+)$", k)
+ if not m:
+ raise TypeError(
+ "Additional arguments should be "
+ "named <dialectname>_<argument>, got '%s'" % k
+ )
+ dialect_name, arg_name = m.group(1, 2)
+
+ try:
+ construct_arg_dictionary = self.dialect_options[dialect_name]
+ except exc.NoSuchModuleError:
+ util.warn(
+ "Can't validate argument %r; can't "
+ "locate any SQLAlchemy dialect named %r"
+ % (k, dialect_name)
+ )
+ self.dialect_options[dialect_name] = d = _DialectArgDict()
+ d._defaults.update({"*": None})
+ d._non_defaults[arg_name] = kwargs[k]
+ else:
+ if (
+ "*" not in construct_arg_dictionary
+ and arg_name not in construct_arg_dictionary
+ ):
+ raise exc.ArgumentError(
+ "Argument %r is not accepted by "
+ "dialect %r on behalf of %r"
+ % (k, dialect_name, self.__class__)
+ )
+ else:
+ construct_arg_dictionary[arg_name] = kwargs[k]
+
+
+class CompileState:
+ """Produces additional object state necessary for a statement to be
+ compiled.
+
+ the :class:`.CompileState` class is at the base of classes that assemble
+ state for a particular statement object that is then used by the
+ compiler. This process is essentially an extension of the process that
+ the SQLCompiler.visit_XYZ() method takes, however there is an emphasis
+ on converting raw user intent into more organized structures rather than
+ producing string output. The top-level :class:`.CompileState` for the
+ statement being executed is also accessible when the execution context
+ works with invoking the statement and collecting results.
+
+ The production of :class:`.CompileState` is specific to the compiler, such
+ as within the :meth:`.SQLCompiler.visit_insert`,
+ :meth:`.SQLCompiler.visit_select` etc. methods. These methods are also
+ responsible for associating the :class:`.CompileState` with the
+ :class:`.SQLCompiler` itself, if the statement is the "toplevel" statement,
+ i.e. the outermost SQL statement that's actually being executed.
+ There can be other :class:`.CompileState` objects that are not the
+ toplevel, such as when a SELECT subquery or CTE-nested
+ INSERT/UPDATE/DELETE is generated.
+
+ .. versionadded:: 1.4
+
+ """
+
+ __slots__ = ("statement", "_ambiguous_table_name_map")
+
+ plugins: Dict[Tuple[str, str], Type[CompileState]] = {}
+
+ _ambiguous_table_name_map: Optional[_AmbiguousTableNameMap]
+
+ @classmethod
+ def create_for_statement(cls, statement, compiler, **kw):
+ # factory construction.
+
+ if statement._propagate_attrs:
+ plugin_name = statement._propagate_attrs.get(
+ "compile_state_plugin", "default"
+ )
+ klass = cls.plugins.get(
+ (plugin_name, statement._effective_plugin_target), None
+ )
+ if klass is None:
+ klass = cls.plugins[
+ ("default", statement._effective_plugin_target)
+ ]
+
+ else:
+ klass = cls.plugins[
+ ("default", statement._effective_plugin_target)
+ ]
+
+ if klass is cls:
+ return cls(statement, compiler, **kw)
+ else:
+ return klass.create_for_statement(statement, compiler, **kw)
+
+ def __init__(self, statement, compiler, **kw):
+ self.statement = statement
+
+ @classmethod
+ def get_plugin_class(
+ cls, statement: Executable
+ ) -> Optional[Type[CompileState]]:
+ plugin_name = statement._propagate_attrs.get(
+ "compile_state_plugin", None
+ )
+
+ if plugin_name:
+ key = (plugin_name, statement._effective_plugin_target)
+ if key in cls.plugins:
+ return cls.plugins[key]
+
+ # there's no case where we call upon get_plugin_class() and want
+ # to get None back, there should always be a default. return that
+ # if there was no plugin-specific class (e.g. Insert with "orm"
+ # plugin)
+ try:
+ return cls.plugins[("default", statement._effective_plugin_target)]
+ except KeyError:
+ return None
+
+ @classmethod
+ def _get_plugin_class_for_plugin(
+ cls, statement: Executable, plugin_name: str
+ ) -> Optional[Type[CompileState]]:
+ try:
+ return cls.plugins[
+ (plugin_name, statement._effective_plugin_target)
+ ]
+ except KeyError:
+ return None
+
+ @classmethod
+ def plugin_for(
+ cls, plugin_name: str, visit_name: str
+ ) -> Callable[[_Fn], _Fn]:
+ def decorate(cls_to_decorate):
+ cls.plugins[(plugin_name, visit_name)] = cls_to_decorate
+ return cls_to_decorate
+
+ return decorate
+
+
+class Generative(HasMemoized):
+ """Provide a method-chaining pattern in conjunction with the
+ @_generative decorator."""
+
+ def _generate(self) -> Self:
+ skip = self._memoized_keys
+ cls = self.__class__
+ s = cls.__new__(cls)
+ if skip:
+ # ensure this iteration remains atomic
+ s.__dict__ = {
+ k: v for k, v in self.__dict__.copy().items() if k not in skip
+ }
+ else:
+ s.__dict__ = self.__dict__.copy()
+ return s
+
+
+class InPlaceGenerative(HasMemoized):
+ """Provide a method-chaining pattern in conjunction with the
+ @_generative decorator that mutates in place."""
+
+ __slots__ = ()
+
+ def _generate(self):
+ skip = self._memoized_keys
+ # note __dict__ needs to be in __slots__ if this is used
+ for k in skip:
+ self.__dict__.pop(k, None)
+ return self
+
+
+class HasCompileState(Generative):
+ """A class that has a :class:`.CompileState` associated with it."""
+
+ _compile_state_plugin: Optional[Type[CompileState]] = None
+
+ _attributes: util.immutabledict[str, Any] = util.EMPTY_DICT
+
+ _compile_state_factory = CompileState.create_for_statement
+
+
+class _MetaOptions(type):
+ """metaclass for the Options class.
+
+ This metaclass is actually necessary despite the availability of the
+ ``__init_subclass__()`` hook as this type also provides custom class-level
+ behavior for the ``__add__()`` method.
+
+ """
+
+ _cache_attrs: Tuple[str, ...]
+
+ def __add__(self, other):
+ o1 = self()
+
+ if set(other).difference(self._cache_attrs):
+ raise TypeError(
+ "dictionary contains attributes not covered by "
+ "Options class %s: %r"
+ % (self, set(other).difference(self._cache_attrs))
+ )
+
+ o1.__dict__.update(other)
+ return o1
+
+ if TYPE_CHECKING:
+
+ def __getattr__(self, key: str) -> Any: ...
+
+ def __setattr__(self, key: str, value: Any) -> None: ...
+
+ def __delattr__(self, key: str) -> None: ...
+
+
+class Options(metaclass=_MetaOptions):
+ """A cacheable option dictionary with defaults."""
+
+ __slots__ = ()
+
+ _cache_attrs: Tuple[str, ...]
+
+ def __init_subclass__(cls) -> None:
+ dict_ = cls.__dict__
+ cls._cache_attrs = tuple(
+ sorted(
+ d
+ for d in dict_
+ if not d.startswith("__")
+ and d not in ("_cache_key_traversal",)
+ )
+ )
+ super().__init_subclass__()
+
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+
+ def __add__(self, other):
+ o1 = self.__class__.__new__(self.__class__)
+ o1.__dict__.update(self.__dict__)
+
+ if set(other).difference(self._cache_attrs):
+ raise TypeError(
+ "dictionary contains attributes not covered by "
+ "Options class %s: %r"
+ % (self, set(other).difference(self._cache_attrs))
+ )
+
+ o1.__dict__.update(other)
+ return o1
+
+ def __eq__(self, other):
+ # TODO: very inefficient. This is used only in test suites
+ # right now.
+ for a, b in zip_longest(self._cache_attrs, other._cache_attrs):
+ if getattr(self, a) != getattr(other, b):
+ return False
+ return True
+
+ def __repr__(self):
+ # TODO: fairly inefficient, used only in debugging right now.
+
+ return "%s(%s)" % (
+ self.__class__.__name__,
+ ", ".join(
+ "%s=%r" % (k, self.__dict__[k])
+ for k in self._cache_attrs
+ if k in self.__dict__
+ ),
+ )
+
+ @classmethod
+ def isinstance(cls, klass: Type[Any]) -> bool:
+ return issubclass(cls, klass)
+
+ @hybridmethod
+ def add_to_element(self, name, value):
+ return self + {name: getattr(self, name) + value}
+
+ @hybridmethod
+ def _state_dict_inst(self) -> Mapping[str, Any]:
+ return self.__dict__
+
+ _state_dict_const: util.immutabledict[str, Any] = util.EMPTY_DICT
+
+ @_state_dict_inst.classlevel
+ def _state_dict(cls) -> Mapping[str, Any]:
+ return cls._state_dict_const
+
+ @classmethod
+ def safe_merge(cls, other):
+ d = other._state_dict()
+
+ # only support a merge with another object of our class
+ # and which does not have attrs that we don't. otherwise
+ # we risk having state that might not be part of our cache
+ # key strategy
+
+ if (
+ cls is not other.__class__
+ and other._cache_attrs
+ and set(other._cache_attrs).difference(cls._cache_attrs)
+ ):
+ raise TypeError(
+ "other element %r is not empty, is not of type %s, "
+ "and contains attributes not covered here %r"
+ % (
+ other,
+ cls,
+ set(other._cache_attrs).difference(cls._cache_attrs),
+ )
+ )
+ return cls + d
+
+ @classmethod
+ def from_execution_options(
+ cls, key, attrs, exec_options, statement_exec_options
+ ):
+ """process Options argument in terms of execution options.
+
+
+ e.g.::
+
+ (
+ load_options,
+ execution_options,
+ ) = QueryContext.default_load_options.from_execution_options(
+ "_sa_orm_load_options",
+ {
+ "populate_existing",
+ "autoflush",
+ "yield_per"
+ },
+ execution_options,
+ statement._execution_options,
+ )
+
+ get back the Options and refresh "_sa_orm_load_options" in the
+ exec options dict w/ the Options as well
+
+ """
+
+ # common case is that no options we are looking for are
+ # in either dictionary, so cancel for that first
+ check_argnames = attrs.intersection(
+ set(exec_options).union(statement_exec_options)
+ )
+
+ existing_options = exec_options.get(key, cls)
+
+ if check_argnames:
+ result = {}
+ for argname in check_argnames:
+ local = "_" + argname
+ if argname in exec_options:
+ result[local] = exec_options[argname]
+ elif argname in statement_exec_options:
+ result[local] = statement_exec_options[argname]
+
+ new_options = existing_options + result
+ exec_options = util.immutabledict().merge_with(
+ exec_options, {key: new_options}
+ )
+ return new_options, exec_options
+
+ else:
+ return existing_options, exec_options
+
+ if TYPE_CHECKING:
+
+ def __getattr__(self, key: str) -> Any: ...
+
+ def __setattr__(self, key: str, value: Any) -> None: ...
+
+ def __delattr__(self, key: str) -> None: ...
+
+
+class CacheableOptions(Options, HasCacheKey):
+ __slots__ = ()
+
+ @hybridmethod
+ def _gen_cache_key_inst(self, anon_map, bindparams):
+ return HasCacheKey._gen_cache_key(self, anon_map, bindparams)
+
+ @_gen_cache_key_inst.classlevel
+ def _gen_cache_key(cls, anon_map, bindparams):
+ return (cls, ())
+
+ @hybridmethod
+ def _generate_cache_key(self):
+ return HasCacheKey._generate_cache_key_for_object(self)
+
+
+class ExecutableOption(HasCopyInternals):
+ __slots__ = ()
+
+ _annotations = util.EMPTY_DICT
+
+ __visit_name__ = "executable_option"
+
+ _is_has_cache_key = False
+
+ _is_core = True
+
+ def _clone(self, **kw):
+ """Create a shallow copy of this ExecutableOption."""
+ c = self.__class__.__new__(self.__class__)
+ c.__dict__ = dict(self.__dict__) # type: ignore
+ return c
+
+
+class Executable(roles.StatementRole):
+ """Mark a :class:`_expression.ClauseElement` as supporting execution.
+
+ :class:`.Executable` is a superclass for all "statement" types
+ of objects, including :func:`select`, :func:`delete`, :func:`update`,
+ :func:`insert`, :func:`text`.
+
+ """
+
+ supports_execution: bool = True
+ _execution_options: _ImmutableExecuteOptions = util.EMPTY_DICT
+ _is_default_generator = False
+ _with_options: Tuple[ExecutableOption, ...] = ()
+ _with_context_options: Tuple[
+ Tuple[Callable[[CompileState], None], Any], ...
+ ] = ()
+ _compile_options: Optional[Union[Type[CacheableOptions], CacheableOptions]]
+
+ _executable_traverse_internals = [
+ ("_with_options", InternalTraversal.dp_executable_options),
+ (
+ "_with_context_options",
+ ExtendedInternalTraversal.dp_with_context_options,
+ ),
+ ("_propagate_attrs", ExtendedInternalTraversal.dp_propagate_attrs),
+ ]
+
+ is_select = False
+ is_update = False
+ is_insert = False
+ is_text = False
+ is_delete = False
+ is_dml = False
+
+ if TYPE_CHECKING:
+ __visit_name__: str
+
+ def _compile_w_cache(
+ self,
+ dialect: Dialect,
+ *,
+ compiled_cache: Optional[CompiledCacheType],
+ column_keys: List[str],
+ for_executemany: bool = False,
+ schema_translate_map: Optional[SchemaTranslateMapType] = None,
+ **kw: Any,
+ ) -> Tuple[
+ Compiled, Optional[Sequence[BindParameter[Any]]], CacheStats
+ ]: ...
+
+ def _execute_on_connection(
+ self,
+ connection: Connection,
+ distilled_params: _CoreMultiExecuteParams,
+ execution_options: CoreExecuteOptionsParameter,
+ ) -> CursorResult[Any]: ...
+
+ def _execute_on_scalar(
+ self,
+ connection: Connection,
+ distilled_params: _CoreMultiExecuteParams,
+ execution_options: CoreExecuteOptionsParameter,
+ ) -> Any: ...
+
+ @util.ro_non_memoized_property
+ def _all_selected_columns(self):
+ raise NotImplementedError()
+
+ @property
+ def _effective_plugin_target(self) -> str:
+ return self.__visit_name__
+
+ @_generative
+ def options(self, *options: ExecutableOption) -> Self:
+ """Apply options to this statement.
+
+ In the general sense, options are any kind of Python object
+ that can be interpreted by the SQL compiler for the statement.
+ These options can be consumed by specific dialects or specific kinds
+ of compilers.
+
+ The most commonly known kind of option are the ORM level options
+ that apply "eager load" and other loading behaviors to an ORM
+ query. However, options can theoretically be used for many other
+ purposes.
+
+ For background on specific kinds of options for specific kinds of
+ statements, refer to the documentation for those option objects.
+
+ .. versionchanged:: 1.4 - added :meth:`.Executable.options` to
+ Core statement objects towards the goal of allowing unified
+ Core / ORM querying capabilities.
+
+ .. seealso::
+
+ :ref:`loading_columns` - refers to options specific to the usage
+ of ORM queries
+
+ :ref:`relationship_loader_options` - refers to options specific
+ to the usage of ORM queries
+
+ """
+ self._with_options += tuple(
+ coercions.expect(roles.ExecutableOptionRole, opt)
+ for opt in options
+ )
+ return self
+
+ @_generative
+ def _set_compile_options(self, compile_options: CacheableOptions) -> Self:
+ """Assign the compile options to a new value.
+
+ :param compile_options: appropriate CacheableOptions structure
+
+ """
+
+ self._compile_options = compile_options
+ return self
+
+ @_generative
+ def _update_compile_options(self, options: CacheableOptions) -> Self:
+ """update the _compile_options with new keys."""
+
+ assert self._compile_options is not None
+ self._compile_options += options
+ return self
+
+ @_generative
+ def _add_context_option(
+ self,
+ callable_: Callable[[CompileState], None],
+ cache_args: Any,
+ ) -> Self:
+ """Add a context option to this statement.
+
+ These are callable functions that will
+ be given the CompileState object upon compilation.
+
+ A second argument cache_args is required, which will be combined with
+ the ``__code__`` identity of the function itself in order to produce a
+ cache key.
+
+ """
+ self._with_context_options += ((callable_, cache_args),)
+ return self
+
+ @overload
+ def execution_options(
+ self,
+ *,
+ compiled_cache: Optional[CompiledCacheType] = ...,
+ logging_token: str = ...,
+ isolation_level: IsolationLevel = ...,
+ no_parameters: bool = False,
+ stream_results: bool = False,
+ max_row_buffer: int = ...,
+ yield_per: int = ...,
+ insertmanyvalues_page_size: int = ...,
+ schema_translate_map: Optional[SchemaTranslateMapType] = ...,
+ populate_existing: bool = False,
+ autoflush: bool = False,
+ synchronize_session: SynchronizeSessionArgument = ...,
+ dml_strategy: DMLStrategyArgument = ...,
+ render_nulls: bool = ...,
+ is_delete_using: bool = ...,
+ is_update_from: bool = ...,
+ preserve_rowcount: bool = False,
+ **opt: Any,
+ ) -> Self: ...
+
+ @overload
+ def execution_options(self, **opt: Any) -> Self: ...
+
+ @_generative
+ def execution_options(self, **kw: Any) -> Self:
+ """Set non-SQL options for the statement which take effect during
+ execution.
+
+ Execution options can be set at many scopes, including per-statement,
+ per-connection, or per execution, using methods such as
+ :meth:`_engine.Connection.execution_options` and parameters which
+ accept a dictionary of options such as
+ :paramref:`_engine.Connection.execute.execution_options` and
+ :paramref:`_orm.Session.execute.execution_options`.
+
+ The primary characteristic of an execution option, as opposed to
+ other kinds of options such as ORM loader options, is that
+ **execution options never affect the compiled SQL of a query, only
+ things that affect how the SQL statement itself is invoked or how
+ results are fetched**. That is, execution options are not part of
+ what's accommodated by SQL compilation nor are they considered part of
+ the cached state of a statement.
+
+ The :meth:`_sql.Executable.execution_options` method is
+ :term:`generative`, as
+ is the case for the method as applied to the :class:`_engine.Engine`
+ and :class:`_orm.Query` objects, which means when the method is called,
+ a copy of the object is returned, which applies the given parameters to
+ that new copy, but leaves the original unchanged::
+
+ statement = select(table.c.x, table.c.y)
+ new_statement = statement.execution_options(my_option=True)
+
+ An exception to this behavior is the :class:`_engine.Connection`
+ object, where the :meth:`_engine.Connection.execution_options` method
+ is explicitly **not** generative.
+
+ The kinds of options that may be passed to
+ :meth:`_sql.Executable.execution_options` and other related methods and
+ parameter dictionaries include parameters that are explicitly consumed
+ by SQLAlchemy Core or ORM, as well as arbitrary keyword arguments not
+ defined by SQLAlchemy, which means the methods and/or parameter
+ dictionaries may be used for user-defined parameters that interact with
+ custom code, which may access the parameters using methods such as
+ :meth:`_sql.Executable.get_execution_options` and
+ :meth:`_engine.Connection.get_execution_options`, or within selected
+ event hooks using a dedicated ``execution_options`` event parameter
+ such as
+ :paramref:`_events.ConnectionEvents.before_execute.execution_options`
+ or :attr:`_orm.ORMExecuteState.execution_options`, e.g.::
+
+ from sqlalchemy import event
+
+ @event.listens_for(some_engine, "before_execute")
+ def _process_opt(conn, statement, multiparams, params, execution_options):
+ "run a SQL function before invoking a statement"
+
+ if execution_options.get("do_special_thing", False):
+ conn.exec_driver_sql("run_special_function()")
+
+ Within the scope of options that are explicitly recognized by
+ SQLAlchemy, most apply to specific classes of objects and not others.
+ The most common execution options include:
+
+ * :paramref:`_engine.Connection.execution_options.isolation_level` -
+ sets the isolation level for a connection or a class of connections
+ via an :class:`_engine.Engine`. This option is accepted only
+ by :class:`_engine.Connection` or :class:`_engine.Engine`.
+
+ * :paramref:`_engine.Connection.execution_options.stream_results` -
+ indicates results should be fetched using a server side cursor;
+ this option is accepted by :class:`_engine.Connection`, by the
+ :paramref:`_engine.Connection.execute.execution_options` parameter
+ on :meth:`_engine.Connection.execute`, and additionally by
+ :meth:`_sql.Executable.execution_options` on a SQL statement object,
+ as well as by ORM constructs like :meth:`_orm.Session.execute`.
+
+ * :paramref:`_engine.Connection.execution_options.compiled_cache` -
+ indicates a dictionary that will serve as the
+ :ref:`SQL compilation cache <sql_caching>`
+ for a :class:`_engine.Connection` or :class:`_engine.Engine`, as
+ well as for ORM methods like :meth:`_orm.Session.execute`.
+ Can be passed as ``None`` to disable caching for statements.
+ This option is not accepted by
+ :meth:`_sql.Executable.execution_options` as it is inadvisable to
+ carry along a compilation cache within a statement object.
+
+ * :paramref:`_engine.Connection.execution_options.schema_translate_map`
+ - a mapping of schema names used by the
+ :ref:`Schema Translate Map <schema_translating>` feature, accepted
+ by :class:`_engine.Connection`, :class:`_engine.Engine`,
+ :class:`_sql.Executable`, as well as by ORM constructs
+ like :meth:`_orm.Session.execute`.
+
+ .. seealso::
+
+ :meth:`_engine.Connection.execution_options`
+
+ :paramref:`_engine.Connection.execute.execution_options`
+
+ :paramref:`_orm.Session.execute.execution_options`
+
+ :ref:`orm_queryguide_execution_options` - documentation on all
+ ORM-specific execution options
+
+ """ # noqa: E501
+ if "isolation_level" in kw:
+ raise exc.ArgumentError(
+ "'isolation_level' execution option may only be specified "
+ "on Connection.execution_options(), or "
+ "per-engine using the isolation_level "
+ "argument to create_engine()."
+ )
+ if "compiled_cache" in kw:
+ raise exc.ArgumentError(
+ "'compiled_cache' execution option may only be specified "
+ "on Connection.execution_options(), not per statement."
+ )
+ self._execution_options = self._execution_options.union(kw)
+ return self
+
+ def get_execution_options(self) -> _ExecuteOptions:
+ """Get the non-SQL options which will take effect during execution.
+
+ .. versionadded:: 1.3
+
+ .. seealso::
+
+ :meth:`.Executable.execution_options`
+ """
+ return self._execution_options
+
+
+class SchemaEventTarget(event.EventTarget):
+ """Base class for elements that are the targets of :class:`.DDLEvents`
+ events.
+
+ This includes :class:`.SchemaItem` as well as :class:`.SchemaType`.
+
+ """
+
+ dispatch: dispatcher[SchemaEventTarget]
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ """Associate with this SchemaEvent's parent object."""
+
+ def _set_parent_with_dispatch(
+ self, parent: SchemaEventTarget, **kw: Any
+ ) -> None:
+ self.dispatch.before_parent_attach(self, parent)
+ self._set_parent(parent, **kw)
+ self.dispatch.after_parent_attach(self, parent)
+
+
+class SchemaVisitor(ClauseVisitor):
+ """Define the visiting for ``SchemaItem`` objects."""
+
+ __traverse_options__ = {"schema_visitor": True}
+
+
+class _SentinelDefaultCharacterization(Enum):
+ NONE = "none"
+ UNKNOWN = "unknown"
+ CLIENTSIDE = "clientside"
+ SENTINEL_DEFAULT = "sentinel_default"
+ SERVERSIDE = "serverside"
+ IDENTITY = "identity"
+ SEQUENCE = "sequence"
+
+
+class _SentinelColumnCharacterization(NamedTuple):
+ columns: Optional[Sequence[Column[Any]]] = None
+ is_explicit: bool = False
+ is_autoinc: bool = False
+ default_characterization: _SentinelDefaultCharacterization = (
+ _SentinelDefaultCharacterization.NONE
+ )
+
+
+_COLKEY = TypeVar("_COLKEY", Union[None, str], str)
+
+_COL_co = TypeVar("_COL_co", bound="ColumnElement[Any]", covariant=True)
+_COL = TypeVar("_COL", bound="KeyedColumnElement[Any]")
+
+
+class _ColumnMetrics(Generic[_COL_co]):
+ __slots__ = ("column",)
+
+ column: _COL_co
+
+ def __init__(
+ self, collection: ColumnCollection[Any, _COL_co], col: _COL_co
+ ):
+ self.column = col
+
+ # proxy_index being non-empty means it was initialized.
+ # so we need to update it
+ pi = collection._proxy_index
+ if pi:
+ for eps_col in col._expanded_proxy_set:
+ pi[eps_col].add(self)
+
+ def get_expanded_proxy_set(self):
+ return self.column._expanded_proxy_set
+
+ def dispose(self, collection):
+ pi = collection._proxy_index
+ if not pi:
+ return
+ for col in self.column._expanded_proxy_set:
+ colset = pi.get(col, None)
+ if colset:
+ colset.discard(self)
+ if colset is not None and not colset:
+ del pi[col]
+
+ def embedded(
+ self,
+ target_set: Union[
+ Set[ColumnElement[Any]], FrozenSet[ColumnElement[Any]]
+ ],
+ ) -> bool:
+ expanded_proxy_set = self.column._expanded_proxy_set
+ for t in target_set.difference(expanded_proxy_set):
+ if not expanded_proxy_set.intersection(_expand_cloned([t])):
+ return False
+ return True
+
+
+class ColumnCollection(Generic[_COLKEY, _COL_co]):
+ """Collection of :class:`_expression.ColumnElement` instances,
+ typically for
+ :class:`_sql.FromClause` objects.
+
+ The :class:`_sql.ColumnCollection` object is most commonly available
+ as the :attr:`_schema.Table.c` or :attr:`_schema.Table.columns` collection
+ on the :class:`_schema.Table` object, introduced at
+ :ref:`metadata_tables_and_columns`.
+
+ The :class:`_expression.ColumnCollection` has both mapping- and sequence-
+ like behaviors. A :class:`_expression.ColumnCollection` usually stores
+ :class:`_schema.Column` objects, which are then accessible both via mapping
+ style access as well as attribute access style.
+
+ To access :class:`_schema.Column` objects using ordinary attribute-style
+ access, specify the name like any other object attribute, such as below
+ a column named ``employee_name`` is accessed::
+
+ >>> employee_table.c.employee_name
+
+ To access columns that have names with special characters or spaces,
+ index-style access is used, such as below which illustrates a column named
+ ``employee ' payment`` is accessed::
+
+ >>> employee_table.c["employee ' payment"]
+
+ As the :class:`_sql.ColumnCollection` object provides a Python dictionary
+ interface, common dictionary method names like
+ :meth:`_sql.ColumnCollection.keys`, :meth:`_sql.ColumnCollection.values`,
+ and :meth:`_sql.ColumnCollection.items` are available, which means that
+ database columns that are keyed under these names also need to use indexed
+ access::
+
+ >>> employee_table.c["values"]
+
+
+ The name for which a :class:`_schema.Column` would be present is normally
+ that of the :paramref:`_schema.Column.key` parameter. In some contexts,
+ such as a :class:`_sql.Select` object that uses a label style set
+ using the :meth:`_sql.Select.set_label_style` method, a column of a certain
+ key may instead be represented under a particular label name such
+ as ``tablename_columnname``::
+
+ >>> from sqlalchemy import select, column, table
+ >>> from sqlalchemy import LABEL_STYLE_TABLENAME_PLUS_COL
+ >>> t = table("t", column("c"))
+ >>> stmt = select(t).set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
+ >>> subq = stmt.subquery()
+ >>> subq.c.t_c
+ <sqlalchemy.sql.elements.ColumnClause at 0x7f59dcf04fa0; t_c>
+
+ :class:`.ColumnCollection` also indexes the columns in order and allows
+ them to be accessible by their integer position::
+
+ >>> cc[0]
+ Column('x', Integer(), table=None)
+ >>> cc[1]
+ Column('y', Integer(), table=None)
+
+ .. versionadded:: 1.4 :class:`_expression.ColumnCollection`
+ allows integer-based
+ index access to the collection.
+
+ Iterating the collection yields the column expressions in order::
+
+ >>> list(cc)
+ [Column('x', Integer(), table=None),
+ Column('y', Integer(), table=None)]
+
+ The base :class:`_expression.ColumnCollection` object can store
+ duplicates, which can
+ mean either two columns with the same key, in which case the column
+ returned by key access is **arbitrary**::
+
+ >>> x1, x2 = Column('x', Integer), Column('x', Integer)
+ >>> cc = ColumnCollection(columns=[(x1.name, x1), (x2.name, x2)])
+ >>> list(cc)
+ [Column('x', Integer(), table=None),
+ Column('x', Integer(), table=None)]
+ >>> cc['x'] is x1
+ False
+ >>> cc['x'] is x2
+ True
+
+ Or it can also mean the same column multiple times. These cases are
+ supported as :class:`_expression.ColumnCollection`
+ is used to represent the columns in
+ a SELECT statement which may include duplicates.
+
+ A special subclass :class:`.DedupeColumnCollection` exists which instead
+ maintains SQLAlchemy's older behavior of not allowing duplicates; this
+ collection is used for schema level objects like :class:`_schema.Table`
+ and
+ :class:`.PrimaryKeyConstraint` where this deduping is helpful. The
+ :class:`.DedupeColumnCollection` class also has additional mutation methods
+ as the schema constructs have more use cases that require removal and
+ replacement of columns.
+
+ .. versionchanged:: 1.4 :class:`_expression.ColumnCollection`
+ now stores duplicate
+ column keys as well as the same column in multiple positions. The
+ :class:`.DedupeColumnCollection` class is added to maintain the
+ former behavior in those cases where deduplication as well as
+ additional replace/remove operations are needed.
+
+
+ """
+
+ __slots__ = "_collection", "_index", "_colset", "_proxy_index"
+
+ _collection: List[Tuple[_COLKEY, _COL_co, _ColumnMetrics[_COL_co]]]
+ _index: Dict[Union[None, str, int], Tuple[_COLKEY, _COL_co]]
+ _proxy_index: Dict[ColumnElement[Any], Set[_ColumnMetrics[_COL_co]]]
+ _colset: Set[_COL_co]
+
+ def __init__(
+ self, columns: Optional[Iterable[Tuple[_COLKEY, _COL_co]]] = None
+ ):
+ object.__setattr__(self, "_colset", set())
+ object.__setattr__(self, "_index", {})
+ object.__setattr__(
+ self, "_proxy_index", collections.defaultdict(util.OrderedSet)
+ )
+ object.__setattr__(self, "_collection", [])
+ if columns:
+ self._initial_populate(columns)
+
+ @util.preload_module("sqlalchemy.sql.elements")
+ def __clause_element__(self) -> ClauseList:
+ elements = util.preloaded.sql_elements
+
+ return elements.ClauseList(
+ _literal_as_text_role=roles.ColumnsClauseRole,
+ group=False,
+ *self._all_columns,
+ )
+
+ def _initial_populate(
+ self, iter_: Iterable[Tuple[_COLKEY, _COL_co]]
+ ) -> None:
+ self._populate_separate_keys(iter_)
+
+ @property
+ def _all_columns(self) -> List[_COL_co]:
+ return [col for (_, col, _) in self._collection]
+
+ def keys(self) -> List[_COLKEY]:
+ """Return a sequence of string key names for all columns in this
+ collection."""
+ return [k for (k, _, _) in self._collection]
+
+ def values(self) -> List[_COL_co]:
+ """Return a sequence of :class:`_sql.ColumnClause` or
+ :class:`_schema.Column` objects for all columns in this
+ collection."""
+ return [col for (_, col, _) in self._collection]
+
+ def items(self) -> List[Tuple[_COLKEY, _COL_co]]:
+ """Return a sequence of (key, column) tuples for all columns in this
+ collection each consisting of a string key name and a
+ :class:`_sql.ColumnClause` or
+ :class:`_schema.Column` object.
+ """
+
+ return [(k, col) for (k, col, _) in self._collection]
+
+ def __bool__(self) -> bool:
+ return bool(self._collection)
+
+ def __len__(self) -> int:
+ return len(self._collection)
+
+ def __iter__(self) -> Iterator[_COL_co]:
+ # turn to a list first to maintain over a course of changes
+ return iter([col for _, col, _ in self._collection])
+
+ @overload
+ def __getitem__(self, key: Union[str, int]) -> _COL_co: ...
+
+ @overload
+ def __getitem__(
+ self, key: Tuple[Union[str, int], ...]
+ ) -> ReadOnlyColumnCollection[_COLKEY, _COL_co]: ...
+
+ @overload
+ def __getitem__(
+ self, key: slice
+ ) -> ReadOnlyColumnCollection[_COLKEY, _COL_co]: ...
+
+ def __getitem__(
+ self, key: Union[str, int, slice, Tuple[Union[str, int], ...]]
+ ) -> Union[ReadOnlyColumnCollection[_COLKEY, _COL_co], _COL_co]:
+ try:
+ if isinstance(key, (tuple, slice)):
+ if isinstance(key, slice):
+ cols = (
+ (sub_key, col)
+ for (sub_key, col, _) in self._collection[key]
+ )
+ else:
+ cols = (self._index[sub_key] for sub_key in key)
+
+ return ColumnCollection(cols).as_readonly()
+ else:
+ return self._index[key][1]
+ except KeyError as err:
+ if isinstance(err.args[0], int):
+ raise IndexError(err.args[0]) from err
+ else:
+ raise
+
+ def __getattr__(self, key: str) -> _COL_co:
+ try:
+ return self._index[key][1]
+ except KeyError as err:
+ raise AttributeError(key) from err
+
+ def __contains__(self, key: str) -> bool:
+ if key not in self._index:
+ if not isinstance(key, str):
+ raise exc.ArgumentError(
+ "__contains__ requires a string argument"
+ )
+ return False
+ else:
+ return True
+
+ def compare(self, other: ColumnCollection[Any, Any]) -> bool:
+ """Compare this :class:`_expression.ColumnCollection` to another
+ based on the names of the keys"""
+
+ for l, r in zip_longest(self, other):
+ if l is not r:
+ return False
+ else:
+ return True
+
+ def __eq__(self, other: Any) -> bool:
+ return self.compare(other)
+
+ def get(
+ self, key: str, default: Optional[_COL_co] = None
+ ) -> Optional[_COL_co]:
+ """Get a :class:`_sql.ColumnClause` or :class:`_schema.Column` object
+ based on a string key name from this
+ :class:`_expression.ColumnCollection`."""
+
+ if key in self._index:
+ return self._index[key][1]
+ else:
+ return default
+
+ def __str__(self) -> str:
+ return "%s(%s)" % (
+ self.__class__.__name__,
+ ", ".join(str(c) for c in self),
+ )
+
+ def __setitem__(self, key: str, value: Any) -> NoReturn:
+ raise NotImplementedError()
+
+ def __delitem__(self, key: str) -> NoReturn:
+ raise NotImplementedError()
+
+ def __setattr__(self, key: str, obj: Any) -> NoReturn:
+ raise NotImplementedError()
+
+ def clear(self) -> NoReturn:
+ """Dictionary clear() is not implemented for
+ :class:`_sql.ColumnCollection`."""
+ raise NotImplementedError()
+
+ def remove(self, column: Any) -> None:
+ raise NotImplementedError()
+
+ def update(self, iter_: Any) -> NoReturn:
+ """Dictionary update() is not implemented for
+ :class:`_sql.ColumnCollection`."""
+ raise NotImplementedError()
+
+ # https://github.com/python/mypy/issues/4266
+ __hash__ = None # type: ignore
+
+ def _populate_separate_keys(
+ self, iter_: Iterable[Tuple[_COLKEY, _COL_co]]
+ ) -> None:
+ """populate from an iterator of (key, column)"""
+
+ self._collection[:] = collection = [
+ (k, c, _ColumnMetrics(self, c)) for k, c in iter_
+ ]
+ self._colset.update(c._deannotate() for _, c, _ in collection)
+ self._index.update(
+ {idx: (k, c) for idx, (k, c, _) in enumerate(collection)}
+ )
+ self._index.update({k: (k, col) for k, col, _ in reversed(collection)})
+
+ def add(
+ self, column: ColumnElement[Any], key: Optional[_COLKEY] = None
+ ) -> None:
+ """Add a column to this :class:`_sql.ColumnCollection`.
+
+ .. note::
+
+ This method is **not normally used by user-facing code**, as the
+ :class:`_sql.ColumnCollection` is usually part of an existing
+ object such as a :class:`_schema.Table`. To add a
+ :class:`_schema.Column` to an existing :class:`_schema.Table`
+ object, use the :meth:`_schema.Table.append_column` method.
+
+ """
+ colkey: _COLKEY
+
+ if key is None:
+ colkey = column.key # type: ignore
+ else:
+ colkey = key
+
+ l = len(self._collection)
+
+ # don't really know how this part is supposed to work w/ the
+ # covariant thing
+
+ _column = cast(_COL_co, column)
+
+ self._collection.append(
+ (colkey, _column, _ColumnMetrics(self, _column))
+ )
+ self._colset.add(_column._deannotate())
+ self._index[l] = (colkey, _column)
+ if colkey not in self._index:
+ self._index[colkey] = (colkey, _column)
+
+ def __getstate__(self) -> Dict[str, Any]:
+ return {
+ "_collection": [(k, c) for k, c, _ in self._collection],
+ "_index": self._index,
+ }
+
+ def __setstate__(self, state: Dict[str, Any]) -> None:
+ object.__setattr__(self, "_index", state["_index"])
+ object.__setattr__(
+ self, "_proxy_index", collections.defaultdict(util.OrderedSet)
+ )
+ object.__setattr__(
+ self,
+ "_collection",
+ [
+ (k, c, _ColumnMetrics(self, c))
+ for (k, c) in state["_collection"]
+ ],
+ )
+ object.__setattr__(
+ self, "_colset", {col for k, col, _ in self._collection}
+ )
+
+ def contains_column(self, col: ColumnElement[Any]) -> bool:
+ """Checks if a column object exists in this collection"""
+ if col not in self._colset:
+ if isinstance(col, str):
+ raise exc.ArgumentError(
+ "contains_column cannot be used with string arguments. "
+ "Use ``col_name in table.c`` instead."
+ )
+ return False
+ else:
+ return True
+
+ def as_readonly(self) -> ReadOnlyColumnCollection[_COLKEY, _COL_co]:
+ """Return a "read only" form of this
+ :class:`_sql.ColumnCollection`."""
+
+ return ReadOnlyColumnCollection(self)
+
+ def _init_proxy_index(self):
+ """populate the "proxy index", if empty.
+
+ proxy index is added in 2.0 to provide more efficient operation
+ for the corresponding_column() method.
+
+ For reasons of both time to construct new .c collections as well as
+ memory conservation for large numbers of large .c collections, the
+ proxy_index is only filled if corresponding_column() is called. once
+ filled it stays that way, and new _ColumnMetrics objects created after
+ that point will populate it with new data. Note this case would be
+ unusual, if not nonexistent, as it means a .c collection is being
+ mutated after corresponding_column() were used, however it is tested in
+ test/base/test_utils.py.
+
+ """
+ pi = self._proxy_index
+ if pi:
+ return
+
+ for _, _, metrics in self._collection:
+ eps = metrics.column._expanded_proxy_set
+
+ for eps_col in eps:
+ pi[eps_col].add(metrics)
+
+ def corresponding_column(
+ self, column: _COL, require_embedded: bool = False
+ ) -> Optional[Union[_COL, _COL_co]]:
+ """Given a :class:`_expression.ColumnElement`, return the exported
+ :class:`_expression.ColumnElement` object from this
+ :class:`_expression.ColumnCollection`
+ which corresponds to that original :class:`_expression.ColumnElement`
+ via a common
+ ancestor column.
+
+ :param column: the target :class:`_expression.ColumnElement`
+ to be matched.
+
+ :param require_embedded: only return corresponding columns for
+ the given :class:`_expression.ColumnElement`, if the given
+ :class:`_expression.ColumnElement`
+ is actually present within a sub-element
+ of this :class:`_expression.Selectable`.
+ Normally the column will match if
+ it merely shares a common ancestor with one of the exported
+ columns of this :class:`_expression.Selectable`.
+
+ .. seealso::
+
+ :meth:`_expression.Selectable.corresponding_column`
+ - invokes this method
+ against the collection returned by
+ :attr:`_expression.Selectable.exported_columns`.
+
+ .. versionchanged:: 1.4 the implementation for ``corresponding_column``
+ was moved onto the :class:`_expression.ColumnCollection` itself.
+
+ """
+ # TODO: cython candidate
+
+ # don't dig around if the column is locally present
+ if column in self._colset:
+ return column
+
+ selected_intersection, selected_metrics = None, None
+ target_set = column.proxy_set
+
+ pi = self._proxy_index
+ if not pi:
+ self._init_proxy_index()
+
+ for current_metrics in (
+ mm for ts in target_set if ts in pi for mm in pi[ts]
+ ):
+ if not require_embedded or current_metrics.embedded(target_set):
+ if selected_metrics is None:
+ # no corresponding column yet, pick this one.
+ selected_metrics = current_metrics
+ continue
+
+ current_intersection = target_set.intersection(
+ current_metrics.column._expanded_proxy_set
+ )
+ if selected_intersection is None:
+ selected_intersection = target_set.intersection(
+ selected_metrics.column._expanded_proxy_set
+ )
+
+ if len(current_intersection) > len(selected_intersection):
+ # 'current' has a larger field of correspondence than
+ # 'selected'. i.e. selectable.c.a1_x->a1.c.x->table.c.x
+ # matches a1.c.x->table.c.x better than
+ # selectable.c.x->table.c.x does.
+
+ selected_metrics = current_metrics
+ selected_intersection = current_intersection
+ elif current_intersection == selected_intersection:
+ # they have the same field of correspondence. see
+ # which proxy_set has fewer columns in it, which
+ # indicates a closer relationship with the root
+ # column. Also take into account the "weight"
+ # attribute which CompoundSelect() uses to give
+ # higher precedence to columns based on vertical
+ # position in the compound statement, and discard
+ # columns that have no reference to the target
+ # column (also occurs with CompoundSelect)
+
+ selected_col_distance = sum(
+ [
+ sc._annotations.get("weight", 1)
+ for sc in (
+ selected_metrics.column._uncached_proxy_list()
+ )
+ if sc.shares_lineage(column)
+ ],
+ )
+ current_col_distance = sum(
+ [
+ sc._annotations.get("weight", 1)
+ for sc in (
+ current_metrics.column._uncached_proxy_list()
+ )
+ if sc.shares_lineage(column)
+ ],
+ )
+ if current_col_distance < selected_col_distance:
+ selected_metrics = current_metrics
+ selected_intersection = current_intersection
+
+ return selected_metrics.column if selected_metrics else None
+
+
+_NAMEDCOL = TypeVar("_NAMEDCOL", bound="NamedColumn[Any]")
+
+
+class DedupeColumnCollection(ColumnCollection[str, _NAMEDCOL]):
+ """A :class:`_expression.ColumnCollection`
+ that maintains deduplicating behavior.
+
+ This is useful by schema level objects such as :class:`_schema.Table` and
+ :class:`.PrimaryKeyConstraint`. The collection includes more
+ sophisticated mutator methods as well to suit schema objects which
+ require mutable column collections.
+
+ .. versionadded:: 1.4
+
+ """
+
+ def add(
+ self, column: ColumnElement[Any], key: Optional[str] = None
+ ) -> None:
+ named_column = cast(_NAMEDCOL, column)
+ if key is not None and named_column.key != key:
+ raise exc.ArgumentError(
+ "DedupeColumnCollection requires columns be under "
+ "the same key as their .key"
+ )
+ key = named_column.key
+
+ if key is None:
+ raise exc.ArgumentError(
+ "Can't add unnamed column to column collection"
+ )
+
+ if key in self._index:
+ existing = self._index[key][1]
+
+ if existing is named_column:
+ return
+
+ self.replace(named_column)
+
+ # pop out memoized proxy_set as this
+ # operation may very well be occurring
+ # in a _make_proxy operation
+ util.memoized_property.reset(named_column, "proxy_set")
+ else:
+ self._append_new_column(key, named_column)
+
+ def _append_new_column(self, key: str, named_column: _NAMEDCOL) -> None:
+ l = len(self._collection)
+ self._collection.append(
+ (key, named_column, _ColumnMetrics(self, named_column))
+ )
+ self._colset.add(named_column._deannotate())
+ self._index[l] = (key, named_column)
+ self._index[key] = (key, named_column)
+
+ def _populate_separate_keys(
+ self, iter_: Iterable[Tuple[str, _NAMEDCOL]]
+ ) -> None:
+ """populate from an iterator of (key, column)"""
+ cols = list(iter_)
+
+ replace_col = []
+ for k, col in cols:
+ if col.key != k:
+ raise exc.ArgumentError(
+ "DedupeColumnCollection requires columns be under "
+ "the same key as their .key"
+ )
+ if col.name in self._index and col.key != col.name:
+ replace_col.append(col)
+ elif col.key in self._index:
+ replace_col.append(col)
+ else:
+ self._index[k] = (k, col)
+ self._collection.append((k, col, _ColumnMetrics(self, col)))
+ self._colset.update(c._deannotate() for (k, c, _) in self._collection)
+
+ self._index.update(
+ (idx, (k, c)) for idx, (k, c, _) in enumerate(self._collection)
+ )
+ for col in replace_col:
+ self.replace(col)
+
+ def extend(self, iter_: Iterable[_NAMEDCOL]) -> None:
+ self._populate_separate_keys((col.key, col) for col in iter_)
+
+ def remove(self, column: _NAMEDCOL) -> None:
+ if column not in self._colset:
+ raise ValueError(
+ "Can't remove column %r; column is not in this collection"
+ % column
+ )
+ del self._index[column.key]
+ self._colset.remove(column)
+ self._collection[:] = [
+ (k, c, metrics)
+ for (k, c, metrics) in self._collection
+ if c is not column
+ ]
+ for metrics in self._proxy_index.get(column, ()):
+ metrics.dispose(self)
+
+ self._index.update(
+ {idx: (k, col) for idx, (k, col, _) in enumerate(self._collection)}
+ )
+ # delete higher index
+ del self._index[len(self._collection)]
+
+ def replace(
+ self,
+ column: _NAMEDCOL,
+ extra_remove: Optional[Iterable[_NAMEDCOL]] = None,
+ ) -> None:
+ """add the given column to this collection, removing unaliased
+ versions of this column as well as existing columns with the
+ same key.
+
+ e.g.::
+
+ t = Table('sometable', metadata, Column('col1', Integer))
+ t.columns.replace(Column('col1', Integer, key='columnone'))
+
+ will remove the original 'col1' from the collection, and add
+ the new column under the name 'columnname'.
+
+ Used by schema.Column to override columns during table reflection.
+
+ """
+
+ if extra_remove:
+ remove_col = set(extra_remove)
+ else:
+ remove_col = set()
+ # remove up to two columns based on matches of name as well as key
+ if column.name in self._index and column.key != column.name:
+ other = self._index[column.name][1]
+ if other.name == other.key:
+ remove_col.add(other)
+
+ if column.key in self._index:
+ remove_col.add(self._index[column.key][1])
+
+ if not remove_col:
+ self._append_new_column(column.key, column)
+ return
+ new_cols: List[Tuple[str, _NAMEDCOL, _ColumnMetrics[_NAMEDCOL]]] = []
+ replaced = False
+ for k, col, metrics in self._collection:
+ if col in remove_col:
+ if not replaced:
+ replaced = True
+ new_cols.append(
+ (column.key, column, _ColumnMetrics(self, column))
+ )
+ else:
+ new_cols.append((k, col, metrics))
+
+ if remove_col:
+ self._colset.difference_update(remove_col)
+
+ for rc in remove_col:
+ for metrics in self._proxy_index.get(rc, ()):
+ metrics.dispose(self)
+
+ if not replaced:
+ new_cols.append((column.key, column, _ColumnMetrics(self, column)))
+
+ self._colset.add(column._deannotate())
+ self._collection[:] = new_cols
+
+ self._index.clear()
+
+ self._index.update(
+ {idx: (k, col) for idx, (k, col, _) in enumerate(self._collection)}
+ )
+ self._index.update({k: (k, col) for (k, col, _) in self._collection})
+
+
+class ReadOnlyColumnCollection(
+ util.ReadOnlyContainer, ColumnCollection[_COLKEY, _COL_co]
+):
+ __slots__ = ("_parent",)
+
+ def __init__(self, collection):
+ object.__setattr__(self, "_parent", collection)
+ object.__setattr__(self, "_colset", collection._colset)
+ object.__setattr__(self, "_index", collection._index)
+ object.__setattr__(self, "_collection", collection._collection)
+ object.__setattr__(self, "_proxy_index", collection._proxy_index)
+
+ def __getstate__(self):
+ return {"_parent": self._parent}
+
+ def __setstate__(self, state):
+ parent = state["_parent"]
+ self.__init__(parent) # type: ignore
+
+ def add(self, column: Any, key: Any = ...) -> Any:
+ self._readonly()
+
+ def extend(self, elements: Any) -> NoReturn:
+ self._readonly()
+
+ def remove(self, item: Any) -> NoReturn:
+ self._readonly()
+
+
+class ColumnSet(util.OrderedSet["ColumnClause[Any]"]):
+ def contains_column(self, col):
+ return col in self
+
+ def extend(self, cols):
+ for col in cols:
+ self.add(col)
+
+ def __eq__(self, other):
+ l = []
+ for c in other:
+ for local in self:
+ if c.shares_lineage(local):
+ l.append(c == local)
+ return elements.and_(*l)
+
+ def __hash__(self):
+ return hash(tuple(x for x in self))
+
+
+def _entity_namespace(
+ entity: Union[_HasEntityNamespace, ExternallyTraversible]
+) -> _EntityNamespace:
+ """Return the nearest .entity_namespace for the given entity.
+
+ If not immediately available, does an iterate to find a sub-element
+ that has one, if any.
+
+ """
+ try:
+ return cast(_HasEntityNamespace, entity).entity_namespace
+ except AttributeError:
+ for elem in visitors.iterate(cast(ExternallyTraversible, entity)):
+ if _is_has_entity_namespace(elem):
+ return elem.entity_namespace
+ else:
+ raise
+
+
+def _entity_namespace_key(
+ entity: Union[_HasEntityNamespace, ExternallyTraversible],
+ key: str,
+ default: Union[SQLCoreOperations[Any], _NoArg] = NO_ARG,
+) -> SQLCoreOperations[Any]:
+ """Return an entry from an entity_namespace.
+
+
+ Raises :class:`_exc.InvalidRequestError` rather than attribute error
+ on not found.
+
+ """
+
+ try:
+ ns = _entity_namespace(entity)
+ if default is not NO_ARG:
+ return getattr(ns, key, default)
+ else:
+ return getattr(ns, key) # type: ignore
+ except AttributeError as err:
+ raise exc.InvalidRequestError(
+ 'Entity namespace for "%s" has no property "%s"' % (entity, key)
+ ) from err
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/cache_key.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/cache_key.py
new file mode 100644
index 0000000..1172d3c
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/cache_key.py
@@ -0,0 +1,1057 @@
+# sql/cache_key.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+from __future__ import annotations
+
+import enum
+from itertools import zip_longest
+import typing
+from typing import Any
+from typing import Callable
+from typing import Dict
+from typing import Iterable
+from typing import Iterator
+from typing import List
+from typing import MutableMapping
+from typing import NamedTuple
+from typing import Optional
+from typing import Sequence
+from typing import Tuple
+from typing import Union
+
+from .visitors import anon_map
+from .visitors import HasTraversalDispatch
+from .visitors import HasTraverseInternals
+from .visitors import InternalTraversal
+from .visitors import prefix_anon_map
+from .. import util
+from ..inspection import inspect
+from ..util import HasMemoized
+from ..util.typing import Literal
+from ..util.typing import Protocol
+
+if typing.TYPE_CHECKING:
+ from .elements import BindParameter
+ from .elements import ClauseElement
+ from .elements import ColumnElement
+ from .visitors import _TraverseInternalsType
+ from ..engine.interfaces import _CoreSingleExecuteParams
+
+
+class _CacheKeyTraversalDispatchType(Protocol):
+ def __call__(
+ s, self: HasCacheKey, visitor: _CacheKeyTraversal
+ ) -> _CacheKeyTraversalDispatchTypeReturn: ...
+
+
+class CacheConst(enum.Enum):
+ NO_CACHE = 0
+
+
+NO_CACHE = CacheConst.NO_CACHE
+
+
+_CacheKeyTraversalType = Union[
+ "_TraverseInternalsType", Literal[CacheConst.NO_CACHE], Literal[None]
+]
+
+
+class CacheTraverseTarget(enum.Enum):
+ CACHE_IN_PLACE = 0
+ CALL_GEN_CACHE_KEY = 1
+ STATIC_CACHE_KEY = 2
+ PROPAGATE_ATTRS = 3
+ ANON_NAME = 4
+
+
+(
+ CACHE_IN_PLACE,
+ CALL_GEN_CACHE_KEY,
+ STATIC_CACHE_KEY,
+ PROPAGATE_ATTRS,
+ ANON_NAME,
+) = tuple(CacheTraverseTarget)
+
+_CacheKeyTraversalDispatchTypeReturn = Sequence[
+ Tuple[
+ str,
+ Any,
+ Union[
+ Callable[..., Tuple[Any, ...]],
+ CacheTraverseTarget,
+ InternalTraversal,
+ ],
+ ]
+]
+
+
+class HasCacheKey:
+ """Mixin for objects which can produce a cache key.
+
+ This class is usually in a hierarchy that starts with the
+ :class:`.HasTraverseInternals` base, but this is optional. Currently,
+ the class should be able to work on its own without including
+ :class:`.HasTraverseInternals`.
+
+ .. seealso::
+
+ :class:`.CacheKey`
+
+ :ref:`sql_caching`
+
+ """
+
+ __slots__ = ()
+
+ _cache_key_traversal: _CacheKeyTraversalType = NO_CACHE
+
+ _is_has_cache_key = True
+
+ _hierarchy_supports_caching = True
+ """private attribute which may be set to False to prevent the
+ inherit_cache warning from being emitted for a hierarchy of subclasses.
+
+ Currently applies to the :class:`.ExecutableDDLElement` hierarchy which
+ does not implement caching.
+
+ """
+
+ inherit_cache: Optional[bool] = None
+ """Indicate if this :class:`.HasCacheKey` instance should make use of the
+ cache key generation scheme used by its immediate superclass.
+
+ The attribute defaults to ``None``, which indicates that a construct has
+ not yet taken into account whether or not its appropriate for it to
+ participate in caching; this is functionally equivalent to setting the
+ value to ``False``, except that a warning is also emitted.
+
+ This flag can be set to ``True`` on a particular class, if the SQL that
+ corresponds to the object does not change based on attributes which
+ are local to this class, and not its superclass.
+
+ .. seealso::
+
+ :ref:`compilerext_caching` - General guideslines for setting the
+ :attr:`.HasCacheKey.inherit_cache` attribute for third-party or user
+ defined SQL constructs.
+
+ """
+
+ __slots__ = ()
+
+ _generated_cache_key_traversal: Any
+
+ @classmethod
+ def _generate_cache_attrs(
+ cls,
+ ) -> Union[_CacheKeyTraversalDispatchType, Literal[CacheConst.NO_CACHE]]:
+ """generate cache key dispatcher for a new class.
+
+ This sets the _generated_cache_key_traversal attribute once called
+ so should only be called once per class.
+
+ """
+ inherit_cache = cls.__dict__.get("inherit_cache", None)
+ inherit = bool(inherit_cache)
+
+ if inherit:
+ _cache_key_traversal = getattr(cls, "_cache_key_traversal", None)
+ if _cache_key_traversal is None:
+ try:
+ assert issubclass(cls, HasTraverseInternals)
+ _cache_key_traversal = cls._traverse_internals
+ except AttributeError:
+ cls._generated_cache_key_traversal = NO_CACHE
+ return NO_CACHE
+
+ assert _cache_key_traversal is not NO_CACHE, (
+ f"class {cls} has _cache_key_traversal=NO_CACHE, "
+ "which conflicts with inherit_cache=True"
+ )
+
+ # TODO: wouldn't we instead get this from our superclass?
+ # also, our superclass may not have this yet, but in any case,
+ # we'd generate for the superclass that has it. this is a little
+ # more complicated, so for the moment this is a little less
+ # efficient on startup but simpler.
+ return _cache_key_traversal_visitor.generate_dispatch(
+ cls,
+ _cache_key_traversal,
+ "_generated_cache_key_traversal",
+ )
+ else:
+ _cache_key_traversal = cls.__dict__.get(
+ "_cache_key_traversal", None
+ )
+ if _cache_key_traversal is None:
+ _cache_key_traversal = cls.__dict__.get(
+ "_traverse_internals", None
+ )
+ if _cache_key_traversal is None:
+ cls._generated_cache_key_traversal = NO_CACHE
+ if (
+ inherit_cache is None
+ and cls._hierarchy_supports_caching
+ ):
+ util.warn(
+ "Class %s will not make use of SQL compilation "
+ "caching as it does not set the 'inherit_cache' "
+ "attribute to ``True``. This can have "
+ "significant performance implications including "
+ "some performance degradations in comparison to "
+ "prior SQLAlchemy versions. Set this attribute "
+ "to True if this object can make use of the cache "
+ "key generated by the superclass. Alternatively, "
+ "this attribute may be set to False which will "
+ "disable this warning." % (cls.__name__),
+ code="cprf",
+ )
+ return NO_CACHE
+
+ return _cache_key_traversal_visitor.generate_dispatch(
+ cls,
+ _cache_key_traversal,
+ "_generated_cache_key_traversal",
+ )
+
+ @util.preload_module("sqlalchemy.sql.elements")
+ def _gen_cache_key(
+ self, anon_map: anon_map, bindparams: List[BindParameter[Any]]
+ ) -> Optional[Tuple[Any, ...]]:
+ """return an optional cache key.
+
+ The cache key is a tuple which can contain any series of
+ objects that are hashable and also identifies
+ this object uniquely within the presence of a larger SQL expression
+ or statement, for the purposes of caching the resulting query.
+
+ The cache key should be based on the SQL compiled structure that would
+ ultimately be produced. That is, two structures that are composed in
+ exactly the same way should produce the same cache key; any difference
+ in the structures that would affect the SQL string or the type handlers
+ should result in a different cache key.
+
+ If a structure cannot produce a useful cache key, the NO_CACHE
+ symbol should be added to the anon_map and the method should
+ return None.
+
+ """
+
+ cls = self.__class__
+
+ id_, found = anon_map.get_anon(self)
+ if found:
+ return (id_, cls)
+
+ dispatcher: Union[
+ Literal[CacheConst.NO_CACHE],
+ _CacheKeyTraversalDispatchType,
+ ]
+
+ try:
+ dispatcher = cls.__dict__["_generated_cache_key_traversal"]
+ except KeyError:
+ # traversals.py -> _preconfigure_traversals()
+ # may be used to run these ahead of time, but
+ # is not enabled right now.
+ # this block will generate any remaining dispatchers.
+ dispatcher = cls._generate_cache_attrs()
+
+ if dispatcher is NO_CACHE:
+ anon_map[NO_CACHE] = True
+ return None
+
+ result: Tuple[Any, ...] = (id_, cls)
+
+ # inline of _cache_key_traversal_visitor.run_generated_dispatch()
+
+ for attrname, obj, meth in dispatcher(
+ self, _cache_key_traversal_visitor
+ ):
+ if obj is not None:
+ # TODO: see if C code can help here as Python lacks an
+ # efficient switch construct
+
+ if meth is STATIC_CACHE_KEY:
+ sck = obj._static_cache_key
+ if sck is NO_CACHE:
+ anon_map[NO_CACHE] = True
+ return None
+ result += (attrname, sck)
+ elif meth is ANON_NAME:
+ elements = util.preloaded.sql_elements
+ if isinstance(obj, elements._anonymous_label):
+ obj = obj.apply_map(anon_map) # type: ignore
+ result += (attrname, obj)
+ elif meth is CALL_GEN_CACHE_KEY:
+ result += (
+ attrname,
+ obj._gen_cache_key(anon_map, bindparams),
+ )
+
+ # remaining cache functions are against
+ # Python tuples, dicts, lists, etc. so we can skip
+ # if they are empty
+ elif obj:
+ if meth is CACHE_IN_PLACE:
+ result += (attrname, obj)
+ elif meth is PROPAGATE_ATTRS:
+ result += (
+ attrname,
+ obj["compile_state_plugin"],
+ (
+ obj["plugin_subject"]._gen_cache_key(
+ anon_map, bindparams
+ )
+ if obj["plugin_subject"]
+ else None
+ ),
+ )
+ elif meth is InternalTraversal.dp_annotations_key:
+ # obj is here is the _annotations dict. Table uses
+ # a memoized version of it. however in other cases,
+ # we generate it given anon_map as we may be from a
+ # Join, Aliased, etc.
+ # see #8790
+
+ if self._gen_static_annotations_cache_key: # type: ignore # noqa: E501
+ result += self._annotations_cache_key # type: ignore # noqa: E501
+ else:
+ result += self._gen_annotations_cache_key(anon_map) # type: ignore # noqa: E501
+
+ elif (
+ meth is InternalTraversal.dp_clauseelement_list
+ or meth is InternalTraversal.dp_clauseelement_tuple
+ or meth
+ is InternalTraversal.dp_memoized_select_entities
+ ):
+ result += (
+ attrname,
+ tuple(
+ [
+ elem._gen_cache_key(anon_map, bindparams)
+ for elem in obj
+ ]
+ ),
+ )
+ else:
+ result += meth( # type: ignore
+ attrname, obj, self, anon_map, bindparams
+ )
+ return result
+
+ def _generate_cache_key(self) -> Optional[CacheKey]:
+ """return a cache key.
+
+ The cache key is a tuple which can contain any series of
+ objects that are hashable and also identifies
+ this object uniquely within the presence of a larger SQL expression
+ or statement, for the purposes of caching the resulting query.
+
+ The cache key should be based on the SQL compiled structure that would
+ ultimately be produced. That is, two structures that are composed in
+ exactly the same way should produce the same cache key; any difference
+ in the structures that would affect the SQL string or the type handlers
+ should result in a different cache key.
+
+ The cache key returned by this method is an instance of
+ :class:`.CacheKey`, which consists of a tuple representing the
+ cache key, as well as a list of :class:`.BindParameter` objects
+ which are extracted from the expression. While two expressions
+ that produce identical cache key tuples will themselves generate
+ identical SQL strings, the list of :class:`.BindParameter` objects
+ indicates the bound values which may have different values in
+ each one; these bound parameters must be consulted in order to
+ execute the statement with the correct parameters.
+
+ a :class:`_expression.ClauseElement` structure that does not implement
+ a :meth:`._gen_cache_key` method and does not implement a
+ :attr:`.traverse_internals` attribute will not be cacheable; when
+ such an element is embedded into a larger structure, this method
+ will return None, indicating no cache key is available.
+
+ """
+
+ bindparams: List[BindParameter[Any]] = []
+
+ _anon_map = anon_map()
+ key = self._gen_cache_key(_anon_map, bindparams)
+ if NO_CACHE in _anon_map:
+ return None
+ else:
+ assert key is not None
+ return CacheKey(key, bindparams)
+
+ @classmethod
+ def _generate_cache_key_for_object(
+ cls, obj: HasCacheKey
+ ) -> Optional[CacheKey]:
+ bindparams: List[BindParameter[Any]] = []
+
+ _anon_map = anon_map()
+ key = obj._gen_cache_key(_anon_map, bindparams)
+ if NO_CACHE in _anon_map:
+ return None
+ else:
+ assert key is not None
+ return CacheKey(key, bindparams)
+
+
+class HasCacheKeyTraverse(HasTraverseInternals, HasCacheKey):
+ pass
+
+
+class MemoizedHasCacheKey(HasCacheKey, HasMemoized):
+ __slots__ = ()
+
+ @HasMemoized.memoized_instancemethod
+ def _generate_cache_key(self) -> Optional[CacheKey]:
+ return HasCacheKey._generate_cache_key(self)
+
+
+class SlotsMemoizedHasCacheKey(HasCacheKey, util.MemoizedSlots):
+ __slots__ = ()
+
+ def _memoized_method__generate_cache_key(self) -> Optional[CacheKey]:
+ return HasCacheKey._generate_cache_key(self)
+
+
+class CacheKey(NamedTuple):
+ """The key used to identify a SQL statement construct in the
+ SQL compilation cache.
+
+ .. seealso::
+
+ :ref:`sql_caching`
+
+ """
+
+ key: Tuple[Any, ...]
+ bindparams: Sequence[BindParameter[Any]]
+
+ # can't set __hash__ attribute because it interferes
+ # with namedtuple
+ # can't use "if not TYPE_CHECKING" because mypy rejects it
+ # inside of a NamedTuple
+ def __hash__(self) -> Optional[int]: # type: ignore
+ """CacheKey itself is not hashable - hash the .key portion"""
+ return None
+
+ def to_offline_string(
+ self,
+ statement_cache: MutableMapping[Any, str],
+ statement: ClauseElement,
+ parameters: _CoreSingleExecuteParams,
+ ) -> str:
+ """Generate an "offline string" form of this :class:`.CacheKey`
+
+ The "offline string" is basically the string SQL for the
+ statement plus a repr of the bound parameter values in series.
+ Whereas the :class:`.CacheKey` object is dependent on in-memory
+ identities in order to work as a cache key, the "offline" version
+ is suitable for a cache that will work for other processes as well.
+
+ The given ``statement_cache`` is a dictionary-like object where the
+ string form of the statement itself will be cached. This dictionary
+ should be in a longer lived scope in order to reduce the time spent
+ stringifying statements.
+
+
+ """
+ if self.key not in statement_cache:
+ statement_cache[self.key] = sql_str = str(statement)
+ else:
+ sql_str = statement_cache[self.key]
+
+ if not self.bindparams:
+ param_tuple = tuple(parameters[key] for key in sorted(parameters))
+ else:
+ param_tuple = tuple(
+ parameters.get(bindparam.key, bindparam.value)
+ for bindparam in self.bindparams
+ )
+
+ return repr((sql_str, param_tuple))
+
+ def __eq__(self, other: Any) -> bool:
+ return bool(self.key == other.key)
+
+ def __ne__(self, other: Any) -> bool:
+ return not (self.key == other.key)
+
+ @classmethod
+ def _diff_tuples(cls, left: CacheKey, right: CacheKey) -> str:
+ ck1 = CacheKey(left, [])
+ ck2 = CacheKey(right, [])
+ return ck1._diff(ck2)
+
+ def _whats_different(self, other: CacheKey) -> Iterator[str]:
+ k1 = self.key
+ k2 = other.key
+
+ stack: List[int] = []
+ pickup_index = 0
+ while True:
+ s1, s2 = k1, k2
+ for idx in stack:
+ s1 = s1[idx]
+ s2 = s2[idx]
+
+ for idx, (e1, e2) in enumerate(zip_longest(s1, s2)):
+ if idx < pickup_index:
+ continue
+ if e1 != e2:
+ if isinstance(e1, tuple) and isinstance(e2, tuple):
+ stack.append(idx)
+ break
+ else:
+ yield "key%s[%d]: %s != %s" % (
+ "".join("[%d]" % id_ for id_ in stack),
+ idx,
+ e1,
+ e2,
+ )
+ else:
+ pickup_index = stack.pop(-1)
+ break
+
+ def _diff(self, other: CacheKey) -> str:
+ return ", ".join(self._whats_different(other))
+
+ def __str__(self) -> str:
+ stack: List[Union[Tuple[Any, ...], HasCacheKey]] = [self.key]
+
+ output = []
+ sentinel = object()
+ indent = -1
+ while stack:
+ elem = stack.pop(0)
+ if elem is sentinel:
+ output.append((" " * (indent * 2)) + "),")
+ indent -= 1
+ elif isinstance(elem, tuple):
+ if not elem:
+ output.append((" " * ((indent + 1) * 2)) + "()")
+ else:
+ indent += 1
+ stack = list(elem) + [sentinel] + stack
+ output.append((" " * (indent * 2)) + "(")
+ else:
+ if isinstance(elem, HasCacheKey):
+ repr_ = "<%s object at %s>" % (
+ type(elem).__name__,
+ hex(id(elem)),
+ )
+ else:
+ repr_ = repr(elem)
+ output.append((" " * (indent * 2)) + " " + repr_ + ", ")
+
+ return "CacheKey(key=%s)" % ("\n".join(output),)
+
+ def _generate_param_dict(self) -> Dict[str, Any]:
+ """used for testing"""
+
+ _anon_map = prefix_anon_map()
+ return {b.key % _anon_map: b.effective_value for b in self.bindparams}
+
+ @util.preload_module("sqlalchemy.sql.elements")
+ def _apply_params_to_element(
+ self, original_cache_key: CacheKey, target_element: ColumnElement[Any]
+ ) -> ColumnElement[Any]:
+ if target_element._is_immutable or original_cache_key is self:
+ return target_element
+
+ elements = util.preloaded.sql_elements
+ return elements._OverrideBinds(
+ target_element, self.bindparams, original_cache_key.bindparams
+ )
+
+
+def _ad_hoc_cache_key_from_args(
+ tokens: Tuple[Any, ...],
+ traverse_args: Iterable[Tuple[str, InternalTraversal]],
+ args: Iterable[Any],
+) -> Tuple[Any, ...]:
+ """a quick cache key generator used by reflection.flexi_cache."""
+ bindparams: List[BindParameter[Any]] = []
+
+ _anon_map = anon_map()
+
+ tup = tokens
+
+ for (attrname, sym), arg in zip(traverse_args, args):
+ key = sym.name
+ visit_key = key.replace("dp_", "visit_")
+
+ if arg is None:
+ tup += (attrname, None)
+ continue
+
+ meth = getattr(_cache_key_traversal_visitor, visit_key)
+ if meth is CACHE_IN_PLACE:
+ tup += (attrname, arg)
+ elif meth in (
+ CALL_GEN_CACHE_KEY,
+ STATIC_CACHE_KEY,
+ ANON_NAME,
+ PROPAGATE_ATTRS,
+ ):
+ raise NotImplementedError(
+ f"Haven't implemented symbol {meth} for ad-hoc key from args"
+ )
+ else:
+ tup += meth(attrname, arg, None, _anon_map, bindparams)
+ return tup
+
+
+class _CacheKeyTraversal(HasTraversalDispatch):
+ # very common elements are inlined into the main _get_cache_key() method
+ # to produce a dramatic savings in Python function call overhead
+
+ visit_has_cache_key = visit_clauseelement = CALL_GEN_CACHE_KEY
+ visit_clauseelement_list = InternalTraversal.dp_clauseelement_list
+ visit_annotations_key = InternalTraversal.dp_annotations_key
+ visit_clauseelement_tuple = InternalTraversal.dp_clauseelement_tuple
+ visit_memoized_select_entities = (
+ InternalTraversal.dp_memoized_select_entities
+ )
+
+ visit_string = visit_boolean = visit_operator = visit_plain_obj = (
+ CACHE_IN_PLACE
+ )
+ visit_statement_hint_list = CACHE_IN_PLACE
+ visit_type = STATIC_CACHE_KEY
+ visit_anon_name = ANON_NAME
+
+ visit_propagate_attrs = PROPAGATE_ATTRS
+
+ def visit_with_context_options(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ return tuple((fn.__code__, c_key) for fn, c_key in obj)
+
+ def visit_inspectable(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ return (attrname, inspect(obj)._gen_cache_key(anon_map, bindparams))
+
+ def visit_string_list(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ return tuple(obj)
+
+ def visit_multi(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ return (
+ attrname,
+ (
+ obj._gen_cache_key(anon_map, bindparams)
+ if isinstance(obj, HasCacheKey)
+ else obj
+ ),
+ )
+
+ def visit_multi_list(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ return (
+ attrname,
+ tuple(
+ (
+ elem._gen_cache_key(anon_map, bindparams)
+ if isinstance(elem, HasCacheKey)
+ else elem
+ )
+ for elem in obj
+ ),
+ )
+
+ def visit_has_cache_key_tuples(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ if not obj:
+ return ()
+ return (
+ attrname,
+ tuple(
+ tuple(
+ elem._gen_cache_key(anon_map, bindparams)
+ for elem in tup_elem
+ )
+ for tup_elem in obj
+ ),
+ )
+
+ def visit_has_cache_key_list(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ if not obj:
+ return ()
+ return (
+ attrname,
+ tuple(elem._gen_cache_key(anon_map, bindparams) for elem in obj),
+ )
+
+ def visit_executable_options(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ if not obj:
+ return ()
+ return (
+ attrname,
+ tuple(
+ elem._gen_cache_key(anon_map, bindparams)
+ for elem in obj
+ if elem._is_has_cache_key
+ ),
+ )
+
+ def visit_inspectable_list(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ return self.visit_has_cache_key_list(
+ attrname, [inspect(o) for o in obj], parent, anon_map, bindparams
+ )
+
+ def visit_clauseelement_tuples(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ return self.visit_has_cache_key_tuples(
+ attrname, obj, parent, anon_map, bindparams
+ )
+
+ def visit_fromclause_ordered_set(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ if not obj:
+ return ()
+ return (
+ attrname,
+ tuple([elem._gen_cache_key(anon_map, bindparams) for elem in obj]),
+ )
+
+ def visit_clauseelement_unordered_set(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ if not obj:
+ return ()
+ cache_keys = [
+ elem._gen_cache_key(anon_map, bindparams) for elem in obj
+ ]
+ return (
+ attrname,
+ tuple(
+ sorted(cache_keys)
+ ), # cache keys all start with (id_, class)
+ )
+
+ def visit_named_ddl_element(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ return (attrname, obj.name)
+
+ def visit_prefix_sequence(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ if not obj:
+ return ()
+
+ return (
+ attrname,
+ tuple(
+ [
+ (clause._gen_cache_key(anon_map, bindparams), strval)
+ for clause, strval in obj
+ ]
+ ),
+ )
+
+ def visit_setup_join_tuple(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ return tuple(
+ (
+ target._gen_cache_key(anon_map, bindparams),
+ (
+ onclause._gen_cache_key(anon_map, bindparams)
+ if onclause is not None
+ else None
+ ),
+ (
+ from_._gen_cache_key(anon_map, bindparams)
+ if from_ is not None
+ else None
+ ),
+ tuple([(key, flags[key]) for key in sorted(flags)]),
+ )
+ for (target, onclause, from_, flags) in obj
+ )
+
+ def visit_table_hint_list(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ if not obj:
+ return ()
+
+ return (
+ attrname,
+ tuple(
+ [
+ (
+ clause._gen_cache_key(anon_map, bindparams),
+ dialect_name,
+ text,
+ )
+ for (clause, dialect_name), text in obj.items()
+ ]
+ ),
+ )
+
+ def visit_plain_dict(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ return (attrname, tuple([(key, obj[key]) for key in sorted(obj)]))
+
+ def visit_dialect_options(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ return (
+ attrname,
+ tuple(
+ (
+ dialect_name,
+ tuple(
+ [
+ (key, obj[dialect_name][key])
+ for key in sorted(obj[dialect_name])
+ ]
+ ),
+ )
+ for dialect_name in sorted(obj)
+ ),
+ )
+
+ def visit_string_clauseelement_dict(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ return (
+ attrname,
+ tuple(
+ (key, obj[key]._gen_cache_key(anon_map, bindparams))
+ for key in sorted(obj)
+ ),
+ )
+
+ def visit_string_multi_dict(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ return (
+ attrname,
+ tuple(
+ (
+ key,
+ (
+ value._gen_cache_key(anon_map, bindparams)
+ if isinstance(value, HasCacheKey)
+ else value
+ ),
+ )
+ for key, value in [(key, obj[key]) for key in sorted(obj)]
+ ),
+ )
+
+ def visit_fromclause_canonical_column_collection(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ # inlining into the internals of ColumnCollection
+ return (
+ attrname,
+ tuple(
+ col._gen_cache_key(anon_map, bindparams)
+ for k, col, _ in obj._collection
+ ),
+ )
+
+ def visit_unknown_structure(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ anon_map[NO_CACHE] = True
+ return ()
+
+ def visit_dml_ordered_values(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ return (
+ attrname,
+ tuple(
+ (
+ (
+ key._gen_cache_key(anon_map, bindparams)
+ if hasattr(key, "__clause_element__")
+ else key
+ ),
+ value._gen_cache_key(anon_map, bindparams),
+ )
+ for key, value in obj
+ ),
+ )
+
+ def visit_dml_values(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ # in py37 we can assume two dictionaries created in the same
+ # insert ordering will retain that sorting
+ return (
+ attrname,
+ tuple(
+ (
+ (
+ k._gen_cache_key(anon_map, bindparams)
+ if hasattr(k, "__clause_element__")
+ else k
+ ),
+ obj[k]._gen_cache_key(anon_map, bindparams),
+ )
+ for k in obj
+ ),
+ )
+
+ def visit_dml_multi_values(
+ self,
+ attrname: str,
+ obj: Any,
+ parent: Any,
+ anon_map: anon_map,
+ bindparams: List[BindParameter[Any]],
+ ) -> Tuple[Any, ...]:
+ # multivalues are simply not cacheable right now
+ anon_map[NO_CACHE] = True
+ return ()
+
+
+_cache_key_traversal_visitor = _CacheKeyTraversal()
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/coercions.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/coercions.py
new file mode 100644
index 0000000..22d6091
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/coercions.py
@@ -0,0 +1,1389 @@
+# sql/coercions.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+# mypy: allow-untyped-defs, allow-untyped-calls
+
+from __future__ import annotations
+
+import collections.abc as collections_abc
+import numbers
+import re
+import typing
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import Dict
+from typing import Iterable
+from typing import Iterator
+from typing import List
+from typing import NoReturn
+from typing import Optional
+from typing import overload
+from typing import Sequence
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from . import operators
+from . import roles
+from . import visitors
+from ._typing import is_from_clause
+from .base import ExecutableOption
+from .base import Options
+from .cache_key import HasCacheKey
+from .visitors import Visitable
+from .. import exc
+from .. import inspection
+from .. import util
+from ..util.typing import Literal
+
+if typing.TYPE_CHECKING:
+ # elements lambdas schema selectable are set by __init__
+ from . import elements
+ from . import lambdas
+ from . import schema
+ from . import selectable
+ from ._typing import _ColumnExpressionArgument
+ from ._typing import _ColumnsClauseArgument
+ from ._typing import _DDLColumnArgument
+ from ._typing import _DMLTableArgument
+ from ._typing import _FromClauseArgument
+ from .dml import _DMLTableElement
+ from .elements import BindParameter
+ from .elements import ClauseElement
+ from .elements import ColumnClause
+ from .elements import ColumnElement
+ from .elements import DQLDMLClauseElement
+ from .elements import NamedColumn
+ from .elements import SQLCoreOperations
+ from .schema import Column
+ from .selectable import _ColumnsClauseElement
+ from .selectable import _JoinTargetProtocol
+ from .selectable import FromClause
+ from .selectable import HasCTE
+ from .selectable import SelectBase
+ from .selectable import Subquery
+ from .visitors import _TraverseCallableType
+
+_SR = TypeVar("_SR", bound=roles.SQLRole)
+_F = TypeVar("_F", bound=Callable[..., Any])
+_StringOnlyR = TypeVar("_StringOnlyR", bound=roles.StringRole)
+_T = TypeVar("_T", bound=Any)
+
+
+def _is_literal(element):
+ """Return whether or not the element is a "literal" in the context
+ of a SQL expression construct.
+
+ """
+
+ return not isinstance(
+ element,
+ (Visitable, schema.SchemaEventTarget),
+ ) and not hasattr(element, "__clause_element__")
+
+
+def _deep_is_literal(element):
+ """Return whether or not the element is a "literal" in the context
+ of a SQL expression construct.
+
+ does a deeper more esoteric check than _is_literal. is used
+ for lambda elements that have to distinguish values that would
+ be bound vs. not without any context.
+
+ """
+
+ if isinstance(element, collections_abc.Sequence) and not isinstance(
+ element, str
+ ):
+ for elem in element:
+ if not _deep_is_literal(elem):
+ return False
+ else:
+ return True
+
+ return (
+ not isinstance(
+ element,
+ (
+ Visitable,
+ schema.SchemaEventTarget,
+ HasCacheKey,
+ Options,
+ util.langhelpers.symbol,
+ ),
+ )
+ and not hasattr(element, "__clause_element__")
+ and (
+ not isinstance(element, type)
+ or not issubclass(element, HasCacheKey)
+ )
+ )
+
+
+def _document_text_coercion(
+ paramname: str, meth_rst: str, param_rst: str
+) -> Callable[[_F], _F]:
+ return util.add_parameter_text(
+ paramname,
+ (
+ ".. warning:: "
+ "The %s argument to %s can be passed as a Python string argument, "
+ "which will be treated "
+ "as **trusted SQL text** and rendered as given. **DO NOT PASS "
+ "UNTRUSTED INPUT TO THIS PARAMETER**."
+ )
+ % (param_rst, meth_rst),
+ )
+
+
+def _expression_collection_was_a_list(
+ attrname: str,
+ fnname: str,
+ args: Union[Sequence[_T], Sequence[Sequence[_T]]],
+) -> Sequence[_T]:
+ if args and isinstance(args[0], (list, set, dict)) and len(args) == 1:
+ if isinstance(args[0], list):
+ raise exc.ArgumentError(
+ f'The "{attrname}" argument to {fnname}(), when '
+ "referring to a sequence "
+ "of items, is now passed as a series of positional "
+ "elements, rather than as a list. "
+ )
+ return cast("Sequence[_T]", args[0])
+
+ return cast("Sequence[_T]", args)
+
+
+@overload
+def expect(
+ role: Type[roles.TruncatedLabelRole],
+ element: Any,
+ **kw: Any,
+) -> str: ...
+
+
+@overload
+def expect(
+ role: Type[roles.DMLColumnRole],
+ element: Any,
+ *,
+ as_key: Literal[True] = ...,
+ **kw: Any,
+) -> str: ...
+
+
+@overload
+def expect(
+ role: Type[roles.LiteralValueRole],
+ element: Any,
+ **kw: Any,
+) -> BindParameter[Any]: ...
+
+
+@overload
+def expect(
+ role: Type[roles.DDLReferredColumnRole],
+ element: Any,
+ **kw: Any,
+) -> Column[Any]: ...
+
+
+@overload
+def expect(
+ role: Type[roles.DDLConstraintColumnRole],
+ element: Any,
+ **kw: Any,
+) -> Union[Column[Any], str]: ...
+
+
+@overload
+def expect(
+ role: Type[roles.StatementOptionRole],
+ element: Any,
+ **kw: Any,
+) -> DQLDMLClauseElement: ...
+
+
+@overload
+def expect(
+ role: Type[roles.LabeledColumnExprRole[Any]],
+ element: _ColumnExpressionArgument[_T],
+ **kw: Any,
+) -> NamedColumn[_T]: ...
+
+
+@overload
+def expect(
+ role: Union[
+ Type[roles.ExpressionElementRole[Any]],
+ Type[roles.LimitOffsetRole],
+ Type[roles.WhereHavingRole],
+ ],
+ element: _ColumnExpressionArgument[_T],
+ **kw: Any,
+) -> ColumnElement[_T]: ...
+
+
+@overload
+def expect(
+ role: Union[
+ Type[roles.ExpressionElementRole[Any]],
+ Type[roles.LimitOffsetRole],
+ Type[roles.WhereHavingRole],
+ Type[roles.OnClauseRole],
+ Type[roles.ColumnArgumentRole],
+ ],
+ element: Any,
+ **kw: Any,
+) -> ColumnElement[Any]: ...
+
+
+@overload
+def expect(
+ role: Type[roles.DMLTableRole],
+ element: _DMLTableArgument,
+ **kw: Any,
+) -> _DMLTableElement: ...
+
+
+@overload
+def expect(
+ role: Type[roles.HasCTERole],
+ element: HasCTE,
+ **kw: Any,
+) -> HasCTE: ...
+
+
+@overload
+def expect(
+ role: Type[roles.SelectStatementRole],
+ element: SelectBase,
+ **kw: Any,
+) -> SelectBase: ...
+
+
+@overload
+def expect(
+ role: Type[roles.FromClauseRole],
+ element: _FromClauseArgument,
+ **kw: Any,
+) -> FromClause: ...
+
+
+@overload
+def expect(
+ role: Type[roles.FromClauseRole],
+ element: SelectBase,
+ *,
+ explicit_subquery: Literal[True] = ...,
+ **kw: Any,
+) -> Subquery: ...
+
+
+@overload
+def expect(
+ role: Type[roles.ColumnsClauseRole],
+ element: _ColumnsClauseArgument[Any],
+ **kw: Any,
+) -> _ColumnsClauseElement: ...
+
+
+@overload
+def expect(
+ role: Type[roles.JoinTargetRole],
+ element: _JoinTargetProtocol,
+ **kw: Any,
+) -> _JoinTargetProtocol: ...
+
+
+# catchall for not-yet-implemented overloads
+@overload
+def expect(
+ role: Type[_SR],
+ element: Any,
+ **kw: Any,
+) -> Any: ...
+
+
+def expect(
+ role: Type[_SR],
+ element: Any,
+ *,
+ apply_propagate_attrs: Optional[ClauseElement] = None,
+ argname: Optional[str] = None,
+ post_inspect: bool = False,
+ disable_inspection: bool = False,
+ **kw: Any,
+) -> Any:
+ if (
+ role.allows_lambda
+ # note callable() will not invoke a __getattr__() method, whereas
+ # hasattr(obj, "__call__") will. by keeping the callable() check here
+ # we prevent most needless calls to hasattr() and therefore
+ # __getattr__(), which is present on ColumnElement.
+ and callable(element)
+ and hasattr(element, "__code__")
+ ):
+ return lambdas.LambdaElement(
+ element,
+ role,
+ lambdas.LambdaOptions(**kw),
+ apply_propagate_attrs=apply_propagate_attrs,
+ )
+
+ # major case is that we are given a ClauseElement already, skip more
+ # elaborate logic up front if possible
+ impl = _impl_lookup[role]
+
+ original_element = element
+
+ if not isinstance(
+ element,
+ (
+ elements.CompilerElement,
+ schema.SchemaItem,
+ schema.FetchedValue,
+ lambdas.PyWrapper,
+ ),
+ ):
+ resolved = None
+
+ if impl._resolve_literal_only:
+ resolved = impl._literal_coercion(element, **kw)
+ else:
+ original_element = element
+
+ is_clause_element = False
+
+ # this is a special performance optimization for ORM
+ # joins used by JoinTargetImpl that we don't go through the
+ # work of creating __clause_element__() when we only need the
+ # original QueryableAttribute, as the former will do clause
+ # adaption and all that which is just thrown away here.
+ if (
+ impl._skip_clauseelement_for_target_match
+ and isinstance(element, role)
+ and hasattr(element, "__clause_element__")
+ ):
+ is_clause_element = True
+ else:
+ while hasattr(element, "__clause_element__"):
+ is_clause_element = True
+
+ if not getattr(element, "is_clause_element", False):
+ element = element.__clause_element__()
+ else:
+ break
+
+ if not is_clause_element:
+ if impl._use_inspection and not disable_inspection:
+ insp = inspection.inspect(element, raiseerr=False)
+ if insp is not None:
+ if post_inspect:
+ insp._post_inspect
+ try:
+ resolved = insp.__clause_element__()
+ except AttributeError:
+ impl._raise_for_expected(original_element, argname)
+
+ if resolved is None:
+ resolved = impl._literal_coercion(
+ element, argname=argname, **kw
+ )
+ else:
+ resolved = element
+ elif isinstance(element, lambdas.PyWrapper):
+ resolved = element._sa__py_wrapper_literal(**kw)
+ else:
+ resolved = element
+
+ if apply_propagate_attrs is not None:
+ if typing.TYPE_CHECKING:
+ assert isinstance(resolved, (SQLCoreOperations, ClauseElement))
+
+ if not apply_propagate_attrs._propagate_attrs and getattr(
+ resolved, "_propagate_attrs", None
+ ):
+ apply_propagate_attrs._propagate_attrs = resolved._propagate_attrs
+
+ if impl._role_class in resolved.__class__.__mro__:
+ if impl._post_coercion:
+ resolved = impl._post_coercion(
+ resolved,
+ argname=argname,
+ original_element=original_element,
+ **kw,
+ )
+ return resolved
+ else:
+ return impl._implicit_coercions(
+ original_element, resolved, argname=argname, **kw
+ )
+
+
+def expect_as_key(
+ role: Type[roles.DMLColumnRole], element: Any, **kw: Any
+) -> str:
+ kw.pop("as_key", None)
+ return expect(role, element, as_key=True, **kw)
+
+
+def expect_col_expression_collection(
+ role: Type[roles.DDLConstraintColumnRole],
+ expressions: Iterable[_DDLColumnArgument],
+) -> Iterator[
+ Tuple[
+ Union[str, Column[Any]],
+ Optional[ColumnClause[Any]],
+ Optional[str],
+ Optional[Union[Column[Any], str]],
+ ]
+]:
+ for expr in expressions:
+ strname = None
+ column = None
+
+ resolved: Union[Column[Any], str] = expect(role, expr)
+ if isinstance(resolved, str):
+ assert isinstance(expr, str)
+ strname = resolved = expr
+ else:
+ cols: List[Column[Any]] = []
+ col_append: _TraverseCallableType[Column[Any]] = cols.append
+ visitors.traverse(resolved, {}, {"column": col_append})
+ if cols:
+ column = cols[0]
+ add_element = column if column is not None else strname
+
+ yield resolved, column, strname, add_element
+
+
+class RoleImpl:
+ __slots__ = ("_role_class", "name", "_use_inspection")
+
+ def _literal_coercion(self, element, **kw):
+ raise NotImplementedError()
+
+ _post_coercion: Any = None
+ _resolve_literal_only = False
+ _skip_clauseelement_for_target_match = False
+
+ def __init__(self, role_class):
+ self._role_class = role_class
+ self.name = role_class._role_name
+ self._use_inspection = issubclass(role_class, roles.UsesInspection)
+
+ def _implicit_coercions(
+ self,
+ element: Any,
+ resolved: Any,
+ argname: Optional[str] = None,
+ **kw: Any,
+ ) -> Any:
+ self._raise_for_expected(element, argname, resolved)
+
+ def _raise_for_expected(
+ self,
+ element: Any,
+ argname: Optional[str] = None,
+ resolved: Optional[Any] = None,
+ advice: Optional[str] = None,
+ code: Optional[str] = None,
+ err: Optional[Exception] = None,
+ **kw: Any,
+ ) -> NoReturn:
+ if resolved is not None and resolved is not element:
+ got = "%r object resolved from %r object" % (resolved, element)
+ else:
+ got = repr(element)
+
+ if argname:
+ msg = "%s expected for argument %r; got %s." % (
+ self.name,
+ argname,
+ got,
+ )
+ else:
+ msg = "%s expected, got %s." % (self.name, got)
+
+ if advice:
+ msg += " " + advice
+
+ raise exc.ArgumentError(msg, code=code) from err
+
+
+class _Deannotate:
+ __slots__ = ()
+
+ def _post_coercion(self, resolved, **kw):
+ from .util import _deep_deannotate
+
+ return _deep_deannotate(resolved)
+
+
+class _StringOnly:
+ __slots__ = ()
+
+ _resolve_literal_only = True
+
+
+class _ReturnsStringKey(RoleImpl):
+ __slots__ = ()
+
+ def _implicit_coercions(self, element, resolved, argname=None, **kw):
+ if isinstance(element, str):
+ return element
+ else:
+ self._raise_for_expected(element, argname, resolved)
+
+ def _literal_coercion(self, element, **kw):
+ return element
+
+
+class _ColumnCoercions(RoleImpl):
+ __slots__ = ()
+
+ def _warn_for_scalar_subquery_coercion(self):
+ util.warn(
+ "implicitly coercing SELECT object to scalar subquery; "
+ "please use the .scalar_subquery() method to produce a scalar "
+ "subquery.",
+ )
+
+ def _implicit_coercions(self, element, resolved, argname=None, **kw):
+ original_element = element
+ if not getattr(resolved, "is_clause_element", False):
+ self._raise_for_expected(original_element, argname, resolved)
+ elif resolved._is_select_base:
+ self._warn_for_scalar_subquery_coercion()
+ return resolved.scalar_subquery()
+ elif resolved._is_from_clause and isinstance(
+ resolved, selectable.Subquery
+ ):
+ self._warn_for_scalar_subquery_coercion()
+ return resolved.element.scalar_subquery()
+ elif self._role_class.allows_lambda and resolved._is_lambda_element:
+ return resolved
+ else:
+ self._raise_for_expected(original_element, argname, resolved)
+
+
+def _no_text_coercion(
+ element: Any,
+ argname: Optional[str] = None,
+ exc_cls: Type[exc.SQLAlchemyError] = exc.ArgumentError,
+ extra: Optional[str] = None,
+ err: Optional[Exception] = None,
+) -> NoReturn:
+ raise exc_cls(
+ "%(extra)sTextual SQL expression %(expr)r %(argname)sshould be "
+ "explicitly declared as text(%(expr)r)"
+ % {
+ "expr": util.ellipses_string(element),
+ "argname": "for argument %s" % (argname,) if argname else "",
+ "extra": "%s " % extra if extra else "",
+ }
+ ) from err
+
+
+class _NoTextCoercion(RoleImpl):
+ __slots__ = ()
+
+ def _literal_coercion(self, element, argname=None, **kw):
+ if isinstance(element, str) and issubclass(
+ elements.TextClause, self._role_class
+ ):
+ _no_text_coercion(element, argname)
+ else:
+ self._raise_for_expected(element, argname)
+
+
+class _CoerceLiterals(RoleImpl):
+ __slots__ = ()
+ _coerce_consts = False
+ _coerce_star = False
+ _coerce_numerics = False
+
+ def _text_coercion(self, element, argname=None):
+ return _no_text_coercion(element, argname)
+
+ def _literal_coercion(self, element, argname=None, **kw):
+ if isinstance(element, str):
+ if self._coerce_star and element == "*":
+ return elements.ColumnClause("*", is_literal=True)
+ else:
+ return self._text_coercion(element, argname, **kw)
+
+ if self._coerce_consts:
+ if element is None:
+ return elements.Null()
+ elif element is False:
+ return elements.False_()
+ elif element is True:
+ return elements.True_()
+
+ if self._coerce_numerics and isinstance(element, (numbers.Number)):
+ return elements.ColumnClause(str(element), is_literal=True)
+
+ self._raise_for_expected(element, argname)
+
+
+class LiteralValueImpl(RoleImpl):
+ _resolve_literal_only = True
+
+ def _implicit_coercions(
+ self,
+ element,
+ resolved,
+ argname,
+ type_=None,
+ literal_execute=False,
+ **kw,
+ ):
+ if not _is_literal(resolved):
+ self._raise_for_expected(
+ element, resolved=resolved, argname=argname, **kw
+ )
+
+ return elements.BindParameter(
+ None,
+ element,
+ type_=type_,
+ unique=True,
+ literal_execute=literal_execute,
+ )
+
+ def _literal_coercion(self, element, argname=None, type_=None, **kw):
+ return element
+
+
+class _SelectIsNotFrom(RoleImpl):
+ __slots__ = ()
+
+ def _raise_for_expected(
+ self,
+ element: Any,
+ argname: Optional[str] = None,
+ resolved: Optional[Any] = None,
+ advice: Optional[str] = None,
+ code: Optional[str] = None,
+ err: Optional[Exception] = None,
+ **kw: Any,
+ ) -> NoReturn:
+ if (
+ not advice
+ and isinstance(element, roles.SelectStatementRole)
+ or isinstance(resolved, roles.SelectStatementRole)
+ ):
+ advice = (
+ "To create a "
+ "FROM clause from a %s object, use the .subquery() method."
+ % (resolved.__class__ if resolved is not None else element,)
+ )
+ code = "89ve"
+ else:
+ code = None
+
+ super()._raise_for_expected(
+ element,
+ argname=argname,
+ resolved=resolved,
+ advice=advice,
+ code=code,
+ err=err,
+ **kw,
+ )
+ # never reached
+ assert False
+
+
+class HasCacheKeyImpl(RoleImpl):
+ __slots__ = ()
+
+ def _implicit_coercions(
+ self,
+ element: Any,
+ resolved: Any,
+ argname: Optional[str] = None,
+ **kw: Any,
+ ) -> Any:
+ if isinstance(element, HasCacheKey):
+ return element
+ else:
+ self._raise_for_expected(element, argname, resolved)
+
+ def _literal_coercion(self, element, **kw):
+ return element
+
+
+class ExecutableOptionImpl(RoleImpl):
+ __slots__ = ()
+
+ def _implicit_coercions(
+ self,
+ element: Any,
+ resolved: Any,
+ argname: Optional[str] = None,
+ **kw: Any,
+ ) -> Any:
+ if isinstance(element, ExecutableOption):
+ return element
+ else:
+ self._raise_for_expected(element, argname, resolved)
+
+ def _literal_coercion(self, element, **kw):
+ return element
+
+
+class ExpressionElementImpl(_ColumnCoercions, RoleImpl):
+ __slots__ = ()
+
+ def _literal_coercion(
+ self, element, name=None, type_=None, argname=None, is_crud=False, **kw
+ ):
+ if (
+ element is None
+ and not is_crud
+ and (type_ is None or not type_.should_evaluate_none)
+ ):
+ # TODO: there's no test coverage now for the
+ # "should_evaluate_none" part of this, as outside of "crud" this
+ # codepath is not normally used except in some special cases
+ return elements.Null()
+ else:
+ try:
+ return elements.BindParameter(
+ name, element, type_, unique=True, _is_crud=is_crud
+ )
+ except exc.ArgumentError as err:
+ self._raise_for_expected(element, err=err)
+
+ def _raise_for_expected(self, element, argname=None, resolved=None, **kw):
+ # select uses implicit coercion with warning instead of raising
+ if isinstance(element, selectable.Values):
+ advice = (
+ "To create a column expression from a VALUES clause, "
+ "use the .scalar_values() method."
+ )
+ elif isinstance(element, roles.AnonymizedFromClauseRole):
+ advice = (
+ "To create a column expression from a FROM clause row "
+ "as a whole, use the .table_valued() method."
+ )
+ else:
+ advice = None
+
+ return super()._raise_for_expected(
+ element, argname=argname, resolved=resolved, advice=advice, **kw
+ )
+
+
+class BinaryElementImpl(ExpressionElementImpl, RoleImpl):
+ __slots__ = ()
+
+ def _literal_coercion(
+ self, element, expr, operator, bindparam_type=None, argname=None, **kw
+ ):
+ try:
+ return expr._bind_param(operator, element, type_=bindparam_type)
+ except exc.ArgumentError as err:
+ self._raise_for_expected(element, err=err)
+
+ def _post_coercion(self, resolved, expr, bindparam_type=None, **kw):
+ if resolved.type._isnull and not expr.type._isnull:
+ resolved = resolved._with_binary_element_type(
+ bindparam_type if bindparam_type is not None else expr.type
+ )
+ return resolved
+
+
+class InElementImpl(RoleImpl):
+ __slots__ = ()
+
+ def _implicit_coercions(
+ self,
+ element: Any,
+ resolved: Any,
+ argname: Optional[str] = None,
+ **kw: Any,
+ ) -> Any:
+ if resolved._is_from_clause:
+ if (
+ isinstance(resolved, selectable.Alias)
+ and resolved.element._is_select_base
+ ):
+ self._warn_for_implicit_coercion(resolved)
+ return self._post_coercion(resolved.element, **kw)
+ else:
+ self._warn_for_implicit_coercion(resolved)
+ return self._post_coercion(resolved.select(), **kw)
+ else:
+ self._raise_for_expected(element, argname, resolved)
+
+ def _warn_for_implicit_coercion(self, elem):
+ util.warn(
+ "Coercing %s object into a select() for use in IN(); "
+ "please pass a select() construct explicitly"
+ % (elem.__class__.__name__)
+ )
+
+ def _literal_coercion(self, element, expr, operator, **kw):
+ if util.is_non_string_iterable(element):
+ non_literal_expressions: Dict[
+ Optional[operators.ColumnOperators],
+ operators.ColumnOperators,
+ ] = {}
+ element = list(element)
+ for o in element:
+ if not _is_literal(o):
+ if not isinstance(o, operators.ColumnOperators):
+ self._raise_for_expected(element, **kw)
+
+ else:
+ non_literal_expressions[o] = o
+ elif o is None:
+ non_literal_expressions[o] = elements.Null()
+
+ if non_literal_expressions:
+ return elements.ClauseList(
+ *[
+ (
+ non_literal_expressions[o]
+ if o in non_literal_expressions
+ else expr._bind_param(operator, o)
+ )
+ for o in element
+ ]
+ )
+ else:
+ return expr._bind_param(operator, element, expanding=True)
+
+ else:
+ self._raise_for_expected(element, **kw)
+
+ def _post_coercion(self, element, expr, operator, **kw):
+ if element._is_select_base:
+ # for IN, we are doing scalar_subquery() coercion without
+ # a warning
+ return element.scalar_subquery()
+ elif isinstance(element, elements.ClauseList):
+ assert not len(element.clauses) == 0
+ return element.self_group(against=operator)
+
+ elif isinstance(element, elements.BindParameter):
+ element = element._clone(maintain_key=True)
+ element.expanding = True
+ element.expand_op = operator
+
+ return element
+ elif isinstance(element, selectable.Values):
+ return element.scalar_values()
+ else:
+ return element
+
+
+class OnClauseImpl(_ColumnCoercions, RoleImpl):
+ __slots__ = ()
+
+ _coerce_consts = True
+
+ def _literal_coercion(
+ self, element, name=None, type_=None, argname=None, is_crud=False, **kw
+ ):
+ self._raise_for_expected(element)
+
+ def _post_coercion(self, resolved, original_element=None, **kw):
+ # this is a hack right now as we want to use coercion on an
+ # ORM InstrumentedAttribute, but we want to return the object
+ # itself if it is one, not its clause element.
+ # ORM context _join and _legacy_join() would need to be improved
+ # to look for annotations in a clause element form.
+ if isinstance(original_element, roles.JoinTargetRole):
+ return original_element
+ return resolved
+
+
+class WhereHavingImpl(_CoerceLiterals, _ColumnCoercions, RoleImpl):
+ __slots__ = ()
+
+ _coerce_consts = True
+
+ def _text_coercion(self, element, argname=None):
+ return _no_text_coercion(element, argname)
+
+
+class StatementOptionImpl(_CoerceLiterals, RoleImpl):
+ __slots__ = ()
+
+ _coerce_consts = True
+
+ def _text_coercion(self, element, argname=None):
+ return elements.TextClause(element)
+
+
+class ColumnArgumentImpl(_NoTextCoercion, RoleImpl):
+ __slots__ = ()
+
+
+class ColumnArgumentOrKeyImpl(_ReturnsStringKey, RoleImpl):
+ __slots__ = ()
+
+
+class StrAsPlainColumnImpl(_CoerceLiterals, RoleImpl):
+ __slots__ = ()
+
+ def _text_coercion(self, element, argname=None):
+ return elements.ColumnClause(element)
+
+
+class ByOfImpl(_CoerceLiterals, _ColumnCoercions, RoleImpl, roles.ByOfRole):
+ __slots__ = ()
+
+ _coerce_consts = True
+
+ def _text_coercion(self, element, argname=None):
+ return elements._textual_label_reference(element)
+
+
+class OrderByImpl(ByOfImpl, RoleImpl):
+ __slots__ = ()
+
+ def _post_coercion(self, resolved, **kw):
+ if (
+ isinstance(resolved, self._role_class)
+ and resolved._order_by_label_element is not None
+ ):
+ return elements._label_reference(resolved)
+ else:
+ return resolved
+
+
+class GroupByImpl(ByOfImpl, RoleImpl):
+ __slots__ = ()
+
+ def _implicit_coercions(
+ self,
+ element: Any,
+ resolved: Any,
+ argname: Optional[str] = None,
+ **kw: Any,
+ ) -> Any:
+ if is_from_clause(resolved):
+ return elements.ClauseList(*resolved.c)
+ else:
+ return resolved
+
+
+class DMLColumnImpl(_ReturnsStringKey, RoleImpl):
+ __slots__ = ()
+
+ def _post_coercion(self, element, as_key=False, **kw):
+ if as_key:
+ return element.key
+ else:
+ return element
+
+
+class ConstExprImpl(RoleImpl):
+ __slots__ = ()
+
+ def _literal_coercion(self, element, argname=None, **kw):
+ if element is None:
+ return elements.Null()
+ elif element is False:
+ return elements.False_()
+ elif element is True:
+ return elements.True_()
+ else:
+ self._raise_for_expected(element, argname)
+
+
+class TruncatedLabelImpl(_StringOnly, RoleImpl):
+ __slots__ = ()
+
+ def _implicit_coercions(
+ self,
+ element: Any,
+ resolved: Any,
+ argname: Optional[str] = None,
+ **kw: Any,
+ ) -> Any:
+ if isinstance(element, str):
+ return resolved
+ else:
+ self._raise_for_expected(element, argname, resolved)
+
+ def _literal_coercion(self, element, argname=None, **kw):
+ """coerce the given value to :class:`._truncated_label`.
+
+ Existing :class:`._truncated_label` and
+ :class:`._anonymous_label` objects are passed
+ unchanged.
+ """
+
+ if isinstance(element, elements._truncated_label):
+ return element
+ else:
+ return elements._truncated_label(element)
+
+
+class DDLExpressionImpl(_Deannotate, _CoerceLiterals, RoleImpl):
+ __slots__ = ()
+
+ _coerce_consts = True
+
+ def _text_coercion(self, element, argname=None):
+ # see #5754 for why we can't easily deprecate this coercion.
+ # essentially expressions like postgresql_where would have to be
+ # text() as they come back from reflection and we don't want to
+ # have text() elements wired into the inspection dictionaries.
+ return elements.TextClause(element)
+
+
+class DDLConstraintColumnImpl(_Deannotate, _ReturnsStringKey, RoleImpl):
+ __slots__ = ()
+
+
+class DDLReferredColumnImpl(DDLConstraintColumnImpl):
+ __slots__ = ()
+
+
+class LimitOffsetImpl(RoleImpl):
+ __slots__ = ()
+
+ def _implicit_coercions(
+ self,
+ element: Any,
+ resolved: Any,
+ argname: Optional[str] = None,
+ **kw: Any,
+ ) -> Any:
+ if resolved is None:
+ return None
+ else:
+ self._raise_for_expected(element, argname, resolved)
+
+ def _literal_coercion(self, element, name, type_, **kw):
+ if element is None:
+ return None
+ else:
+ value = util.asint(element)
+ return selectable._OffsetLimitParam(
+ name, value, type_=type_, unique=True
+ )
+
+
+class LabeledColumnExprImpl(ExpressionElementImpl):
+ __slots__ = ()
+
+ def _implicit_coercions(
+ self,
+ element: Any,
+ resolved: Any,
+ argname: Optional[str] = None,
+ **kw: Any,
+ ) -> Any:
+ if isinstance(resolved, roles.ExpressionElementRole):
+ return resolved.label(None)
+ else:
+ new = super()._implicit_coercions(
+ element, resolved, argname=argname, **kw
+ )
+ if isinstance(new, roles.ExpressionElementRole):
+ return new.label(None)
+ else:
+ self._raise_for_expected(element, argname, resolved)
+
+
+class ColumnsClauseImpl(_SelectIsNotFrom, _CoerceLiterals, RoleImpl):
+ __slots__ = ()
+
+ _coerce_consts = True
+ _coerce_numerics = True
+ _coerce_star = True
+
+ _guess_straight_column = re.compile(r"^\w\S*$", re.I)
+
+ def _raise_for_expected(
+ self, element, argname=None, resolved=None, advice=None, **kw
+ ):
+ if not advice and isinstance(element, list):
+ advice = (
+ f"Did you mean to say select("
+ f"{', '.join(repr(e) for e in element)})?"
+ )
+
+ return super()._raise_for_expected(
+ element, argname=argname, resolved=resolved, advice=advice, **kw
+ )
+
+ def _text_coercion(self, element, argname=None):
+ element = str(element)
+
+ guess_is_literal = not self._guess_straight_column.match(element)
+ raise exc.ArgumentError(
+ "Textual column expression %(column)r %(argname)sshould be "
+ "explicitly declared with text(%(column)r), "
+ "or use %(literal_column)s(%(column)r) "
+ "for more specificity"
+ % {
+ "column": util.ellipses_string(element),
+ "argname": "for argument %s" % (argname,) if argname else "",
+ "literal_column": (
+ "literal_column" if guess_is_literal else "column"
+ ),
+ }
+ )
+
+
+class ReturnsRowsImpl(RoleImpl):
+ __slots__ = ()
+
+
+class StatementImpl(_CoerceLiterals, RoleImpl):
+ __slots__ = ()
+
+ def _post_coercion(self, resolved, original_element, argname=None, **kw):
+ if resolved is not original_element and not isinstance(
+ original_element, str
+ ):
+ # use same method as Connection uses; this will later raise
+ # ObjectNotExecutableError
+ try:
+ original_element._execute_on_connection
+ except AttributeError:
+ util.warn_deprecated(
+ "Object %r should not be used directly in a SQL statement "
+ "context, such as passing to methods such as "
+ "session.execute(). This usage will be disallowed in a "
+ "future release. "
+ "Please use Core select() / update() / delete() etc. "
+ "with Session.execute() and other statement execution "
+ "methods." % original_element,
+ "1.4",
+ )
+
+ return resolved
+
+ def _implicit_coercions(
+ self,
+ element: Any,
+ resolved: Any,
+ argname: Optional[str] = None,
+ **kw: Any,
+ ) -> Any:
+ if resolved._is_lambda_element:
+ return resolved
+ else:
+ return super()._implicit_coercions(
+ element, resolved, argname=argname, **kw
+ )
+
+
+class SelectStatementImpl(_NoTextCoercion, RoleImpl):
+ __slots__ = ()
+
+ def _implicit_coercions(
+ self,
+ element: Any,
+ resolved: Any,
+ argname: Optional[str] = None,
+ **kw: Any,
+ ) -> Any:
+ if resolved._is_text_clause:
+ return resolved.columns()
+ else:
+ self._raise_for_expected(element, argname, resolved)
+
+
+class HasCTEImpl(ReturnsRowsImpl):
+ __slots__ = ()
+
+
+class IsCTEImpl(RoleImpl):
+ __slots__ = ()
+
+
+class JoinTargetImpl(RoleImpl):
+ __slots__ = ()
+
+ _skip_clauseelement_for_target_match = True
+
+ def _literal_coercion(self, element, argname=None, **kw):
+ self._raise_for_expected(element, argname)
+
+ def _implicit_coercions(
+ self,
+ element: Any,
+ resolved: Any,
+ argname: Optional[str] = None,
+ legacy: bool = False,
+ **kw: Any,
+ ) -> Any:
+ if isinstance(element, roles.JoinTargetRole):
+ # note that this codepath no longer occurs as of
+ # #6550, unless JoinTargetImpl._skip_clauseelement_for_target_match
+ # were set to False.
+ return element
+ elif legacy and resolved._is_select_base:
+ util.warn_deprecated(
+ "Implicit coercion of SELECT and textual SELECT "
+ "constructs into FROM clauses is deprecated; please call "
+ ".subquery() on any Core select or ORM Query object in "
+ "order to produce a subquery object.",
+ version="1.4",
+ )
+ # TODO: doing _implicit_subquery here causes tests to fail,
+ # how was this working before? probably that ORM
+ # join logic treated it as a select and subquery would happen
+ # in _ORMJoin->Join
+ return resolved
+ else:
+ self._raise_for_expected(element, argname, resolved)
+
+
+class FromClauseImpl(_SelectIsNotFrom, _NoTextCoercion, RoleImpl):
+ __slots__ = ()
+
+ def _implicit_coercions(
+ self,
+ element: Any,
+ resolved: Any,
+ argname: Optional[str] = None,
+ explicit_subquery: bool = False,
+ allow_select: bool = True,
+ **kw: Any,
+ ) -> Any:
+ if resolved._is_select_base:
+ if explicit_subquery:
+ return resolved.subquery()
+ elif allow_select:
+ util.warn_deprecated(
+ "Implicit coercion of SELECT and textual SELECT "
+ "constructs into FROM clauses is deprecated; please call "
+ ".subquery() on any Core select or ORM Query object in "
+ "order to produce a subquery object.",
+ version="1.4",
+ )
+ return resolved._implicit_subquery
+ elif resolved._is_text_clause:
+ return resolved
+ else:
+ self._raise_for_expected(element, argname, resolved)
+
+ def _post_coercion(self, element, deannotate=False, **kw):
+ if deannotate:
+ return element._deannotate()
+ else:
+ return element
+
+
+class StrictFromClauseImpl(FromClauseImpl):
+ __slots__ = ()
+
+ def _implicit_coercions(
+ self,
+ element: Any,
+ resolved: Any,
+ argname: Optional[str] = None,
+ explicit_subquery: bool = False,
+ allow_select: bool = False,
+ **kw: Any,
+ ) -> Any:
+ if resolved._is_select_base and allow_select:
+ util.warn_deprecated(
+ "Implicit coercion of SELECT and textual SELECT constructs "
+ "into FROM clauses is deprecated; please call .subquery() "
+ "on any Core select or ORM Query object in order to produce a "
+ "subquery object.",
+ version="1.4",
+ )
+ return resolved._implicit_subquery
+ else:
+ self._raise_for_expected(element, argname, resolved)
+
+
+class AnonymizedFromClauseImpl(StrictFromClauseImpl):
+ __slots__ = ()
+
+ def _post_coercion(self, element, flat=False, name=None, **kw):
+ assert name is None
+
+ return element._anonymous_fromclause(flat=flat)
+
+
+class DMLTableImpl(_SelectIsNotFrom, _NoTextCoercion, RoleImpl):
+ __slots__ = ()
+
+ def _post_coercion(self, element, **kw):
+ if "dml_table" in element._annotations:
+ return element._annotations["dml_table"]
+ else:
+ return element
+
+
+class DMLSelectImpl(_NoTextCoercion, RoleImpl):
+ __slots__ = ()
+
+ def _implicit_coercions(
+ self,
+ element: Any,
+ resolved: Any,
+ argname: Optional[str] = None,
+ **kw: Any,
+ ) -> Any:
+ if resolved._is_from_clause:
+ if (
+ isinstance(resolved, selectable.Alias)
+ and resolved.element._is_select_base
+ ):
+ return resolved.element
+ else:
+ return resolved.select()
+ else:
+ self._raise_for_expected(element, argname, resolved)
+
+
+class CompoundElementImpl(_NoTextCoercion, RoleImpl):
+ __slots__ = ()
+
+ def _raise_for_expected(self, element, argname=None, resolved=None, **kw):
+ if isinstance(element, roles.FromClauseRole):
+ if element._is_subquery:
+ advice = (
+ "Use the plain select() object without "
+ "calling .subquery() or .alias()."
+ )
+ else:
+ advice = (
+ "To SELECT from any FROM clause, use the .select() method."
+ )
+ else:
+ advice = None
+ return super()._raise_for_expected(
+ element, argname=argname, resolved=resolved, advice=advice, **kw
+ )
+
+
+_impl_lookup = {}
+
+
+for name in dir(roles):
+ cls = getattr(roles, name)
+ if name.endswith("Role"):
+ name = name.replace("Role", "Impl")
+ if name in globals():
+ impl = globals()[name](cls)
+ _impl_lookup[cls] = impl
+
+if not TYPE_CHECKING:
+ ee_impl = _impl_lookup[roles.ExpressionElementRole]
+
+ for py_type in (int, bool, str, float):
+ _impl_lookup[roles.ExpressionElementRole[py_type]] = ee_impl
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/compiler.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/compiler.py
new file mode 100644
index 0000000..c354ba8
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/compiler.py
@@ -0,0 +1,7811 @@
+# sql/compiler.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+# mypy: allow-untyped-defs, allow-untyped-calls
+
+"""Base SQL and DDL compiler implementations.
+
+Classes provided include:
+
+:class:`.compiler.SQLCompiler` - renders SQL
+strings
+
+:class:`.compiler.DDLCompiler` - renders DDL
+(data definition language) strings
+
+:class:`.compiler.GenericTypeCompiler` - renders
+type specification strings.
+
+To generate user-defined SQL strings, see
+:doc:`/ext/compiler`.
+
+"""
+from __future__ import annotations
+
+import collections
+import collections.abc as collections_abc
+import contextlib
+from enum import IntEnum
+import functools
+import itertools
+import operator
+import re
+from time import perf_counter
+import typing
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import ClassVar
+from typing import Dict
+from typing import FrozenSet
+from typing import Iterable
+from typing import Iterator
+from typing import List
+from typing import Mapping
+from typing import MutableMapping
+from typing import NamedTuple
+from typing import NoReturn
+from typing import Optional
+from typing import Pattern
+from typing import Sequence
+from typing import Set
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import Union
+
+from . import base
+from . import coercions
+from . import crud
+from . import elements
+from . import functions
+from . import operators
+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 _de_clone
+from .base import _from_objects
+from .base import _NONE_NAME
+from .base import _SentinelDefaultCharacterization
+from .base import Executable
+from .base import NO_ARG
+from .elements import ClauseElement
+from .elements import quoted_name
+from .schema import Column
+from .sqltypes import TupleType
+from .type_api import TypeEngine
+from .visitors import prefix_anon_map
+from .visitors import Visitable
+from .. import exc
+from .. import util
+from ..util import FastIntFlag
+from ..util.typing import Literal
+from ..util.typing import Protocol
+from ..util.typing import TypedDict
+
+if typing.TYPE_CHECKING:
+ from .annotation import _AnnotationDict
+ from .base import _AmbiguousTableNameMap
+ from .base import CompileState
+ from .cache_key import CacheKey
+ from .ddl import ExecutableDDLElement
+ from .dml import Insert
+ from .dml import UpdateBase
+ from .dml import ValuesBase
+ from .elements import _truncated_label
+ from .elements import BindParameter
+ from .elements import ColumnClause
+ from .elements import ColumnElement
+ from .elements import Label
+ from .functions import Function
+ from .schema import Table
+ from .selectable import AliasedReturnsRows
+ from .selectable import CompoundSelectState
+ from .selectable import CTE
+ from .selectable import FromClause
+ from .selectable import NamedFromClause
+ from .selectable import ReturnsRows
+ from .selectable import Select
+ from .selectable import SelectState
+ from .type_api import _BindProcessorType
+ from ..engine.cursor import CursorResultMetaData
+ from ..engine.interfaces import _CoreSingleExecuteParams
+ from ..engine.interfaces import _DBAPIAnyExecuteParams
+ from ..engine.interfaces import _DBAPIMultiExecuteParams
+ from ..engine.interfaces import _DBAPISingleExecuteParams
+ from ..engine.interfaces import _ExecuteOptions
+ from ..engine.interfaces import _GenericSetInputSizesType
+ from ..engine.interfaces import _MutableCoreSingleExecuteParams
+ from ..engine.interfaces import Dialect
+ from ..engine.interfaces import SchemaTranslateMapType
+
+_FromHintsType = Dict["FromClause", str]
+
+RESERVED_WORDS = {
+ "all",
+ "analyse",
+ "analyze",
+ "and",
+ "any",
+ "array",
+ "as",
+ "asc",
+ "asymmetric",
+ "authorization",
+ "between",
+ "binary",
+ "both",
+ "case",
+ "cast",
+ "check",
+ "collate",
+ "column",
+ "constraint",
+ "create",
+ "cross",
+ "current_date",
+ "current_role",
+ "current_time",
+ "current_timestamp",
+ "current_user",
+ "default",
+ "deferrable",
+ "desc",
+ "distinct",
+ "do",
+ "else",
+ "end",
+ "except",
+ "false",
+ "for",
+ "foreign",
+ "freeze",
+ "from",
+ "full",
+ "grant",
+ "group",
+ "having",
+ "ilike",
+ "in",
+ "initially",
+ "inner",
+ "intersect",
+ "into",
+ "is",
+ "isnull",
+ "join",
+ "leading",
+ "left",
+ "like",
+ "limit",
+ "localtime",
+ "localtimestamp",
+ "natural",
+ "new",
+ "not",
+ "notnull",
+ "null",
+ "off",
+ "offset",
+ "old",
+ "on",
+ "only",
+ "or",
+ "order",
+ "outer",
+ "overlaps",
+ "placing",
+ "primary",
+ "references",
+ "right",
+ "select",
+ "session_user",
+ "set",
+ "similar",
+ "some",
+ "symmetric",
+ "table",
+ "then",
+ "to",
+ "trailing",
+ "true",
+ "union",
+ "unique",
+ "user",
+ "using",
+ "verbose",
+ "when",
+ "where",
+}
+
+LEGAL_CHARACTERS = re.compile(r"^[A-Z0-9_$]+$", re.I)
+LEGAL_CHARACTERS_PLUS_SPACE = re.compile(r"^[A-Z0-9_ $]+$", re.I)
+ILLEGAL_INITIAL_CHARACTERS = {str(x) for x in range(0, 10)}.union(["$"])
+
+FK_ON_DELETE = re.compile(
+ r"^(?:RESTRICT|CASCADE|SET NULL|NO ACTION|SET DEFAULT)$", re.I
+)
+FK_ON_UPDATE = re.compile(
+ r"^(?:RESTRICT|CASCADE|SET NULL|NO ACTION|SET DEFAULT)$", re.I
+)
+FK_INITIALLY = re.compile(r"^(?:DEFERRED|IMMEDIATE)$", re.I)
+BIND_PARAMS = re.compile(r"(?<![:\w\$\x5c]):([\w\$]+)(?![:\w\$])", re.UNICODE)
+BIND_PARAMS_ESC = re.compile(r"\x5c(:[\w\$]*)(?![:\w\$])", re.UNICODE)
+
+_pyformat_template = "%%(%(name)s)s"
+BIND_TEMPLATES = {
+ "pyformat": _pyformat_template,
+ "qmark": "?",
+ "format": "%%s",
+ "numeric": ":[_POSITION]",
+ "numeric_dollar": "$[_POSITION]",
+ "named": ":%(name)s",
+}
+
+
+OPERATORS = {
+ # binary
+ operators.and_: " AND ",
+ operators.or_: " OR ",
+ operators.add: " + ",
+ operators.mul: " * ",
+ operators.sub: " - ",
+ operators.mod: " % ",
+ operators.neg: "-",
+ operators.lt: " < ",
+ operators.le: " <= ",
+ operators.ne: " != ",
+ operators.gt: " > ",
+ operators.ge: " >= ",
+ operators.eq: " = ",
+ operators.is_distinct_from: " IS DISTINCT FROM ",
+ operators.is_not_distinct_from: " IS NOT DISTINCT FROM ",
+ operators.concat_op: " || ",
+ operators.match_op: " MATCH ",
+ operators.not_match_op: " NOT MATCH ",
+ operators.in_op: " IN ",
+ operators.not_in_op: " NOT IN ",
+ operators.comma_op: ", ",
+ operators.from_: " FROM ",
+ operators.as_: " AS ",
+ operators.is_: " IS ",
+ operators.is_not: " IS NOT ",
+ operators.collate: " COLLATE ",
+ # unary
+ operators.exists: "EXISTS ",
+ operators.distinct_op: "DISTINCT ",
+ operators.inv: "NOT ",
+ operators.any_op: "ANY ",
+ operators.all_op: "ALL ",
+ # modifiers
+ operators.desc_op: " DESC",
+ operators.asc_op: " ASC",
+ operators.nulls_first_op: " NULLS FIRST",
+ operators.nulls_last_op: " NULLS LAST",
+ # bitwise
+ operators.bitwise_xor_op: " ^ ",
+ operators.bitwise_or_op: " | ",
+ operators.bitwise_and_op: " & ",
+ operators.bitwise_not_op: "~",
+ operators.bitwise_lshift_op: " << ",
+ operators.bitwise_rshift_op: " >> ",
+}
+
+FUNCTIONS: Dict[Type[Function[Any]], str] = {
+ functions.coalesce: "coalesce",
+ functions.current_date: "CURRENT_DATE",
+ functions.current_time: "CURRENT_TIME",
+ functions.current_timestamp: "CURRENT_TIMESTAMP",
+ functions.current_user: "CURRENT_USER",
+ functions.localtime: "LOCALTIME",
+ functions.localtimestamp: "LOCALTIMESTAMP",
+ functions.random: "random",
+ functions.sysdate: "sysdate",
+ functions.session_user: "SESSION_USER",
+ functions.user: "USER",
+ functions.cube: "CUBE",
+ functions.rollup: "ROLLUP",
+ functions.grouping_sets: "GROUPING SETS",
+}
+
+
+EXTRACT_MAP = {
+ "month": "month",
+ "day": "day",
+ "year": "year",
+ "second": "second",
+ "hour": "hour",
+ "doy": "doy",
+ "minute": "minute",
+ "quarter": "quarter",
+ "dow": "dow",
+ "week": "week",
+ "epoch": "epoch",
+ "milliseconds": "milliseconds",
+ "microseconds": "microseconds",
+ "timezone_hour": "timezone_hour",
+ "timezone_minute": "timezone_minute",
+}
+
+COMPOUND_KEYWORDS = {
+ selectable._CompoundSelectKeyword.UNION: "UNION",
+ selectable._CompoundSelectKeyword.UNION_ALL: "UNION ALL",
+ selectable._CompoundSelectKeyword.EXCEPT: "EXCEPT",
+ selectable._CompoundSelectKeyword.EXCEPT_ALL: "EXCEPT ALL",
+ selectable._CompoundSelectKeyword.INTERSECT: "INTERSECT",
+ selectable._CompoundSelectKeyword.INTERSECT_ALL: "INTERSECT ALL",
+}
+
+
+class ResultColumnsEntry(NamedTuple):
+ """Tracks a column expression that is expected to be represented
+ in the result rows for this statement.
+
+ This normally refers to the columns clause of a SELECT statement
+ but may also refer to a RETURNING clause, as well as for dialect-specific
+ emulations.
+
+ """
+
+ keyname: str
+ """string name that's expected in cursor.description"""
+
+ name: str
+ """column name, may be labeled"""
+
+ objects: Tuple[Any, ...]
+ """sequence of objects that should be able to locate this column
+ in a RowMapping. This is typically string names and aliases
+ as well as Column objects.
+
+ """
+
+ type: TypeEngine[Any]
+ """Datatype to be associated with this column. This is where
+ the "result processing" logic directly links the compiled statement
+ to the rows that come back from the cursor.
+
+ """
+
+
+class _ResultMapAppender(Protocol):
+ def __call__(
+ self,
+ keyname: str,
+ name: str,
+ objects: Sequence[Any],
+ type_: TypeEngine[Any],
+ ) -> None: ...
+
+
+# integer indexes into ResultColumnsEntry used by cursor.py.
+# some profiling showed integer access faster than named tuple
+RM_RENDERED_NAME: Literal[0] = 0
+RM_NAME: Literal[1] = 1
+RM_OBJECTS: Literal[2] = 2
+RM_TYPE: Literal[3] = 3
+
+
+class _BaseCompilerStackEntry(TypedDict):
+ asfrom_froms: Set[FromClause]
+ correlate_froms: Set[FromClause]
+ selectable: ReturnsRows
+
+
+class _CompilerStackEntry(_BaseCompilerStackEntry, total=False):
+ compile_state: CompileState
+ need_result_map_for_nested: bool
+ need_result_map_for_compound: bool
+ select_0: ReturnsRows
+ insert_from_select: Select[Any]
+
+
+class ExpandedState(NamedTuple):
+ """represents state to use when producing "expanded" and
+ "post compile" bound parameters for a statement.
+
+ "expanded" parameters are parameters that are generated at
+ statement execution time to suit a number of parameters passed, the most
+ prominent example being the individual elements inside of an IN expression.
+
+ "post compile" parameters are parameters where the SQL literal value
+ will be rendered into the SQL statement at execution time, rather than
+ being passed as separate parameters to the driver.
+
+ To create an :class:`.ExpandedState` instance, use the
+ :meth:`.SQLCompiler.construct_expanded_state` method on any
+ :class:`.SQLCompiler` instance.
+
+ """
+
+ statement: str
+ """String SQL statement with parameters fully expanded"""
+
+ parameters: _CoreSingleExecuteParams
+ """Parameter dictionary with parameters fully expanded.
+
+ For a statement that uses named parameters, this dictionary will map
+ exactly to the names in the statement. For a statement that uses
+ positional parameters, the :attr:`.ExpandedState.positional_parameters`
+ will yield a tuple with the positional parameter set.
+
+ """
+
+ processors: Mapping[str, _BindProcessorType[Any]]
+ """mapping of bound value processors"""
+
+ positiontup: Optional[Sequence[str]]
+ """Sequence of string names indicating the order of positional
+ parameters"""
+
+ parameter_expansion: Mapping[str, List[str]]
+ """Mapping representing the intermediary link from original parameter
+ name to list of "expanded" parameter names, for those parameters that
+ were expanded."""
+
+ @property
+ def positional_parameters(self) -> Tuple[Any, ...]:
+ """Tuple of positional parameters, for statements that were compiled
+ using a positional paramstyle.
+
+ """
+ if self.positiontup is None:
+ raise exc.InvalidRequestError(
+ "statement does not use a positional paramstyle"
+ )
+ return tuple(self.parameters[key] for key in self.positiontup)
+
+ @property
+ def additional_parameters(self) -> _CoreSingleExecuteParams:
+ """synonym for :attr:`.ExpandedState.parameters`."""
+ return self.parameters
+
+
+class _InsertManyValues(NamedTuple):
+ """represents state to use for executing an "insertmanyvalues" statement.
+
+ The primary consumers of this object are the
+ :meth:`.SQLCompiler._deliver_insertmanyvalues_batches` and
+ :meth:`.DefaultDialect._deliver_insertmanyvalues_batches` methods.
+
+ .. versionadded:: 2.0
+
+ """
+
+ is_default_expr: bool
+ """if True, the statement is of the form
+ ``INSERT INTO TABLE DEFAULT VALUES``, and can't be rewritten as a "batch"
+
+ """
+
+ single_values_expr: str
+ """The rendered "values" clause of the INSERT statement.
+
+ This is typically the parenthesized section e.g. "(?, ?, ?)" or similar.
+ The insertmanyvalues logic uses this string as a search and replace
+ target.
+
+ """
+
+ insert_crud_params: List[crud._CrudParamElementStr]
+ """List of Column / bind names etc. used while rewriting the statement"""
+
+ num_positional_params_counted: int
+ """the number of bound parameters in a single-row statement.
+
+ This count may be larger or smaller than the actual number of columns
+ targeted in the INSERT, as it accommodates for SQL expressions
+ in the values list that may have zero or more parameters embedded
+ within them.
+
+ This count is part of what's used to organize rewritten parameter lists
+ when batching.
+
+ """
+
+ sort_by_parameter_order: bool = False
+ """if the deterministic_returnined_order parameter were used on the
+ insert.
+
+ All of the attributes following this will only be used if this is True.
+
+ """
+
+ includes_upsert_behaviors: bool = False
+ """if True, we have to accommodate for upsert behaviors.
+
+ This will in some cases downgrade "insertmanyvalues" that requests
+ deterministic ordering.
+
+ """
+
+ sentinel_columns: Optional[Sequence[Column[Any]]] = None
+ """List of sentinel columns that were located.
+
+ This list is only here if the INSERT asked for
+ sort_by_parameter_order=True,
+ and dialect-appropriate sentinel columns were located.
+
+ .. versionadded:: 2.0.10
+
+ """
+
+ num_sentinel_columns: int = 0
+ """how many sentinel columns are in the above list, if any.
+
+ This is the same as
+ ``len(sentinel_columns) if sentinel_columns is not None else 0``
+
+ """
+
+ sentinel_param_keys: Optional[Sequence[str]] = None
+ """parameter str keys in each param dictionary / tuple
+ that would link to the client side "sentinel" values for that row, which
+ we can use to match up parameter sets to result rows.
+
+ This is only present if sentinel_columns is present and the INSERT
+ statement actually refers to client side values for these sentinel
+ columns.
+
+ .. versionadded:: 2.0.10
+
+ .. versionchanged:: 2.0.29 - the sequence is now string dictionary keys
+ only, used against the "compiled parameteters" collection before
+ the parameters were converted by bound parameter processors
+
+ """
+
+ implicit_sentinel: bool = False
+ """if True, we have exactly one sentinel column and it uses a server side
+ value, currently has to generate an incrementing integer value.
+
+ The dialect in question would have asserted that it supports receiving
+ these values back and sorting on that value as a means of guaranteeing
+ correlation with the incoming parameter list.
+
+ .. versionadded:: 2.0.10
+
+ """
+
+ embed_values_counter: bool = False
+ """Whether to embed an incrementing integer counter in each parameter
+ set within the VALUES clause as parameters are batched over.
+
+ This is only used for a specific INSERT..SELECT..VALUES..RETURNING syntax
+ where a subquery is used to produce value tuples. Current support
+ includes PostgreSQL, Microsoft SQL Server.
+
+ .. versionadded:: 2.0.10
+
+ """
+
+
+class _InsertManyValuesBatch(NamedTuple):
+ """represents an individual batch SQL statement for insertmanyvalues.
+
+ This is passed through the
+ :meth:`.SQLCompiler._deliver_insertmanyvalues_batches` and
+ :meth:`.DefaultDialect._deliver_insertmanyvalues_batches` methods out
+ to the :class:`.Connection` within the
+ :meth:`.Connection._exec_insertmany_context` method.
+
+ .. versionadded:: 2.0.10
+
+ """
+
+ replaced_statement: str
+ replaced_parameters: _DBAPIAnyExecuteParams
+ processed_setinputsizes: Optional[_GenericSetInputSizesType]
+ batch: Sequence[_DBAPISingleExecuteParams]
+ sentinel_values: Sequence[Tuple[Any, ...]]
+ current_batch_size: int
+ batchnum: int
+ total_batches: int
+ rows_sorted: bool
+ is_downgraded: bool
+
+
+class InsertmanyvaluesSentinelOpts(FastIntFlag):
+ """bitflag enum indicating styles of PK defaults
+ which can work as implicit sentinel columns
+
+ """
+
+ NOT_SUPPORTED = 1
+ AUTOINCREMENT = 2
+ IDENTITY = 4
+ SEQUENCE = 8
+
+ ANY_AUTOINCREMENT = AUTOINCREMENT | IDENTITY | SEQUENCE
+ _SUPPORTED_OR_NOT = NOT_SUPPORTED | ANY_AUTOINCREMENT
+
+ USE_INSERT_FROM_SELECT = 16
+ RENDER_SELECT_COL_CASTS = 64
+
+
+class CompilerState(IntEnum):
+ COMPILING = 0
+ """statement is present, compilation phase in progress"""
+
+ STRING_APPLIED = 1
+ """statement is present, string form of the statement has been applied.
+
+ Additional processors by subclasses may still be pending.
+
+ """
+
+ NO_STATEMENT = 2
+ """compiler does not have a statement to compile, is used
+ for method access"""
+
+
+class Linting(IntEnum):
+ """represent preferences for the 'SQL linting' feature.
+
+ this feature currently includes support for flagging cartesian products
+ in SQL statements.
+
+ """
+
+ NO_LINTING = 0
+ "Disable all linting."
+
+ COLLECT_CARTESIAN_PRODUCTS = 1
+ """Collect data on FROMs and cartesian products and gather into
+ 'self.from_linter'"""
+
+ WARN_LINTING = 2
+ "Emit warnings for linters that find problems"
+
+ FROM_LINTING = COLLECT_CARTESIAN_PRODUCTS | WARN_LINTING
+ """Warn for cartesian products; combines COLLECT_CARTESIAN_PRODUCTS
+ and WARN_LINTING"""
+
+
+NO_LINTING, COLLECT_CARTESIAN_PRODUCTS, WARN_LINTING, FROM_LINTING = tuple(
+ Linting
+)
+
+
+class FromLinter(collections.namedtuple("FromLinter", ["froms", "edges"])):
+ """represents current state for the "cartesian product" detection
+ feature."""
+
+ def lint(self, start=None):
+ froms = self.froms
+ if not froms:
+ return None, None
+
+ edges = set(self.edges)
+ the_rest = set(froms)
+
+ if start is not None:
+ start_with = start
+ the_rest.remove(start_with)
+ else:
+ start_with = the_rest.pop()
+
+ stack = collections.deque([start_with])
+
+ while stack and the_rest:
+ node = stack.popleft()
+ the_rest.discard(node)
+
+ # comparison of nodes in edges here is based on hash equality, as
+ # there are "annotated" elements that match the non-annotated ones.
+ # to remove the need for in-python hash() calls, use native
+ # containment routines (e.g. "node in edge", "edge.index(node)")
+ to_remove = {edge for edge in edges if node in edge}
+
+ # appendleft the node in each edge that is not
+ # the one that matched.
+ stack.extendleft(edge[not edge.index(node)] for edge in to_remove)
+ edges.difference_update(to_remove)
+
+ # FROMS left over? boom
+ if the_rest:
+ return the_rest, start_with
+ else:
+ return None, None
+
+ def warn(self, stmt_type="SELECT"):
+ the_rest, start_with = self.lint()
+
+ # FROMS left over? boom
+ if the_rest:
+ froms = the_rest
+ if froms:
+ template = (
+ "{stmt_type} statement has a cartesian product between "
+ "FROM element(s) {froms} and "
+ 'FROM element "{start}". Apply join condition(s) '
+ "between each element to resolve."
+ )
+ froms_str = ", ".join(
+ f'"{self.froms[from_]}"' for from_ in froms
+ )
+ message = template.format(
+ stmt_type=stmt_type,
+ froms=froms_str,
+ start=self.froms[start_with],
+ )
+
+ util.warn(message)
+
+
+class Compiled:
+ """Represent a compiled SQL or DDL expression.
+
+ The ``__str__`` method of the ``Compiled`` object should produce
+ the actual text of the statement. ``Compiled`` objects are
+ specific to their underlying database dialect, and also may
+ or may not be specific to the columns referenced within a
+ particular set of bind parameters. In no case should the
+ ``Compiled`` object be dependent on the actual values of those
+ bind parameters, even though it may reference those values as
+ defaults.
+ """
+
+ statement: Optional[ClauseElement] = None
+ "The statement to compile."
+ string: str = ""
+ "The string representation of the ``statement``"
+
+ state: CompilerState
+ """description of the compiler's state"""
+
+ is_sql = False
+ is_ddl = False
+
+ _cached_metadata: Optional[CursorResultMetaData] = None
+
+ _result_columns: Optional[List[ResultColumnsEntry]] = None
+
+ schema_translate_map: Optional[SchemaTranslateMapType] = None
+
+ execution_options: _ExecuteOptions = util.EMPTY_DICT
+ """
+ Execution options propagated from the statement. In some cases,
+ sub-elements of the statement can modify these.
+ """
+
+ preparer: IdentifierPreparer
+
+ _annotations: _AnnotationDict = util.EMPTY_DICT
+
+ compile_state: Optional[CompileState] = None
+ """Optional :class:`.CompileState` object that maintains additional
+ state used by the compiler.
+
+ Major executable objects such as :class:`_expression.Insert`,
+ :class:`_expression.Update`, :class:`_expression.Delete`,
+ :class:`_expression.Select` will generate this
+ state when compiled in order to calculate additional information about the
+ object. For the top level object that is to be executed, the state can be
+ stored here where it can also have applicability towards result set
+ processing.
+
+ .. versionadded:: 1.4
+
+ """
+
+ dml_compile_state: Optional[CompileState] = None
+ """Optional :class:`.CompileState` assigned at the same point that
+ .isinsert, .isupdate, or .isdelete is assigned.
+
+ This will normally be the same object as .compile_state, with the
+ exception of cases like the :class:`.ORMFromStatementCompileState`
+ object.
+
+ .. versionadded:: 1.4.40
+
+ """
+
+ cache_key: Optional[CacheKey] = None
+ """The :class:`.CacheKey` that was generated ahead of creating this
+ :class:`.Compiled` object.
+
+ This is used for routines that need access to the original
+ :class:`.CacheKey` instance generated when the :class:`.Compiled`
+ instance was first cached, typically in order to reconcile
+ the original list of :class:`.BindParameter` objects with a
+ per-statement list that's generated on each call.
+
+ """
+
+ _gen_time: float
+ """Generation time of this :class:`.Compiled`, used for reporting
+ cache stats."""
+
+ def __init__(
+ self,
+ dialect: Dialect,
+ statement: Optional[ClauseElement],
+ schema_translate_map: Optional[SchemaTranslateMapType] = None,
+ render_schema_translate: bool = False,
+ compile_kwargs: Mapping[str, Any] = util.immutabledict(),
+ ):
+ """Construct a new :class:`.Compiled` object.
+
+ :param dialect: :class:`.Dialect` to compile against.
+
+ :param statement: :class:`_expression.ClauseElement` to be compiled.
+
+ :param schema_translate_map: dictionary of schema names to be
+ translated when forming the resultant SQL
+
+ .. seealso::
+
+ :ref:`schema_translating`
+
+ :param compile_kwargs: additional kwargs that will be
+ passed to the initial call to :meth:`.Compiled.process`.
+
+
+ """
+ self.dialect = dialect
+ self.preparer = self.dialect.identifier_preparer
+ if schema_translate_map:
+ self.schema_translate_map = schema_translate_map
+ self.preparer = self.preparer._with_schema_translate(
+ schema_translate_map
+ )
+
+ if statement is not None:
+ self.state = CompilerState.COMPILING
+ self.statement = statement
+ self.can_execute = statement.supports_execution
+ self._annotations = statement._annotations
+ if self.can_execute:
+ if TYPE_CHECKING:
+ assert isinstance(statement, Executable)
+ self.execution_options = statement._execution_options
+ self.string = self.process(self.statement, **compile_kwargs)
+
+ if render_schema_translate:
+ self.string = self.preparer._render_schema_translates(
+ self.string, schema_translate_map
+ )
+
+ self.state = CompilerState.STRING_APPLIED
+ else:
+ self.state = CompilerState.NO_STATEMENT
+
+ self._gen_time = perf_counter()
+
+ def __init_subclass__(cls) -> None:
+ cls._init_compiler_cls()
+ return super().__init_subclass__()
+
+ @classmethod
+ def _init_compiler_cls(cls):
+ pass
+
+ def _execute_on_connection(
+ self, connection, distilled_params, execution_options
+ ):
+ if self.can_execute:
+ return connection._execute_compiled(
+ self, distilled_params, execution_options
+ )
+ else:
+ raise exc.ObjectNotExecutableError(self.statement)
+
+ def visit_unsupported_compilation(self, element, err, **kw):
+ raise exc.UnsupportedCompilationError(self, type(element)) from err
+
+ @property
+ def sql_compiler(self):
+ """Return a Compiled that is capable of processing SQL expressions.
+
+ If this compiler is one, it would likely just return 'self'.
+
+ """
+
+ raise NotImplementedError()
+
+ def process(self, obj: Visitable, **kwargs: Any) -> str:
+ return obj._compiler_dispatch(self, **kwargs)
+
+ def __str__(self) -> str:
+ """Return the string text of the generated SQL or DDL."""
+
+ if self.state is CompilerState.STRING_APPLIED:
+ return self.string
+ else:
+ return ""
+
+ def construct_params(
+ self,
+ params: Optional[_CoreSingleExecuteParams] = None,
+ extracted_parameters: Optional[Sequence[BindParameter[Any]]] = None,
+ escape_names: bool = True,
+ ) -> Optional[_MutableCoreSingleExecuteParams]:
+ """Return the bind params for this compiled object.
+
+ :param params: a dict of string/object pairs whose values will
+ override bind values compiled in to the
+ statement.
+ """
+
+ raise NotImplementedError()
+
+ @property
+ def params(self):
+ """Return the bind params for this compiled object."""
+ return self.construct_params()
+
+
+class TypeCompiler(util.EnsureKWArg):
+ """Produces DDL specification for TypeEngine objects."""
+
+ ensure_kwarg = r"visit_\w+"
+
+ def __init__(self, dialect: Dialect):
+ self.dialect = dialect
+
+ def process(self, type_: TypeEngine[Any], **kw: Any) -> str:
+ if (
+ type_._variant_mapping
+ and self.dialect.name in type_._variant_mapping
+ ):
+ type_ = type_._variant_mapping[self.dialect.name]
+ return type_._compiler_dispatch(self, **kw)
+
+ def visit_unsupported_compilation(
+ self, element: Any, err: Exception, **kw: Any
+ ) -> NoReturn:
+ raise exc.UnsupportedCompilationError(self, element) from err
+
+
+# this was a Visitable, but to allow accurate detection of
+# column elements this is actually a column element
+class _CompileLabel(
+ roles.BinaryElementRole[Any], elements.CompilerColumnElement
+):
+ """lightweight label object which acts as an expression.Label."""
+
+ __visit_name__ = "label"
+ __slots__ = "element", "name", "_alt_names"
+
+ def __init__(self, col, name, alt_names=()):
+ self.element = col
+ self.name = name
+ self._alt_names = (col,) + alt_names
+
+ @property
+ def proxy_set(self):
+ return self.element.proxy_set
+
+ @property
+ def type(self):
+ return self.element.type
+
+ def self_group(self, **kw):
+ return self
+
+
+class ilike_case_insensitive(
+ roles.BinaryElementRole[Any], elements.CompilerColumnElement
+):
+ """produce a wrapping element for a case-insensitive portion of
+ an ILIKE construct.
+
+ The construct usually renders the ``lower()`` function, but on
+ PostgreSQL will pass silently with the assumption that "ILIKE"
+ is being used.
+
+ .. versionadded:: 2.0
+
+ """
+
+ __visit_name__ = "ilike_case_insensitive_operand"
+ __slots__ = "element", "comparator"
+
+ def __init__(self, element):
+ self.element = element
+ self.comparator = element.comparator
+
+ @property
+ def proxy_set(self):
+ return self.element.proxy_set
+
+ @property
+ def type(self):
+ return self.element.type
+
+ def self_group(self, **kw):
+ return self
+
+ def _with_binary_element_type(self, type_):
+ return ilike_case_insensitive(
+ self.element._with_binary_element_type(type_)
+ )
+
+
+class SQLCompiler(Compiled):
+ """Default implementation of :class:`.Compiled`.
+
+ Compiles :class:`_expression.ClauseElement` objects into SQL strings.
+
+ """
+
+ extract_map = EXTRACT_MAP
+
+ bindname_escape_characters: ClassVar[Mapping[str, str]] = (
+ util.immutabledict(
+ {
+ "%": "P",
+ "(": "A",
+ ")": "Z",
+ ":": "C",
+ ".": "_",
+ "[": "_",
+ "]": "_",
+ " ": "_",
+ }
+ )
+ )
+ """A mapping (e.g. dict or similar) containing a lookup of
+ characters keyed to replacement characters which will be applied to all
+ 'bind names' used in SQL statements as a form of 'escaping'; the given
+ characters are replaced entirely with the 'replacement' character when
+ rendered in the SQL statement, and a similar translation is performed
+ on the incoming names used in parameter dictionaries passed to methods
+ like :meth:`_engine.Connection.execute`.
+
+ This allows bound parameter names used in :func:`_sql.bindparam` and
+ other constructs to have any arbitrary characters present without any
+ concern for characters that aren't allowed at all on the target database.
+
+ Third party dialects can establish their own dictionary here to replace the
+ default mapping, which will ensure that the particular characters in the
+ mapping will never appear in a bound parameter name.
+
+ The dictionary is evaluated at **class creation time**, so cannot be
+ modified at runtime; it must be present on the class when the class
+ is first declared.
+
+ Note that for dialects that have additional bound parameter rules such
+ as additional restrictions on leading characters, the
+ :meth:`_sql.SQLCompiler.bindparam_string` method may need to be augmented.
+ See the cx_Oracle compiler for an example of this.
+
+ .. versionadded:: 2.0.0rc1
+
+ """
+
+ _bind_translate_re: ClassVar[Pattern[str]]
+ _bind_translate_chars: ClassVar[Mapping[str, str]]
+
+ is_sql = True
+
+ compound_keywords = COMPOUND_KEYWORDS
+
+ isdelete: bool = False
+ isinsert: bool = False
+ isupdate: bool = False
+ """class-level defaults which can be set at the instance
+ level to define if this Compiled instance represents
+ INSERT/UPDATE/DELETE
+ """
+
+ postfetch: Optional[List[Column[Any]]]
+ """list of columns that can be post-fetched after INSERT or UPDATE to
+ receive server-updated values"""
+
+ insert_prefetch: Sequence[Column[Any]] = ()
+ """list of columns for which default values should be evaluated before
+ an INSERT takes place"""
+
+ update_prefetch: Sequence[Column[Any]] = ()
+ """list of columns for which onupdate default values should be evaluated
+ before an UPDATE takes place"""
+
+ implicit_returning: Optional[Sequence[ColumnElement[Any]]] = None
+ """list of "implicit" returning columns for a toplevel INSERT or UPDATE
+ statement, used to receive newly generated values of columns.
+
+ .. versionadded:: 2.0 ``implicit_returning`` replaces the previous
+ ``returning`` collection, which was not a generalized RETURNING
+ collection and instead was in fact specific to the "implicit returning"
+ feature.
+
+ """
+
+ isplaintext: bool = False
+
+ binds: Dict[str, BindParameter[Any]]
+ """a dictionary of bind parameter keys to BindParameter instances."""
+
+ bind_names: Dict[BindParameter[Any], str]
+ """a dictionary of BindParameter instances to "compiled" names
+ that are actually present in the generated SQL"""
+
+ stack: List[_CompilerStackEntry]
+ """major statements such as SELECT, INSERT, UPDATE, DELETE are
+ tracked in this stack using an entry format."""
+
+ returning_precedes_values: bool = False
+ """set to True classwide to generate RETURNING
+ clauses before the VALUES or WHERE clause (i.e. MSSQL)
+ """
+
+ render_table_with_column_in_update_from: bool = False
+ """set to True classwide to indicate the SET clause
+ in a multi-table UPDATE statement should qualify
+ columns with the table name (i.e. MySQL only)
+ """
+
+ ansi_bind_rules: bool = False
+ """SQL 92 doesn't allow bind parameters to be used
+ in the columns clause of a SELECT, nor does it allow
+ ambiguous expressions like "? = ?". A compiler
+ subclass can set this flag to False if the target
+ driver/DB enforces this
+ """
+
+ bindtemplate: str
+ """template to render bound parameters based on paramstyle."""
+
+ compilation_bindtemplate: str
+ """template used by compiler to render parameters before positional
+ paramstyle application"""
+
+ _numeric_binds_identifier_char: str
+ """Character that's used to as the identifier of a numerical bind param.
+ For example if this char is set to ``$``, numerical binds will be rendered
+ in the form ``$1, $2, $3``.
+ """
+
+ _result_columns: List[ResultColumnsEntry]
+ """relates label names in the final SQL to a tuple of local
+ column/label name, ColumnElement object (if any) and
+ TypeEngine. CursorResult uses this for type processing and
+ column targeting"""
+
+ _textual_ordered_columns: bool = False
+ """tell the result object that the column names as rendered are important,
+ but they are also "ordered" vs. what is in the compiled object here.
+
+ As of 1.4.42 this condition is only present when the statement is a
+ TextualSelect, e.g. text("....").columns(...), where it is required
+ that the columns are considered positionally and not by name.
+
+ """
+
+ _ad_hoc_textual: bool = False
+ """tell the result that we encountered text() or '*' constructs in the
+ middle of the result columns, but we also have compiled columns, so
+ if the number of columns in cursor.description does not match how many
+ expressions we have, that means we can't rely on positional at all and
+ should match on name.
+
+ """
+
+ _ordered_columns: bool = True
+ """
+ if False, means we can't be sure the list of entries
+ in _result_columns is actually the rendered order. Usually
+ True unless using an unordered TextualSelect.
+ """
+
+ _loose_column_name_matching: bool = False
+ """tell the result object that the SQL statement is textual, wants to match
+ up to Column objects, and may be using the ._tq_label in the SELECT rather
+ than the base name.
+
+ """
+
+ _numeric_binds: bool = False
+ """
+ True if paramstyle is "numeric". This paramstyle is trickier than
+ all the others.
+
+ """
+
+ _render_postcompile: bool = False
+ """
+ whether to render out POSTCOMPILE params during the compile phase.
+
+ This attribute is used only for end-user invocation of stmt.compile();
+ it's never used for actual statement execution, where instead the
+ dialect internals access and render the internal postcompile structure
+ directly.
+
+ """
+
+ _post_compile_expanded_state: Optional[ExpandedState] = None
+ """When render_postcompile is used, the ``ExpandedState`` used to create
+ the "expanded" SQL is assigned here, and then used by the ``.params``
+ accessor and ``.construct_params()`` methods for their return values.
+
+ .. versionadded:: 2.0.0rc1
+
+ """
+
+ _pre_expanded_string: Optional[str] = None
+ """Stores the original string SQL before 'post_compile' is applied,
+ for cases where 'post_compile' were used.
+
+ """
+
+ _pre_expanded_positiontup: Optional[List[str]] = None
+
+ _insertmanyvalues: Optional[_InsertManyValues] = None
+
+ _insert_crud_params: Optional[crud._CrudParamSequence] = None
+
+ literal_execute_params: FrozenSet[BindParameter[Any]] = frozenset()
+ """bindparameter objects that are rendered as literal values at statement
+ execution time.
+
+ """
+
+ post_compile_params: FrozenSet[BindParameter[Any]] = frozenset()
+ """bindparameter objects that are rendered as bound parameter placeholders
+ at statement execution time.
+
+ """
+
+ escaped_bind_names: util.immutabledict[str, str] = util.EMPTY_DICT
+ """Late escaping of bound parameter names that has to be converted
+ to the original name when looking in the parameter dictionary.
+
+ """
+
+ has_out_parameters = False
+ """if True, there are bindparam() objects that have the isoutparam
+ flag set."""
+
+ postfetch_lastrowid = False
+ """if True, and this in insert, use cursor.lastrowid to populate
+ result.inserted_primary_key. """
+
+ _cache_key_bind_match: Optional[
+ Tuple[
+ Dict[
+ BindParameter[Any],
+ List[BindParameter[Any]],
+ ],
+ Dict[
+ str,
+ BindParameter[Any],
+ ],
+ ]
+ ] = None
+ """a mapping that will relate the BindParameter object we compile
+ to those that are part of the extracted collection of parameters
+ in the cache key, if we were given a cache key.
+
+ """
+
+ positiontup: Optional[List[str]] = None
+ """for a compiled construct that uses a positional paramstyle, will be
+ a sequence of strings, indicating the names of bound parameters in order.
+
+ This is used in order to render bound parameters in their correct order,
+ and is combined with the :attr:`_sql.Compiled.params` dictionary to
+ render parameters.
+
+ This sequence always contains the unescaped name of the parameters.
+
+ .. seealso::
+
+ :ref:`faq_sql_expression_string` - includes a usage example for
+ debugging use cases.
+
+ """
+ _values_bindparam: Optional[List[str]] = None
+
+ _visited_bindparam: Optional[List[str]] = None
+
+ inline: bool = False
+
+ ctes: Optional[MutableMapping[CTE, str]]
+
+ # Detect same CTE references - Dict[(level, name), cte]
+ # Level is required for supporting nesting
+ ctes_by_level_name: Dict[Tuple[int, str], CTE]
+
+ # To retrieve key/level in ctes_by_level_name -
+ # Dict[cte_reference, (level, cte_name, cte_opts)]
+ level_name_by_cte: Dict[CTE, Tuple[int, str, selectable._CTEOpts]]
+
+ ctes_recursive: bool
+
+ _post_compile_pattern = re.compile(r"__\[POSTCOMPILE_(\S+?)(~~.+?~~)?\]")
+ _pyformat_pattern = re.compile(r"%\(([^)]+?)\)s")
+ _positional_pattern = re.compile(
+ f"{_pyformat_pattern.pattern}|{_post_compile_pattern.pattern}"
+ )
+
+ @classmethod
+ def _init_compiler_cls(cls):
+ cls._init_bind_translate()
+
+ @classmethod
+ def _init_bind_translate(cls):
+ reg = re.escape("".join(cls.bindname_escape_characters))
+ cls._bind_translate_re = re.compile(f"[{reg}]")
+ cls._bind_translate_chars = cls.bindname_escape_characters
+
+ def __init__(
+ self,
+ dialect: Dialect,
+ statement: Optional[ClauseElement],
+ cache_key: Optional[CacheKey] = None,
+ column_keys: Optional[Sequence[str]] = None,
+ for_executemany: bool = False,
+ linting: Linting = NO_LINTING,
+ _supporting_against: Optional[SQLCompiler] = None,
+ **kwargs: Any,
+ ):
+ """Construct a new :class:`.SQLCompiler` object.
+
+ :param dialect: :class:`.Dialect` to be used
+
+ :param statement: :class:`_expression.ClauseElement` to be compiled
+
+ :param column_keys: a list of column names to be compiled into an
+ INSERT or UPDATE statement.
+
+ :param for_executemany: whether INSERT / UPDATE statements should
+ expect that they are to be invoked in an "executemany" style,
+ which may impact how the statement will be expected to return the
+ values of defaults and autoincrement / sequences and similar.
+ Depending on the backend and driver in use, support for retrieving
+ these values may be disabled which means SQL expressions may
+ be rendered inline, RETURNING may not be rendered, etc.
+
+ :param kwargs: additional keyword arguments to be consumed by the
+ superclass.
+
+ """
+ self.column_keys = column_keys
+
+ self.cache_key = cache_key
+
+ if cache_key:
+ cksm = {b.key: b for b in cache_key[1]}
+ ckbm = {b: [b] for b in cache_key[1]}
+ self._cache_key_bind_match = (ckbm, cksm)
+
+ # compile INSERT/UPDATE defaults/sequences to expect executemany
+ # style execution, which may mean no pre-execute of defaults,
+ # or no RETURNING
+ self.for_executemany = for_executemany
+
+ self.linting = linting
+
+ # a dictionary of bind parameter keys to BindParameter
+ # instances.
+ self.binds = {}
+
+ # a dictionary of BindParameter instances to "compiled" names
+ # that are actually present in the generated SQL
+ self.bind_names = util.column_dict()
+
+ # stack which keeps track of nested SELECT statements
+ self.stack = []
+
+ self._result_columns = []
+
+ # true if the paramstyle is positional
+ self.positional = dialect.positional
+ if self.positional:
+ self._numeric_binds = nb = dialect.paramstyle.startswith("numeric")
+ if nb:
+ self._numeric_binds_identifier_char = (
+ "$" if dialect.paramstyle == "numeric_dollar" else ":"
+ )
+
+ self.compilation_bindtemplate = _pyformat_template
+ else:
+ self.compilation_bindtemplate = BIND_TEMPLATES[dialect.paramstyle]
+
+ self.ctes = None
+
+ self.label_length = (
+ dialect.label_length or dialect.max_identifier_length
+ )
+
+ # a map which tracks "anonymous" identifiers that are created on
+ # the fly here
+ self.anon_map = prefix_anon_map()
+
+ # a map which tracks "truncated" names based on
+ # dialect.label_length or dialect.max_identifier_length
+ self.truncated_names: Dict[Tuple[str, str], str] = {}
+ self._truncated_counters: Dict[str, int] = {}
+
+ Compiled.__init__(self, dialect, statement, **kwargs)
+
+ if self.isinsert or self.isupdate or self.isdelete:
+ if TYPE_CHECKING:
+ assert isinstance(statement, UpdateBase)
+
+ if self.isinsert or self.isupdate:
+ if TYPE_CHECKING:
+ assert isinstance(statement, ValuesBase)
+ if statement._inline:
+ self.inline = True
+ elif self.for_executemany and (
+ not self.isinsert
+ or (
+ self.dialect.insert_executemany_returning
+ and statement._return_defaults
+ )
+ ):
+ self.inline = True
+
+ self.bindtemplate = BIND_TEMPLATES[dialect.paramstyle]
+
+ if _supporting_against:
+ self.__dict__.update(
+ {
+ k: v
+ for k, v in _supporting_against.__dict__.items()
+ if k
+ not in {
+ "state",
+ "dialect",
+ "preparer",
+ "positional",
+ "_numeric_binds",
+ "compilation_bindtemplate",
+ "bindtemplate",
+ }
+ }
+ )
+
+ if self.state is CompilerState.STRING_APPLIED:
+ if self.positional:
+ if self._numeric_binds:
+ self._process_numeric()
+ else:
+ self._process_positional()
+
+ if self._render_postcompile:
+ parameters = self.construct_params(
+ escape_names=False,
+ _no_postcompile=True,
+ )
+
+ self._process_parameters_for_postcompile(
+ parameters, _populate_self=True
+ )
+
+ @property
+ def insert_single_values_expr(self) -> Optional[str]:
+ """When an INSERT is compiled with a single set of parameters inside
+ a VALUES expression, the string is assigned here, where it can be
+ used for insert batching schemes to rewrite the VALUES expression.
+
+ .. versionadded:: 1.3.8
+
+ .. versionchanged:: 2.0 This collection is no longer used by
+ SQLAlchemy's built-in dialects, in favor of the currently
+ internal ``_insertmanyvalues`` collection that is used only by
+ :class:`.SQLCompiler`.
+
+ """
+ if self._insertmanyvalues is None:
+ return None
+ else:
+ return self._insertmanyvalues.single_values_expr
+
+ @util.ro_memoized_property
+ def effective_returning(self) -> Optional[Sequence[ColumnElement[Any]]]:
+ """The effective "returning" columns for INSERT, UPDATE or DELETE.
+
+ This is either the so-called "implicit returning" columns which are
+ calculated by the compiler on the fly, or those present based on what's
+ present in ``self.statement._returning`` (expanded into individual
+ columns using the ``._all_selected_columns`` attribute) i.e. those set
+ explicitly using the :meth:`.UpdateBase.returning` method.
+
+ .. versionadded:: 2.0
+
+ """
+ if self.implicit_returning:
+ return self.implicit_returning
+ elif self.statement is not None and is_dml(self.statement):
+ return [
+ c
+ for c in self.statement._all_selected_columns
+ if is_column_element(c)
+ ]
+
+ else:
+ return None
+
+ @property
+ def returning(self):
+ """backwards compatibility; returns the
+ effective_returning collection.
+
+ """
+ return self.effective_returning
+
+ @property
+ def current_executable(self):
+ """Return the current 'executable' that is being compiled.
+
+ This is currently the :class:`_sql.Select`, :class:`_sql.Insert`,
+ :class:`_sql.Update`, :class:`_sql.Delete`,
+ :class:`_sql.CompoundSelect` object that is being compiled.
+ Specifically it's assigned to the ``self.stack`` list of elements.
+
+ When a statement like the above is being compiled, it normally
+ is also assigned to the ``.statement`` attribute of the
+ :class:`_sql.Compiler` object. However, all SQL constructs are
+ ultimately nestable, and this attribute should never be consulted
+ by a ``visit_`` method, as it is not guaranteed to be assigned
+ nor guaranteed to correspond to the current statement being compiled.
+
+ .. versionadded:: 1.3.21
+
+ For compatibility with previous versions, use the following
+ recipe::
+
+ statement = getattr(self, "current_executable", False)
+ if statement is False:
+ statement = self.stack[-1]["selectable"]
+
+ For versions 1.4 and above, ensure only .current_executable
+ is used; the format of "self.stack" may change.
+
+
+ """
+ try:
+ return self.stack[-1]["selectable"]
+ except IndexError as ie:
+ raise IndexError("Compiler does not have a stack entry") from ie
+
+ @property
+ def prefetch(self):
+ return list(self.insert_prefetch) + list(self.update_prefetch)
+
+ @util.memoized_property
+ def _global_attributes(self) -> Dict[Any, Any]:
+ return {}
+
+ @util.memoized_instancemethod
+ def _init_cte_state(self) -> MutableMapping[CTE, str]:
+ """Initialize collections related to CTEs only if
+ a CTE is located, to save on the overhead of
+ these collections otherwise.
+
+ """
+ # collect CTEs to tack on top of a SELECT
+ # To store the query to print - Dict[cte, text_query]
+ ctes: MutableMapping[CTE, str] = util.OrderedDict()
+ self.ctes = ctes
+
+ # Detect same CTE references - Dict[(level, name), cte]
+ # Level is required for supporting nesting
+ self.ctes_by_level_name = {}
+
+ # To retrieve key/level in ctes_by_level_name -
+ # Dict[cte_reference, (level, cte_name, cte_opts)]
+ self.level_name_by_cte = {}
+
+ self.ctes_recursive = False
+
+ return ctes
+
+ @contextlib.contextmanager
+ def _nested_result(self):
+ """special API to support the use case of 'nested result sets'"""
+ result_columns, ordered_columns = (
+ self._result_columns,
+ self._ordered_columns,
+ )
+ self._result_columns, self._ordered_columns = [], False
+
+ try:
+ if self.stack:
+ entry = self.stack[-1]
+ entry["need_result_map_for_nested"] = True
+ else:
+ entry = None
+ yield self._result_columns, self._ordered_columns
+ finally:
+ if entry:
+ entry.pop("need_result_map_for_nested")
+ self._result_columns, self._ordered_columns = (
+ result_columns,
+ ordered_columns,
+ )
+
+ def _process_positional(self):
+ assert not self.positiontup
+ assert self.state is CompilerState.STRING_APPLIED
+ assert not self._numeric_binds
+
+ if self.dialect.paramstyle == "format":
+ placeholder = "%s"
+ else:
+ assert self.dialect.paramstyle == "qmark"
+ placeholder = "?"
+
+ positions = []
+
+ def find_position(m: re.Match[str]) -> str:
+ normal_bind = m.group(1)
+ if normal_bind:
+ positions.append(normal_bind)
+ return placeholder
+ else:
+ # this a post-compile bind
+ positions.append(m.group(2))
+ return m.group(0)
+
+ self.string = re.sub(
+ self._positional_pattern, find_position, self.string
+ )
+
+ if self.escaped_bind_names:
+ reverse_escape = {v: k for k, v in self.escaped_bind_names.items()}
+ assert len(self.escaped_bind_names) == len(reverse_escape)
+ self.positiontup = [
+ reverse_escape.get(name, name) for name in positions
+ ]
+ else:
+ self.positiontup = positions
+
+ if self._insertmanyvalues:
+ positions = []
+
+ single_values_expr = re.sub(
+ self._positional_pattern,
+ find_position,
+ self._insertmanyvalues.single_values_expr,
+ )
+ insert_crud_params = [
+ (
+ v[0],
+ v[1],
+ re.sub(self._positional_pattern, find_position, v[2]),
+ v[3],
+ )
+ for v in self._insertmanyvalues.insert_crud_params
+ ]
+
+ self._insertmanyvalues = self._insertmanyvalues._replace(
+ single_values_expr=single_values_expr,
+ insert_crud_params=insert_crud_params,
+ )
+
+ def _process_numeric(self):
+ assert self._numeric_binds
+ assert self.state is CompilerState.STRING_APPLIED
+
+ num = 1
+ param_pos: Dict[str, str] = {}
+ order: Iterable[str]
+ if self._insertmanyvalues and self._values_bindparam is not None:
+ # bindparams that are not in values are always placed first.
+ # this avoids the need of changing them when using executemany
+ # values () ()
+ order = itertools.chain(
+ (
+ name
+ for name in self.bind_names.values()
+ if name not in self._values_bindparam
+ ),
+ self.bind_names.values(),
+ )
+ else:
+ order = self.bind_names.values()
+
+ for bind_name in order:
+ if bind_name in param_pos:
+ continue
+ bind = self.binds[bind_name]
+ if (
+ bind in self.post_compile_params
+ or bind in self.literal_execute_params
+ ):
+ # set to None to just mark the in positiontup, it will not
+ # be replaced below.
+ param_pos[bind_name] = None # type: ignore
+ else:
+ ph = f"{self._numeric_binds_identifier_char}{num}"
+ num += 1
+ param_pos[bind_name] = ph
+
+ self.next_numeric_pos = num
+
+ self.positiontup = list(param_pos)
+ if self.escaped_bind_names:
+ len_before = len(param_pos)
+ param_pos = {
+ self.escaped_bind_names.get(name, name): pos
+ for name, pos in param_pos.items()
+ }
+ assert len(param_pos) == len_before
+
+ # Can't use format here since % chars are not escaped.
+ self.string = self._pyformat_pattern.sub(
+ lambda m: param_pos[m.group(1)], self.string
+ )
+
+ if self._insertmanyvalues:
+ single_values_expr = (
+ # format is ok here since single_values_expr includes only
+ # place-holders
+ self._insertmanyvalues.single_values_expr
+ % param_pos
+ )
+ insert_crud_params = [
+ (v[0], v[1], "%s", v[3])
+ for v in self._insertmanyvalues.insert_crud_params
+ ]
+
+ self._insertmanyvalues = self._insertmanyvalues._replace(
+ # This has the numbers (:1, :2)
+ single_values_expr=single_values_expr,
+ # The single binds are instead %s so they can be formatted
+ insert_crud_params=insert_crud_params,
+ )
+
+ @util.memoized_property
+ def _bind_processors(
+ self,
+ ) -> MutableMapping[
+ str, Union[_BindProcessorType[Any], Sequence[_BindProcessorType[Any]]]
+ ]:
+ # mypy is not able to see the two value types as the above Union,
+ # it just sees "object". don't know how to resolve
+ return {
+ key: value # type: ignore
+ for key, value in (
+ (
+ self.bind_names[bindparam],
+ (
+ bindparam.type._cached_bind_processor(self.dialect)
+ if not bindparam.type._is_tuple_type
+ else tuple(
+ elem_type._cached_bind_processor(self.dialect)
+ for elem_type in cast(
+ TupleType, bindparam.type
+ ).types
+ )
+ ),
+ )
+ for bindparam in self.bind_names
+ )
+ if value is not None
+ }
+
+ def is_subquery(self):
+ return len(self.stack) > 1
+
+ @property
+ def sql_compiler(self):
+ return self
+
+ def construct_expanded_state(
+ self,
+ params: Optional[_CoreSingleExecuteParams] = None,
+ escape_names: bool = True,
+ ) -> ExpandedState:
+ """Return a new :class:`.ExpandedState` for a given parameter set.
+
+ For queries that use "expanding" or other late-rendered parameters,
+ this method will provide for both the finalized SQL string as well
+ as the parameters that would be used for a particular parameter set.
+
+ .. versionadded:: 2.0.0rc1
+
+ """
+ parameters = self.construct_params(
+ params,
+ escape_names=escape_names,
+ _no_postcompile=True,
+ )
+ return self._process_parameters_for_postcompile(
+ parameters,
+ )
+
+ def construct_params(
+ self,
+ params: Optional[_CoreSingleExecuteParams] = None,
+ extracted_parameters: Optional[Sequence[BindParameter[Any]]] = None,
+ escape_names: bool = True,
+ _group_number: Optional[int] = None,
+ _check: bool = True,
+ _no_postcompile: bool = False,
+ ) -> _MutableCoreSingleExecuteParams:
+ """return a dictionary of bind parameter keys and values"""
+
+ if self._render_postcompile and not _no_postcompile:
+ assert self._post_compile_expanded_state is not None
+ if not params:
+ return dict(self._post_compile_expanded_state.parameters)
+ else:
+ raise exc.InvalidRequestError(
+ "can't construct new parameters when render_postcompile "
+ "is used; the statement is hard-linked to the original "
+ "parameters. Use construct_expanded_state to generate a "
+ "new statement and parameters."
+ )
+
+ has_escaped_names = escape_names and bool(self.escaped_bind_names)
+
+ if extracted_parameters:
+ # related the bound parameters collected in the original cache key
+ # to those collected in the incoming cache key. They will not have
+ # matching names but they will line up positionally in the same
+ # way. The parameters present in self.bind_names may be clones of
+ # these original cache key params in the case of DML but the .key
+ # will be guaranteed to match.
+ if self.cache_key is None:
+ raise exc.CompileError(
+ "This compiled object has no original cache key; "
+ "can't pass extracted_parameters to construct_params"
+ )
+ else:
+ orig_extracted = self.cache_key[1]
+
+ ckbm_tuple = self._cache_key_bind_match
+ assert ckbm_tuple is not None
+ ckbm, _ = ckbm_tuple
+ resolved_extracted = {
+ bind: extracted
+ for b, extracted in zip(orig_extracted, extracted_parameters)
+ for bind in ckbm[b]
+ }
+ else:
+ resolved_extracted = None
+
+ if params:
+ pd = {}
+ for bindparam, name in self.bind_names.items():
+ escaped_name = (
+ self.escaped_bind_names.get(name, name)
+ if has_escaped_names
+ else name
+ )
+
+ if bindparam.key in params:
+ pd[escaped_name] = params[bindparam.key]
+ elif name in params:
+ pd[escaped_name] = params[name]
+
+ elif _check and bindparam.required:
+ if _group_number:
+ raise exc.InvalidRequestError(
+ "A value is required for bind parameter %r, "
+ "in parameter group %d"
+ % (bindparam.key, _group_number),
+ code="cd3x",
+ )
+ else:
+ raise exc.InvalidRequestError(
+ "A value is required for bind parameter %r"
+ % bindparam.key,
+ code="cd3x",
+ )
+ else:
+ if resolved_extracted:
+ value_param = resolved_extracted.get(
+ bindparam, bindparam
+ )
+ else:
+ value_param = bindparam
+
+ if bindparam.callable:
+ pd[escaped_name] = value_param.effective_value
+ else:
+ pd[escaped_name] = value_param.value
+ return pd
+ else:
+ pd = {}
+ for bindparam, name in self.bind_names.items():
+ escaped_name = (
+ self.escaped_bind_names.get(name, name)
+ if has_escaped_names
+ else name
+ )
+
+ if _check and bindparam.required:
+ if _group_number:
+ raise exc.InvalidRequestError(
+ "A value is required for bind parameter %r, "
+ "in parameter group %d"
+ % (bindparam.key, _group_number),
+ code="cd3x",
+ )
+ else:
+ raise exc.InvalidRequestError(
+ "A value is required for bind parameter %r"
+ % bindparam.key,
+ code="cd3x",
+ )
+
+ if resolved_extracted:
+ value_param = resolved_extracted.get(bindparam, bindparam)
+ else:
+ value_param = bindparam
+
+ if bindparam.callable:
+ pd[escaped_name] = value_param.effective_value
+ else:
+ pd[escaped_name] = value_param.value
+
+ return pd
+
+ @util.memoized_instancemethod
+ def _get_set_input_sizes_lookup(self):
+ dialect = self.dialect
+
+ include_types = dialect.include_set_input_sizes
+ exclude_types = dialect.exclude_set_input_sizes
+
+ dbapi = dialect.dbapi
+
+ def lookup_type(typ):
+ dbtype = typ._unwrapped_dialect_impl(dialect).get_dbapi_type(dbapi)
+
+ if (
+ dbtype is not None
+ and (exclude_types is None or dbtype not in exclude_types)
+ and (include_types is None or dbtype in include_types)
+ ):
+ return dbtype
+ else:
+ return None
+
+ inputsizes = {}
+
+ literal_execute_params = self.literal_execute_params
+
+ for bindparam in self.bind_names:
+ if bindparam in literal_execute_params:
+ continue
+
+ if bindparam.type._is_tuple_type:
+ inputsizes[bindparam] = [
+ lookup_type(typ)
+ for typ in cast(TupleType, bindparam.type).types
+ ]
+ else:
+ inputsizes[bindparam] = lookup_type(bindparam.type)
+
+ return inputsizes
+
+ @property
+ def params(self):
+ """Return the bind param dictionary embedded into this
+ compiled object, for those values that are present.
+
+ .. seealso::
+
+ :ref:`faq_sql_expression_string` - includes a usage example for
+ debugging use cases.
+
+ """
+ return self.construct_params(_check=False)
+
+ def _process_parameters_for_postcompile(
+ self,
+ parameters: _MutableCoreSingleExecuteParams,
+ _populate_self: bool = False,
+ ) -> ExpandedState:
+ """handle special post compile parameters.
+
+ These include:
+
+ * "expanding" parameters -typically IN tuples that are rendered
+ on a per-parameter basis for an otherwise fixed SQL statement string.
+
+ * literal_binds compiled with the literal_execute flag. Used for
+ things like SQL Server "TOP N" where the driver does not accommodate
+ N as a bound parameter.
+
+ """
+
+ expanded_parameters = {}
+ new_positiontup: Optional[List[str]]
+
+ pre_expanded_string = self._pre_expanded_string
+ if pre_expanded_string is None:
+ pre_expanded_string = self.string
+
+ if self.positional:
+ new_positiontup = []
+
+ pre_expanded_positiontup = self._pre_expanded_positiontup
+ if pre_expanded_positiontup is None:
+ pre_expanded_positiontup = self.positiontup
+
+ else:
+ new_positiontup = pre_expanded_positiontup = None
+
+ processors = self._bind_processors
+ single_processors = cast(
+ "Mapping[str, _BindProcessorType[Any]]", processors
+ )
+ tuple_processors = cast(
+ "Mapping[str, Sequence[_BindProcessorType[Any]]]", processors
+ )
+
+ new_processors: Dict[str, _BindProcessorType[Any]] = {}
+
+ replacement_expressions: Dict[str, Any] = {}
+ to_update_sets: Dict[str, Any] = {}
+
+ # notes:
+ # *unescaped* parameter names in:
+ # self.bind_names, self.binds, self._bind_processors, self.positiontup
+ #
+ # *escaped* parameter names in:
+ # construct_params(), replacement_expressions
+
+ numeric_positiontup: Optional[List[str]] = None
+
+ if self.positional and pre_expanded_positiontup is not None:
+ names: Iterable[str] = pre_expanded_positiontup
+ if self._numeric_binds:
+ numeric_positiontup = []
+ else:
+ names = self.bind_names.values()
+
+ ebn = self.escaped_bind_names
+ for name in names:
+ escaped_name = ebn.get(name, name) if ebn else name
+ parameter = self.binds[name]
+
+ if parameter in self.literal_execute_params:
+ if escaped_name not in replacement_expressions:
+ replacement_expressions[escaped_name] = (
+ self.render_literal_bindparam(
+ parameter,
+ render_literal_value=parameters.pop(escaped_name),
+ )
+ )
+ continue
+
+ if parameter in self.post_compile_params:
+ if escaped_name in replacement_expressions:
+ to_update = to_update_sets[escaped_name]
+ values = None
+ else:
+ # we are removing the parameter from parameters
+ # because it is a list value, which is not expected by
+ # TypeEngine objects that would otherwise be asked to
+ # process it. the single name is being replaced with
+ # individual numbered parameters for each value in the
+ # param.
+ #
+ # note we are also inserting *escaped* parameter names
+ # into the given dictionary. default dialect will
+ # use these param names directly as they will not be
+ # in the escaped_bind_names dictionary.
+ values = parameters.pop(name)
+
+ leep_res = self._literal_execute_expanding_parameter(
+ escaped_name, parameter, values
+ )
+ (to_update, replacement_expr) = leep_res
+
+ to_update_sets[escaped_name] = to_update
+ replacement_expressions[escaped_name] = replacement_expr
+
+ if not parameter.literal_execute:
+ parameters.update(to_update)
+ if parameter.type._is_tuple_type:
+ assert values is not None
+ new_processors.update(
+ (
+ "%s_%s_%s" % (name, i, j),
+ tuple_processors[name][j - 1],
+ )
+ for i, tuple_element in enumerate(values, 1)
+ for j, _ in enumerate(tuple_element, 1)
+ if name in tuple_processors
+ and tuple_processors[name][j - 1] is not None
+ )
+ else:
+ new_processors.update(
+ (key, single_processors[name])
+ for key, _ in to_update
+ if name in single_processors
+ )
+ if numeric_positiontup is not None:
+ numeric_positiontup.extend(
+ name for name, _ in to_update
+ )
+ elif new_positiontup is not None:
+ # to_update has escaped names, but that's ok since
+ # these are new names, that aren't in the
+ # escaped_bind_names dict.
+ new_positiontup.extend(name for name, _ in to_update)
+ expanded_parameters[name] = [
+ expand_key for expand_key, _ in to_update
+ ]
+ elif new_positiontup is not None:
+ new_positiontup.append(name)
+
+ def process_expanding(m):
+ key = m.group(1)
+ expr = replacement_expressions[key]
+
+ # if POSTCOMPILE included a bind_expression, render that
+ # around each element
+ if m.group(2):
+ tok = m.group(2).split("~~")
+ be_left, be_right = tok[1], tok[3]
+ expr = ", ".join(
+ "%s%s%s" % (be_left, exp, be_right)
+ for exp in expr.split(", ")
+ )
+ return expr
+
+ statement = re.sub(
+ self._post_compile_pattern, process_expanding, pre_expanded_string
+ )
+
+ if numeric_positiontup is not None:
+ assert new_positiontup is not None
+ param_pos = {
+ key: f"{self._numeric_binds_identifier_char}{num}"
+ for num, key in enumerate(
+ numeric_positiontup, self.next_numeric_pos
+ )
+ }
+ # Can't use format here since % chars are not escaped.
+ statement = self._pyformat_pattern.sub(
+ lambda m: param_pos[m.group(1)], statement
+ )
+ new_positiontup.extend(numeric_positiontup)
+
+ expanded_state = ExpandedState(
+ statement,
+ parameters,
+ new_processors,
+ new_positiontup,
+ expanded_parameters,
+ )
+
+ if _populate_self:
+ # this is for the "render_postcompile" flag, which is not
+ # otherwise used internally and is for end-user debugging and
+ # special use cases.
+ self._pre_expanded_string = pre_expanded_string
+ self._pre_expanded_positiontup = pre_expanded_positiontup
+ self.string = expanded_state.statement
+ self.positiontup = (
+ list(expanded_state.positiontup or ())
+ if self.positional
+ else None
+ )
+ self._post_compile_expanded_state = expanded_state
+
+ return expanded_state
+
+ @util.preload_module("sqlalchemy.engine.cursor")
+ def _create_result_map(self):
+ """utility method used for unit tests only."""
+ cursor = util.preloaded.engine_cursor
+ return cursor.CursorResultMetaData._create_description_match_map(
+ self._result_columns
+ )
+
+ # assigned by crud.py for insert/update statements
+ _get_bind_name_for_col: _BindNameForColProtocol
+
+ @util.memoized_property
+ def _within_exec_param_key_getter(self) -> Callable[[Any], str]:
+ getter = self._get_bind_name_for_col
+ return getter
+
+ @util.memoized_property
+ @util.preload_module("sqlalchemy.engine.result")
+ def _inserted_primary_key_from_lastrowid_getter(self):
+ result = util.preloaded.engine_result
+
+ param_key_getter = self._within_exec_param_key_getter
+
+ assert self.compile_state is not None
+ statement = self.compile_state.statement
+
+ if TYPE_CHECKING:
+ assert isinstance(statement, Insert)
+
+ table = statement.table
+
+ getters = [
+ (operator.methodcaller("get", param_key_getter(col), None), col)
+ for col in table.primary_key
+ ]
+
+ autoinc_getter = None
+ autoinc_col = table._autoincrement_column
+ if autoinc_col is not None:
+ # apply type post processors to the lastrowid
+ lastrowid_processor = autoinc_col.type._cached_result_processor(
+ self.dialect, None
+ )
+ autoinc_key = param_key_getter(autoinc_col)
+
+ # if a bind value is present for the autoincrement column
+ # in the parameters, we need to do the logic dictated by
+ # #7998; honor a non-None user-passed parameter over lastrowid.
+ # previously in the 1.4 series we weren't fetching lastrowid
+ # at all if the key were present in the parameters
+ if autoinc_key in self.binds:
+
+ def _autoinc_getter(lastrowid, parameters):
+ param_value = parameters.get(autoinc_key, lastrowid)
+ if param_value is not None:
+ # they supplied non-None parameter, use that.
+ # SQLite at least is observed to return the wrong
+ # cursor.lastrowid for INSERT..ON CONFLICT so it
+ # can't be used in all cases
+ return param_value
+ else:
+ # use lastrowid
+ return lastrowid
+
+ # work around mypy https://github.com/python/mypy/issues/14027
+ autoinc_getter = _autoinc_getter
+
+ else:
+ lastrowid_processor = None
+
+ row_fn = result.result_tuple([col.key for col in table.primary_key])
+
+ def get(lastrowid, parameters):
+ """given cursor.lastrowid value and the parameters used for INSERT,
+ return a "row" that represents the primary key, either by
+ using the "lastrowid" or by extracting values from the parameters
+ that were sent along with the INSERT.
+
+ """
+ if lastrowid_processor is not None:
+ lastrowid = lastrowid_processor(lastrowid)
+
+ if lastrowid is None:
+ return row_fn(getter(parameters) for getter, col in getters)
+ else:
+ return row_fn(
+ (
+ (
+ autoinc_getter(lastrowid, parameters)
+ if autoinc_getter is not None
+ else lastrowid
+ )
+ if col is autoinc_col
+ else getter(parameters)
+ )
+ for getter, col in getters
+ )
+
+ return get
+
+ @util.memoized_property
+ @util.preload_module("sqlalchemy.engine.result")
+ def _inserted_primary_key_from_returning_getter(self):
+ if typing.TYPE_CHECKING:
+ from ..engine import result
+ else:
+ result = util.preloaded.engine_result
+
+ assert self.compile_state is not None
+ statement = self.compile_state.statement
+
+ if TYPE_CHECKING:
+ assert isinstance(statement, Insert)
+
+ param_key_getter = self._within_exec_param_key_getter
+ table = statement.table
+
+ returning = self.implicit_returning
+ assert returning is not None
+ ret = {col: idx for idx, col in enumerate(returning)}
+
+ getters = cast(
+ "List[Tuple[Callable[[Any], Any], bool]]",
+ [
+ (
+ (operator.itemgetter(ret[col]), True)
+ if col in ret
+ else (
+ operator.methodcaller(
+ "get", param_key_getter(col), None
+ ),
+ False,
+ )
+ )
+ for col in table.primary_key
+ ],
+ )
+
+ row_fn = result.result_tuple([col.key for col in table.primary_key])
+
+ def get(row, parameters):
+ return row_fn(
+ getter(row) if use_row else getter(parameters)
+ for getter, use_row in getters
+ )
+
+ return get
+
+ def default_from(self):
+ """Called when a SELECT statement has no froms, and no FROM clause is
+ to be appended.
+
+ Gives Oracle a chance to tack on a ``FROM DUAL`` to the string output.
+
+ """
+ return ""
+
+ def visit_override_binds(self, override_binds, **kw):
+ """SQL compile the nested element of an _OverrideBinds with
+ bindparams swapped out.
+
+ The _OverrideBinds is not normally expected to be compiled; it
+ is meant to be used when an already cached statement is to be used,
+ the compilation was already performed, and only the bound params should
+ be swapped in at execution time.
+
+ However, there are test cases that exericise this object, and
+ additionally the ORM subquery loader is known to feed in expressions
+ which include this construct into new queries (discovered in #11173),
+ so it has to do the right thing at compile time as well.
+
+ """
+
+ # get SQL text first
+ sqltext = override_binds.element._compiler_dispatch(self, **kw)
+
+ # for a test compile that is not for caching, change binds after the
+ # fact. note that we don't try to
+ # swap the bindparam as we compile, because our element may be
+ # elsewhere in the statement already (e.g. a subquery or perhaps a
+ # CTE) and was already visited / compiled. See
+ # test_relationship_criteria.py ->
+ # test_selectinload_local_criteria_subquery
+ for k in override_binds.translate:
+ if k not in self.binds:
+ continue
+ bp = self.binds[k]
+
+ # so this would work, just change the value of bp in place.
+ # but we dont want to mutate things outside.
+ # bp.value = override_binds.translate[bp.key]
+ # continue
+
+ # instead, need to replace bp with new_bp or otherwise accommodate
+ # in all internal collections
+ new_bp = bp._with_value(
+ override_binds.translate[bp.key],
+ maintain_key=True,
+ required=False,
+ )
+
+ name = self.bind_names[bp]
+ self.binds[k] = self.binds[name] = new_bp
+ self.bind_names[new_bp] = name
+ self.bind_names.pop(bp, None)
+
+ if bp in self.post_compile_params:
+ self.post_compile_params |= {new_bp}
+ if bp in self.literal_execute_params:
+ self.literal_execute_params |= {new_bp}
+
+ ckbm_tuple = self._cache_key_bind_match
+ if ckbm_tuple:
+ ckbm, cksm = ckbm_tuple
+ for bp in bp._cloned_set:
+ if bp.key in cksm:
+ cb = cksm[bp.key]
+ ckbm[cb].append(new_bp)
+
+ return sqltext
+
+ def visit_grouping(self, grouping, asfrom=False, **kwargs):
+ return "(" + grouping.element._compiler_dispatch(self, **kwargs) + ")"
+
+ def visit_select_statement_grouping(self, grouping, **kwargs):
+ return "(" + grouping.element._compiler_dispatch(self, **kwargs) + ")"
+
+ def visit_label_reference(
+ self, element, within_columns_clause=False, **kwargs
+ ):
+ if self.stack and self.dialect.supports_simple_order_by_label:
+ try:
+ compile_state = cast(
+ "Union[SelectState, CompoundSelectState]",
+ self.stack[-1]["compile_state"],
+ )
+ except KeyError as ke:
+ raise exc.CompileError(
+ "Can't resolve label reference for ORDER BY / "
+ "GROUP BY / DISTINCT etc."
+ ) from ke
+
+ (
+ with_cols,
+ only_froms,
+ only_cols,
+ ) = compile_state._label_resolve_dict
+ if within_columns_clause:
+ resolve_dict = only_froms
+ else:
+ resolve_dict = only_cols
+
+ # this can be None in the case that a _label_reference()
+ # were subject to a replacement operation, in which case
+ # the replacement of the Label element may have changed
+ # to something else like a ColumnClause expression.
+ order_by_elem = element.element._order_by_label_element
+
+ if (
+ order_by_elem is not None
+ and order_by_elem.name in resolve_dict
+ and order_by_elem.shares_lineage(
+ resolve_dict[order_by_elem.name]
+ )
+ ):
+ kwargs["render_label_as_label"] = (
+ element.element._order_by_label_element
+ )
+ return self.process(
+ element.element,
+ within_columns_clause=within_columns_clause,
+ **kwargs,
+ )
+
+ def visit_textual_label_reference(
+ self, element, within_columns_clause=False, **kwargs
+ ):
+ if not self.stack:
+ # compiling the element outside of the context of a SELECT
+ return self.process(element._text_clause)
+
+ try:
+ compile_state = cast(
+ "Union[SelectState, CompoundSelectState]",
+ self.stack[-1]["compile_state"],
+ )
+ except KeyError as ke:
+ coercions._no_text_coercion(
+ element.element,
+ extra=(
+ "Can't resolve label reference for ORDER BY / "
+ "GROUP BY / DISTINCT etc."
+ ),
+ exc_cls=exc.CompileError,
+ err=ke,
+ )
+
+ with_cols, only_froms, only_cols = compile_state._label_resolve_dict
+ try:
+ if within_columns_clause:
+ col = only_froms[element.element]
+ else:
+ col = with_cols[element.element]
+ except KeyError as err:
+ coercions._no_text_coercion(
+ element.element,
+ extra=(
+ "Can't resolve label reference for ORDER BY / "
+ "GROUP BY / DISTINCT etc."
+ ),
+ exc_cls=exc.CompileError,
+ err=err,
+ )
+ else:
+ kwargs["render_label_as_label"] = col
+ return self.process(
+ col, within_columns_clause=within_columns_clause, **kwargs
+ )
+
+ def visit_label(
+ self,
+ label,
+ add_to_result_map=None,
+ within_label_clause=False,
+ within_columns_clause=False,
+ render_label_as_label=None,
+ result_map_targets=(),
+ **kw,
+ ):
+ # only render labels within the columns clause
+ # or ORDER BY clause of a select. dialect-specific compilers
+ # can modify this behavior.
+ render_label_with_as = (
+ within_columns_clause and not within_label_clause
+ )
+ render_label_only = render_label_as_label is label
+
+ if render_label_only or render_label_with_as:
+ if isinstance(label.name, elements._truncated_label):
+ labelname = self._truncated_identifier("colident", label.name)
+ else:
+ labelname = label.name
+
+ if render_label_with_as:
+ if add_to_result_map is not None:
+ add_to_result_map(
+ labelname,
+ label.name,
+ (label, labelname) + label._alt_names + result_map_targets,
+ label.type,
+ )
+ return (
+ label.element._compiler_dispatch(
+ self,
+ within_columns_clause=True,
+ within_label_clause=True,
+ **kw,
+ )
+ + OPERATORS[operators.as_]
+ + self.preparer.format_label(label, labelname)
+ )
+ elif render_label_only:
+ return self.preparer.format_label(label, labelname)
+ else:
+ return label.element._compiler_dispatch(
+ self, within_columns_clause=False, **kw
+ )
+
+ def _fallback_column_name(self, column):
+ raise exc.CompileError(
+ "Cannot compile Column object until its 'name' is assigned."
+ )
+
+ def visit_lambda_element(self, element, **kw):
+ sql_element = element._resolved
+ return self.process(sql_element, **kw)
+
+ def visit_column(
+ self,
+ column: ColumnClause[Any],
+ add_to_result_map: Optional[_ResultMapAppender] = None,
+ include_table: bool = True,
+ result_map_targets: Tuple[Any, ...] = (),
+ ambiguous_table_name_map: Optional[_AmbiguousTableNameMap] = None,
+ **kwargs: Any,
+ ) -> str:
+ name = orig_name = column.name
+ if name is None:
+ name = self._fallback_column_name(column)
+
+ is_literal = column.is_literal
+ if not is_literal and isinstance(name, elements._truncated_label):
+ name = self._truncated_identifier("colident", name)
+
+ if add_to_result_map is not None:
+ targets = (column, name, column.key) + result_map_targets
+ if column._tq_label:
+ targets += (column._tq_label,)
+
+ add_to_result_map(name, orig_name, targets, column.type)
+
+ if is_literal:
+ # note we are not currently accommodating for
+ # literal_column(quoted_name('ident', True)) here
+ name = self.escape_literal_column(name)
+ else:
+ name = self.preparer.quote(name)
+ table = column.table
+ if table is None or not include_table or not table.named_with_column:
+ return name
+ else:
+ effective_schema = self.preparer.schema_for_object(table)
+
+ if effective_schema:
+ schema_prefix = (
+ self.preparer.quote_schema(effective_schema) + "."
+ )
+ else:
+ schema_prefix = ""
+
+ if TYPE_CHECKING:
+ assert isinstance(table, NamedFromClause)
+ tablename = table.name
+
+ if (
+ not effective_schema
+ and ambiguous_table_name_map
+ and tablename in ambiguous_table_name_map
+ ):
+ tablename = ambiguous_table_name_map[tablename]
+
+ if isinstance(tablename, elements._truncated_label):
+ tablename = self._truncated_identifier("alias", tablename)
+
+ return schema_prefix + self.preparer.quote(tablename) + "." + name
+
+ def visit_collation(self, element, **kw):
+ return self.preparer.format_collation(element.collation)
+
+ def visit_fromclause(self, fromclause, **kwargs):
+ return fromclause.name
+
+ def visit_index(self, index, **kwargs):
+ return index.name
+
+ def visit_typeclause(self, typeclause, **kw):
+ kw["type_expression"] = typeclause
+ kw["identifier_preparer"] = self.preparer
+ return self.dialect.type_compiler_instance.process(
+ typeclause.type, **kw
+ )
+
+ def post_process_text(self, text):
+ if self.preparer._double_percents:
+ text = text.replace("%", "%%")
+ return text
+
+ def escape_literal_column(self, text):
+ if self.preparer._double_percents:
+ text = text.replace("%", "%%")
+ return text
+
+ def visit_textclause(self, textclause, add_to_result_map=None, **kw):
+ def do_bindparam(m):
+ name = m.group(1)
+ if name in textclause._bindparams:
+ return self.process(textclause._bindparams[name], **kw)
+ else:
+ return self.bindparam_string(name, **kw)
+
+ if not self.stack:
+ self.isplaintext = True
+
+ if add_to_result_map:
+ # text() object is present in the columns clause of a
+ # select(). Add a no-name entry to the result map so that
+ # row[text()] produces a result
+ add_to_result_map(None, None, (textclause,), sqltypes.NULLTYPE)
+
+ # un-escape any \:params
+ return BIND_PARAMS_ESC.sub(
+ lambda m: m.group(1),
+ BIND_PARAMS.sub(
+ do_bindparam, self.post_process_text(textclause.text)
+ ),
+ )
+
+ def visit_textual_select(
+ self, taf, compound_index=None, asfrom=False, **kw
+ ):
+ toplevel = not self.stack
+ entry = self._default_stack_entry if toplevel else self.stack[-1]
+
+ new_entry: _CompilerStackEntry = {
+ "correlate_froms": set(),
+ "asfrom_froms": set(),
+ "selectable": taf,
+ }
+ self.stack.append(new_entry)
+
+ if taf._independent_ctes:
+ self._dispatch_independent_ctes(taf, kw)
+
+ populate_result_map = (
+ toplevel
+ or (
+ compound_index == 0
+ and entry.get("need_result_map_for_compound", False)
+ )
+ or entry.get("need_result_map_for_nested", False)
+ )
+
+ if populate_result_map:
+ self._ordered_columns = self._textual_ordered_columns = (
+ taf.positional
+ )
+
+ # enable looser result column matching when the SQL text links to
+ # Column objects by name only
+ self._loose_column_name_matching = not taf.positional and bool(
+ taf.column_args
+ )
+
+ for c in taf.column_args:
+ self.process(
+ c,
+ within_columns_clause=True,
+ add_to_result_map=self._add_to_result_map,
+ )
+
+ text = self.process(taf.element, **kw)
+ if self.ctes:
+ nesting_level = len(self.stack) if not toplevel else None
+ text = self._render_cte_clause(nesting_level=nesting_level) + text
+
+ self.stack.pop(-1)
+
+ return text
+
+ def visit_null(self, expr, **kw):
+ return "NULL"
+
+ def visit_true(self, expr, **kw):
+ if self.dialect.supports_native_boolean:
+ return "true"
+ else:
+ return "1"
+
+ def visit_false(self, expr, **kw):
+ if self.dialect.supports_native_boolean:
+ return "false"
+ else:
+ return "0"
+
+ def _generate_delimited_list(self, elements, separator, **kw):
+ return separator.join(
+ s
+ for s in (c._compiler_dispatch(self, **kw) for c in elements)
+ if s
+ )
+
+ def _generate_delimited_and_list(self, clauses, **kw):
+ lcc, clauses = elements.BooleanClauseList._process_clauses_for_boolean(
+ operators.and_,
+ elements.True_._singleton,
+ elements.False_._singleton,
+ clauses,
+ )
+ if lcc == 1:
+ return clauses[0]._compiler_dispatch(self, **kw)
+ else:
+ separator = OPERATORS[operators.and_]
+ return separator.join(
+ s
+ for s in (c._compiler_dispatch(self, **kw) for c in clauses)
+ if s
+ )
+
+ def visit_tuple(self, clauselist, **kw):
+ return "(%s)" % self.visit_clauselist(clauselist, **kw)
+
+ def visit_clauselist(self, clauselist, **kw):
+ sep = clauselist.operator
+ if sep is None:
+ sep = " "
+ else:
+ sep = OPERATORS[clauselist.operator]
+
+ return self._generate_delimited_list(clauselist.clauses, sep, **kw)
+
+ def visit_expression_clauselist(self, clauselist, **kw):
+ operator_ = clauselist.operator
+
+ disp = self._get_operator_dispatch(
+ operator_, "expression_clauselist", None
+ )
+ if disp:
+ return disp(clauselist, operator_, **kw)
+
+ try:
+ opstring = OPERATORS[operator_]
+ except KeyError as err:
+ raise exc.UnsupportedCompilationError(self, operator_) from err
+ else:
+ kw["_in_operator_expression"] = True
+ return self._generate_delimited_list(
+ clauselist.clauses, opstring, **kw
+ )
+
+ def visit_case(self, clause, **kwargs):
+ x = "CASE "
+ if clause.value is not None:
+ x += clause.value._compiler_dispatch(self, **kwargs) + " "
+ for cond, result in clause.whens:
+ x += (
+ "WHEN "
+ + cond._compiler_dispatch(self, **kwargs)
+ + " THEN "
+ + result._compiler_dispatch(self, **kwargs)
+ + " "
+ )
+ if clause.else_ is not None:
+ x += (
+ "ELSE " + clause.else_._compiler_dispatch(self, **kwargs) + " "
+ )
+ x += "END"
+ return x
+
+ def visit_type_coerce(self, type_coerce, **kw):
+ return type_coerce.typed_expression._compiler_dispatch(self, **kw)
+
+ def visit_cast(self, cast, **kwargs):
+ type_clause = cast.typeclause._compiler_dispatch(self, **kwargs)
+ match = re.match("(.*)( COLLATE .*)", type_clause)
+ return "CAST(%s AS %s)%s" % (
+ cast.clause._compiler_dispatch(self, **kwargs),
+ match.group(1) if match else type_clause,
+ match.group(2) if match else "",
+ )
+
+ def _format_frame_clause(self, range_, **kw):
+ return "%s AND %s" % (
+ (
+ "UNBOUNDED PRECEDING"
+ if range_[0] is elements.RANGE_UNBOUNDED
+ else (
+ "CURRENT ROW"
+ if range_[0] is elements.RANGE_CURRENT
+ else (
+ "%s PRECEDING"
+ % (
+ self.process(
+ elements.literal(abs(range_[0])), **kw
+ ),
+ )
+ if range_[0] < 0
+ else "%s FOLLOWING"
+ % (self.process(elements.literal(range_[0]), **kw),)
+ )
+ )
+ ),
+ (
+ "UNBOUNDED FOLLOWING"
+ if range_[1] is elements.RANGE_UNBOUNDED
+ else (
+ "CURRENT ROW"
+ if range_[1] is elements.RANGE_CURRENT
+ else (
+ "%s PRECEDING"
+ % (
+ self.process(
+ elements.literal(abs(range_[1])), **kw
+ ),
+ )
+ if range_[1] < 0
+ else "%s FOLLOWING"
+ % (self.process(elements.literal(range_[1]), **kw),)
+ )
+ )
+ ),
+ )
+
+ def visit_over(self, over, **kwargs):
+ text = over.element._compiler_dispatch(self, **kwargs)
+ if over.range_:
+ range_ = "RANGE BETWEEN %s" % self._format_frame_clause(
+ over.range_, **kwargs
+ )
+ elif over.rows:
+ range_ = "ROWS BETWEEN %s" % self._format_frame_clause(
+ over.rows, **kwargs
+ )
+ else:
+ range_ = None
+
+ return "%s OVER (%s)" % (
+ text,
+ " ".join(
+ [
+ "%s BY %s"
+ % (word, clause._compiler_dispatch(self, **kwargs))
+ for word, clause in (
+ ("PARTITION", over.partition_by),
+ ("ORDER", over.order_by),
+ )
+ if clause is not None and len(clause)
+ ]
+ + ([range_] if range_ else [])
+ ),
+ )
+
+ def visit_withingroup(self, withingroup, **kwargs):
+ return "%s WITHIN GROUP (ORDER BY %s)" % (
+ withingroup.element._compiler_dispatch(self, **kwargs),
+ withingroup.order_by._compiler_dispatch(self, **kwargs),
+ )
+
+ def visit_funcfilter(self, funcfilter, **kwargs):
+ return "%s FILTER (WHERE %s)" % (
+ funcfilter.func._compiler_dispatch(self, **kwargs),
+ funcfilter.criterion._compiler_dispatch(self, **kwargs),
+ )
+
+ def visit_extract(self, extract, **kwargs):
+ field = self.extract_map.get(extract.field, extract.field)
+ return "EXTRACT(%s FROM %s)" % (
+ field,
+ extract.expr._compiler_dispatch(self, **kwargs),
+ )
+
+ def visit_scalar_function_column(self, element, **kw):
+ compiled_fn = self.visit_function(element.fn, **kw)
+ compiled_col = self.visit_column(element, **kw)
+ return "(%s).%s" % (compiled_fn, compiled_col)
+
+ def visit_function(
+ self,
+ func: Function[Any],
+ add_to_result_map: Optional[_ResultMapAppender] = None,
+ **kwargs: Any,
+ ) -> str:
+ if add_to_result_map is not None:
+ add_to_result_map(func.name, func.name, (), func.type)
+
+ disp = getattr(self, "visit_%s_func" % func.name.lower(), None)
+
+ text: str
+
+ if disp:
+ text = disp(func, **kwargs)
+ else:
+ name = FUNCTIONS.get(func._deannotate().__class__, None)
+ if name:
+ if func._has_args:
+ name += "%(expr)s"
+ else:
+ name = func.name
+ name = (
+ self.preparer.quote(name)
+ if self.preparer._requires_quotes_illegal_chars(name)
+ or isinstance(name, elements.quoted_name)
+ else name
+ )
+ name = name + "%(expr)s"
+ text = ".".join(
+ [
+ (
+ self.preparer.quote(tok)
+ if self.preparer._requires_quotes_illegal_chars(tok)
+ or isinstance(name, elements.quoted_name)
+ else tok
+ )
+ for tok in func.packagenames
+ ]
+ + [name]
+ ) % {"expr": self.function_argspec(func, **kwargs)}
+
+ if func._with_ordinality:
+ text += " WITH ORDINALITY"
+ return text
+
+ def visit_next_value_func(self, next_value, **kw):
+ return self.visit_sequence(next_value.sequence)
+
+ def visit_sequence(self, sequence, **kw):
+ raise NotImplementedError(
+ "Dialect '%s' does not support sequence increments."
+ % self.dialect.name
+ )
+
+ def function_argspec(self, func, **kwargs):
+ return func.clause_expr._compiler_dispatch(self, **kwargs)
+
+ def visit_compound_select(
+ self, cs, asfrom=False, compound_index=None, **kwargs
+ ):
+ toplevel = not self.stack
+
+ compile_state = cs._compile_state_factory(cs, self, **kwargs)
+
+ if toplevel and not self.compile_state:
+ self.compile_state = compile_state
+
+ compound_stmt = compile_state.statement
+
+ entry = self._default_stack_entry if toplevel else self.stack[-1]
+ need_result_map = toplevel or (
+ not compound_index
+ and entry.get("need_result_map_for_compound", False)
+ )
+
+ # indicates there is already a CompoundSelect in play
+ if compound_index == 0:
+ entry["select_0"] = cs
+
+ self.stack.append(
+ {
+ "correlate_froms": entry["correlate_froms"],
+ "asfrom_froms": entry["asfrom_froms"],
+ "selectable": cs,
+ "compile_state": compile_state,
+ "need_result_map_for_compound": need_result_map,
+ }
+ )
+
+ if compound_stmt._independent_ctes:
+ self._dispatch_independent_ctes(compound_stmt, kwargs)
+
+ keyword = self.compound_keywords[cs.keyword]
+
+ text = (" " + keyword + " ").join(
+ (
+ c._compiler_dispatch(
+ self, asfrom=asfrom, compound_index=i, **kwargs
+ )
+ for i, c in enumerate(cs.selects)
+ )
+ )
+
+ kwargs["include_table"] = False
+ text += self.group_by_clause(cs, **dict(asfrom=asfrom, **kwargs))
+ text += self.order_by_clause(cs, **kwargs)
+ if cs._has_row_limiting_clause:
+ text += self._row_limit_clause(cs, **kwargs)
+
+ if self.ctes:
+ nesting_level = len(self.stack) if not toplevel else None
+ text = (
+ self._render_cte_clause(
+ nesting_level=nesting_level,
+ include_following_stack=True,
+ )
+ + text
+ )
+
+ self.stack.pop(-1)
+ return text
+
+ def _row_limit_clause(self, cs, **kwargs):
+ if cs._fetch_clause is not None:
+ return self.fetch_clause(cs, **kwargs)
+ else:
+ return self.limit_clause(cs, **kwargs)
+
+ def _get_operator_dispatch(self, operator_, qualifier1, qualifier2):
+ attrname = "visit_%s_%s%s" % (
+ operator_.__name__,
+ qualifier1,
+ "_" + qualifier2 if qualifier2 else "",
+ )
+ return getattr(self, attrname, None)
+
+ def visit_unary(
+ self, unary, add_to_result_map=None, result_map_targets=(), **kw
+ ):
+ if add_to_result_map is not None:
+ result_map_targets += (unary,)
+ kw["add_to_result_map"] = add_to_result_map
+ kw["result_map_targets"] = result_map_targets
+
+ if unary.operator:
+ if unary.modifier:
+ raise exc.CompileError(
+ "Unary expression does not support operator "
+ "and modifier simultaneously"
+ )
+ disp = self._get_operator_dispatch(
+ unary.operator, "unary", "operator"
+ )
+ if disp:
+ return disp(unary, unary.operator, **kw)
+ else:
+ return self._generate_generic_unary_operator(
+ unary, OPERATORS[unary.operator], **kw
+ )
+ elif unary.modifier:
+ disp = self._get_operator_dispatch(
+ unary.modifier, "unary", "modifier"
+ )
+ if disp:
+ return disp(unary, unary.modifier, **kw)
+ else:
+ return self._generate_generic_unary_modifier(
+ unary, OPERATORS[unary.modifier], **kw
+ )
+ else:
+ raise exc.CompileError(
+ "Unary expression has no operator or modifier"
+ )
+
+ def visit_truediv_binary(self, binary, operator, **kw):
+ if self.dialect.div_is_floordiv:
+ return (
+ self.process(binary.left, **kw)
+ + " / "
+ # TODO: would need a fast cast again here,
+ # unless we want to use an implicit cast like "+ 0.0"
+ + self.process(
+ elements.Cast(
+ binary.right,
+ (
+ binary.right.type
+ if binary.right.type._type_affinity
+ is sqltypes.Numeric
+ else sqltypes.Numeric()
+ ),
+ ),
+ **kw,
+ )
+ )
+ else:
+ return (
+ self.process(binary.left, **kw)
+ + " / "
+ + self.process(binary.right, **kw)
+ )
+
+ def visit_floordiv_binary(self, binary, operator, **kw):
+ if (
+ self.dialect.div_is_floordiv
+ and binary.right.type._type_affinity is sqltypes.Integer
+ ):
+ return (
+ self.process(binary.left, **kw)
+ + " / "
+ + self.process(binary.right, **kw)
+ )
+ else:
+ return "FLOOR(%s)" % (
+ self.process(binary.left, **kw)
+ + " / "
+ + self.process(binary.right, **kw)
+ )
+
+ def visit_is_true_unary_operator(self, element, operator, **kw):
+ if (
+ element._is_implicitly_boolean
+ or self.dialect.supports_native_boolean
+ ):
+ return self.process(element.element, **kw)
+ else:
+ return "%s = 1" % self.process(element.element, **kw)
+
+ def visit_is_false_unary_operator(self, element, operator, **kw):
+ if (
+ element._is_implicitly_boolean
+ or self.dialect.supports_native_boolean
+ ):
+ return "NOT %s" % self.process(element.element, **kw)
+ else:
+ return "%s = 0" % self.process(element.element, **kw)
+
+ def visit_not_match_op_binary(self, binary, operator, **kw):
+ return "NOT %s" % self.visit_binary(
+ binary, override_operator=operators.match_op
+ )
+
+ def visit_not_in_op_binary(self, binary, operator, **kw):
+ # The brackets are required in the NOT IN operation because the empty
+ # case is handled using the form "(col NOT IN (null) OR 1 = 1)".
+ # The presence of the OR makes the brackets required.
+ return "(%s)" % self._generate_generic_binary(
+ binary, OPERATORS[operator], **kw
+ )
+
+ def visit_empty_set_op_expr(self, type_, expand_op, **kw):
+ if expand_op is operators.not_in_op:
+ if len(type_) > 1:
+ return "(%s)) OR (1 = 1" % (
+ ", ".join("NULL" for element in type_)
+ )
+ else:
+ return "NULL) OR (1 = 1"
+ elif expand_op is operators.in_op:
+ if len(type_) > 1:
+ return "(%s)) AND (1 != 1" % (
+ ", ".join("NULL" for element in type_)
+ )
+ else:
+ return "NULL) AND (1 != 1"
+ else:
+ return self.visit_empty_set_expr(type_)
+
+ def visit_empty_set_expr(self, element_types, **kw):
+ raise NotImplementedError(
+ "Dialect '%s' does not support empty set expression."
+ % self.dialect.name
+ )
+
+ def _literal_execute_expanding_parameter_literal_binds(
+ self, parameter, values, bind_expression_template=None
+ ):
+ typ_dialect_impl = parameter.type._unwrapped_dialect_impl(self.dialect)
+
+ if not values:
+ # empty IN expression. note we don't need to use
+ # bind_expression_template here because there are no
+ # expressions to render.
+
+ if typ_dialect_impl._is_tuple_type:
+ replacement_expression = (
+ "VALUES " if self.dialect.tuple_in_values else ""
+ ) + self.visit_empty_set_op_expr(
+ parameter.type.types, parameter.expand_op
+ )
+
+ else:
+ replacement_expression = self.visit_empty_set_op_expr(
+ [parameter.type], parameter.expand_op
+ )
+
+ elif typ_dialect_impl._is_tuple_type or (
+ typ_dialect_impl._isnull
+ and isinstance(values[0], collections_abc.Sequence)
+ and not isinstance(values[0], (str, bytes))
+ ):
+ if typ_dialect_impl._has_bind_expression:
+ raise NotImplementedError(
+ "bind_expression() on TupleType not supported with "
+ "literal_binds"
+ )
+
+ replacement_expression = (
+ "VALUES " if self.dialect.tuple_in_values else ""
+ ) + ", ".join(
+ "(%s)"
+ % (
+ ", ".join(
+ self.render_literal_value(value, param_type)
+ for value, param_type in zip(
+ tuple_element, parameter.type.types
+ )
+ )
+ )
+ for i, tuple_element in enumerate(values)
+ )
+ else:
+ if bind_expression_template:
+ post_compile_pattern = self._post_compile_pattern
+ m = post_compile_pattern.search(bind_expression_template)
+ assert m and m.group(
+ 2
+ ), "unexpected format for expanding parameter"
+
+ tok = m.group(2).split("~~")
+ be_left, be_right = tok[1], tok[3]
+ replacement_expression = ", ".join(
+ "%s%s%s"
+ % (
+ be_left,
+ self.render_literal_value(value, parameter.type),
+ be_right,
+ )
+ for value in values
+ )
+ else:
+ replacement_expression = ", ".join(
+ self.render_literal_value(value, parameter.type)
+ for value in values
+ )
+
+ return (), replacement_expression
+
+ def _literal_execute_expanding_parameter(self, name, parameter, values):
+ if parameter.literal_execute:
+ return self._literal_execute_expanding_parameter_literal_binds(
+ parameter, values
+ )
+
+ dialect = self.dialect
+ typ_dialect_impl = parameter.type._unwrapped_dialect_impl(dialect)
+
+ if self._numeric_binds:
+ bind_template = self.compilation_bindtemplate
+ else:
+ bind_template = self.bindtemplate
+
+ if (
+ self.dialect._bind_typing_render_casts
+ and typ_dialect_impl.render_bind_cast
+ ):
+
+ def _render_bindtemplate(name):
+ return self.render_bind_cast(
+ parameter.type,
+ typ_dialect_impl,
+ bind_template % {"name": name},
+ )
+
+ else:
+
+ def _render_bindtemplate(name):
+ return bind_template % {"name": name}
+
+ if not values:
+ to_update = []
+ if typ_dialect_impl._is_tuple_type:
+ replacement_expression = self.visit_empty_set_op_expr(
+ parameter.type.types, parameter.expand_op
+ )
+ else:
+ replacement_expression = self.visit_empty_set_op_expr(
+ [parameter.type], parameter.expand_op
+ )
+
+ elif typ_dialect_impl._is_tuple_type or (
+ typ_dialect_impl._isnull
+ and isinstance(values[0], collections_abc.Sequence)
+ and not isinstance(values[0], (str, bytes))
+ ):
+ assert not typ_dialect_impl._is_array
+ to_update = [
+ ("%s_%s_%s" % (name, i, j), value)
+ for i, tuple_element in enumerate(values, 1)
+ for j, value in enumerate(tuple_element, 1)
+ ]
+
+ replacement_expression = (
+ "VALUES " if dialect.tuple_in_values else ""
+ ) + ", ".join(
+ "(%s)"
+ % (
+ ", ".join(
+ _render_bindtemplate(
+ to_update[i * len(tuple_element) + j][0]
+ )
+ for j, value in enumerate(tuple_element)
+ )
+ )
+ for i, tuple_element in enumerate(values)
+ )
+ else:
+ to_update = [
+ ("%s_%s" % (name, i), value)
+ for i, value in enumerate(values, 1)
+ ]
+ replacement_expression = ", ".join(
+ _render_bindtemplate(key) for key, value in to_update
+ )
+
+ return to_update, replacement_expression
+
+ def visit_binary(
+ self,
+ binary,
+ override_operator=None,
+ eager_grouping=False,
+ from_linter=None,
+ lateral_from_linter=None,
+ **kw,
+ ):
+ if from_linter and operators.is_comparison(binary.operator):
+ if lateral_from_linter is not None:
+ enclosing_lateral = kw["enclosing_lateral"]
+ lateral_from_linter.edges.update(
+ itertools.product(
+ _de_clone(
+ binary.left._from_objects + [enclosing_lateral]
+ ),
+ _de_clone(
+ binary.right._from_objects + [enclosing_lateral]
+ ),
+ )
+ )
+ else:
+ from_linter.edges.update(
+ itertools.product(
+ _de_clone(binary.left._from_objects),
+ _de_clone(binary.right._from_objects),
+ )
+ )
+
+ # don't allow "? = ?" to render
+ if (
+ self.ansi_bind_rules
+ and isinstance(binary.left, elements.BindParameter)
+ and isinstance(binary.right, elements.BindParameter)
+ ):
+ kw["literal_execute"] = True
+
+ operator_ = override_operator or binary.operator
+ disp = self._get_operator_dispatch(operator_, "binary", None)
+ if disp:
+ return disp(binary, operator_, **kw)
+ else:
+ try:
+ opstring = OPERATORS[operator_]
+ except KeyError as err:
+ raise exc.UnsupportedCompilationError(self, operator_) from err
+ else:
+ return self._generate_generic_binary(
+ binary,
+ opstring,
+ from_linter=from_linter,
+ lateral_from_linter=lateral_from_linter,
+ **kw,
+ )
+
+ def visit_function_as_comparison_op_binary(self, element, operator, **kw):
+ return self.process(element.sql_function, **kw)
+
+ def visit_mod_binary(self, binary, operator, **kw):
+ if self.preparer._double_percents:
+ return (
+ self.process(binary.left, **kw)
+ + " %% "
+ + self.process(binary.right, **kw)
+ )
+ else:
+ return (
+ self.process(binary.left, **kw)
+ + " % "
+ + self.process(binary.right, **kw)
+ )
+
+ def visit_custom_op_binary(self, element, operator, **kw):
+ kw["eager_grouping"] = operator.eager_grouping
+ return self._generate_generic_binary(
+ element,
+ " " + self.escape_literal_column(operator.opstring) + " ",
+ **kw,
+ )
+
+ def visit_custom_op_unary_operator(self, element, operator, **kw):
+ return self._generate_generic_unary_operator(
+ element, self.escape_literal_column(operator.opstring) + " ", **kw
+ )
+
+ def visit_custom_op_unary_modifier(self, element, operator, **kw):
+ return self._generate_generic_unary_modifier(
+ element, " " + self.escape_literal_column(operator.opstring), **kw
+ )
+
+ def _generate_generic_binary(
+ self, binary, opstring, eager_grouping=False, **kw
+ ):
+ _in_operator_expression = kw.get("_in_operator_expression", False)
+
+ kw["_in_operator_expression"] = True
+ kw["_binary_op"] = binary.operator
+ text = (
+ binary.left._compiler_dispatch(
+ self, eager_grouping=eager_grouping, **kw
+ )
+ + opstring
+ + binary.right._compiler_dispatch(
+ self, eager_grouping=eager_grouping, **kw
+ )
+ )
+
+ if _in_operator_expression and eager_grouping:
+ text = "(%s)" % text
+ return text
+
+ def _generate_generic_unary_operator(self, unary, opstring, **kw):
+ return opstring + unary.element._compiler_dispatch(self, **kw)
+
+ def _generate_generic_unary_modifier(self, unary, opstring, **kw):
+ return unary.element._compiler_dispatch(self, **kw) + opstring
+
+ @util.memoized_property
+ def _like_percent_literal(self):
+ return elements.literal_column("'%'", type_=sqltypes.STRINGTYPE)
+
+ def visit_ilike_case_insensitive_operand(self, element, **kw):
+ return f"lower({element.element._compiler_dispatch(self, **kw)})"
+
+ def visit_contains_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.right = percent.concat(binary.right).concat(percent)
+ return self.visit_like_op_binary(binary, operator, **kw)
+
+ def visit_not_contains_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.right = percent.concat(binary.right).concat(percent)
+ return self.visit_not_like_op_binary(binary, operator, **kw)
+
+ def visit_icontains_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.left = ilike_case_insensitive(binary.left)
+ binary.right = percent.concat(
+ ilike_case_insensitive(binary.right)
+ ).concat(percent)
+ return self.visit_ilike_op_binary(binary, operator, **kw)
+
+ def visit_not_icontains_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.left = ilike_case_insensitive(binary.left)
+ binary.right = percent.concat(
+ ilike_case_insensitive(binary.right)
+ ).concat(percent)
+ return self.visit_not_ilike_op_binary(binary, operator, **kw)
+
+ def visit_startswith_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.right = percent._rconcat(binary.right)
+ return self.visit_like_op_binary(binary, operator, **kw)
+
+ def visit_not_startswith_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.right = percent._rconcat(binary.right)
+ return self.visit_not_like_op_binary(binary, operator, **kw)
+
+ def visit_istartswith_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.left = ilike_case_insensitive(binary.left)
+ binary.right = percent._rconcat(ilike_case_insensitive(binary.right))
+ return self.visit_ilike_op_binary(binary, operator, **kw)
+
+ def visit_not_istartswith_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.left = ilike_case_insensitive(binary.left)
+ binary.right = percent._rconcat(ilike_case_insensitive(binary.right))
+ return self.visit_not_ilike_op_binary(binary, operator, **kw)
+
+ def visit_endswith_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.right = percent.concat(binary.right)
+ return self.visit_like_op_binary(binary, operator, **kw)
+
+ def visit_not_endswith_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.right = percent.concat(binary.right)
+ return self.visit_not_like_op_binary(binary, operator, **kw)
+
+ def visit_iendswith_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.left = ilike_case_insensitive(binary.left)
+ binary.right = percent.concat(ilike_case_insensitive(binary.right))
+ return self.visit_ilike_op_binary(binary, operator, **kw)
+
+ def visit_not_iendswith_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.left = ilike_case_insensitive(binary.left)
+ binary.right = percent.concat(ilike_case_insensitive(binary.right))
+ return self.visit_not_ilike_op_binary(binary, operator, **kw)
+
+ def visit_like_op_binary(self, binary, operator, **kw):
+ escape = binary.modifiers.get("escape", None)
+
+ return "%s LIKE %s" % (
+ binary.left._compiler_dispatch(self, **kw),
+ binary.right._compiler_dispatch(self, **kw),
+ ) + (
+ " ESCAPE " + self.render_literal_value(escape, sqltypes.STRINGTYPE)
+ if escape is not None
+ else ""
+ )
+
+ def visit_not_like_op_binary(self, binary, operator, **kw):
+ escape = binary.modifiers.get("escape", None)
+ return "%s NOT LIKE %s" % (
+ binary.left._compiler_dispatch(self, **kw),
+ binary.right._compiler_dispatch(self, **kw),
+ ) + (
+ " ESCAPE " + self.render_literal_value(escape, sqltypes.STRINGTYPE)
+ if escape is not None
+ else ""
+ )
+
+ def visit_ilike_op_binary(self, binary, operator, **kw):
+ if operator is operators.ilike_op:
+ binary = binary._clone()
+ binary.left = ilike_case_insensitive(binary.left)
+ binary.right = ilike_case_insensitive(binary.right)
+ # else we assume ilower() has been applied
+
+ return self.visit_like_op_binary(binary, operator, **kw)
+
+ def visit_not_ilike_op_binary(self, binary, operator, **kw):
+ if operator is operators.not_ilike_op:
+ binary = binary._clone()
+ binary.left = ilike_case_insensitive(binary.left)
+ binary.right = ilike_case_insensitive(binary.right)
+ # else we assume ilower() has been applied
+
+ return self.visit_not_like_op_binary(binary, operator, **kw)
+
+ def visit_between_op_binary(self, binary, operator, **kw):
+ symmetric = binary.modifiers.get("symmetric", False)
+ return self._generate_generic_binary(
+ binary, " BETWEEN SYMMETRIC " if symmetric else " BETWEEN ", **kw
+ )
+
+ def visit_not_between_op_binary(self, binary, operator, **kw):
+ symmetric = binary.modifiers.get("symmetric", False)
+ return self._generate_generic_binary(
+ binary,
+ " NOT BETWEEN SYMMETRIC " if symmetric else " NOT BETWEEN ",
+ **kw,
+ )
+
+ def visit_regexp_match_op_binary(self, binary, operator, **kw):
+ raise exc.CompileError(
+ "%s dialect does not support regular expressions"
+ % self.dialect.name
+ )
+
+ def visit_not_regexp_match_op_binary(self, binary, operator, **kw):
+ raise exc.CompileError(
+ "%s dialect does not support regular expressions"
+ % self.dialect.name
+ )
+
+ def visit_regexp_replace_op_binary(self, binary, operator, **kw):
+ raise exc.CompileError(
+ "%s dialect does not support regular expression replacements"
+ % self.dialect.name
+ )
+
+ def visit_bindparam(
+ self,
+ bindparam,
+ within_columns_clause=False,
+ literal_binds=False,
+ skip_bind_expression=False,
+ literal_execute=False,
+ render_postcompile=False,
+ **kwargs,
+ ):
+
+ if not skip_bind_expression:
+ impl = bindparam.type.dialect_impl(self.dialect)
+ if impl._has_bind_expression:
+ bind_expression = impl.bind_expression(bindparam)
+ wrapped = self.process(
+ bind_expression,
+ skip_bind_expression=True,
+ within_columns_clause=within_columns_clause,
+ literal_binds=literal_binds and not bindparam.expanding,
+ literal_execute=literal_execute,
+ render_postcompile=render_postcompile,
+ **kwargs,
+ )
+ if bindparam.expanding:
+ # for postcompile w/ expanding, move the "wrapped" part
+ # of this into the inside
+
+ m = re.match(
+ r"^(.*)\(__\[POSTCOMPILE_(\S+?)\]\)(.*)$", wrapped
+ )
+ assert m, "unexpected format for expanding parameter"
+ wrapped = "(__[POSTCOMPILE_%s~~%s~~REPL~~%s~~])" % (
+ m.group(2),
+ m.group(1),
+ m.group(3),
+ )
+
+ if literal_binds:
+ ret = self.render_literal_bindparam(
+ bindparam,
+ within_columns_clause=True,
+ bind_expression_template=wrapped,
+ **kwargs,
+ )
+ return "(%s)" % ret
+
+ return wrapped
+
+ if not literal_binds:
+ literal_execute = (
+ literal_execute
+ or bindparam.literal_execute
+ or (within_columns_clause and self.ansi_bind_rules)
+ )
+ post_compile = literal_execute or bindparam.expanding
+ else:
+ post_compile = False
+
+ if literal_binds:
+ ret = self.render_literal_bindparam(
+ bindparam, within_columns_clause=True, **kwargs
+ )
+ if bindparam.expanding:
+ ret = "(%s)" % ret
+ return ret
+
+ name = self._truncate_bindparam(bindparam)
+
+ if name in self.binds:
+ existing = self.binds[name]
+ if existing is not bindparam:
+ if (
+ (existing.unique or bindparam.unique)
+ and not existing.proxy_set.intersection(
+ bindparam.proxy_set
+ )
+ and not existing._cloned_set.intersection(
+ bindparam._cloned_set
+ )
+ ):
+ raise exc.CompileError(
+ "Bind parameter '%s' conflicts with "
+ "unique bind parameter of the same name" % name
+ )
+ elif existing.expanding != bindparam.expanding:
+ raise exc.CompileError(
+ "Can't reuse bound parameter name '%s' in both "
+ "'expanding' (e.g. within an IN expression) and "
+ "non-expanding contexts. If this parameter is to "
+ "receive a list/array value, set 'expanding=True' on "
+ "it for expressions that aren't IN, otherwise use "
+ "a different parameter name." % (name,)
+ )
+ elif existing._is_crud or bindparam._is_crud:
+ if existing._is_crud and bindparam._is_crud:
+ # TODO: this condition is not well understood.
+ # see tests in test/sql/test_update.py
+ raise exc.CompileError(
+ "Encountered unsupported case when compiling an "
+ "INSERT or UPDATE statement. If this is a "
+ "multi-table "
+ "UPDATE statement, please provide string-named "
+ "arguments to the "
+ "values() method with distinct names; support for "
+ "multi-table UPDATE statements that "
+ "target multiple tables for UPDATE is very "
+ "limited",
+ )
+ else:
+ raise exc.CompileError(
+ f"bindparam() name '{bindparam.key}' is reserved "
+ "for automatic usage in the VALUES or SET "
+ "clause of this "
+ "insert/update statement. Please use a "
+ "name other than column name when using "
+ "bindparam() "
+ "with insert() or update() (for example, "
+ f"'b_{bindparam.key}')."
+ )
+
+ self.binds[bindparam.key] = self.binds[name] = bindparam
+
+ # if we are given a cache key that we're going to match against,
+ # relate the bindparam here to one that is most likely present
+ # in the "extracted params" portion of the cache key. this is used
+ # to set up a positional mapping that is used to determine the
+ # correct parameters for a subsequent use of this compiled with
+ # a different set of parameter values. here, we accommodate for
+ # parameters that may have been cloned both before and after the cache
+ # key was been generated.
+ ckbm_tuple = self._cache_key_bind_match
+
+ if ckbm_tuple:
+ ckbm, cksm = ckbm_tuple
+ for bp in bindparam._cloned_set:
+ if bp.key in cksm:
+ cb = cksm[bp.key]
+ ckbm[cb].append(bindparam)
+
+ if bindparam.isoutparam:
+ self.has_out_parameters = True
+
+ if post_compile:
+ if render_postcompile:
+ self._render_postcompile = True
+
+ if literal_execute:
+ self.literal_execute_params |= {bindparam}
+ else:
+ self.post_compile_params |= {bindparam}
+
+ ret = self.bindparam_string(
+ name,
+ post_compile=post_compile,
+ expanding=bindparam.expanding,
+ bindparam_type=bindparam.type,
+ **kwargs,
+ )
+
+ if bindparam.expanding:
+ ret = "(%s)" % ret
+
+ return ret
+
+ def render_bind_cast(self, type_, dbapi_type, sqltext):
+ raise NotImplementedError()
+
+ def render_literal_bindparam(
+ self,
+ bindparam,
+ render_literal_value=NO_ARG,
+ bind_expression_template=None,
+ **kw,
+ ):
+ if render_literal_value is not NO_ARG:
+ value = render_literal_value
+ else:
+ if bindparam.value is None and bindparam.callable is None:
+ op = kw.get("_binary_op", None)
+ if op and op not in (operators.is_, operators.is_not):
+ util.warn_limited(
+ "Bound parameter '%s' rendering literal NULL in a SQL "
+ "expression; comparisons to NULL should not use "
+ "operators outside of 'is' or 'is not'",
+ (bindparam.key,),
+ )
+ return self.process(sqltypes.NULLTYPE, **kw)
+ value = bindparam.effective_value
+
+ if bindparam.expanding:
+ leep = self._literal_execute_expanding_parameter_literal_binds
+ to_update, replacement_expr = leep(
+ bindparam,
+ value,
+ bind_expression_template=bind_expression_template,
+ )
+ return replacement_expr
+ else:
+ return self.render_literal_value(value, bindparam.type)
+
+ def render_literal_value(self, value, type_):
+ """Render the value of a bind parameter as a quoted literal.
+
+ This is used for statement sections that do not accept bind parameters
+ on the target driver/database.
+
+ This should be implemented by subclasses using the quoting services
+ of the DBAPI.
+
+ """
+
+ if value is None and not type_.should_evaluate_none:
+ # issue #10535 - handle NULL in the compiler without placing
+ # this onto each type, except for "evaluate None" types
+ # (e.g. JSON)
+ return self.process(elements.Null._instance())
+
+ processor = type_._cached_literal_processor(self.dialect)
+ if processor:
+ 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 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):
+ if bindparam in self.bind_names:
+ return self.bind_names[bindparam]
+
+ bind_name = bindparam.key
+ if isinstance(bind_name, elements._truncated_label):
+ bind_name = self._truncated_identifier("bindparam", bind_name)
+
+ # add to bind_names for translation
+ self.bind_names[bindparam] = bind_name
+
+ return bind_name
+
+ def _truncated_identifier(
+ self, ident_class: str, name: _truncated_label
+ ) -> str:
+ if (ident_class, name) in self.truncated_names:
+ return self.truncated_names[(ident_class, name)]
+
+ anonname = name.apply_map(self.anon_map)
+
+ if len(anonname) > self.label_length - 6:
+ counter = self._truncated_counters.get(ident_class, 1)
+ truncname = (
+ anonname[0 : max(self.label_length - 6, 0)]
+ + "_"
+ + hex(counter)[2:]
+ )
+ self._truncated_counters[ident_class] = counter + 1
+ else:
+ truncname = anonname
+ self.truncated_names[(ident_class, name)] = truncname
+ return truncname
+
+ def _anonymize(self, name: str) -> str:
+ return name % self.anon_map
+
+ def bindparam_string(
+ self,
+ name: str,
+ post_compile: bool = False,
+ expanding: bool = False,
+ escaped_from: Optional[str] = None,
+ bindparam_type: Optional[TypeEngine[Any]] = None,
+ accumulate_bind_names: Optional[Set[str]] = None,
+ visited_bindparam: Optional[List[str]] = None,
+ **kw: Any,
+ ) -> str:
+ # TODO: accumulate_bind_names is passed by crud.py to gather
+ # names on a per-value basis, visited_bindparam is passed by
+ # visit_insert() to collect all parameters in the statement.
+ # see if this gathering can be simplified somehow
+ if accumulate_bind_names is not None:
+ accumulate_bind_names.add(name)
+ if visited_bindparam is not None:
+ visited_bindparam.append(name)
+
+ if not escaped_from:
+ if self._bind_translate_re.search(name):
+ # not quite the translate use case as we want to
+ # also get a quick boolean if we even found
+ # unusual characters in the name
+ new_name = self._bind_translate_re.sub(
+ lambda m: self._bind_translate_chars[m.group(0)],
+ name,
+ )
+ escaped_from = name
+ name = new_name
+
+ if escaped_from:
+ self.escaped_bind_names = self.escaped_bind_names.union(
+ {escaped_from: name}
+ )
+ if post_compile:
+ ret = "__[POSTCOMPILE_%s]" % name
+ if expanding:
+ # for expanding, bound parameters or literal values will be
+ # rendered per item
+ return ret
+
+ # otherwise, for non-expanding "literal execute", apply
+ # bind casts as determined by the datatype
+ if bindparam_type is not None:
+ type_impl = bindparam_type._unwrapped_dialect_impl(
+ self.dialect
+ )
+ if type_impl.render_literal_cast:
+ ret = self.render_bind_cast(bindparam_type, type_impl, ret)
+ return ret
+ elif self.state is CompilerState.COMPILING:
+ ret = self.compilation_bindtemplate % {"name": name}
+ else:
+ ret = self.bindtemplate % {"name": name}
+
+ if (
+ bindparam_type is not None
+ and self.dialect._bind_typing_render_casts
+ ):
+ type_impl = bindparam_type._unwrapped_dialect_impl(self.dialect)
+ if type_impl.render_bind_cast:
+ ret = self.render_bind_cast(bindparam_type, type_impl, ret)
+
+ return ret
+
+ def _dispatch_independent_ctes(self, stmt, kw):
+ local_kw = kw.copy()
+ local_kw.pop("cte_opts", None)
+ for cte, opt in zip(
+ stmt._independent_ctes, stmt._independent_ctes_opts
+ ):
+ cte._compiler_dispatch(self, cte_opts=opt, **local_kw)
+
+ def visit_cte(
+ self,
+ cte: CTE,
+ asfrom: bool = False,
+ ashint: bool = False,
+ fromhints: Optional[_FromHintsType] = None,
+ visiting_cte: Optional[CTE] = None,
+ from_linter: Optional[FromLinter] = None,
+ cte_opts: selectable._CTEOpts = selectable._CTEOpts(False),
+ **kwargs: Any,
+ ) -> Optional[str]:
+ self_ctes = self._init_cte_state()
+ assert self_ctes is self.ctes
+
+ kwargs["visiting_cte"] = cte
+
+ cte_name = cte.name
+
+ if isinstance(cte_name, elements._truncated_label):
+ cte_name = self._truncated_identifier("alias", cte_name)
+
+ is_new_cte = True
+ embedded_in_current_named_cte = False
+
+ _reference_cte = cte._get_reference_cte()
+
+ nesting = cte.nesting or cte_opts.nesting
+
+ # check for CTE already encountered
+ if _reference_cte in self.level_name_by_cte:
+ cte_level, _, existing_cte_opts = self.level_name_by_cte[
+ _reference_cte
+ ]
+ assert _ == cte_name
+
+ cte_level_name = (cte_level, cte_name)
+ existing_cte = self.ctes_by_level_name[cte_level_name]
+
+ # check if we are receiving it here with a specific
+ # "nest_here" location; if so, move it to this location
+
+ if cte_opts.nesting:
+ if existing_cte_opts.nesting:
+ raise exc.CompileError(
+ "CTE is stated as 'nest_here' in "
+ "more than one location"
+ )
+
+ old_level_name = (cte_level, cte_name)
+ cte_level = len(self.stack) if nesting else 1
+ cte_level_name = new_level_name = (cte_level, cte_name)
+
+ del self.ctes_by_level_name[old_level_name]
+ self.ctes_by_level_name[new_level_name] = existing_cte
+ self.level_name_by_cte[_reference_cte] = new_level_name + (
+ cte_opts,
+ )
+
+ else:
+ cte_level = len(self.stack) if nesting else 1
+ cte_level_name = (cte_level, cte_name)
+
+ if cte_level_name in self.ctes_by_level_name:
+ existing_cte = self.ctes_by_level_name[cte_level_name]
+ else:
+ existing_cte = None
+
+ if existing_cte is not None:
+ embedded_in_current_named_cte = visiting_cte is existing_cte
+
+ # we've generated a same-named CTE that we are enclosed in,
+ # or this is the same CTE. just return the name.
+ if cte is existing_cte._restates or cte is existing_cte:
+ is_new_cte = False
+ elif existing_cte is cte._restates:
+ # we've generated a same-named CTE that is
+ # enclosed in us - we take precedence, so
+ # discard the text for the "inner".
+ del self_ctes[existing_cte]
+
+ existing_cte_reference_cte = existing_cte._get_reference_cte()
+
+ assert existing_cte_reference_cte is _reference_cte
+ assert existing_cte_reference_cte is existing_cte
+
+ del self.level_name_by_cte[existing_cte_reference_cte]
+ else:
+ # if the two CTEs are deep-copy identical, consider them
+ # the same, **if** they are clones, that is, they came from
+ # the ORM or other visit method
+ if (
+ cte._is_clone_of is not None
+ or existing_cte._is_clone_of is not None
+ ) and cte.compare(existing_cte):
+ is_new_cte = False
+ else:
+ raise exc.CompileError(
+ "Multiple, unrelated CTEs found with "
+ "the same name: %r" % cte_name
+ )
+
+ if not asfrom and not is_new_cte:
+ return None
+
+ if cte._cte_alias is not None:
+ pre_alias_cte = cte._cte_alias
+ cte_pre_alias_name = cte._cte_alias.name
+ if isinstance(cte_pre_alias_name, elements._truncated_label):
+ cte_pre_alias_name = self._truncated_identifier(
+ "alias", cte_pre_alias_name
+ )
+ else:
+ pre_alias_cte = cte
+ cte_pre_alias_name = None
+
+ if is_new_cte:
+ self.ctes_by_level_name[cte_level_name] = cte
+ self.level_name_by_cte[_reference_cte] = cte_level_name + (
+ cte_opts,
+ )
+
+ if pre_alias_cte not in self.ctes:
+ self.visit_cte(pre_alias_cte, **kwargs)
+
+ if not cte_pre_alias_name and cte not in self_ctes:
+ if cte.recursive:
+ self.ctes_recursive = True
+ text = self.preparer.format_alias(cte, cte_name)
+ if cte.recursive:
+ col_source = cte.element
+
+ # TODO: can we get at the .columns_plus_names collection
+ # that is already (or will be?) generated for the SELECT
+ # rather than calling twice?
+ recur_cols = [
+ # TODO: proxy_name is not technically safe,
+ # see test_cte->
+ # test_with_recursive_no_name_currently_buggy. not
+ # clear what should be done with such a case
+ fallback_label_name or proxy_name
+ for (
+ _,
+ proxy_name,
+ fallback_label_name,
+ c,
+ repeated,
+ ) in (col_source._generate_columns_plus_names(True))
+ if not repeated
+ ]
+
+ text += "(%s)" % (
+ ", ".join(
+ self.preparer.format_label_name(
+ ident, anon_map=self.anon_map
+ )
+ for ident in recur_cols
+ )
+ )
+
+ assert kwargs.get("subquery", False) is False
+
+ if not self.stack:
+ # toplevel, this is a stringify of the
+ # cte directly. just compile the inner
+ # the way alias() does.
+ return cte.element._compiler_dispatch(
+ self, asfrom=asfrom, **kwargs
+ )
+ else:
+ prefixes = self._generate_prefixes(
+ cte, cte._prefixes, **kwargs
+ )
+ inner = cte.element._compiler_dispatch(
+ self, asfrom=True, **kwargs
+ )
+
+ text += " AS %s\n(%s)" % (prefixes, inner)
+
+ if cte._suffixes:
+ text += " " + self._generate_prefixes(
+ cte, cte._suffixes, **kwargs
+ )
+
+ self_ctes[cte] = text
+
+ if asfrom:
+ if from_linter:
+ from_linter.froms[cte._de_clone()] = cte_name
+
+ if not is_new_cte and embedded_in_current_named_cte:
+ return self.preparer.format_alias(cte, cte_name)
+
+ if cte_pre_alias_name:
+ text = self.preparer.format_alias(cte, cte_pre_alias_name)
+ if self.preparer._requires_quotes(cte_name):
+ cte_name = self.preparer.quote(cte_name)
+ text += self.get_render_as_alias_suffix(cte_name)
+ return text
+ else:
+ return self.preparer.format_alias(cte, cte_name)
+
+ return None
+
+ def visit_table_valued_alias(self, element, **kw):
+ if element.joins_implicitly:
+ kw["from_linter"] = None
+ if element._is_lateral:
+ return self.visit_lateral(element, **kw)
+ else:
+ return self.visit_alias(element, **kw)
+
+ def visit_table_valued_column(self, element, **kw):
+ return self.visit_column(element, **kw)
+
+ def visit_alias(
+ self,
+ alias,
+ asfrom=False,
+ ashint=False,
+ iscrud=False,
+ fromhints=None,
+ subquery=False,
+ lateral=False,
+ enclosing_alias=None,
+ from_linter=None,
+ **kwargs,
+ ):
+ if lateral:
+ if "enclosing_lateral" not in kwargs:
+ # if lateral is set and enclosing_lateral is not
+ # present, we assume we are being called directly
+ # from visit_lateral() and we need to set enclosing_lateral.
+ assert alias._is_lateral
+ kwargs["enclosing_lateral"] = alias
+
+ # for lateral objects, we track a second from_linter that is...
+ # lateral! to the level above us.
+ if (
+ from_linter
+ and "lateral_from_linter" not in kwargs
+ and "enclosing_lateral" in kwargs
+ ):
+ kwargs["lateral_from_linter"] = from_linter
+
+ if enclosing_alias is not None and enclosing_alias.element is alias:
+ inner = alias.element._compiler_dispatch(
+ self,
+ asfrom=asfrom,
+ ashint=ashint,
+ iscrud=iscrud,
+ fromhints=fromhints,
+ lateral=lateral,
+ enclosing_alias=alias,
+ **kwargs,
+ )
+ if subquery and (asfrom or lateral):
+ inner = "(%s)" % (inner,)
+ return inner
+ else:
+ enclosing_alias = kwargs["enclosing_alias"] = alias
+
+ if asfrom or ashint:
+ if isinstance(alias.name, elements._truncated_label):
+ alias_name = self._truncated_identifier("alias", alias.name)
+ else:
+ alias_name = alias.name
+
+ if ashint:
+ return self.preparer.format_alias(alias, alias_name)
+ elif asfrom:
+ if from_linter:
+ from_linter.froms[alias._de_clone()] = alias_name
+
+ inner = alias.element._compiler_dispatch(
+ self, asfrom=True, lateral=lateral, **kwargs
+ )
+ if subquery:
+ inner = "(%s)" % (inner,)
+
+ ret = inner + self.get_render_as_alias_suffix(
+ self.preparer.format_alias(alias, alias_name)
+ )
+
+ if alias._supports_derived_columns and alias._render_derived:
+ ret += "(%s)" % (
+ ", ".join(
+ "%s%s"
+ % (
+ self.preparer.quote(col.name),
+ (
+ " %s"
+ % self.dialect.type_compiler_instance.process(
+ col.type, **kwargs
+ )
+ if alias._render_derived_w_types
+ else ""
+ ),
+ )
+ for col in alias.c
+ )
+ )
+
+ if fromhints and alias in fromhints:
+ ret = self.format_from_hint_text(
+ ret, alias, fromhints[alias], iscrud
+ )
+
+ return ret
+ else:
+ # note we cancel the "subquery" flag here as well
+ return alias.element._compiler_dispatch(
+ self, lateral=lateral, **kwargs
+ )
+
+ def visit_subquery(self, subquery, **kw):
+ kw["subquery"] = True
+ return self.visit_alias(subquery, **kw)
+
+ def visit_lateral(self, lateral_, **kw):
+ kw["lateral"] = True
+ return "LATERAL %s" % self.visit_alias(lateral_, **kw)
+
+ def visit_tablesample(self, tablesample, asfrom=False, **kw):
+ text = "%s TABLESAMPLE %s" % (
+ self.visit_alias(tablesample, asfrom=True, **kw),
+ tablesample._get_method()._compiler_dispatch(self, **kw),
+ )
+
+ if tablesample.seed is not None:
+ text += " REPEATABLE (%s)" % (
+ tablesample.seed._compiler_dispatch(self, **kw)
+ )
+
+ return text
+
+ def _render_values(self, element, **kw):
+ kw.setdefault("literal_binds", element.literal_binds)
+ tuples = ", ".join(
+ self.process(
+ elements.Tuple(
+ types=element._column_types, *elem
+ ).self_group(),
+ **kw,
+ )
+ for chunk in element._data
+ for elem in chunk
+ )
+ return f"VALUES {tuples}"
+
+ def visit_values(self, element, asfrom=False, from_linter=None, **kw):
+ v = self._render_values(element, **kw)
+
+ if element._unnamed:
+ name = None
+ elif isinstance(element.name, elements._truncated_label):
+ name = self._truncated_identifier("values", element.name)
+ else:
+ name = element.name
+
+ if element._is_lateral:
+ lateral = "LATERAL "
+ else:
+ lateral = ""
+
+ if asfrom:
+ if from_linter:
+ from_linter.froms[element._de_clone()] = (
+ name if name is not None else "(unnamed VALUES element)"
+ )
+
+ if name:
+ kw["include_table"] = False
+ v = "%s(%s)%s (%s)" % (
+ lateral,
+ v,
+ self.get_render_as_alias_suffix(self.preparer.quote(name)),
+ (
+ ", ".join(
+ c._compiler_dispatch(self, **kw)
+ for c in element.columns
+ )
+ ),
+ )
+ else:
+ v = "%s(%s)" % (lateral, v)
+ return v
+
+ def visit_scalar_values(self, element, **kw):
+ return f"({self._render_values(element, **kw)})"
+
+ def get_render_as_alias_suffix(self, alias_name_text):
+ return " AS " + alias_name_text
+
+ def _add_to_result_map(
+ self,
+ keyname: str,
+ name: str,
+ objects: Tuple[Any, ...],
+ type_: TypeEngine[Any],
+ ) -> None:
+ if keyname is None or keyname == "*":
+ self._ordered_columns = False
+ self._ad_hoc_textual = True
+ if type_._is_tuple_type:
+ raise exc.CompileError(
+ "Most backends don't support SELECTing "
+ "from a tuple() object. If this is an ORM query, "
+ "consider using the Bundle object."
+ )
+ self._result_columns.append(
+ ResultColumnsEntry(keyname, name, objects, type_)
+ )
+
+ def _label_returning_column(
+ self, stmt, column, populate_result_map, column_clause_args=None, **kw
+ ):
+ """Render a column with necessary labels inside of a RETURNING clause.
+
+ This method is provided for individual dialects in place of calling
+ the _label_select_column method directly, so that the two use cases
+ of RETURNING vs. SELECT can be disambiguated going forward.
+
+ .. versionadded:: 1.4.21
+
+ """
+ return self._label_select_column(
+ None,
+ column,
+ populate_result_map,
+ False,
+ {} if column_clause_args is None else column_clause_args,
+ **kw,
+ )
+
+ def _label_select_column(
+ self,
+ select,
+ column,
+ populate_result_map,
+ asfrom,
+ column_clause_args,
+ name=None,
+ proxy_name=None,
+ fallback_label_name=None,
+ within_columns_clause=True,
+ column_is_repeated=False,
+ need_column_expressions=False,
+ include_table=True,
+ ):
+ """produce labeled columns present in a select()."""
+ impl = column.type.dialect_impl(self.dialect)
+
+ if impl._has_column_expression and (
+ need_column_expressions or populate_result_map
+ ):
+ col_expr = impl.column_expression(column)
+ else:
+ col_expr = column
+
+ if populate_result_map:
+ # pass an "add_to_result_map" callable into the compilation
+ # of embedded columns. this collects information about the
+ # column as it will be fetched in the result and is coordinated
+ # with cursor.description when the query is executed.
+ add_to_result_map = self._add_to_result_map
+
+ # if the SELECT statement told us this column is a repeat,
+ # wrap the callable with one that prevents the addition of the
+ # targets
+ if column_is_repeated:
+ _add_to_result_map = add_to_result_map
+
+ def add_to_result_map(keyname, name, objects, type_):
+ _add_to_result_map(keyname, name, (), type_)
+
+ # if we redefined col_expr for type expressions, wrap the
+ # callable with one that adds the original column to the targets
+ elif col_expr is not column:
+ _add_to_result_map = add_to_result_map
+
+ def add_to_result_map(keyname, name, objects, type_):
+ _add_to_result_map(
+ keyname, name, (column,) + objects, type_
+ )
+
+ else:
+ add_to_result_map = None
+
+ # this method is used by some of the dialects for RETURNING,
+ # which has different inputs. _label_returning_column was added
+ # as the better target for this now however for 1.4 we will keep
+ # _label_select_column directly compatible with this use case.
+ # these assertions right now set up the current expected inputs
+ assert within_columns_clause, (
+ "_label_select_column is only relevant within "
+ "the columns clause of a SELECT or RETURNING"
+ )
+ if isinstance(column, elements.Label):
+ if col_expr is not column:
+ result_expr = _CompileLabel(
+ col_expr, column.name, alt_names=(column.element,)
+ )
+ else:
+ result_expr = col_expr
+
+ elif name:
+ # here, _columns_plus_names has determined there's an explicit
+ # label name we need to use. this is the default for
+ # tablenames_plus_columnnames as well as when columns are being
+ # deduplicated on name
+
+ assert (
+ proxy_name is not None
+ ), "proxy_name is required if 'name' is passed"
+
+ result_expr = _CompileLabel(
+ col_expr,
+ name,
+ alt_names=(
+ proxy_name,
+ # this is a hack to allow legacy result column lookups
+ # to work as they did before; this goes away in 2.0.
+ # TODO: this only seems to be tested indirectly
+ # via test/orm/test_deprecations.py. should be a
+ # resultset test for this
+ column._tq_label,
+ ),
+ )
+ else:
+ # determine here whether this column should be rendered in
+ # a labelled context or not, as we were given no required label
+ # name from the caller. Here we apply heuristics based on the kind
+ # of SQL expression involved.
+
+ if col_expr is not column:
+ # type-specific expression wrapping the given column,
+ # so we render a label
+ render_with_label = True
+ elif isinstance(column, elements.ColumnClause):
+ # table-bound column, we render its name as a label if we are
+ # inside of a subquery only
+ render_with_label = (
+ asfrom
+ and not column.is_literal
+ and column.table is not None
+ )
+ elif isinstance(column, elements.TextClause):
+ render_with_label = False
+ elif isinstance(column, elements.UnaryExpression):
+ render_with_label = column.wraps_column_expression or asfrom
+ elif (
+ # general class of expressions that don't have a SQL-column
+ # addressible name. includes scalar selects, bind parameters,
+ # SQL functions, others
+ not isinstance(column, elements.NamedColumn)
+ # deeper check that indicates there's no natural "name" to
+ # this element, which accommodates for custom SQL constructs
+ # that might have a ".name" attribute (but aren't SQL
+ # functions) but are not implementing this more recently added
+ # base class. in theory the "NamedColumn" check should be
+ # enough, however here we seek to maintain legacy behaviors
+ # as well.
+ and column._non_anon_label is None
+ ):
+ render_with_label = True
+ else:
+ render_with_label = False
+
+ if render_with_label:
+ if not fallback_label_name:
+ # used by the RETURNING case right now. we generate it
+ # here as 3rd party dialects may be referring to
+ # _label_select_column method directly instead of the
+ # just-added _label_returning_column method
+ assert not column_is_repeated
+ fallback_label_name = column._anon_name_label
+
+ fallback_label_name = (
+ elements._truncated_label(fallback_label_name)
+ if not isinstance(
+ fallback_label_name, elements._truncated_label
+ )
+ else fallback_label_name
+ )
+
+ result_expr = _CompileLabel(
+ col_expr, fallback_label_name, alt_names=(proxy_name,)
+ )
+ else:
+ result_expr = col_expr
+
+ column_clause_args.update(
+ within_columns_clause=within_columns_clause,
+ add_to_result_map=add_to_result_map,
+ include_table=include_table,
+ )
+ return result_expr._compiler_dispatch(self, **column_clause_args)
+
+ def format_from_hint_text(self, sqltext, table, hint, iscrud):
+ hinttext = self.get_from_hint_text(table, hint)
+ if hinttext:
+ sqltext += " " + hinttext
+ return sqltext
+
+ def get_select_hint_text(self, byfroms):
+ return None
+
+ def get_from_hint_text(self, table, text):
+ return None
+
+ def get_crud_hint_text(self, table, text):
+ return None
+
+ def get_statement_hint_text(self, hint_texts):
+ return " ".join(hint_texts)
+
+ _default_stack_entry: _CompilerStackEntry
+
+ if not typing.TYPE_CHECKING:
+ _default_stack_entry = util.immutabledict(
+ [("correlate_froms", frozenset()), ("asfrom_froms", frozenset())]
+ )
+
+ def _display_froms_for_select(
+ self, select_stmt, asfrom, lateral=False, **kw
+ ):
+ # utility method to help external dialects
+ # get the correct from list for a select.
+ # specifically the oracle dialect needs this feature
+ # right now.
+ toplevel = not self.stack
+ entry = self._default_stack_entry if toplevel else self.stack[-1]
+
+ compile_state = select_stmt._compile_state_factory(select_stmt, self)
+
+ correlate_froms = entry["correlate_froms"]
+ asfrom_froms = entry["asfrom_froms"]
+
+ if asfrom and not lateral:
+ froms = compile_state._get_display_froms(
+ explicit_correlate_froms=correlate_froms.difference(
+ asfrom_froms
+ ),
+ implicit_correlate_froms=(),
+ )
+ else:
+ froms = compile_state._get_display_froms(
+ explicit_correlate_froms=correlate_froms,
+ implicit_correlate_froms=asfrom_froms,
+ )
+ return froms
+
+ translate_select_structure: Any = None
+ """if not ``None``, should be a callable which accepts ``(select_stmt,
+ **kw)`` and returns a select object. this is used for structural changes
+ mostly to accommodate for LIMIT/OFFSET schemes
+
+ """
+
+ def visit_select(
+ self,
+ select_stmt,
+ asfrom=False,
+ insert_into=False,
+ fromhints=None,
+ compound_index=None,
+ select_wraps_for=None,
+ lateral=False,
+ from_linter=None,
+ **kwargs,
+ ):
+ assert select_wraps_for is None, (
+ "SQLAlchemy 1.4 requires use of "
+ "the translate_select_structure hook for structural "
+ "translations of SELECT objects"
+ )
+
+ # initial setup of SELECT. the compile_state_factory may now
+ # be creating a totally different SELECT from the one that was
+ # passed in. for ORM use this will convert from an ORM-state
+ # SELECT to a regular "Core" SELECT. other composed operations
+ # such as computation of joins will be performed.
+
+ kwargs["within_columns_clause"] = False
+
+ compile_state = select_stmt._compile_state_factory(
+ select_stmt, self, **kwargs
+ )
+ kwargs["ambiguous_table_name_map"] = (
+ compile_state._ambiguous_table_name_map
+ )
+
+ select_stmt = compile_state.statement
+
+ toplevel = not self.stack
+
+ if toplevel and not self.compile_state:
+ self.compile_state = compile_state
+
+ is_embedded_select = compound_index is not None or insert_into
+
+ # translate step for Oracle, SQL Server which often need to
+ # restructure the SELECT to allow for LIMIT/OFFSET and possibly
+ # other conditions
+ if self.translate_select_structure:
+ new_select_stmt = self.translate_select_structure(
+ select_stmt, asfrom=asfrom, **kwargs
+ )
+
+ # if SELECT was restructured, maintain a link to the originals
+ # and assemble a new compile state
+ if new_select_stmt is not select_stmt:
+ compile_state_wraps_for = compile_state
+ select_wraps_for = select_stmt
+ select_stmt = new_select_stmt
+
+ compile_state = select_stmt._compile_state_factory(
+ select_stmt, self, **kwargs
+ )
+ select_stmt = compile_state.statement
+
+ entry = self._default_stack_entry if toplevel else self.stack[-1]
+
+ populate_result_map = need_column_expressions = (
+ toplevel
+ or entry.get("need_result_map_for_compound", False)
+ or entry.get("need_result_map_for_nested", False)
+ )
+
+ # indicates there is a CompoundSelect in play and we are not the
+ # first select
+ if compound_index:
+ populate_result_map = False
+
+ # this was first proposed as part of #3372; however, it is not
+ # reached in current tests and could possibly be an assertion
+ # instead.
+ if not populate_result_map and "add_to_result_map" in kwargs:
+ del kwargs["add_to_result_map"]
+
+ froms = self._setup_select_stack(
+ select_stmt, compile_state, entry, asfrom, lateral, compound_index
+ )
+
+ column_clause_args = kwargs.copy()
+ column_clause_args.update(
+ {"within_label_clause": False, "within_columns_clause": False}
+ )
+
+ text = "SELECT " # we're off to a good start !
+
+ if select_stmt._hints:
+ hint_text, byfrom = self._setup_select_hints(select_stmt)
+ if hint_text:
+ text += hint_text + " "
+ else:
+ byfrom = None
+
+ if select_stmt._independent_ctes:
+ self._dispatch_independent_ctes(select_stmt, kwargs)
+
+ if select_stmt._prefixes:
+ text += self._generate_prefixes(
+ select_stmt, select_stmt._prefixes, **kwargs
+ )
+
+ text += self.get_select_precolumns(select_stmt, **kwargs)
+ # the actual list of columns to print in the SELECT column list.
+ inner_columns = [
+ c
+ for c in [
+ self._label_select_column(
+ select_stmt,
+ column,
+ populate_result_map,
+ asfrom,
+ column_clause_args,
+ name=name,
+ proxy_name=proxy_name,
+ fallback_label_name=fallback_label_name,
+ column_is_repeated=repeated,
+ need_column_expressions=need_column_expressions,
+ )
+ for (
+ name,
+ proxy_name,
+ fallback_label_name,
+ column,
+ repeated,
+ ) in compile_state.columns_plus_names
+ ]
+ if c is not None
+ ]
+
+ if populate_result_map and select_wraps_for is not None:
+ # if this select was generated from translate_select,
+ # rewrite the targeted columns in the result map
+
+ translate = dict(
+ zip(
+ [
+ name
+ for (
+ key,
+ proxy_name,
+ fallback_label_name,
+ name,
+ repeated,
+ ) in compile_state.columns_plus_names
+ ],
+ [
+ name
+ for (
+ key,
+ proxy_name,
+ fallback_label_name,
+ name,
+ repeated,
+ ) in compile_state_wraps_for.columns_plus_names
+ ],
+ )
+ )
+
+ self._result_columns = [
+ ResultColumnsEntry(
+ key, name, tuple(translate.get(o, o) for o in obj), type_
+ )
+ for key, name, obj, type_ in self._result_columns
+ ]
+
+ text = self._compose_select_body(
+ text,
+ select_stmt,
+ compile_state,
+ inner_columns,
+ froms,
+ byfrom,
+ toplevel,
+ kwargs,
+ )
+
+ if select_stmt._statement_hints:
+ per_dialect = [
+ ht
+ for (dialect_name, ht) in select_stmt._statement_hints
+ if dialect_name in ("*", self.dialect.name)
+ ]
+ if per_dialect:
+ text += " " + self.get_statement_hint_text(per_dialect)
+
+ # In compound query, CTEs are shared at the compound level
+ if self.ctes and (not is_embedded_select or toplevel):
+ nesting_level = len(self.stack) if not toplevel else None
+ text = self._render_cte_clause(nesting_level=nesting_level) + text
+
+ if select_stmt._suffixes:
+ text += " " + self._generate_prefixes(
+ select_stmt, select_stmt._suffixes, **kwargs
+ )
+
+ self.stack.pop(-1)
+
+ return text
+
+ def _setup_select_hints(
+ self, select: Select[Any]
+ ) -> Tuple[str, _FromHintsType]:
+ byfrom = {
+ from_: hinttext
+ % {"name": from_._compiler_dispatch(self, ashint=True)}
+ for (from_, dialect), hinttext in select._hints.items()
+ if dialect in ("*", self.dialect.name)
+ }
+ hint_text = self.get_select_hint_text(byfrom)
+ return hint_text, byfrom
+
+ def _setup_select_stack(
+ self, select, compile_state, entry, asfrom, lateral, compound_index
+ ):
+ correlate_froms = entry["correlate_froms"]
+ asfrom_froms = entry["asfrom_froms"]
+
+ if compound_index == 0:
+ entry["select_0"] = select
+ elif compound_index:
+ select_0 = entry["select_0"]
+ numcols = len(select_0._all_selected_columns)
+
+ if len(compile_state.columns_plus_names) != numcols:
+ raise exc.CompileError(
+ "All selectables passed to "
+ "CompoundSelect must have identical numbers of "
+ "columns; select #%d has %d columns, select "
+ "#%d has %d"
+ % (
+ 1,
+ numcols,
+ compound_index + 1,
+ len(select._all_selected_columns),
+ )
+ )
+
+ if asfrom and not lateral:
+ froms = compile_state._get_display_froms(
+ explicit_correlate_froms=correlate_froms.difference(
+ asfrom_froms
+ ),
+ implicit_correlate_froms=(),
+ )
+ else:
+ froms = compile_state._get_display_froms(
+ explicit_correlate_froms=correlate_froms,
+ implicit_correlate_froms=asfrom_froms,
+ )
+
+ new_correlate_froms = set(_from_objects(*froms))
+ all_correlate_froms = new_correlate_froms.union(correlate_froms)
+
+ new_entry: _CompilerStackEntry = {
+ "asfrom_froms": new_correlate_froms,
+ "correlate_froms": all_correlate_froms,
+ "selectable": select,
+ "compile_state": compile_state,
+ }
+ self.stack.append(new_entry)
+
+ return froms
+
+ def _compose_select_body(
+ self,
+ text,
+ select,
+ compile_state,
+ inner_columns,
+ froms,
+ byfrom,
+ toplevel,
+ kwargs,
+ ):
+ text += ", ".join(inner_columns)
+
+ if self.linting & COLLECT_CARTESIAN_PRODUCTS:
+ from_linter = FromLinter({}, set())
+ warn_linting = self.linting & WARN_LINTING
+ if toplevel:
+ self.from_linter = from_linter
+ else:
+ from_linter = None
+ warn_linting = False
+
+ # adjust the whitespace for no inner columns, part of #9440,
+ # so that a no-col SELECT comes out as "SELECT WHERE..." or
+ # "SELECT FROM ...".
+ # while it would be better to have built the SELECT starting string
+ # without trailing whitespace first, then add whitespace only if inner
+ # cols were present, this breaks compatibility with various custom
+ # compilation schemes that are currently being tested.
+ if not inner_columns:
+ text = text.rstrip()
+
+ if froms:
+ text += " \nFROM "
+
+ if select._hints:
+ text += ", ".join(
+ [
+ f._compiler_dispatch(
+ self,
+ asfrom=True,
+ fromhints=byfrom,
+ from_linter=from_linter,
+ **kwargs,
+ )
+ for f in froms
+ ]
+ )
+ else:
+ text += ", ".join(
+ [
+ f._compiler_dispatch(
+ self,
+ asfrom=True,
+ from_linter=from_linter,
+ **kwargs,
+ )
+ for f in froms
+ ]
+ )
+ else:
+ text += self.default_from()
+
+ if select._where_criteria:
+ t = self._generate_delimited_and_list(
+ select._where_criteria, from_linter=from_linter, **kwargs
+ )
+ if t:
+ text += " \nWHERE " + t
+
+ if warn_linting:
+ assert from_linter is not None
+ from_linter.warn()
+
+ if select._group_by_clauses:
+ text += self.group_by_clause(select, **kwargs)
+
+ if select._having_criteria:
+ t = self._generate_delimited_and_list(
+ select._having_criteria, **kwargs
+ )
+ if t:
+ text += " \nHAVING " + t
+
+ if select._order_by_clauses:
+ text += self.order_by_clause(select, **kwargs)
+
+ if select._has_row_limiting_clause:
+ text += self._row_limit_clause(select, **kwargs)
+
+ if select._for_update_arg is not None:
+ text += self.for_update_clause(select, **kwargs)
+
+ return text
+
+ def _generate_prefixes(self, stmt, prefixes, **kw):
+ clause = " ".join(
+ prefix._compiler_dispatch(self, **kw)
+ for prefix, dialect_name in prefixes
+ if dialect_name in (None, "*") or dialect_name == self.dialect.name
+ )
+ if clause:
+ clause += " "
+ return clause
+
+ def _render_cte_clause(
+ self,
+ nesting_level=None,
+ include_following_stack=False,
+ ):
+ """
+ include_following_stack
+ Also render the nesting CTEs on the next stack. Useful for
+ SQL structures like UNION or INSERT that can wrap SELECT
+ statements containing nesting CTEs.
+ """
+ if not self.ctes:
+ return ""
+
+ ctes: MutableMapping[CTE, str]
+
+ if nesting_level and nesting_level > 1:
+ ctes = util.OrderedDict()
+ for cte in list(self.ctes.keys()):
+ cte_level, cte_name, cte_opts = self.level_name_by_cte[
+ cte._get_reference_cte()
+ ]
+ nesting = cte.nesting or cte_opts.nesting
+ is_rendered_level = cte_level == nesting_level or (
+ include_following_stack and cte_level == nesting_level + 1
+ )
+ if not (nesting and is_rendered_level):
+ continue
+
+ ctes[cte] = self.ctes[cte]
+
+ else:
+ ctes = self.ctes
+
+ if not ctes:
+ return ""
+ ctes_recursive = any([cte.recursive for cte in ctes])
+
+ cte_text = self.get_cte_preamble(ctes_recursive) + " "
+ cte_text += ", \n".join([txt for txt in ctes.values()])
+ cte_text += "\n "
+
+ if nesting_level and nesting_level > 1:
+ for cte in list(ctes.keys()):
+ cte_level, cte_name, cte_opts = self.level_name_by_cte[
+ cte._get_reference_cte()
+ ]
+ del self.ctes[cte]
+ del self.ctes_by_level_name[(cte_level, cte_name)]
+ del self.level_name_by_cte[cte._get_reference_cte()]
+
+ return cte_text
+
+ def get_cte_preamble(self, recursive):
+ if recursive:
+ return "WITH RECURSIVE"
+ else:
+ return "WITH"
+
+ def get_select_precolumns(self, select, **kw):
+ """Called when building a ``SELECT`` statement, position is just
+ before column list.
+
+ """
+ if select._distinct_on:
+ util.warn_deprecated(
+ "DISTINCT ON is currently supported only by the PostgreSQL "
+ "dialect. Use of DISTINCT ON for other backends is currently "
+ "silently ignored, however this usage is deprecated, and will "
+ "raise CompileError in a future release for all backends "
+ "that do not support this syntax.",
+ version="1.4",
+ )
+ return "DISTINCT " if select._distinct else ""
+
+ def group_by_clause(self, select, **kw):
+ """allow dialects to customize how GROUP BY is rendered."""
+
+ group_by = self._generate_delimited_list(
+ select._group_by_clauses, OPERATORS[operators.comma_op], **kw
+ )
+ if group_by:
+ return " GROUP BY " + group_by
+ else:
+ return ""
+
+ def order_by_clause(self, select, **kw):
+ """allow dialects to customize how ORDER BY is rendered."""
+
+ order_by = self._generate_delimited_list(
+ select._order_by_clauses, OPERATORS[operators.comma_op], **kw
+ )
+
+ if order_by:
+ return " ORDER BY " + order_by
+ else:
+ return ""
+
+ def for_update_clause(self, select, **kw):
+ return " FOR UPDATE"
+
+ def returning_clause(
+ self,
+ stmt: UpdateBase,
+ returning_cols: Sequence[ColumnElement[Any]],
+ *,
+ populate_result_map: bool,
+ **kw: Any,
+ ) -> str:
+ columns = [
+ self._label_returning_column(
+ stmt,
+ column,
+ populate_result_map,
+ fallback_label_name=fallback_label_name,
+ column_is_repeated=repeated,
+ name=name,
+ proxy_name=proxy_name,
+ **kw,
+ )
+ for (
+ name,
+ proxy_name,
+ fallback_label_name,
+ column,
+ repeated,
+ ) in stmt._generate_columns_plus_names(
+ True, cols=base._select_iterables(returning_cols)
+ )
+ ]
+
+ return "RETURNING " + ", ".join(columns)
+
+ def limit_clause(self, select, **kw):
+ text = ""
+ if select._limit_clause is not None:
+ text += "\n LIMIT " + self.process(select._limit_clause, **kw)
+ if select._offset_clause is not None:
+ if select._limit_clause is None:
+ text += "\n LIMIT -1"
+ text += " OFFSET " + self.process(select._offset_clause, **kw)
+ return text
+
+ def fetch_clause(
+ self,
+ select,
+ fetch_clause=None,
+ require_offset=False,
+ use_literal_execute_for_simple_int=False,
+ **kw,
+ ):
+ if fetch_clause is None:
+ fetch_clause = select._fetch_clause
+ fetch_clause_options = select._fetch_clause_options
+ else:
+ fetch_clause_options = {"percent": False, "with_ties": False}
+
+ text = ""
+
+ if select._offset_clause is not None:
+ offset_clause = select._offset_clause
+ if (
+ use_literal_execute_for_simple_int
+ and select._simple_int_clause(offset_clause)
+ ):
+ offset_clause = offset_clause.render_literal_execute()
+ offset_str = self.process(offset_clause, **kw)
+ text += "\n OFFSET %s ROWS" % offset_str
+ elif require_offset:
+ text += "\n OFFSET 0 ROWS"
+
+ if fetch_clause is not None:
+ if (
+ use_literal_execute_for_simple_int
+ and select._simple_int_clause(fetch_clause)
+ ):
+ fetch_clause = fetch_clause.render_literal_execute()
+ text += "\n FETCH FIRST %s%s ROWS %s" % (
+ self.process(fetch_clause, **kw),
+ " PERCENT" if fetch_clause_options["percent"] else "",
+ "WITH TIES" if fetch_clause_options["with_ties"] else "ONLY",
+ )
+ return text
+
+ def visit_table(
+ self,
+ table,
+ asfrom=False,
+ iscrud=False,
+ ashint=False,
+ fromhints=None,
+ use_schema=True,
+ from_linter=None,
+ ambiguous_table_name_map=None,
+ **kwargs,
+ ):
+ if from_linter:
+ from_linter.froms[table] = table.fullname
+
+ if asfrom or ashint:
+ effective_schema = self.preparer.schema_for_object(table)
+
+ if use_schema and effective_schema:
+ ret = (
+ self.preparer.quote_schema(effective_schema)
+ + "."
+ + self.preparer.quote(table.name)
+ )
+ else:
+ ret = self.preparer.quote(table.name)
+
+ if (
+ not effective_schema
+ and ambiguous_table_name_map
+ and table.name in ambiguous_table_name_map
+ ):
+ anon_name = self._truncated_identifier(
+ "alias", ambiguous_table_name_map[table.name]
+ )
+
+ ret = ret + self.get_render_as_alias_suffix(
+ self.preparer.format_alias(None, anon_name)
+ )
+
+ if fromhints and table in fromhints:
+ ret = self.format_from_hint_text(
+ ret, table, fromhints[table], iscrud
+ )
+ return ret
+ else:
+ return ""
+
+ def visit_join(self, join, asfrom=False, from_linter=None, **kwargs):
+ if from_linter:
+ from_linter.edges.update(
+ itertools.product(
+ _de_clone(join.left._from_objects),
+ _de_clone(join.right._from_objects),
+ )
+ )
+
+ if join.full:
+ join_type = " FULL OUTER JOIN "
+ elif join.isouter:
+ join_type = " LEFT OUTER JOIN "
+ else:
+ join_type = " JOIN "
+ return (
+ join.left._compiler_dispatch(
+ self, asfrom=True, from_linter=from_linter, **kwargs
+ )
+ + join_type
+ + join.right._compiler_dispatch(
+ self, asfrom=True, from_linter=from_linter, **kwargs
+ )
+ + " ON "
+ # TODO: likely need asfrom=True here?
+ + join.onclause._compiler_dispatch(
+ self, from_linter=from_linter, **kwargs
+ )
+ )
+
+ def _setup_crud_hints(self, stmt, table_text):
+ dialect_hints = {
+ table: hint_text
+ for (table, dialect), hint_text in stmt._hints.items()
+ if dialect in ("*", self.dialect.name)
+ }
+ if stmt.table in dialect_hints:
+ table_text = self.format_from_hint_text(
+ table_text, stmt.table, dialect_hints[stmt.table], True
+ )
+ return dialect_hints, table_text
+
+ # within the realm of "insertmanyvalues sentinel columns",
+ # these lookups match different kinds of Column() configurations
+ # to specific backend capabilities. they are broken into two
+ # lookups, one for autoincrement columns and the other for non
+ # autoincrement columns
+ _sentinel_col_non_autoinc_lookup = util.immutabledict(
+ {
+ _SentinelDefaultCharacterization.CLIENTSIDE: (
+ InsertmanyvaluesSentinelOpts._SUPPORTED_OR_NOT
+ ),
+ _SentinelDefaultCharacterization.SENTINEL_DEFAULT: (
+ InsertmanyvaluesSentinelOpts._SUPPORTED_OR_NOT
+ ),
+ _SentinelDefaultCharacterization.NONE: (
+ InsertmanyvaluesSentinelOpts._SUPPORTED_OR_NOT
+ ),
+ _SentinelDefaultCharacterization.IDENTITY: (
+ InsertmanyvaluesSentinelOpts.IDENTITY
+ ),
+ _SentinelDefaultCharacterization.SEQUENCE: (
+ InsertmanyvaluesSentinelOpts.SEQUENCE
+ ),
+ }
+ )
+ _sentinel_col_autoinc_lookup = _sentinel_col_non_autoinc_lookup.union(
+ {
+ _SentinelDefaultCharacterization.NONE: (
+ InsertmanyvaluesSentinelOpts.AUTOINCREMENT
+ ),
+ }
+ )
+
+ def _get_sentinel_column_for_table(
+ self, table: Table
+ ) -> Optional[Sequence[Column[Any]]]:
+ """given a :class:`.Table`, return a usable sentinel column or
+ columns for this dialect if any.
+
+ Return None if no sentinel columns could be identified, or raise an
+ error if a column was marked as a sentinel explicitly but isn't
+ compatible with this dialect.
+
+ """
+
+ sentinel_opts = self.dialect.insertmanyvalues_implicit_sentinel
+ sentinel_characteristics = table._sentinel_column_characteristics
+
+ sent_cols = sentinel_characteristics.columns
+
+ if sent_cols is None:
+ return None
+
+ if sentinel_characteristics.is_autoinc:
+ bitmask = self._sentinel_col_autoinc_lookup.get(
+ sentinel_characteristics.default_characterization, 0
+ )
+ else:
+ bitmask = self._sentinel_col_non_autoinc_lookup.get(
+ sentinel_characteristics.default_characterization, 0
+ )
+
+ if sentinel_opts & bitmask:
+ return sent_cols
+
+ if sentinel_characteristics.is_explicit:
+ # a column was explicitly marked as insert_sentinel=True,
+ # however it is not compatible with this dialect. they should
+ # not indicate this column as a sentinel if they need to include
+ # this dialect.
+
+ # TODO: do we want non-primary key explicit sentinel cols
+ # that can gracefully degrade for some backends?
+ # insert_sentinel="degrade" perhaps. not for the initial release.
+ # I am hoping people are generally not dealing with this sentinel
+ # business at all.
+
+ # if is_explicit is True, there will be only one sentinel column.
+
+ raise exc.InvalidRequestError(
+ f"Column {sent_cols[0]} can't be explicitly "
+ "marked as a sentinel column when using the "
+ f"{self.dialect.name} dialect, as the "
+ "particular type of default generation on this column is "
+ "not currently compatible with this dialect's specific "
+ f"INSERT..RETURNING syntax which can receive the "
+ "server-generated value in "
+ "a deterministic way. To remove this error, remove "
+ "insert_sentinel=True from primary key autoincrement "
+ "columns; these columns are automatically used as "
+ "sentinels for supported dialects in any case."
+ )
+
+ return None
+
+ def _deliver_insertmanyvalues_batches(
+ self,
+ statement: str,
+ parameters: _DBAPIMultiExecuteParams,
+ compiled_parameters: List[_MutableCoreSingleExecuteParams],
+ generic_setinputsizes: Optional[_GenericSetInputSizesType],
+ batch_size: int,
+ sort_by_parameter_order: bool,
+ schema_translate_map: Optional[SchemaTranslateMapType],
+ ) -> Iterator[_InsertManyValuesBatch]:
+ imv = self._insertmanyvalues
+ assert imv is not None
+
+ if not imv.sentinel_param_keys:
+ _sentinel_from_params = None
+ else:
+ _sentinel_from_params = operator.itemgetter(
+ *imv.sentinel_param_keys
+ )
+
+ lenparams = len(parameters)
+ if imv.is_default_expr and not self.dialect.supports_default_metavalue:
+ # backend doesn't support
+ # INSERT INTO table (pk_col) VALUES (DEFAULT), (DEFAULT), ...
+ # at the moment this is basically SQL Server due to
+ # not being able to use DEFAULT for identity column
+ # just yield out that many single statements! still
+ # faster than a whole connection.execute() call ;)
+ #
+ # note we still are taking advantage of the fact that we know
+ # we are using RETURNING. The generalized approach of fetching
+ # cursor.lastrowid etc. still goes through the more heavyweight
+ # "ExecutionContext per statement" system as it isn't usable
+ # as a generic "RETURNING" approach
+ use_row_at_a_time = True
+ downgraded = False
+ elif not self.dialect.supports_multivalues_insert or (
+ sort_by_parameter_order
+ and self._result_columns
+ and (imv.sentinel_columns is None or imv.includes_upsert_behaviors)
+ ):
+ # deterministic order was requested and the compiler could
+ # not organize sentinel columns for this dialect/statement.
+ # use row at a time
+ use_row_at_a_time = True
+ downgraded = True
+ else:
+ use_row_at_a_time = False
+ downgraded = False
+
+ if use_row_at_a_time:
+ for batchnum, (param, compiled_param) in enumerate(
+ cast(
+ "Sequence[Tuple[_DBAPISingleExecuteParams, _MutableCoreSingleExecuteParams]]", # noqa: E501
+ zip(parameters, compiled_parameters),
+ ),
+ 1,
+ ):
+ yield _InsertManyValuesBatch(
+ statement,
+ param,
+ generic_setinputsizes,
+ [param],
+ (
+ [_sentinel_from_params(compiled_param)]
+ if _sentinel_from_params
+ else []
+ ),
+ 1,
+ batchnum,
+ lenparams,
+ sort_by_parameter_order,
+ downgraded,
+ )
+ return
+
+ if schema_translate_map:
+ rst = functools.partial(
+ self.preparer._render_schema_translates,
+ schema_translate_map=schema_translate_map,
+ )
+ else:
+ rst = None
+
+ imv_single_values_expr = imv.single_values_expr
+ if rst:
+ imv_single_values_expr = rst(imv_single_values_expr)
+
+ executemany_values = f"({imv_single_values_expr})"
+ statement = statement.replace(executemany_values, "__EXECMANY_TOKEN__")
+
+ # Use optional insertmanyvalues_max_parameters
+ # to further shrink the batch size so that there are no more than
+ # insertmanyvalues_max_parameters params.
+ # Currently used by SQL Server, which limits statements to 2100 bound
+ # parameters (actually 2099).
+ max_params = self.dialect.insertmanyvalues_max_parameters
+ if max_params:
+ total_num_of_params = len(self.bind_names)
+ num_params_per_batch = len(imv.insert_crud_params)
+ num_params_outside_of_batch = (
+ total_num_of_params - num_params_per_batch
+ )
+ batch_size = min(
+ batch_size,
+ (
+ (max_params - num_params_outside_of_batch)
+ // num_params_per_batch
+ ),
+ )
+
+ batches = cast("List[Sequence[Any]]", list(parameters))
+ compiled_batches = cast(
+ "List[Sequence[Any]]", list(compiled_parameters)
+ )
+
+ processed_setinputsizes: Optional[_GenericSetInputSizesType] = None
+ batchnum = 1
+ total_batches = lenparams // batch_size + (
+ 1 if lenparams % batch_size else 0
+ )
+
+ insert_crud_params = imv.insert_crud_params
+ assert insert_crud_params is not None
+
+ if rst:
+ insert_crud_params = [
+ (col, key, rst(expr), st)
+ for col, key, expr, st in insert_crud_params
+ ]
+
+ escaped_bind_names: Mapping[str, str]
+ expand_pos_lower_index = expand_pos_upper_index = 0
+
+ if not self.positional:
+ if self.escaped_bind_names:
+ escaped_bind_names = self.escaped_bind_names
+ else:
+ escaped_bind_names = {}
+
+ all_keys = set(parameters[0])
+
+ def apply_placeholders(keys, formatted):
+ for key in keys:
+ key = escaped_bind_names.get(key, key)
+ formatted = formatted.replace(
+ self.bindtemplate % {"name": key},
+ self.bindtemplate
+ % {"name": f"{key}__EXECMANY_INDEX__"},
+ )
+ return formatted
+
+ if imv.embed_values_counter:
+ imv_values_counter = ", _IMV_VALUES_COUNTER"
+ else:
+ imv_values_counter = ""
+ formatted_values_clause = f"""({', '.join(
+ apply_placeholders(bind_keys, formatted)
+ for _, _, formatted, bind_keys in insert_crud_params
+ )}{imv_values_counter})"""
+
+ keys_to_replace = all_keys.intersection(
+ escaped_bind_names.get(key, key)
+ for _, _, _, bind_keys in insert_crud_params
+ for key in bind_keys
+ )
+ base_parameters = {
+ key: parameters[0][key]
+ for key in all_keys.difference(keys_to_replace)
+ }
+ executemany_values_w_comma = ""
+ else:
+ formatted_values_clause = ""
+ keys_to_replace = set()
+ base_parameters = {}
+
+ if imv.embed_values_counter:
+ executemany_values_w_comma = (
+ f"({imv_single_values_expr}, _IMV_VALUES_COUNTER), "
+ )
+ else:
+ executemany_values_w_comma = f"({imv_single_values_expr}), "
+
+ all_names_we_will_expand: Set[str] = set()
+ for elem in imv.insert_crud_params:
+ all_names_we_will_expand.update(elem[3])
+
+ # get the start and end position in a particular list
+ # of parameters where we will be doing the "expanding".
+ # statements can have params on either side or both sides,
+ # given RETURNING and CTEs
+ if all_names_we_will_expand:
+ positiontup = self.positiontup
+ assert positiontup is not None
+
+ all_expand_positions = {
+ idx
+ for idx, name in enumerate(positiontup)
+ if name in all_names_we_will_expand
+ }
+ expand_pos_lower_index = min(all_expand_positions)
+ expand_pos_upper_index = max(all_expand_positions) + 1
+ assert (
+ len(all_expand_positions)
+ == expand_pos_upper_index - expand_pos_lower_index
+ )
+
+ if self._numeric_binds:
+ escaped = re.escape(self._numeric_binds_identifier_char)
+ executemany_values_w_comma = re.sub(
+ rf"{escaped}\d+", "%s", executemany_values_w_comma
+ )
+
+ while batches:
+ batch = batches[0:batch_size]
+ compiled_batch = compiled_batches[0:batch_size]
+
+ batches[0:batch_size] = []
+ compiled_batches[0:batch_size] = []
+
+ if batches:
+ current_batch_size = batch_size
+ else:
+ current_batch_size = len(batch)
+
+ if generic_setinputsizes:
+ # if setinputsizes is present, expand this collection to
+ # suit the batch length as well
+ # currently this will be mssql+pyodbc for internal dialects
+ processed_setinputsizes = [
+ (new_key, len_, typ)
+ for new_key, len_, typ in (
+ (f"{key}_{index}", len_, typ)
+ for index in range(current_batch_size)
+ for key, len_, typ in generic_setinputsizes
+ )
+ ]
+
+ replaced_parameters: Any
+ if self.positional:
+ num_ins_params = imv.num_positional_params_counted
+
+ batch_iterator: Iterable[Sequence[Any]]
+ extra_params_left: Sequence[Any]
+ extra_params_right: Sequence[Any]
+
+ if num_ins_params == len(batch[0]):
+ extra_params_left = extra_params_right = ()
+ batch_iterator = batch
+ else:
+ extra_params_left = batch[0][:expand_pos_lower_index]
+ extra_params_right = batch[0][expand_pos_upper_index:]
+ batch_iterator = (
+ b[expand_pos_lower_index:expand_pos_upper_index]
+ for b in batch
+ )
+
+ if imv.embed_values_counter:
+ expanded_values_string = (
+ "".join(
+ executemany_values_w_comma.replace(
+ "_IMV_VALUES_COUNTER", str(i)
+ )
+ for i, _ in enumerate(batch)
+ )
+ )[:-2]
+ else:
+ expanded_values_string = (
+ (executemany_values_w_comma * current_batch_size)
+ )[:-2]
+
+ if self._numeric_binds and num_ins_params > 0:
+ # numeric will always number the parameters inside of
+ # VALUES (and thus order self.positiontup) to be higher
+ # than non-VALUES parameters, no matter where in the
+ # statement those non-VALUES parameters appear (this is
+ # ensured in _process_numeric by numbering first all
+ # params that are not in _values_bindparam)
+ # therefore all extra params are always
+ # on the left side and numbered lower than the VALUES
+ # parameters
+ assert not extra_params_right
+
+ start = expand_pos_lower_index + 1
+ end = num_ins_params * (current_batch_size) + start
+
+ # need to format here, since statement may contain
+ # unescaped %, while values_string contains just (%s, %s)
+ positions = tuple(
+ f"{self._numeric_binds_identifier_char}{i}"
+ for i in range(start, end)
+ )
+ expanded_values_string = expanded_values_string % positions
+
+ replaced_statement = statement.replace(
+ "__EXECMANY_TOKEN__", expanded_values_string
+ )
+
+ replaced_parameters = tuple(
+ itertools.chain.from_iterable(batch_iterator)
+ )
+
+ replaced_parameters = (
+ extra_params_left
+ + replaced_parameters
+ + extra_params_right
+ )
+
+ else:
+ replaced_values_clauses = []
+ replaced_parameters = base_parameters.copy()
+
+ for i, param in enumerate(batch):
+ fmv = formatted_values_clause.replace(
+ "EXECMANY_INDEX__", str(i)
+ )
+ if imv.embed_values_counter:
+ fmv = fmv.replace("_IMV_VALUES_COUNTER", str(i))
+
+ replaced_values_clauses.append(fmv)
+ replaced_parameters.update(
+ {f"{key}__{i}": param[key] for key in keys_to_replace}
+ )
+
+ replaced_statement = statement.replace(
+ "__EXECMANY_TOKEN__",
+ ", ".join(replaced_values_clauses),
+ )
+
+ yield _InsertManyValuesBatch(
+ replaced_statement,
+ replaced_parameters,
+ processed_setinputsizes,
+ batch,
+ (
+ [_sentinel_from_params(cb) for cb in compiled_batch]
+ if _sentinel_from_params
+ else []
+ ),
+ current_batch_size,
+ batchnum,
+ total_batches,
+ sort_by_parameter_order,
+ False,
+ )
+ batchnum += 1
+
+ def visit_insert(
+ self, insert_stmt, visited_bindparam=None, visiting_cte=None, **kw
+ ):
+ compile_state = insert_stmt._compile_state_factory(
+ insert_stmt, self, **kw
+ )
+ insert_stmt = compile_state.statement
+
+ if visiting_cte is not None:
+ kw["visiting_cte"] = visiting_cte
+ toplevel = False
+ else:
+ toplevel = not self.stack
+
+ if toplevel:
+ self.isinsert = True
+ if not self.dml_compile_state:
+ self.dml_compile_state = compile_state
+ if not self.compile_state:
+ self.compile_state = compile_state
+
+ self.stack.append(
+ {
+ "correlate_froms": set(),
+ "asfrom_froms": set(),
+ "selectable": insert_stmt,
+ }
+ )
+
+ counted_bindparam = 0
+
+ # reset any incoming "visited_bindparam" collection
+ visited_bindparam = None
+
+ # for positional, insertmanyvalues needs to know how many
+ # bound parameters are in the VALUES sequence; there's no simple
+ # rule because default expressions etc. can have zero or more
+ # params inside them. After multiple attempts to figure this out,
+ # this very simplistic "count after" works and is
+ # likely the least amount of callcounts, though looks clumsy
+ if self.positional and visiting_cte is None:
+ # if we are inside a CTE, don't count parameters
+ # here since they wont be for insertmanyvalues. keep
+ # visited_bindparam at None so no counting happens.
+ # see #9173
+ visited_bindparam = []
+
+ crud_params_struct = crud._get_crud_params(
+ self,
+ insert_stmt,
+ compile_state,
+ toplevel,
+ visited_bindparam=visited_bindparam,
+ **kw,
+ )
+
+ if self.positional and visited_bindparam is not None:
+ counted_bindparam = len(visited_bindparam)
+ if self._numeric_binds:
+ if self._values_bindparam is not None:
+ self._values_bindparam += visited_bindparam
+ else:
+ self._values_bindparam = visited_bindparam
+
+ crud_params_single = crud_params_struct.single_params
+
+ if (
+ not crud_params_single
+ and not self.dialect.supports_default_values
+ and not self.dialect.supports_default_metavalue
+ and not self.dialect.supports_empty_insert
+ ):
+ raise exc.CompileError(
+ "The '%s' dialect with current database "
+ "version settings does not support empty "
+ "inserts." % self.dialect.name
+ )
+
+ if compile_state._has_multi_parameters:
+ if not self.dialect.supports_multivalues_insert:
+ raise exc.CompileError(
+ "The '%s' dialect with current database "
+ "version settings does not support "
+ "in-place multirow inserts." % self.dialect.name
+ )
+ elif (
+ self.implicit_returning or insert_stmt._returning
+ ) and insert_stmt._sort_by_parameter_order:
+ raise exc.CompileError(
+ "RETURNING cannot be determinstically sorted when "
+ "using an INSERT which includes multi-row values()."
+ )
+ crud_params_single = crud_params_struct.single_params
+ else:
+ crud_params_single = crud_params_struct.single_params
+
+ preparer = self.preparer
+ supports_default_values = self.dialect.supports_default_values
+
+ text = "INSERT "
+
+ if insert_stmt._prefixes:
+ text += self._generate_prefixes(
+ insert_stmt, insert_stmt._prefixes, **kw
+ )
+
+ text += "INTO "
+ table_text = preparer.format_table(insert_stmt.table)
+
+ if insert_stmt._hints:
+ _, table_text = self._setup_crud_hints(insert_stmt, table_text)
+
+ if insert_stmt._independent_ctes:
+ self._dispatch_independent_ctes(insert_stmt, kw)
+
+ text += table_text
+
+ if crud_params_single or not supports_default_values:
+ text += " (%s)" % ", ".join(
+ [expr for _, expr, _, _ in crud_params_single]
+ )
+
+ # look for insertmanyvalues attributes that would have been configured
+ # by crud.py as it scanned through the columns to be part of the
+ # INSERT
+ use_insertmanyvalues = crud_params_struct.use_insertmanyvalues
+ named_sentinel_params: Optional[Sequence[str]] = None
+ add_sentinel_cols = None
+ implicit_sentinel = False
+
+ returning_cols = self.implicit_returning or insert_stmt._returning
+ if returning_cols:
+ add_sentinel_cols = crud_params_struct.use_sentinel_columns
+ if add_sentinel_cols is not None:
+ assert use_insertmanyvalues
+
+ # search for the sentinel column explicitly present
+ # in the INSERT columns list, and additionally check that
+ # this column has a bound parameter name set up that's in the
+ # parameter list. If both of these cases are present, it means
+ # we will have a client side value for the sentinel in each
+ # parameter set.
+
+ _params_by_col = {
+ col: param_names
+ for col, _, _, param_names in crud_params_single
+ }
+ named_sentinel_params = []
+ for _add_sentinel_col in add_sentinel_cols:
+ if _add_sentinel_col not in _params_by_col:
+ named_sentinel_params = None
+ break
+ param_name = self._within_exec_param_key_getter(
+ _add_sentinel_col
+ )
+ if param_name not in _params_by_col[_add_sentinel_col]:
+ named_sentinel_params = None
+ break
+ named_sentinel_params.append(param_name)
+
+ if named_sentinel_params is None:
+ # if we are not going to have a client side value for
+ # the sentinel in the parameter set, that means it's
+ # an autoincrement, an IDENTITY, or a server-side SQL
+ # expression like nextval('seqname'). So this is
+ # an "implicit" sentinel; we will look for it in
+ # RETURNING
+ # only, and then sort on it. For this case on PG,
+ # SQL Server we have to use a special INSERT form
+ # that guarantees the server side function lines up with
+ # the entries in the VALUES.
+ if (
+ self.dialect.insertmanyvalues_implicit_sentinel
+ & InsertmanyvaluesSentinelOpts.ANY_AUTOINCREMENT
+ ):
+ implicit_sentinel = True
+ else:
+ # here, we are not using a sentinel at all
+ # and we are likely the SQLite dialect.
+ # The first add_sentinel_col that we have should not
+ # be marked as "insert_sentinel=True". if it was,
+ # an error should have been raised in
+ # _get_sentinel_column_for_table.
+ assert not add_sentinel_cols[0]._insert_sentinel, (
+ "sentinel selection rules should have prevented "
+ "us from getting here for this dialect"
+ )
+
+ # always put the sentinel columns last. even if they are
+ # in the returning list already, they will be there twice
+ # then.
+ returning_cols = list(returning_cols) + list(add_sentinel_cols)
+
+ returning_clause = self.returning_clause(
+ insert_stmt,
+ returning_cols,
+ populate_result_map=toplevel,
+ )
+
+ if self.returning_precedes_values:
+ text += " " + returning_clause
+
+ else:
+ returning_clause = None
+
+ if insert_stmt.select is not None:
+ # placed here by crud.py
+ select_text = self.process(
+ self.stack[-1]["insert_from_select"], insert_into=True, **kw
+ )
+
+ if self.ctes and self.dialect.cte_follows_insert:
+ nesting_level = len(self.stack) if not toplevel else None
+ text += " %s%s" % (
+ self._render_cte_clause(
+ nesting_level=nesting_level,
+ include_following_stack=True,
+ ),
+ select_text,
+ )
+ else:
+ text += " %s" % select_text
+ elif not crud_params_single and supports_default_values:
+ text += " DEFAULT VALUES"
+ if use_insertmanyvalues:
+ self._insertmanyvalues = _InsertManyValues(
+ True,
+ self.dialect.default_metavalue_token,
+ cast(
+ "List[crud._CrudParamElementStr]", crud_params_single
+ ),
+ counted_bindparam,
+ sort_by_parameter_order=(
+ insert_stmt._sort_by_parameter_order
+ ),
+ includes_upsert_behaviors=(
+ insert_stmt._post_values_clause is not None
+ ),
+ sentinel_columns=add_sentinel_cols,
+ num_sentinel_columns=(
+ len(add_sentinel_cols) if add_sentinel_cols else 0
+ ),
+ implicit_sentinel=implicit_sentinel,
+ )
+ elif compile_state._has_multi_parameters:
+ text += " VALUES %s" % (
+ ", ".join(
+ "(%s)"
+ % (", ".join(value for _, _, value, _ in crud_param_set))
+ for crud_param_set in crud_params_struct.all_multi_params
+ ),
+ )
+ else:
+ insert_single_values_expr = ", ".join(
+ [
+ value
+ for _, _, value, _ in cast(
+ "List[crud._CrudParamElementStr]",
+ crud_params_single,
+ )
+ ]
+ )
+
+ if use_insertmanyvalues:
+ if (
+ implicit_sentinel
+ and (
+ self.dialect.insertmanyvalues_implicit_sentinel
+ & InsertmanyvaluesSentinelOpts.USE_INSERT_FROM_SELECT
+ )
+ # this is checking if we have
+ # INSERT INTO table (id) VALUES (DEFAULT).
+ and not (crud_params_struct.is_default_metavalue_only)
+ ):
+ # if we have a sentinel column that is server generated,
+ # then for selected backends render the VALUES list as a
+ # subquery. This is the orderable form supported by
+ # PostgreSQL and SQL Server.
+ embed_sentinel_value = True
+
+ render_bind_casts = (
+ self.dialect.insertmanyvalues_implicit_sentinel
+ & InsertmanyvaluesSentinelOpts.RENDER_SELECT_COL_CASTS
+ )
+
+ colnames = ", ".join(
+ f"p{i}" for i, _ in enumerate(crud_params_single)
+ )
+
+ if render_bind_casts:
+ # render casts for the SELECT list. For PG, we are
+ # already rendering bind casts in the parameter list,
+ # selectively for the more "tricky" types like ARRAY.
+ # however, even for the "easy" types, if the parameter
+ # is NULL for every entry, PG gives up and says
+ # "it must be TEXT", which fails for other easy types
+ # like ints. So we cast on this side too.
+ colnames_w_cast = ", ".join(
+ self.render_bind_cast(
+ col.type,
+ col.type._unwrapped_dialect_impl(self.dialect),
+ f"p{i}",
+ )
+ for i, (col, *_) in enumerate(crud_params_single)
+ )
+ else:
+ colnames_w_cast = colnames
+
+ text += (
+ f" SELECT {colnames_w_cast} FROM "
+ f"(VALUES ({insert_single_values_expr})) "
+ f"AS imp_sen({colnames}, sen_counter) "
+ "ORDER BY sen_counter"
+ )
+ else:
+ # otherwise, if no sentinel or backend doesn't support
+ # orderable subquery form, use a plain VALUES list
+ embed_sentinel_value = False
+ text += f" VALUES ({insert_single_values_expr})"
+
+ self._insertmanyvalues = _InsertManyValues(
+ is_default_expr=False,
+ single_values_expr=insert_single_values_expr,
+ insert_crud_params=cast(
+ "List[crud._CrudParamElementStr]",
+ crud_params_single,
+ ),
+ num_positional_params_counted=counted_bindparam,
+ sort_by_parameter_order=(
+ insert_stmt._sort_by_parameter_order
+ ),
+ includes_upsert_behaviors=(
+ insert_stmt._post_values_clause is not None
+ ),
+ sentinel_columns=add_sentinel_cols,
+ num_sentinel_columns=(
+ len(add_sentinel_cols) if add_sentinel_cols else 0
+ ),
+ sentinel_param_keys=named_sentinel_params,
+ implicit_sentinel=implicit_sentinel,
+ embed_values_counter=embed_sentinel_value,
+ )
+
+ else:
+ text += f" VALUES ({insert_single_values_expr})"
+
+ if insert_stmt._post_values_clause is not None:
+ post_values_clause = self.process(
+ insert_stmt._post_values_clause, **kw
+ )
+ if post_values_clause:
+ text += " " + post_values_clause
+
+ if returning_clause and not self.returning_precedes_values:
+ text += " " + returning_clause
+
+ if self.ctes and not self.dialect.cte_follows_insert:
+ nesting_level = len(self.stack) if not toplevel else None
+ text = (
+ self._render_cte_clause(
+ nesting_level=nesting_level,
+ include_following_stack=True,
+ )
+ + text
+ )
+
+ self.stack.pop(-1)
+
+ return text
+
+ def update_limit_clause(self, update_stmt):
+ """Provide a hook for MySQL to add LIMIT to the UPDATE"""
+ return None
+
+ def update_tables_clause(self, update_stmt, from_table, extra_froms, **kw):
+ """Provide a hook to override the initial table clause
+ in an UPDATE statement.
+
+ MySQL overrides this.
+
+ """
+ kw["asfrom"] = True
+ return from_table._compiler_dispatch(self, iscrud=True, **kw)
+
+ def update_from_clause(
+ self, update_stmt, from_table, extra_froms, from_hints, **kw
+ ):
+ """Provide a hook to override the generation of an
+ UPDATE..FROM clause.
+
+ MySQL and MSSQL override this.
+
+ """
+ raise NotImplementedError(
+ "This backend does not support multiple-table "
+ "criteria within UPDATE"
+ )
+
+ def visit_update(self, update_stmt, visiting_cte=None, **kw):
+ compile_state = update_stmt._compile_state_factory(
+ update_stmt, self, **kw
+ )
+ update_stmt = compile_state.statement
+
+ if visiting_cte is not None:
+ kw["visiting_cte"] = visiting_cte
+ toplevel = False
+ else:
+ toplevel = not self.stack
+
+ if toplevel:
+ self.isupdate = True
+ if not self.dml_compile_state:
+ self.dml_compile_state = compile_state
+ if not self.compile_state:
+ self.compile_state = compile_state
+
+ if self.linting & COLLECT_CARTESIAN_PRODUCTS:
+ from_linter = FromLinter({}, set())
+ warn_linting = self.linting & WARN_LINTING
+ if toplevel:
+ self.from_linter = from_linter
+ else:
+ from_linter = None
+ warn_linting = False
+
+ extra_froms = compile_state._extra_froms
+ is_multitable = bool(extra_froms)
+
+ if is_multitable:
+ # main table might be a JOIN
+ main_froms = set(_from_objects(update_stmt.table))
+ render_extra_froms = [
+ f for f in extra_froms if f not in main_froms
+ ]
+ correlate_froms = main_froms.union(extra_froms)
+ else:
+ render_extra_froms = []
+ correlate_froms = {update_stmt.table}
+
+ self.stack.append(
+ {
+ "correlate_froms": correlate_froms,
+ "asfrom_froms": correlate_froms,
+ "selectable": update_stmt,
+ }
+ )
+
+ text = "UPDATE "
+
+ if update_stmt._prefixes:
+ text += self._generate_prefixes(
+ update_stmt, update_stmt._prefixes, **kw
+ )
+
+ table_text = self.update_tables_clause(
+ update_stmt,
+ update_stmt.table,
+ render_extra_froms,
+ from_linter=from_linter,
+ **kw,
+ )
+ crud_params_struct = crud._get_crud_params(
+ self, update_stmt, compile_state, toplevel, **kw
+ )
+ crud_params = crud_params_struct.single_params
+
+ if update_stmt._hints:
+ dialect_hints, table_text = self._setup_crud_hints(
+ update_stmt, table_text
+ )
+ else:
+ dialect_hints = None
+
+ if update_stmt._independent_ctes:
+ self._dispatch_independent_ctes(update_stmt, kw)
+
+ text += table_text
+
+ text += " SET "
+ text += ", ".join(
+ expr + "=" + value
+ for _, expr, value, _ in cast(
+ "List[Tuple[Any, str, str, Any]]", crud_params
+ )
+ )
+
+ if self.implicit_returning or update_stmt._returning:
+ if self.returning_precedes_values:
+ text += " " + self.returning_clause(
+ update_stmt,
+ self.implicit_returning or update_stmt._returning,
+ populate_result_map=toplevel,
+ )
+
+ if extra_froms:
+ extra_from_text = self.update_from_clause(
+ update_stmt,
+ update_stmt.table,
+ render_extra_froms,
+ dialect_hints,
+ from_linter=from_linter,
+ **kw,
+ )
+ if extra_from_text:
+ text += " " + extra_from_text
+
+ if update_stmt._where_criteria:
+ t = self._generate_delimited_and_list(
+ update_stmt._where_criteria, from_linter=from_linter, **kw
+ )
+ if t:
+ text += " WHERE " + t
+
+ limit_clause = self.update_limit_clause(update_stmt)
+ if limit_clause:
+ text += " " + limit_clause
+
+ if (
+ self.implicit_returning or update_stmt._returning
+ ) and not self.returning_precedes_values:
+ text += " " + self.returning_clause(
+ update_stmt,
+ self.implicit_returning or update_stmt._returning,
+ populate_result_map=toplevel,
+ )
+
+ if self.ctes:
+ nesting_level = len(self.stack) if not toplevel else None
+ text = self._render_cte_clause(nesting_level=nesting_level) + text
+
+ if warn_linting:
+ assert from_linter is not None
+ from_linter.warn(stmt_type="UPDATE")
+
+ self.stack.pop(-1)
+
+ return text
+
+ def delete_extra_from_clause(
+ self, update_stmt, from_table, extra_froms, from_hints, **kw
+ ):
+ """Provide a hook to override the generation of an
+ DELETE..FROM clause.
+
+ This can be used to implement DELETE..USING for example.
+
+ MySQL and MSSQL override this.
+
+ """
+ raise NotImplementedError(
+ "This backend does not support multiple-table "
+ "criteria within DELETE"
+ )
+
+ def delete_table_clause(self, delete_stmt, from_table, extra_froms, **kw):
+ return from_table._compiler_dispatch(
+ self, asfrom=True, iscrud=True, **kw
+ )
+
+ def visit_delete(self, delete_stmt, visiting_cte=None, **kw):
+ compile_state = delete_stmt._compile_state_factory(
+ delete_stmt, self, **kw
+ )
+ delete_stmt = compile_state.statement
+
+ if visiting_cte is not None:
+ kw["visiting_cte"] = visiting_cte
+ toplevel = False
+ else:
+ toplevel = not self.stack
+
+ if toplevel:
+ self.isdelete = True
+ if not self.dml_compile_state:
+ self.dml_compile_state = compile_state
+ if not self.compile_state:
+ self.compile_state = compile_state
+
+ if self.linting & COLLECT_CARTESIAN_PRODUCTS:
+ from_linter = FromLinter({}, set())
+ warn_linting = self.linting & WARN_LINTING
+ if toplevel:
+ self.from_linter = from_linter
+ else:
+ from_linter = None
+ warn_linting = False
+
+ extra_froms = compile_state._extra_froms
+
+ correlate_froms = {delete_stmt.table}.union(extra_froms)
+ self.stack.append(
+ {
+ "correlate_froms": correlate_froms,
+ "asfrom_froms": correlate_froms,
+ "selectable": delete_stmt,
+ }
+ )
+
+ text = "DELETE "
+
+ if delete_stmt._prefixes:
+ text += self._generate_prefixes(
+ delete_stmt, delete_stmt._prefixes, **kw
+ )
+
+ text += "FROM "
+
+ try:
+ table_text = self.delete_table_clause(
+ delete_stmt,
+ delete_stmt.table,
+ extra_froms,
+ from_linter=from_linter,
+ )
+ except TypeError:
+ # anticipate 3rd party dialects that don't include **kw
+ # TODO: remove in 2.1
+ table_text = self.delete_table_clause(
+ delete_stmt, delete_stmt.table, extra_froms
+ )
+ if from_linter:
+ _ = self.process(delete_stmt.table, from_linter=from_linter)
+
+ crud._get_crud_params(self, delete_stmt, compile_state, toplevel, **kw)
+
+ if delete_stmt._hints:
+ dialect_hints, table_text = self._setup_crud_hints(
+ delete_stmt, table_text
+ )
+ else:
+ dialect_hints = None
+
+ if delete_stmt._independent_ctes:
+ self._dispatch_independent_ctes(delete_stmt, kw)
+
+ text += table_text
+
+ if (
+ self.implicit_returning or delete_stmt._returning
+ ) and self.returning_precedes_values:
+ text += " " + self.returning_clause(
+ delete_stmt,
+ self.implicit_returning or delete_stmt._returning,
+ populate_result_map=toplevel,
+ )
+
+ if extra_froms:
+ extra_from_text = self.delete_extra_from_clause(
+ delete_stmt,
+ delete_stmt.table,
+ extra_froms,
+ dialect_hints,
+ from_linter=from_linter,
+ **kw,
+ )
+ if extra_from_text:
+ text += " " + extra_from_text
+
+ if delete_stmt._where_criteria:
+ t = self._generate_delimited_and_list(
+ delete_stmt._where_criteria, from_linter=from_linter, **kw
+ )
+ if t:
+ text += " WHERE " + t
+
+ if (
+ self.implicit_returning or delete_stmt._returning
+ ) and not self.returning_precedes_values:
+ text += " " + self.returning_clause(
+ delete_stmt,
+ self.implicit_returning or delete_stmt._returning,
+ populate_result_map=toplevel,
+ )
+
+ if self.ctes:
+ nesting_level = len(self.stack) if not toplevel else None
+ text = self._render_cte_clause(nesting_level=nesting_level) + text
+
+ if warn_linting:
+ assert from_linter is not None
+ from_linter.warn(stmt_type="DELETE")
+
+ self.stack.pop(-1)
+
+ return text
+
+ def visit_savepoint(self, savepoint_stmt, **kw):
+ return "SAVEPOINT %s" % self.preparer.format_savepoint(savepoint_stmt)
+
+ def visit_rollback_to_savepoint(self, savepoint_stmt, **kw):
+ return "ROLLBACK TO SAVEPOINT %s" % self.preparer.format_savepoint(
+ savepoint_stmt
+ )
+
+ def visit_release_savepoint(self, savepoint_stmt, **kw):
+ return "RELEASE SAVEPOINT %s" % self.preparer.format_savepoint(
+ savepoint_stmt
+ )
+
+
+class StrSQLCompiler(SQLCompiler):
+ """A :class:`.SQLCompiler` subclass which allows a small selection
+ of non-standard SQL features to render into a string value.
+
+ The :class:`.StrSQLCompiler` is invoked whenever a Core expression
+ element is directly stringified without calling upon the
+ :meth:`_expression.ClauseElement.compile` method.
+ It can render a limited set
+ of non-standard SQL constructs to assist in basic stringification,
+ however for more substantial custom or dialect-specific SQL constructs,
+ it will be necessary to make use of
+ :meth:`_expression.ClauseElement.compile`
+ directly.
+
+ .. seealso::
+
+ :ref:`faq_sql_expression_string`
+
+ """
+
+ def _fallback_column_name(self, column):
+ return "<name unknown>"
+
+ @util.preload_module("sqlalchemy.engine.url")
+ def visit_unsupported_compilation(self, element, err, **kw):
+ if element.stringify_dialect != "default":
+ url = util.preloaded.engine_url
+ dialect = url.URL.create(element.stringify_dialect).get_dialect()()
+
+ compiler = dialect.statement_compiler(
+ dialect, None, _supporting_against=self
+ )
+ if not isinstance(compiler, StrSQLCompiler):
+ return compiler.process(element, **kw)
+
+ return super().visit_unsupported_compilation(element, err)
+
+ def visit_getitem_binary(self, binary, operator, **kw):
+ return "%s[%s]" % (
+ self.process(binary.left, **kw),
+ self.process(binary.right, **kw),
+ )
+
+ def visit_json_getitem_op_binary(self, binary, operator, **kw):
+ return self.visit_getitem_binary(binary, operator, **kw)
+
+ def visit_json_path_getitem_op_binary(self, binary, operator, **kw):
+ return self.visit_getitem_binary(binary, operator, **kw)
+
+ def visit_sequence(self, seq, **kw):
+ return "<next sequence value: %s>" % self.preparer.format_sequence(seq)
+
+ def returning_clause(
+ self,
+ stmt: UpdateBase,
+ returning_cols: Sequence[ColumnElement[Any]],
+ *,
+ populate_result_map: bool,
+ **kw: Any,
+ ) -> str:
+ columns = [
+ self._label_select_column(None, c, True, False, {})
+ for c in base._select_iterables(returning_cols)
+ ]
+ return "RETURNING " + ", ".join(columns)
+
+ def update_from_clause(
+ self, update_stmt, from_table, extra_froms, from_hints, **kw
+ ):
+ kw["asfrom"] = True
+ return "FROM " + ", ".join(
+ t._compiler_dispatch(self, fromhints=from_hints, **kw)
+ for t in extra_froms
+ )
+
+ def delete_extra_from_clause(
+ self, update_stmt, from_table, extra_froms, from_hints, **kw
+ ):
+ kw["asfrom"] = True
+ return ", " + ", ".join(
+ t._compiler_dispatch(self, fromhints=from_hints, **kw)
+ for t in extra_froms
+ )
+
+ def visit_empty_set_expr(self, type_, **kw):
+ return "SELECT 1 WHERE 1!=1"
+
+ def get_from_hint_text(self, table, text):
+ return "[%s]" % text
+
+ def visit_regexp_match_op_binary(self, binary, operator, **kw):
+ return self._generate_generic_binary(binary, " <regexp> ", **kw)
+
+ def visit_not_regexp_match_op_binary(self, binary, operator, **kw):
+ return self._generate_generic_binary(binary, " <not regexp> ", **kw)
+
+ def visit_regexp_replace_op_binary(self, binary, operator, **kw):
+ return "<regexp replace>(%s, %s)" % (
+ binary.left._compiler_dispatch(self, **kw),
+ binary.right._compiler_dispatch(self, **kw),
+ )
+
+ def visit_try_cast(self, cast, **kwargs):
+ return "TRY_CAST(%s AS %s)" % (
+ cast.clause._compiler_dispatch(self, **kwargs),
+ cast.typeclause._compiler_dispatch(self, **kwargs),
+ )
+
+
+class DDLCompiler(Compiled):
+ is_ddl = True
+
+ if TYPE_CHECKING:
+
+ def __init__(
+ self,
+ dialect: Dialect,
+ statement: ExecutableDDLElement,
+ schema_translate_map: Optional[SchemaTranslateMapType] = ...,
+ render_schema_translate: bool = ...,
+ compile_kwargs: Mapping[str, Any] = ...,
+ ): ...
+
+ @util.memoized_property
+ def sql_compiler(self):
+ return self.dialect.statement_compiler(
+ self.dialect, None, schema_translate_map=self.schema_translate_map
+ )
+
+ @util.memoized_property
+ def type_compiler(self):
+ return self.dialect.type_compiler_instance
+
+ def construct_params(
+ self,
+ params: Optional[_CoreSingleExecuteParams] = None,
+ extracted_parameters: Optional[Sequence[BindParameter[Any]]] = None,
+ escape_names: bool = True,
+ ) -> Optional[_MutableCoreSingleExecuteParams]:
+ return None
+
+ def visit_ddl(self, ddl, **kwargs):
+ # table events can substitute table and schema name
+ context = ddl.context
+ if isinstance(ddl.target, schema.Table):
+ context = context.copy()
+
+ preparer = self.preparer
+ path = preparer.format_table_seq(ddl.target)
+ if len(path) == 1:
+ table, sch = path[0], ""
+ else:
+ table, sch = path[-1], path[0]
+
+ context.setdefault("table", table)
+ context.setdefault("schema", sch)
+ context.setdefault("fullname", preparer.format_table(ddl.target))
+
+ return self.sql_compiler.post_process_text(ddl.statement % context)
+
+ def visit_create_schema(self, create, **kw):
+ text = "CREATE SCHEMA "
+ if create.if_not_exists:
+ text += "IF NOT EXISTS "
+ return text + self.preparer.format_schema(create.element)
+
+ def visit_drop_schema(self, drop, **kw):
+ text = "DROP SCHEMA "
+ if drop.if_exists:
+ text += "IF EXISTS "
+ text += self.preparer.format_schema(drop.element)
+ if drop.cascade:
+ text += " CASCADE"
+ return text
+
+ def visit_create_table(self, create, **kw):
+ table = create.element
+ preparer = self.preparer
+
+ text = "\nCREATE "
+ if table._prefixes:
+ text += " ".join(table._prefixes) + " "
+
+ text += "TABLE "
+ if create.if_not_exists:
+ text += "IF NOT EXISTS "
+
+ text += preparer.format_table(table) + " "
+
+ create_table_suffix = self.create_table_suffix(table)
+ if create_table_suffix:
+ text += create_table_suffix + " "
+
+ text += "("
+
+ separator = "\n"
+
+ # if only one primary key, specify it along with the column
+ first_pk = False
+ for create_column in create.columns:
+ column = create_column.element
+ try:
+ processed = self.process(
+ create_column, first_pk=column.primary_key and not first_pk
+ )
+ if processed is not None:
+ text += separator
+ separator = ", \n"
+ text += "\t" + processed
+ if column.primary_key:
+ first_pk = True
+ except exc.CompileError as ce:
+ raise exc.CompileError(
+ "(in table '%s', column '%s'): %s"
+ % (table.description, column.name, ce.args[0])
+ ) from ce
+
+ const = self.create_table_constraints(
+ table,
+ _include_foreign_key_constraints=create.include_foreign_key_constraints, # noqa
+ )
+ if const:
+ text += separator + "\t" + const
+
+ text += "\n)%s\n\n" % self.post_create_table(table)
+ return text
+
+ def visit_create_column(self, create, first_pk=False, **kw):
+ column = create.element
+
+ if column.system:
+ return None
+
+ text = self.get_column_specification(column, first_pk=first_pk)
+ const = " ".join(
+ self.process(constraint) for constraint in column.constraints
+ )
+ if const:
+ text += " " + const
+
+ return text
+
+ def create_table_constraints(
+ self, table, _include_foreign_key_constraints=None, **kw
+ ):
+ # On some DB order is significant: visit PK first, then the
+ # other constraints (engine.ReflectionTest.testbasic failed on FB2)
+ constraints = []
+ if table.primary_key:
+ constraints.append(table.primary_key)
+
+ all_fkcs = table.foreign_key_constraints
+ if _include_foreign_key_constraints is not None:
+ omit_fkcs = all_fkcs.difference(_include_foreign_key_constraints)
+ else:
+ omit_fkcs = set()
+
+ constraints.extend(
+ [
+ c
+ for c in table._sorted_constraints
+ if c is not table.primary_key and c not in omit_fkcs
+ ]
+ )
+
+ return ", \n\t".join(
+ p
+ for p in (
+ self.process(constraint)
+ for constraint in constraints
+ if (constraint._should_create_for_compiler(self))
+ and (
+ not self.dialect.supports_alter
+ or not getattr(constraint, "use_alter", False)
+ )
+ )
+ if p is not None
+ )
+
+ def visit_drop_table(self, drop, **kw):
+ text = "\nDROP TABLE "
+ if drop.if_exists:
+ text += "IF EXISTS "
+ return text + self.preparer.format_table(drop.element)
+
+ def visit_drop_view(self, drop, **kw):
+ return "\nDROP VIEW " + self.preparer.format_table(drop.element)
+
+ def _verify_index_table(self, index):
+ if index.table is None:
+ raise exc.CompileError(
+ "Index '%s' is not associated with any table." % index.name
+ )
+
+ def visit_create_index(
+ self, create, include_schema=False, include_table_schema=True, **kw
+ ):
+ index = create.element
+ self._verify_index_table(index)
+ preparer = self.preparer
+ text = "CREATE "
+ if index.unique:
+ text += "UNIQUE "
+ if index.name is None:
+ raise exc.CompileError(
+ "CREATE INDEX requires that the index have a name"
+ )
+
+ text += "INDEX "
+ if create.if_not_exists:
+ text += "IF NOT EXISTS "
+
+ text += "%s ON %s (%s)" % (
+ self._prepared_index_name(index, include_schema=include_schema),
+ preparer.format_table(
+ index.table, use_schema=include_table_schema
+ ),
+ ", ".join(
+ self.sql_compiler.process(
+ expr, include_table=False, literal_binds=True
+ )
+ for expr in index.expressions
+ ),
+ )
+ return text
+
+ def visit_drop_index(self, drop, **kw):
+ index = drop.element
+
+ if index.name is None:
+ raise exc.CompileError(
+ "DROP INDEX requires that the index have a name"
+ )
+ text = "\nDROP INDEX "
+ if drop.if_exists:
+ text += "IF EXISTS "
+
+ return text + self._prepared_index_name(index, include_schema=True)
+
+ def _prepared_index_name(self, index, include_schema=False):
+ if index.table is not None:
+ effective_schema = self.preparer.schema_for_object(index.table)
+ else:
+ effective_schema = None
+ if include_schema and effective_schema:
+ schema_name = self.preparer.quote_schema(effective_schema)
+ else:
+ schema_name = None
+
+ index_name = self.preparer.format_index(index)
+
+ if schema_name:
+ index_name = schema_name + "." + index_name
+ return index_name
+
+ def visit_add_constraint(self, create, **kw):
+ return "ALTER TABLE %s ADD %s" % (
+ self.preparer.format_table(create.element.table),
+ self.process(create.element),
+ )
+
+ def visit_set_table_comment(self, create, **kw):
+ return "COMMENT ON TABLE %s IS %s" % (
+ self.preparer.format_table(create.element),
+ self.sql_compiler.render_literal_value(
+ create.element.comment, sqltypes.String()
+ ),
+ )
+
+ def visit_drop_table_comment(self, drop, **kw):
+ return "COMMENT ON TABLE %s IS NULL" % self.preparer.format_table(
+ drop.element
+ )
+
+ def visit_set_column_comment(self, create, **kw):
+ return "COMMENT ON COLUMN %s IS %s" % (
+ self.preparer.format_column(
+ create.element, use_table=True, use_schema=True
+ ),
+ self.sql_compiler.render_literal_value(
+ create.element.comment, sqltypes.String()
+ ),
+ )
+
+ def visit_drop_column_comment(self, drop, **kw):
+ return "COMMENT ON COLUMN %s IS NULL" % self.preparer.format_column(
+ drop.element, use_table=True
+ )
+
+ def visit_set_constraint_comment(self, create, **kw):
+ raise exc.UnsupportedCompilationError(self, type(create))
+
+ def visit_drop_constraint_comment(self, drop, **kw):
+ raise exc.UnsupportedCompilationError(self, type(drop))
+
+ def get_identity_options(self, identity_options):
+ text = []
+ if identity_options.increment is not None:
+ text.append("INCREMENT BY %d" % identity_options.increment)
+ if identity_options.start is not None:
+ text.append("START WITH %d" % identity_options.start)
+ if identity_options.minvalue is not None:
+ text.append("MINVALUE %d" % identity_options.minvalue)
+ if identity_options.maxvalue is not None:
+ text.append("MAXVALUE %d" % identity_options.maxvalue)
+ if identity_options.nominvalue is not None:
+ text.append("NO MINVALUE")
+ if identity_options.nomaxvalue is not None:
+ text.append("NO MAXVALUE")
+ if identity_options.cache is not None:
+ text.append("CACHE %d" % identity_options.cache)
+ if identity_options.cycle is not None:
+ text.append("CYCLE" if identity_options.cycle else "NO CYCLE")
+ return " ".join(text)
+
+ def visit_create_sequence(self, create, prefix=None, **kw):
+ text = "CREATE SEQUENCE "
+ if create.if_not_exists:
+ text += "IF NOT EXISTS "
+ text += self.preparer.format_sequence(create.element)
+
+ if prefix:
+ text += prefix
+ options = self.get_identity_options(create.element)
+ if options:
+ text += " " + options
+ return text
+
+ def visit_drop_sequence(self, drop, **kw):
+ text = "DROP SEQUENCE "
+ if drop.if_exists:
+ text += "IF EXISTS "
+ return text + self.preparer.format_sequence(drop.element)
+
+ def visit_drop_constraint(self, drop, **kw):
+ constraint = drop.element
+ if constraint.name is not None:
+ formatted_name = self.preparer.format_constraint(constraint)
+ else:
+ formatted_name = None
+
+ if formatted_name is None:
+ raise exc.CompileError(
+ "Can't emit DROP CONSTRAINT for constraint %r; "
+ "it has no name" % drop.element
+ )
+ return "ALTER TABLE %s DROP CONSTRAINT %s%s%s" % (
+ self.preparer.format_table(drop.element.table),
+ "IF EXISTS " if drop.if_exists else "",
+ formatted_name,
+ " CASCADE" if drop.cascade else "",
+ )
+
+ def get_column_specification(self, column, **kwargs):
+ colspec = (
+ self.preparer.format_column(column)
+ + " "
+ + self.dialect.type_compiler_instance.process(
+ column.type, type_expression=column
+ )
+ )
+ default = self.get_column_default_string(column)
+ if default is not None:
+ colspec += " DEFAULT " + default
+
+ if column.computed is not None:
+ colspec += " " + self.process(column.computed)
+
+ if (
+ column.identity is not None
+ and self.dialect.supports_identity_columns
+ ):
+ colspec += " " + self.process(column.identity)
+
+ if not column.nullable and (
+ not column.identity or not self.dialect.supports_identity_columns
+ ):
+ colspec += " NOT NULL"
+ return colspec
+
+ def create_table_suffix(self, table):
+ return ""
+
+ def post_create_table(self, table):
+ return ""
+
+ def get_column_default_string(self, column):
+ if isinstance(column.server_default, schema.DefaultClause):
+ return self.render_default_string(column.server_default.arg)
+ else:
+ return None
+
+ def render_default_string(self, default):
+ if isinstance(default, str):
+ return self.sql_compiler.render_literal_value(
+ default, sqltypes.STRINGTYPE
+ )
+ else:
+ return self.sql_compiler.process(default, literal_binds=True)
+
+ def visit_table_or_column_check_constraint(self, constraint, **kw):
+ if constraint.is_column_level:
+ return self.visit_column_check_constraint(constraint)
+ else:
+ return self.visit_check_constraint(constraint)
+
+ def visit_check_constraint(self, constraint, **kw):
+ text = ""
+ if constraint.name is not None:
+ formatted_name = self.preparer.format_constraint(constraint)
+ if formatted_name is not None:
+ text += "CONSTRAINT %s " % formatted_name
+ text += "CHECK (%s)" % self.sql_compiler.process(
+ constraint.sqltext, include_table=False, literal_binds=True
+ )
+ text += self.define_constraint_deferrability(constraint)
+ return text
+
+ def visit_column_check_constraint(self, constraint, **kw):
+ text = ""
+ if constraint.name is not None:
+ formatted_name = self.preparer.format_constraint(constraint)
+ if formatted_name is not None:
+ text += "CONSTRAINT %s " % formatted_name
+ text += "CHECK (%s)" % self.sql_compiler.process(
+ constraint.sqltext, include_table=False, literal_binds=True
+ )
+ text += self.define_constraint_deferrability(constraint)
+ return text
+
+ def visit_primary_key_constraint(self, constraint, **kw):
+ if len(constraint) == 0:
+ return ""
+ text = ""
+ if constraint.name is not None:
+ formatted_name = self.preparer.format_constraint(constraint)
+ if formatted_name is not None:
+ text += "CONSTRAINT %s " % formatted_name
+ text += "PRIMARY KEY "
+ text += "(%s)" % ", ".join(
+ self.preparer.quote(c.name)
+ for c in (
+ constraint.columns_autoinc_first
+ if constraint._implicit_generated
+ else constraint.columns
+ )
+ )
+ text += self.define_constraint_deferrability(constraint)
+ return text
+
+ def visit_foreign_key_constraint(self, constraint, **kw):
+ preparer = self.preparer
+ text = ""
+ if constraint.name is not None:
+ formatted_name = self.preparer.format_constraint(constraint)
+ if formatted_name is not None:
+ text += "CONSTRAINT %s " % formatted_name
+ remote_table = list(constraint.elements)[0].column.table
+ text += "FOREIGN KEY(%s) REFERENCES %s (%s)" % (
+ ", ".join(
+ preparer.quote(f.parent.name) for f in constraint.elements
+ ),
+ self.define_constraint_remote_table(
+ constraint, remote_table, preparer
+ ),
+ ", ".join(
+ preparer.quote(f.column.name) for f in constraint.elements
+ ),
+ )
+ text += self.define_constraint_match(constraint)
+ text += self.define_constraint_cascades(constraint)
+ text += self.define_constraint_deferrability(constraint)
+ return text
+
+ def define_constraint_remote_table(self, constraint, table, preparer):
+ """Format the remote table clause of a CREATE CONSTRAINT clause."""
+
+ return preparer.format_table(table)
+
+ def visit_unique_constraint(self, constraint, **kw):
+ if len(constraint) == 0:
+ return ""
+ text = ""
+ if constraint.name is not None:
+ formatted_name = self.preparer.format_constraint(constraint)
+ if formatted_name is not None:
+ text += "CONSTRAINT %s " % formatted_name
+ text += "UNIQUE %s(%s)" % (
+ self.define_unique_constraint_distinct(constraint, **kw),
+ ", ".join(self.preparer.quote(c.name) for c in constraint),
+ )
+ text += self.define_constraint_deferrability(constraint)
+ return text
+
+ def define_unique_constraint_distinct(self, constraint, **kw):
+ return ""
+
+ def define_constraint_cascades(self, constraint):
+ text = ""
+ if constraint.ondelete is not None:
+ text += " ON DELETE %s" % self.preparer.validate_sql_phrase(
+ constraint.ondelete, FK_ON_DELETE
+ )
+ if constraint.onupdate is not None:
+ text += " ON UPDATE %s" % self.preparer.validate_sql_phrase(
+ constraint.onupdate, FK_ON_UPDATE
+ )
+ return text
+
+ def define_constraint_deferrability(self, constraint):
+ text = ""
+ if constraint.deferrable is not None:
+ if constraint.deferrable:
+ text += " DEFERRABLE"
+ else:
+ text += " NOT DEFERRABLE"
+ if constraint.initially is not None:
+ text += " INITIALLY %s" % self.preparer.validate_sql_phrase(
+ constraint.initially, FK_INITIALLY
+ )
+ return text
+
+ def define_constraint_match(self, constraint):
+ text = ""
+ if constraint.match is not None:
+ text += " MATCH %s" % constraint.match
+ return text
+
+ def visit_computed_column(self, generated, **kw):
+ text = "GENERATED ALWAYS AS (%s)" % self.sql_compiler.process(
+ generated.sqltext, include_table=False, literal_binds=True
+ )
+ if generated.persisted is True:
+ text += " STORED"
+ elif generated.persisted is False:
+ text += " VIRTUAL"
+ return text
+
+ def visit_identity_column(self, identity, **kw):
+ text = "GENERATED %s AS IDENTITY" % (
+ "ALWAYS" if identity.always else "BY DEFAULT",
+ )
+ options = self.get_identity_options(identity)
+ if options:
+ text += " (%s)" % options
+ return text
+
+
+class GenericTypeCompiler(TypeCompiler):
+ def visit_FLOAT(self, type_, **kw):
+ return "FLOAT"
+
+ def visit_DOUBLE(self, type_, **kw):
+ return "DOUBLE"
+
+ def visit_DOUBLE_PRECISION(self, type_, **kw):
+ return "DOUBLE PRECISION"
+
+ def visit_REAL(self, type_, **kw):
+ return "REAL"
+
+ def visit_NUMERIC(self, type_, **kw):
+ if type_.precision is None:
+ return "NUMERIC"
+ elif type_.scale is None:
+ return "NUMERIC(%(precision)s)" % {"precision": type_.precision}
+ else:
+ return "NUMERIC(%(precision)s, %(scale)s)" % {
+ "precision": type_.precision,
+ "scale": type_.scale,
+ }
+
+ def visit_DECIMAL(self, type_, **kw):
+ if type_.precision is None:
+ return "DECIMAL"
+ elif type_.scale is None:
+ return "DECIMAL(%(precision)s)" % {"precision": type_.precision}
+ else:
+ return "DECIMAL(%(precision)s, %(scale)s)" % {
+ "precision": type_.precision,
+ "scale": type_.scale,
+ }
+
+ def visit_INTEGER(self, type_, **kw):
+ return "INTEGER"
+
+ def visit_SMALLINT(self, type_, **kw):
+ return "SMALLINT"
+
+ def visit_BIGINT(self, type_, **kw):
+ return "BIGINT"
+
+ def visit_TIMESTAMP(self, type_, **kw):
+ return "TIMESTAMP"
+
+ def visit_DATETIME(self, type_, **kw):
+ return "DATETIME"
+
+ def visit_DATE(self, type_, **kw):
+ return "DATE"
+
+ def visit_TIME(self, type_, **kw):
+ return "TIME"
+
+ def visit_CLOB(self, type_, **kw):
+ return "CLOB"
+
+ def visit_NCLOB(self, type_, **kw):
+ return "NCLOB"
+
+ def _render_string_type(self, type_, name, length_override=None):
+ text = name
+ if length_override:
+ text += "(%d)" % length_override
+ elif type_.length:
+ text += "(%d)" % type_.length
+ if type_.collation:
+ text += ' COLLATE "%s"' % type_.collation
+ return text
+
+ def visit_CHAR(self, type_, **kw):
+ return self._render_string_type(type_, "CHAR")
+
+ def visit_NCHAR(self, type_, **kw):
+ return self._render_string_type(type_, "NCHAR")
+
+ def visit_VARCHAR(self, type_, **kw):
+ return self._render_string_type(type_, "VARCHAR")
+
+ def visit_NVARCHAR(self, type_, **kw):
+ return self._render_string_type(type_, "NVARCHAR")
+
+ def visit_TEXT(self, type_, **kw):
+ return self._render_string_type(type_, "TEXT")
+
+ def visit_UUID(self, type_, **kw):
+ return "UUID"
+
+ def visit_BLOB(self, type_, **kw):
+ return "BLOB"
+
+ def visit_BINARY(self, type_, **kw):
+ return "BINARY" + (type_.length and "(%d)" % type_.length or "")
+
+ def visit_VARBINARY(self, type_, **kw):
+ return "VARBINARY" + (type_.length and "(%d)" % type_.length or "")
+
+ def visit_BOOLEAN(self, type_, **kw):
+ return "BOOLEAN"
+
+ def visit_uuid(self, type_, **kw):
+ if not type_.native_uuid or not self.dialect.supports_native_uuid:
+ return self._render_string_type(type_, "CHAR", length_override=32)
+ else:
+ return self.visit_UUID(type_, **kw)
+
+ def visit_large_binary(self, type_, **kw):
+ return self.visit_BLOB(type_, **kw)
+
+ def visit_boolean(self, type_, **kw):
+ return self.visit_BOOLEAN(type_, **kw)
+
+ def visit_time(self, type_, **kw):
+ return self.visit_TIME(type_, **kw)
+
+ def visit_datetime(self, type_, **kw):
+ return self.visit_DATETIME(type_, **kw)
+
+ def visit_date(self, type_, **kw):
+ return self.visit_DATE(type_, **kw)
+
+ def visit_big_integer(self, type_, **kw):
+ return self.visit_BIGINT(type_, **kw)
+
+ def visit_small_integer(self, type_, **kw):
+ return self.visit_SMALLINT(type_, **kw)
+
+ def visit_integer(self, type_, **kw):
+ return self.visit_INTEGER(type_, **kw)
+
+ def visit_real(self, type_, **kw):
+ return self.visit_REAL(type_, **kw)
+
+ def visit_float(self, type_, **kw):
+ return self.visit_FLOAT(type_, **kw)
+
+ def visit_double(self, type_, **kw):
+ return self.visit_DOUBLE(type_, **kw)
+
+ def visit_numeric(self, type_, **kw):
+ return self.visit_NUMERIC(type_, **kw)
+
+ def visit_string(self, type_, **kw):
+ return self.visit_VARCHAR(type_, **kw)
+
+ def visit_unicode(self, type_, **kw):
+ return self.visit_VARCHAR(type_, **kw)
+
+ def visit_text(self, type_, **kw):
+ return self.visit_TEXT(type_, **kw)
+
+ def visit_unicode_text(self, type_, **kw):
+ return self.visit_TEXT(type_, **kw)
+
+ def visit_enum(self, type_, **kw):
+ return self.visit_VARCHAR(type_, **kw)
+
+ def visit_null(self, type_, **kw):
+ raise exc.CompileError(
+ "Can't generate DDL for %r; "
+ "did you forget to specify a "
+ "type on this Column?" % type_
+ )
+
+ def visit_type_decorator(self, type_, **kw):
+ return self.process(type_.type_engine(self.dialect), **kw)
+
+ def visit_user_defined(self, type_, **kw):
+ return type_.get_col_spec(**kw)
+
+
+class StrSQLTypeCompiler(GenericTypeCompiler):
+ def process(self, type_, **kw):
+ try:
+ _compiler_dispatch = type_._compiler_dispatch
+ except AttributeError:
+ return self._visit_unknown(type_, **kw)
+ else:
+ return _compiler_dispatch(self, **kw)
+
+ def __getattr__(self, key):
+ if key.startswith("visit_"):
+ return self._visit_unknown
+ else:
+ raise AttributeError(key)
+
+ def _visit_unknown(self, type_, **kw):
+ if type_.__class__.__name__ == type_.__class__.__name__.upper():
+ return type_.__class__.__name__
+ else:
+ return repr(type_)
+
+ def visit_null(self, type_, **kw):
+ return "NULL"
+
+ def visit_user_defined(self, type_, **kw):
+ try:
+ get_col_spec = type_.get_col_spec
+ except AttributeError:
+ return repr(type_)
+ else:
+ return get_col_spec(**kw)
+
+
+class _SchemaForObjectCallable(Protocol):
+ def __call__(self, obj: Any) -> str: ...
+
+
+class _BindNameForColProtocol(Protocol):
+ def __call__(self, col: ColumnClause[Any]) -> str: ...
+
+
+class IdentifierPreparer:
+ """Handle quoting and case-folding of identifiers based on options."""
+
+ reserved_words = RESERVED_WORDS
+
+ legal_characters = LEGAL_CHARACTERS
+
+ illegal_initial_characters = ILLEGAL_INITIAL_CHARACTERS
+
+ initial_quote: str
+
+ final_quote: str
+
+ _strings: MutableMapping[str, str]
+
+ schema_for_object: _SchemaForObjectCallable = operator.attrgetter("schema")
+ """Return the .schema attribute for an object.
+
+ For the default IdentifierPreparer, the schema for an object is always
+ the value of the ".schema" attribute. if the preparer is replaced
+ with one that has a non-empty schema_translate_map, the value of the
+ ".schema" attribute is rendered a symbol that will be converted to a
+ real schema name from the mapping post-compile.
+
+ """
+
+ _includes_none_schema_translate: bool = False
+
+ def __init__(
+ self,
+ dialect,
+ initial_quote='"',
+ final_quote=None,
+ escape_quote='"',
+ quote_case_sensitive_collations=True,
+ omit_schema=False,
+ ):
+ """Construct a new ``IdentifierPreparer`` object.
+
+ initial_quote
+ Character that begins a delimited identifier.
+
+ final_quote
+ Character that ends a delimited identifier. Defaults to
+ `initial_quote`.
+
+ omit_schema
+ Prevent prepending schema name. Useful for databases that do
+ not support schemae.
+ """
+
+ self.dialect = dialect
+ self.initial_quote = initial_quote
+ self.final_quote = final_quote or self.initial_quote
+ self.escape_quote = escape_quote
+ self.escape_to_quote = self.escape_quote * 2
+ self.omit_schema = omit_schema
+ self.quote_case_sensitive_collations = quote_case_sensitive_collations
+ self._strings = {}
+ self._double_percents = self.dialect.paramstyle in (
+ "format",
+ "pyformat",
+ )
+
+ def _with_schema_translate(self, schema_translate_map):
+ prep = self.__class__.__new__(self.__class__)
+ prep.__dict__.update(self.__dict__)
+
+ includes_none = None in schema_translate_map
+
+ def symbol_getter(obj):
+ name = obj.schema
+ if obj._use_schema_map and (name is not None or includes_none):
+ if name is not None and ("[" in name or "]" in name):
+ raise exc.CompileError(
+ "Square bracket characters ([]) not supported "
+ "in schema translate name '%s'" % name
+ )
+ return quoted_name(
+ "__[SCHEMA_%s]" % (name or "_none"), quote=False
+ )
+ else:
+ return obj.schema
+
+ prep.schema_for_object = symbol_getter
+ prep._includes_none_schema_translate = includes_none
+ return prep
+
+ def _render_schema_translates(self, statement, schema_translate_map):
+ d = schema_translate_map
+ if None in d:
+ if not self._includes_none_schema_translate:
+ raise exc.InvalidRequestError(
+ "schema translate map which previously did not have "
+ "`None` present as a key now has `None` present; compiled "
+ "statement may lack adequate placeholders. Please use "
+ "consistent keys in successive "
+ "schema_translate_map dictionaries."
+ )
+
+ d["_none"] = d[None]
+
+ def replace(m):
+ name = m.group(2)
+ if name in d:
+ effective_schema = d[name]
+ else:
+ if name in (None, "_none"):
+ raise exc.InvalidRequestError(
+ "schema translate map which previously had `None` "
+ "present as a key now no longer has it present; don't "
+ "know how to apply schema for compiled statement. "
+ "Please use consistent keys in successive "
+ "schema_translate_map dictionaries."
+ )
+ effective_schema = name
+
+ if not effective_schema:
+ effective_schema = self.dialect.default_schema_name
+ if not effective_schema:
+ # TODO: no coverage here
+ raise exc.CompileError(
+ "Dialect has no default schema name; can't "
+ "use None as dynamic schema target."
+ )
+ return self.quote_schema(effective_schema)
+
+ return re.sub(r"(__\[SCHEMA_([^\]]+)\])", replace, statement)
+
+ def _escape_identifier(self, value: str) -> str:
+ """Escape an identifier.
+
+ Subclasses should override this to provide database-dependent
+ escaping behavior.
+ """
+
+ value = value.replace(self.escape_quote, self.escape_to_quote)
+ if self._double_percents:
+ value = value.replace("%", "%%")
+ return value
+
+ def _unescape_identifier(self, value: str) -> str:
+ """Canonicalize an escaped identifier.
+
+ Subclasses should override this to provide database-dependent
+ unescaping behavior that reverses _escape_identifier.
+ """
+
+ return value.replace(self.escape_to_quote, self.escape_quote)
+
+ def validate_sql_phrase(self, element, reg):
+ """keyword sequence filter.
+
+ a filter for elements that are intended to represent keyword sequences,
+ such as "INITIALLY", "INITIALLY DEFERRED", etc. no special characters
+ should be present.
+
+ .. versionadded:: 1.3
+
+ """
+
+ if element is not None and not reg.match(element):
+ raise exc.CompileError(
+ "Unexpected SQL phrase: %r (matching against %r)"
+ % (element, reg.pattern)
+ )
+ return element
+
+ def quote_identifier(self, value: str) -> str:
+ """Quote an identifier.
+
+ Subclasses should override this to provide database-dependent
+ quoting behavior.
+ """
+
+ return (
+ self.initial_quote
+ + self._escape_identifier(value)
+ + self.final_quote
+ )
+
+ def _requires_quotes(self, value: str) -> bool:
+ """Return True if the given identifier requires quoting."""
+ lc_value = value.lower()
+ return (
+ lc_value in self.reserved_words
+ or value[0] in self.illegal_initial_characters
+ or not self.legal_characters.match(str(value))
+ or (lc_value != value)
+ )
+
+ def _requires_quotes_illegal_chars(self, value):
+ """Return True if the given identifier requires quoting, but
+ not taking case convention into account."""
+ return not self.legal_characters.match(str(value))
+
+ def quote_schema(self, schema: str, force: Any = None) -> str:
+ """Conditionally quote a schema name.
+
+
+ The name is quoted if it is a reserved word, contains quote-necessary
+ characters, or is an instance of :class:`.quoted_name` which includes
+ ``quote`` set to ``True``.
+
+ Subclasses can override this to provide database-dependent
+ quoting behavior for schema names.
+
+ :param schema: string schema name
+ :param force: unused
+
+ .. deprecated:: 0.9
+
+ The :paramref:`.IdentifierPreparer.quote_schema.force`
+ parameter is deprecated and will be removed in a future
+ release. This flag has no effect on the behavior of the
+ :meth:`.IdentifierPreparer.quote` method; please refer to
+ :class:`.quoted_name`.
+
+ """
+ if force is not None:
+ # not using the util.deprecated_params() decorator in this
+ # case because of the additional function call overhead on this
+ # very performance-critical spot.
+ util.warn_deprecated(
+ "The IdentifierPreparer.quote_schema.force parameter is "
+ "deprecated and will be removed in a future release. This "
+ "flag has no effect on the behavior of the "
+ "IdentifierPreparer.quote method; please refer to "
+ "quoted_name().",
+ # deprecated 0.9. warning from 1.3
+ version="0.9",
+ )
+
+ return self.quote(schema)
+
+ def quote(self, ident: str, force: Any = None) -> str:
+ """Conditionally quote an identifier.
+
+ The identifier is quoted if it is a reserved word, contains
+ quote-necessary characters, or is an instance of
+ :class:`.quoted_name` which includes ``quote`` set to ``True``.
+
+ Subclasses can override this to provide database-dependent
+ quoting behavior for identifier names.
+
+ :param ident: string identifier
+ :param force: unused
+
+ .. deprecated:: 0.9
+
+ The :paramref:`.IdentifierPreparer.quote.force`
+ parameter is deprecated and will be removed in a future
+ release. This flag has no effect on the behavior of the
+ :meth:`.IdentifierPreparer.quote` method; please refer to
+ :class:`.quoted_name`.
+
+ """
+ if force is not None:
+ # not using the util.deprecated_params() decorator in this
+ # case because of the additional function call overhead on this
+ # very performance-critical spot.
+ util.warn_deprecated(
+ "The IdentifierPreparer.quote.force parameter is "
+ "deprecated and will be removed in a future release. This "
+ "flag has no effect on the behavior of the "
+ "IdentifierPreparer.quote method; please refer to "
+ "quoted_name().",
+ # deprecated 0.9. warning from 1.3
+ version="0.9",
+ )
+
+ force = getattr(ident, "quote", None)
+
+ if force is None:
+ if ident in self._strings:
+ return self._strings[ident]
+ else:
+ if self._requires_quotes(ident):
+ self._strings[ident] = self.quote_identifier(ident)
+ else:
+ self._strings[ident] = ident
+ return self._strings[ident]
+ elif force:
+ return self.quote_identifier(ident)
+ else:
+ return ident
+
+ def format_collation(self, collation_name):
+ if self.quote_case_sensitive_collations:
+ return self.quote(collation_name)
+ else:
+ return collation_name
+
+ def format_sequence(self, sequence, use_schema=True):
+ name = self.quote(sequence.name)
+
+ effective_schema = self.schema_for_object(sequence)
+
+ if (
+ not self.omit_schema
+ and use_schema
+ and effective_schema is not None
+ ):
+ name = self.quote_schema(effective_schema) + "." + name
+ return name
+
+ def format_label(
+ self, label: Label[Any], name: Optional[str] = None
+ ) -> str:
+ return self.quote(name or label.name)
+
+ def format_alias(
+ self, alias: Optional[AliasedReturnsRows], name: Optional[str] = None
+ ) -> str:
+ if name is None:
+ assert alias is not None
+ return self.quote(alias.name)
+ else:
+ return self.quote(name)
+
+ def format_savepoint(self, savepoint, name=None):
+ # Running the savepoint name through quoting is unnecessary
+ # for all known dialects. This is here to support potential
+ # third party use cases
+ ident = name or savepoint.ident
+ if self._requires_quotes(ident):
+ ident = self.quote_identifier(ident)
+ return ident
+
+ @util.preload_module("sqlalchemy.sql.naming")
+ def format_constraint(self, constraint, _alembic_quote=True):
+ naming = util.preloaded.sql_naming
+
+ if constraint.name is _NONE_NAME:
+ name = naming._constraint_name_for_table(
+ constraint, constraint.table
+ )
+
+ if name is None:
+ return None
+ else:
+ name = constraint.name
+
+ if constraint.__visit_name__ == "index":
+ return self.truncate_and_render_index_name(
+ name, _alembic_quote=_alembic_quote
+ )
+ else:
+ return self.truncate_and_render_constraint_name(
+ name, _alembic_quote=_alembic_quote
+ )
+
+ def truncate_and_render_index_name(self, name, _alembic_quote=True):
+ # calculate these at format time so that ad-hoc changes
+ # to dialect.max_identifier_length etc. can be reflected
+ # as IdentifierPreparer is long lived
+ max_ = (
+ self.dialect.max_index_name_length
+ or self.dialect.max_identifier_length
+ )
+ return self._truncate_and_render_maxlen_name(
+ name, max_, _alembic_quote
+ )
+
+ def truncate_and_render_constraint_name(self, name, _alembic_quote=True):
+ # calculate these at format time so that ad-hoc changes
+ # to dialect.max_identifier_length etc. can be reflected
+ # as IdentifierPreparer is long lived
+ max_ = (
+ self.dialect.max_constraint_name_length
+ or self.dialect.max_identifier_length
+ )
+ return self._truncate_and_render_maxlen_name(
+ name, max_, _alembic_quote
+ )
+
+ def _truncate_and_render_maxlen_name(self, name, max_, _alembic_quote):
+ if isinstance(name, elements._truncated_label):
+ if len(name) > max_:
+ name = name[0 : max_ - 8] + "_" + util.md5_hex(name)[-4:]
+ else:
+ self.dialect.validate_identifier(name)
+
+ if not _alembic_quote:
+ return name
+ else:
+ return self.quote(name)
+
+ def format_index(self, index):
+ return self.format_constraint(index)
+
+ def format_table(self, table, use_schema=True, name=None):
+ """Prepare a quoted table and schema name."""
+
+ if name is None:
+ name = table.name
+
+ result = self.quote(name)
+
+ effective_schema = self.schema_for_object(table)
+
+ if not self.omit_schema and use_schema and effective_schema:
+ result = self.quote_schema(effective_schema) + "." + result
+ return result
+
+ def format_schema(self, name):
+ """Prepare a quoted schema name."""
+
+ return self.quote(name)
+
+ def format_label_name(
+ self,
+ name,
+ anon_map=None,
+ ):
+ """Prepare a quoted column name."""
+
+ if anon_map is not None and isinstance(
+ name, elements._truncated_label
+ ):
+ name = name.apply_map(anon_map)
+
+ return self.quote(name)
+
+ def format_column(
+ self,
+ column,
+ use_table=False,
+ name=None,
+ table_name=None,
+ use_schema=False,
+ anon_map=None,
+ ):
+ """Prepare a quoted column name."""
+
+ if name is None:
+ name = column.name
+
+ if anon_map is not None and isinstance(
+ name, elements._truncated_label
+ ):
+ name = name.apply_map(anon_map)
+
+ if not getattr(column, "is_literal", False):
+ if use_table:
+ return (
+ self.format_table(
+ column.table, use_schema=use_schema, name=table_name
+ )
+ + "."
+ + self.quote(name)
+ )
+ else:
+ return self.quote(name)
+ else:
+ # literal textual elements get stuck into ColumnClause a lot,
+ # which shouldn't get quoted
+
+ if use_table:
+ return (
+ self.format_table(
+ column.table, use_schema=use_schema, name=table_name
+ )
+ + "."
+ + name
+ )
+ else:
+ return name
+
+ def format_table_seq(self, table, use_schema=True):
+ """Format table name and schema as a tuple."""
+
+ # Dialects with more levels in their fully qualified references
+ # ('database', 'owner', etc.) could override this and return
+ # a longer sequence.
+
+ effective_schema = self.schema_for_object(table)
+
+ if not self.omit_schema and use_schema and effective_schema:
+ return (
+ self.quote_schema(effective_schema),
+ self.format_table(table, use_schema=False),
+ )
+ else:
+ return (self.format_table(table, use_schema=False),)
+
+ @util.memoized_property
+ def _r_identifiers(self):
+ initial, final, escaped_final = (
+ re.escape(s)
+ for s in (
+ self.initial_quote,
+ self.final_quote,
+ self._escape_identifier(self.final_quote),
+ )
+ )
+ r = re.compile(
+ r"(?:"
+ r"(?:%(initial)s((?:%(escaped)s|[^%(final)s])+)%(final)s"
+ r"|([^\.]+))(?=\.|$))+"
+ % {"initial": initial, "final": final, "escaped": escaped_final}
+ )
+ return r
+
+ def unformat_identifiers(self, identifiers):
+ """Unpack 'schema.table.column'-like strings into components."""
+
+ r = self._r_identifiers
+ return [
+ self._unescape_identifier(i)
+ for i in [a or b for a, b in r.findall(identifiers)]
+ ]
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/crud.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/crud.py
new file mode 100644
index 0000000..499a19d
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/crud.py
@@ -0,0 +1,1669 @@
+# sql/crud.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+# mypy: allow-untyped-defs, allow-untyped-calls
+
+"""Functions used by compiler.py to determine the parameters rendered
+within INSERT and UPDATE statements.
+
+"""
+from __future__ import annotations
+
+import functools
+import operator
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import Dict
+from typing import Iterable
+from typing import List
+from typing import MutableMapping
+from typing import NamedTuple
+from typing import Optional
+from typing import overload
+from typing import Sequence
+from typing import Set
+from typing import Tuple
+from typing import TYPE_CHECKING
+from typing import Union
+
+from . import coercions
+from . import dml
+from . import elements
+from . import roles
+from .base import _DefaultDescriptionTuple
+from .dml import isinsert as _compile_state_isinsert
+from .elements import ColumnClause
+from .schema import default_is_clause_element
+from .schema import default_is_sequence
+from .selectable import Select
+from .selectable import TableClause
+from .. import exc
+from .. import util
+from ..util.typing import Literal
+
+if TYPE_CHECKING:
+ from .compiler import _BindNameForColProtocol
+ from .compiler import SQLCompiler
+ from .dml import _DMLColumnElement
+ from .dml import DMLState
+ from .dml import ValuesBase
+ from .elements import ColumnElement
+ from .elements import KeyedColumnElement
+ from .schema import _SQLExprDefault
+ from .schema import Column
+
+REQUIRED = util.symbol(
+ "REQUIRED",
+ """
+Placeholder for the value within a :class:`.BindParameter`
+which is required to be present when the statement is passed
+to :meth:`_engine.Connection.execute`.
+
+This symbol is typically used when a :func:`_expression.insert`
+or :func:`_expression.update` statement is compiled without parameter
+values present.
+
+""",
+)
+
+
+def _as_dml_column(c: ColumnElement[Any]) -> ColumnClause[Any]:
+ if not isinstance(c, ColumnClause):
+ raise exc.CompileError(
+ f"Can't create DML statement against column expression {c!r}"
+ )
+ return c
+
+
+_CrudParamElement = Tuple[
+ "ColumnElement[Any]",
+ str, # column name
+ Optional[
+ Union[str, "_SQLExprDefault"]
+ ], # bound parameter string or SQL expression to apply
+ Iterable[str],
+]
+_CrudParamElementStr = Tuple[
+ "KeyedColumnElement[Any]",
+ str, # column name
+ str, # bound parameter string
+ Iterable[str],
+]
+_CrudParamElementSQLExpr = Tuple[
+ "ColumnClause[Any]",
+ str,
+ "_SQLExprDefault", # SQL expression to apply
+ Iterable[str],
+]
+
+_CrudParamSequence = List[_CrudParamElement]
+
+
+class _CrudParams(NamedTuple):
+ single_params: _CrudParamSequence
+ all_multi_params: List[Sequence[_CrudParamElementStr]]
+ is_default_metavalue_only: bool = False
+ use_insertmanyvalues: bool = False
+ use_sentinel_columns: Optional[Sequence[Column[Any]]] = None
+
+
+def _get_crud_params(
+ compiler: SQLCompiler,
+ stmt: ValuesBase,
+ compile_state: DMLState,
+ toplevel: bool,
+ **kw: Any,
+) -> _CrudParams:
+ """create a set of tuples representing column/string pairs for use
+ in an INSERT or UPDATE statement.
+
+ Also generates the Compiled object's postfetch, prefetch, and
+ returning column collections, used for default handling and ultimately
+ populating the CursorResult's prefetch_cols() and postfetch_cols()
+ collections.
+
+ """
+
+ # note: the _get_crud_params() system was written with the notion in mind
+ # that INSERT, UPDATE, DELETE are always the top level statement and
+ # that there is only one of them. With the addition of CTEs that can
+ # make use of DML, this assumption is no longer accurate; the DML
+ # statement is not necessarily the top-level "row returning" thing
+ # and it is also theoretically possible (fortunately nobody has asked yet)
+ # to have a single statement with multiple DMLs inside of it via CTEs.
+
+ # the current _get_crud_params() design doesn't accommodate these cases
+ # right now. It "just works" for a CTE that has a single DML inside of
+ # it, and for a CTE with multiple DML, it's not clear what would happen.
+
+ # overall, the "compiler.XYZ" collections here would need to be in a
+ # per-DML structure of some kind, and DefaultDialect would need to
+ # navigate these collections on a per-statement basis, with additional
+ # emphasis on the "toplevel returning data" statement. However we
+ # still need to run through _get_crud_params() for all DML as we have
+ # Python / SQL generated column defaults that need to be rendered.
+
+ # if there is user need for this kind of thing, it's likely a post 2.0
+ # kind of change as it would require deep changes to DefaultDialect
+ # as well as here.
+
+ compiler.postfetch = []
+ compiler.insert_prefetch = []
+ compiler.update_prefetch = []
+ compiler.implicit_returning = []
+
+ visiting_cte = kw.get("visiting_cte", None)
+ if visiting_cte is not None:
+ # for insert -> CTE -> insert, don't populate an incoming
+ # _crud_accumulate_bind_names collection; the INSERT we process here
+ # will not be inline within the VALUES of the enclosing INSERT as the
+ # CTE is placed on the outside. See issue #9173
+ kw.pop("accumulate_bind_names", None)
+ assert (
+ "accumulate_bind_names" not in kw
+ ), "Don't know how to handle insert within insert without a CTE"
+
+ # getters - these are normally just column.key,
+ # but in the case of mysql multi-table update, the rules for
+ # .key must conditionally take tablename into account
+ (
+ _column_as_key,
+ _getattr_col_key,
+ _col_bind_name,
+ ) = _key_getters_for_crud_column(compiler, stmt, compile_state)
+
+ compiler._get_bind_name_for_col = _col_bind_name
+
+ if stmt._returning and stmt._return_defaults:
+ raise exc.CompileError(
+ "Can't compile statement that includes returning() and "
+ "return_defaults() simultaneously"
+ )
+
+ if compile_state.isdelete:
+ _setup_delete_return_defaults(
+ compiler,
+ stmt,
+ compile_state,
+ (),
+ _getattr_col_key,
+ _column_as_key,
+ _col_bind_name,
+ (),
+ (),
+ toplevel,
+ kw,
+ )
+ return _CrudParams([], [])
+
+ # no parameters in the statement, no parameters in the
+ # compiled params - return binds for all columns
+ if compiler.column_keys is None and compile_state._no_parameters:
+ return _CrudParams(
+ [
+ (
+ c,
+ compiler.preparer.format_column(c),
+ _create_bind_param(compiler, c, None, required=True),
+ (c.key,),
+ )
+ for c in stmt.table.columns
+ if not c._omit_from_statements
+ ],
+ [],
+ )
+
+ stmt_parameter_tuples: Optional[
+ List[Tuple[Union[str, ColumnClause[Any]], Any]]
+ ]
+ spd: Optional[MutableMapping[_DMLColumnElement, Any]]
+
+ if (
+ _compile_state_isinsert(compile_state)
+ and compile_state._has_multi_parameters
+ ):
+ mp = compile_state._multi_parameters
+ assert mp is not None
+ spd = mp[0]
+ stmt_parameter_tuples = list(spd.items())
+ spd_str_key = {_column_as_key(key) for key in spd}
+ elif compile_state._ordered_values:
+ spd = compile_state._dict_parameters
+ stmt_parameter_tuples = compile_state._ordered_values
+ assert spd is not None
+ spd_str_key = {_column_as_key(key) for key in spd}
+ elif compile_state._dict_parameters:
+ spd = compile_state._dict_parameters
+ stmt_parameter_tuples = list(spd.items())
+ spd_str_key = {_column_as_key(key) for key in spd}
+ else:
+ stmt_parameter_tuples = spd = spd_str_key = None
+
+ # if we have statement parameters - set defaults in the
+ # compiled params
+ if compiler.column_keys is None:
+ parameters = {}
+ elif stmt_parameter_tuples:
+ assert spd_str_key is not None
+ parameters = {
+ _column_as_key(key): REQUIRED
+ for key in compiler.column_keys
+ if key not in spd_str_key
+ }
+ else:
+ parameters = {
+ _column_as_key(key): REQUIRED for key in compiler.column_keys
+ }
+
+ # create a list of column assignment clauses as tuples
+ values: List[_CrudParamElement] = []
+
+ if stmt_parameter_tuples is not None:
+ _get_stmt_parameter_tuples_params(
+ compiler,
+ compile_state,
+ parameters,
+ stmt_parameter_tuples,
+ _column_as_key,
+ values,
+ kw,
+ )
+
+ check_columns: Dict[str, ColumnClause[Any]] = {}
+
+ # special logic that only occurs for multi-table UPDATE
+ # statements
+ if dml.isupdate(compile_state) and compile_state.is_multitable:
+ _get_update_multitable_params(
+ compiler,
+ stmt,
+ compile_state,
+ stmt_parameter_tuples,
+ check_columns,
+ _col_bind_name,
+ _getattr_col_key,
+ values,
+ kw,
+ )
+
+ if _compile_state_isinsert(compile_state) and stmt._select_names:
+ # is an insert from select, is not a multiparams
+
+ assert not compile_state._has_multi_parameters
+
+ _scan_insert_from_select_cols(
+ compiler,
+ stmt,
+ compile_state,
+ parameters,
+ _getattr_col_key,
+ _column_as_key,
+ _col_bind_name,
+ check_columns,
+ values,
+ toplevel,
+ kw,
+ )
+ use_insertmanyvalues = False
+ use_sentinel_columns = None
+ else:
+ use_insertmanyvalues, use_sentinel_columns = _scan_cols(
+ compiler,
+ stmt,
+ compile_state,
+ parameters,
+ _getattr_col_key,
+ _column_as_key,
+ _col_bind_name,
+ check_columns,
+ values,
+ toplevel,
+ kw,
+ )
+
+ if parameters and stmt_parameter_tuples:
+ check = (
+ set(parameters)
+ .intersection(_column_as_key(k) for k, v in stmt_parameter_tuples)
+ .difference(check_columns)
+ )
+ if check:
+ raise exc.CompileError(
+ "Unconsumed column names: %s"
+ % (", ".join("%s" % (c,) for c in check))
+ )
+
+ is_default_metavalue_only = False
+
+ if (
+ _compile_state_isinsert(compile_state)
+ and compile_state._has_multi_parameters
+ ):
+ # is a multiparams, is not an insert from a select
+ assert not stmt._select_names
+ multi_extended_values = _extend_values_for_multiparams(
+ compiler,
+ stmt,
+ compile_state,
+ cast(
+ "Sequence[_CrudParamElementStr]",
+ values,
+ ),
+ cast("Callable[..., str]", _column_as_key),
+ kw,
+ )
+ return _CrudParams(values, multi_extended_values)
+ elif (
+ not values
+ and compiler.for_executemany
+ and compiler.dialect.supports_default_metavalue
+ ):
+ # convert an "INSERT DEFAULT VALUES"
+ # into INSERT (firstcol) VALUES (DEFAULT) which can be turned
+ # into an in-place multi values. This supports
+ # insert_executemany_returning mode :)
+ values = [
+ (
+ _as_dml_column(stmt.table.columns[0]),
+ compiler.preparer.format_column(stmt.table.columns[0]),
+ compiler.dialect.default_metavalue_token,
+ (),
+ )
+ ]
+ is_default_metavalue_only = True
+
+ return _CrudParams(
+ values,
+ [],
+ is_default_metavalue_only=is_default_metavalue_only,
+ use_insertmanyvalues=use_insertmanyvalues,
+ use_sentinel_columns=use_sentinel_columns,
+ )
+
+
+@overload
+def _create_bind_param(
+ compiler: SQLCompiler,
+ col: ColumnElement[Any],
+ value: Any,
+ process: Literal[True] = ...,
+ required: bool = False,
+ name: Optional[str] = None,
+ **kw: Any,
+) -> str: ...
+
+
+@overload
+def _create_bind_param(
+ compiler: SQLCompiler,
+ col: ColumnElement[Any],
+ value: Any,
+ **kw: Any,
+) -> str: ...
+
+
+def _create_bind_param(
+ compiler: SQLCompiler,
+ col: ColumnElement[Any],
+ value: Any,
+ process: bool = True,
+ required: bool = False,
+ name: Optional[str] = None,
+ **kw: Any,
+) -> Union[str, elements.BindParameter[Any]]:
+ if name is None:
+ name = col.key
+ bindparam = elements.BindParameter(
+ name, value, type_=col.type, required=required
+ )
+ bindparam._is_crud = True
+ if process:
+ return bindparam._compiler_dispatch(compiler, **kw)
+ else:
+ return bindparam
+
+
+def _handle_values_anonymous_param(compiler, col, value, name, **kw):
+ # the insert() and update() constructs as of 1.4 will now produce anonymous
+ # bindparam() objects in the values() collections up front when given plain
+ # literal values. This is so that cache key behaviors, which need to
+ # produce bound parameters in deterministic order without invoking any
+ # compilation here, can be applied to these constructs when they include
+ # values() (but not yet multi-values, which are not included in caching
+ # right now).
+ #
+ # in order to produce the desired "crud" style name for these parameters,
+ # which will also be targetable in engine/default.py through the usual
+ # conventions, apply our desired name to these unique parameters by
+ # populating the compiler truncated names cache with the desired name,
+ # rather than having
+ # compiler.visit_bindparam()->compiler._truncated_identifier make up a
+ # name. Saves on call counts also.
+
+ # for INSERT/UPDATE that's a CTE, we don't need names to match to
+ # external parameters and these would also conflict in the case where
+ # multiple insert/update are combined together using CTEs
+ is_cte = "visiting_cte" in kw
+
+ if (
+ not is_cte
+ and value.unique
+ and isinstance(value.key, elements._truncated_label)
+ ):
+ compiler.truncated_names[("bindparam", value.key)] = name
+
+ if value.type._isnull:
+ # either unique parameter, or other bound parameters that were
+ # passed in directly
+ # set type to that of the column unconditionally
+ value = value._with_binary_element_type(col.type)
+
+ return value._compiler_dispatch(compiler, **kw)
+
+
+def _key_getters_for_crud_column(
+ compiler: SQLCompiler, stmt: ValuesBase, compile_state: DMLState
+) -> Tuple[
+ Callable[[Union[str, ColumnClause[Any]]], Union[str, Tuple[str, str]]],
+ Callable[[ColumnClause[Any]], Union[str, Tuple[str, str]]],
+ _BindNameForColProtocol,
+]:
+ if dml.isupdate(compile_state) and compile_state._extra_froms:
+ # when extra tables are present, refer to the columns
+ # in those extra tables as table-qualified, including in
+ # dictionaries and when rendering bind param names.
+ # the "main" table of the statement remains unqualified,
+ # allowing the most compatibility with a non-multi-table
+ # statement.
+ _et = set(compile_state._extra_froms)
+
+ c_key_role = functools.partial(
+ coercions.expect_as_key, roles.DMLColumnRole
+ )
+
+ def _column_as_key(
+ key: Union[ColumnClause[Any], str]
+ ) -> Union[str, Tuple[str, str]]:
+ str_key = c_key_role(key)
+ if hasattr(key, "table") and key.table in _et:
+ return (key.table.name, str_key) # type: ignore
+ else:
+ return str_key
+
+ def _getattr_col_key(
+ col: ColumnClause[Any],
+ ) -> Union[str, Tuple[str, str]]:
+ if col.table in _et:
+ return (col.table.name, col.key) # type: ignore
+ else:
+ return col.key
+
+ def _col_bind_name(col: ColumnClause[Any]) -> str:
+ if col.table in _et:
+ if TYPE_CHECKING:
+ assert isinstance(col.table, TableClause)
+ return "%s_%s" % (col.table.name, col.key)
+ else:
+ return col.key
+
+ else:
+ _column_as_key = functools.partial(
+ coercions.expect_as_key, roles.DMLColumnRole
+ )
+ _getattr_col_key = _col_bind_name = operator.attrgetter("key") # type: ignore # noqa: E501
+
+ return _column_as_key, _getattr_col_key, _col_bind_name
+
+
+def _scan_insert_from_select_cols(
+ compiler,
+ stmt,
+ compile_state,
+ parameters,
+ _getattr_col_key,
+ _column_as_key,
+ _col_bind_name,
+ check_columns,
+ values,
+ toplevel,
+ kw,
+):
+ cols = [stmt.table.c[_column_as_key(name)] for name in stmt._select_names]
+
+ assert compiler.stack[-1]["selectable"] is stmt
+
+ compiler.stack[-1]["insert_from_select"] = stmt.select
+
+ add_select_cols: List[_CrudParamElementSQLExpr] = []
+ if stmt.include_insert_from_select_defaults:
+ col_set = set(cols)
+ for col in stmt.table.columns:
+ # omit columns that were not in the SELECT statement.
+ # this will omit columns marked as omit_from_statements naturally,
+ # as long as that col was not explicit in the SELECT.
+ # if an omit_from_statements col has a "default" on it, then
+ # we need to include it, as these defaults should still fire off.
+ # but, if it has that default and it's the "sentinel" default,
+ # we don't do sentinel default operations for insert_from_select
+ # here so we again omit it.
+ if (
+ col not in col_set
+ and col.default
+ and not col.default.is_sentinel
+ ):
+ cols.append(col)
+
+ for c in cols:
+ col_key = _getattr_col_key(c)
+ if col_key in parameters and col_key not in check_columns:
+ parameters.pop(col_key)
+ values.append((c, compiler.preparer.format_column(c), None, ()))
+ else:
+ _append_param_insert_select_hasdefault(
+ compiler, stmt, c, add_select_cols, kw
+ )
+
+ if add_select_cols:
+ values.extend(add_select_cols)
+ ins_from_select = compiler.stack[-1]["insert_from_select"]
+ if not isinstance(ins_from_select, Select):
+ raise exc.CompileError(
+ f"Can't extend statement for INSERT..FROM SELECT to include "
+ f"additional default-holding column(s) "
+ f"""{
+ ', '.join(repr(key) for _, key, _, _ in add_select_cols)
+ }. Convert the selectable to a subquery() first, or pass """
+ "include_defaults=False to Insert.from_select() to skip these "
+ "columns."
+ )
+ ins_from_select = ins_from_select._generate()
+ # copy raw_columns
+ ins_from_select._raw_columns = list(ins_from_select._raw_columns) + [
+ expr for _, _, expr, _ in add_select_cols
+ ]
+ compiler.stack[-1]["insert_from_select"] = ins_from_select
+
+
+def _scan_cols(
+ compiler,
+ stmt,
+ compile_state,
+ parameters,
+ _getattr_col_key,
+ _column_as_key,
+ _col_bind_name,
+ check_columns,
+ values,
+ toplevel,
+ kw,
+):
+ (
+ need_pks,
+ implicit_returning,
+ implicit_return_defaults,
+ postfetch_lastrowid,
+ use_insertmanyvalues,
+ use_sentinel_columns,
+ ) = _get_returning_modifiers(compiler, stmt, compile_state, toplevel)
+
+ assert compile_state.isupdate or compile_state.isinsert
+
+ if compile_state._parameter_ordering:
+ parameter_ordering = [
+ _column_as_key(key) for key in compile_state._parameter_ordering
+ ]
+ ordered_keys = set(parameter_ordering)
+ cols = [
+ stmt.table.c[key]
+ for key in parameter_ordering
+ if isinstance(key, str) and key in stmt.table.c
+ ] + [c for c in stmt.table.c if c.key not in ordered_keys]
+
+ else:
+ cols = stmt.table.columns
+
+ isinsert = _compile_state_isinsert(compile_state)
+ if isinsert and not compile_state._has_multi_parameters:
+ # new rules for #7998. fetch lastrowid or implicit returning
+ # for autoincrement column even if parameter is NULL, for DBs that
+ # override NULL param for primary key (sqlite, mysql/mariadb)
+ autoincrement_col = stmt.table._autoincrement_column
+ insert_null_pk_still_autoincrements = (
+ compiler.dialect.insert_null_pk_still_autoincrements
+ )
+ else:
+ autoincrement_col = insert_null_pk_still_autoincrements = None
+
+ if stmt._supplemental_returning:
+ supplemental_returning = set(stmt._supplemental_returning)
+ else:
+ supplemental_returning = set()
+
+ compiler_implicit_returning = compiler.implicit_returning
+
+ # TODO - see TODO(return_defaults_columns) below
+ # cols_in_params = set()
+
+ for c in cols:
+ # scan through every column in the target table
+
+ col_key = _getattr_col_key(c)
+
+ if col_key in parameters and col_key not in check_columns:
+ # parameter is present for the column. use that.
+
+ _append_param_parameter(
+ compiler,
+ stmt,
+ compile_state,
+ c,
+ col_key,
+ parameters,
+ _col_bind_name,
+ implicit_returning,
+ implicit_return_defaults,
+ postfetch_lastrowid,
+ values,
+ autoincrement_col,
+ insert_null_pk_still_autoincrements,
+ kw,
+ )
+
+ # TODO - see TODO(return_defaults_columns) below
+ # cols_in_params.add(c)
+
+ elif isinsert:
+ # no parameter is present and it's an insert.
+
+ if c.primary_key and need_pks:
+ # it's a primary key column, it will need to be generated by a
+ # default generator of some kind, and the statement expects
+ # inserted_primary_key to be available.
+
+ if implicit_returning:
+ # we can use RETURNING, find out how to invoke this
+ # column and get the value where RETURNING is an option.
+ # we can inline server-side functions in this case.
+
+ _append_param_insert_pk_returning(
+ compiler, stmt, c, values, kw
+ )
+ else:
+ # otherwise, find out how to invoke this column
+ # and get its value where RETURNING is not an option.
+ # if we have to invoke a server-side function, we need
+ # to pre-execute it. or if this is a straight
+ # autoincrement column and the dialect supports it
+ # we can use cursor.lastrowid.
+
+ _append_param_insert_pk_no_returning(
+ compiler, stmt, c, values, kw
+ )
+
+ elif c.default is not None:
+ # column has a default, but it's not a pk column, or it is but
+ # we don't need to get the pk back.
+ if not c.default.is_sentinel or (
+ use_sentinel_columns is not None
+ ):
+ _append_param_insert_hasdefault(
+ compiler, stmt, c, implicit_return_defaults, values, kw
+ )
+
+ elif c.server_default is not None:
+ # column has a DDL-level default, and is either not a pk
+ # column or we don't need the pk.
+ if implicit_return_defaults and c in implicit_return_defaults:
+ compiler_implicit_returning.append(c)
+ elif not c.primary_key:
+ compiler.postfetch.append(c)
+
+ elif implicit_return_defaults and c in implicit_return_defaults:
+ compiler_implicit_returning.append(c)
+
+ elif (
+ c.primary_key
+ and c is not stmt.table._autoincrement_column
+ and not c.nullable
+ ):
+ _warn_pk_with_no_anticipated_value(c)
+
+ elif compile_state.isupdate:
+ # no parameter is present and it's an insert.
+
+ _append_param_update(
+ compiler,
+ compile_state,
+ stmt,
+ c,
+ implicit_return_defaults,
+ values,
+ kw,
+ )
+
+ # adding supplemental cols to implicit_returning in table
+ # order so that order is maintained between multiple INSERT
+ # statements which may have different parameters included, but all
+ # have the same RETURNING clause
+ if (
+ c in supplemental_returning
+ and c not in compiler_implicit_returning
+ ):
+ compiler_implicit_returning.append(c)
+
+ if supplemental_returning:
+ # we should have gotten every col into implicit_returning,
+ # however supplemental returning can also have SQL functions etc.
+ # in it
+ remaining_supplemental = supplemental_returning.difference(
+ compiler_implicit_returning
+ )
+ compiler_implicit_returning.extend(
+ c
+ for c in stmt._supplemental_returning
+ if c in remaining_supplemental
+ )
+
+ # TODO(return_defaults_columns): there can still be more columns in
+ # _return_defaults_columns in the case that they are from something like an
+ # aliased of the table. we can add them here, however this breaks other ORM
+ # things. so this is for another day. see
+ # test/orm/dml/test_update_delete_where.py -> test_update_from_alias
+
+ # if stmt._return_defaults_columns:
+ # compiler_implicit_returning.extend(
+ # set(stmt._return_defaults_columns)
+ # .difference(compiler_implicit_returning)
+ # .difference(cols_in_params)
+ # )
+
+ return (use_insertmanyvalues, use_sentinel_columns)
+
+
+def _setup_delete_return_defaults(
+ compiler,
+ stmt,
+ compile_state,
+ parameters,
+ _getattr_col_key,
+ _column_as_key,
+ _col_bind_name,
+ check_columns,
+ values,
+ toplevel,
+ kw,
+):
+ (_, _, implicit_return_defaults, *_) = _get_returning_modifiers(
+ compiler, stmt, compile_state, toplevel
+ )
+
+ if not implicit_return_defaults:
+ return
+
+ if stmt._return_defaults_columns:
+ compiler.implicit_returning.extend(implicit_return_defaults)
+
+ if stmt._supplemental_returning:
+ ir_set = set(compiler.implicit_returning)
+ compiler.implicit_returning.extend(
+ c for c in stmt._supplemental_returning if c not in ir_set
+ )
+
+
+def _append_param_parameter(
+ compiler,
+ stmt,
+ compile_state,
+ c,
+ col_key,
+ parameters,
+ _col_bind_name,
+ implicit_returning,
+ implicit_return_defaults,
+ postfetch_lastrowid,
+ values,
+ autoincrement_col,
+ insert_null_pk_still_autoincrements,
+ kw,
+):
+ value = parameters.pop(col_key)
+
+ col_value = compiler.preparer.format_column(
+ c, use_table=compile_state.include_table_with_column_exprs
+ )
+
+ accumulated_bind_names: Set[str] = set()
+
+ if coercions._is_literal(value):
+ if (
+ insert_null_pk_still_autoincrements
+ and c.primary_key
+ and c is autoincrement_col
+ ):
+ # support use case for #7998, fetch autoincrement cols
+ # even if value was given.
+
+ if postfetch_lastrowid:
+ compiler.postfetch_lastrowid = True
+ elif implicit_returning:
+ compiler.implicit_returning.append(c)
+
+ value = _create_bind_param(
+ compiler,
+ c,
+ value,
+ required=value is REQUIRED,
+ name=(
+ _col_bind_name(c)
+ if not _compile_state_isinsert(compile_state)
+ or not compile_state._has_multi_parameters
+ else "%s_m0" % _col_bind_name(c)
+ ),
+ accumulate_bind_names=accumulated_bind_names,
+ **kw,
+ )
+ elif value._is_bind_parameter:
+ if (
+ insert_null_pk_still_autoincrements
+ and value.value is None
+ and c.primary_key
+ and c is autoincrement_col
+ ):
+ # support use case for #7998, fetch autoincrement cols
+ # even if value was given
+ if implicit_returning:
+ compiler.implicit_returning.append(c)
+ elif compiler.dialect.postfetch_lastrowid:
+ compiler.postfetch_lastrowid = True
+
+ value = _handle_values_anonymous_param(
+ compiler,
+ c,
+ value,
+ name=(
+ _col_bind_name(c)
+ if not _compile_state_isinsert(compile_state)
+ or not compile_state._has_multi_parameters
+ else "%s_m0" % _col_bind_name(c)
+ ),
+ accumulate_bind_names=accumulated_bind_names,
+ **kw,
+ )
+ else:
+ # value is a SQL expression
+ value = compiler.process(
+ value.self_group(),
+ accumulate_bind_names=accumulated_bind_names,
+ **kw,
+ )
+
+ if compile_state.isupdate:
+ if implicit_return_defaults and c in implicit_return_defaults:
+ compiler.implicit_returning.append(c)
+
+ else:
+ compiler.postfetch.append(c)
+ else:
+ if c.primary_key:
+ if implicit_returning:
+ compiler.implicit_returning.append(c)
+ elif compiler.dialect.postfetch_lastrowid:
+ compiler.postfetch_lastrowid = True
+
+ elif implicit_return_defaults and (c in implicit_return_defaults):
+ compiler.implicit_returning.append(c)
+
+ else:
+ # postfetch specifically means, "we can SELECT the row we just
+ # inserted by primary key to get back the server generated
+ # defaults". so by definition this can't be used to get the
+ # primary key value back, because we need to have it ahead of
+ # time.
+
+ compiler.postfetch.append(c)
+
+ values.append((c, col_value, value, accumulated_bind_names))
+
+
+def _append_param_insert_pk_returning(compiler, stmt, c, values, kw):
+ """Create a primary key expression in the INSERT statement where
+ we want to populate result.inserted_primary_key and RETURNING
+ is available.
+
+ """
+ if c.default is not None:
+ if c.default.is_sequence:
+ if compiler.dialect.supports_sequences and (
+ not c.default.optional
+ or not compiler.dialect.sequences_optional
+ ):
+ accumulated_bind_names: Set[str] = set()
+ values.append(
+ (
+ c,
+ compiler.preparer.format_column(c),
+ compiler.process(
+ c.default,
+ accumulate_bind_names=accumulated_bind_names,
+ **kw,
+ ),
+ accumulated_bind_names,
+ )
+ )
+ compiler.implicit_returning.append(c)
+ elif c.default.is_clause_element:
+ accumulated_bind_names = set()
+ values.append(
+ (
+ c,
+ compiler.preparer.format_column(c),
+ compiler.process(
+ c.default.arg.self_group(),
+ accumulate_bind_names=accumulated_bind_names,
+ **kw,
+ ),
+ accumulated_bind_names,
+ )
+ )
+ compiler.implicit_returning.append(c)
+ else:
+ # client side default. OK we can't use RETURNING, need to
+ # do a "prefetch", which in fact fetches the default value
+ # on the Python side
+ values.append(
+ (
+ c,
+ compiler.preparer.format_column(c),
+ _create_insert_prefetch_bind_param(compiler, c, **kw),
+ (c.key,),
+ )
+ )
+ elif c is stmt.table._autoincrement_column or c.server_default is not None:
+ compiler.implicit_returning.append(c)
+ elif not c.nullable:
+ # no .default, no .server_default, not autoincrement, we have
+ # no indication this primary key column will have any value
+ _warn_pk_with_no_anticipated_value(c)
+
+
+def _append_param_insert_pk_no_returning(compiler, stmt, c, values, kw):
+ """Create a primary key expression in the INSERT statement where
+ we want to populate result.inserted_primary_key and we cannot use
+ RETURNING.
+
+ Depending on the kind of default here we may create a bound parameter
+ in the INSERT statement and pre-execute a default generation function,
+ or we may use cursor.lastrowid if supported by the dialect.
+
+
+ """
+
+ if (
+ # column has a Python-side default
+ c.default is not None
+ and (
+ # and it either is not a sequence, or it is and we support
+ # sequences and want to invoke it
+ not c.default.is_sequence
+ or (
+ compiler.dialect.supports_sequences
+ and (
+ not c.default.optional
+ or not compiler.dialect.sequences_optional
+ )
+ )
+ )
+ ) or (
+ # column is the "autoincrement column"
+ c is stmt.table._autoincrement_column
+ and (
+ # dialect can't use cursor.lastrowid
+ not compiler.dialect.postfetch_lastrowid
+ and (
+ # column has a Sequence and we support those
+ (
+ c.default is not None
+ and c.default.is_sequence
+ and compiler.dialect.supports_sequences
+ )
+ or
+ # column has no default on it, but dialect can run the
+ # "autoincrement" mechanism explicitly, e.g. PostgreSQL
+ # SERIAL we know the sequence name
+ (
+ c.default is None
+ and compiler.dialect.preexecute_autoincrement_sequences
+ )
+ )
+ )
+ ):
+ # do a pre-execute of the default
+ values.append(
+ (
+ c,
+ compiler.preparer.format_column(c),
+ _create_insert_prefetch_bind_param(compiler, c, **kw),
+ (c.key,),
+ )
+ )
+ elif (
+ c.default is None
+ and c.server_default is None
+ and not c.nullable
+ and c is not stmt.table._autoincrement_column
+ ):
+ # no .default, no .server_default, not autoincrement, we have
+ # no indication this primary key column will have any value
+ _warn_pk_with_no_anticipated_value(c)
+ elif compiler.dialect.postfetch_lastrowid:
+ # finally, where it seems like there will be a generated primary key
+ # value and we haven't set up any other way to fetch it, and the
+ # dialect supports cursor.lastrowid, switch on the lastrowid flag so
+ # that the DefaultExecutionContext calls upon cursor.lastrowid
+ compiler.postfetch_lastrowid = True
+
+
+def _append_param_insert_hasdefault(
+ compiler, stmt, c, implicit_return_defaults, values, kw
+):
+ if c.default.is_sequence:
+ if compiler.dialect.supports_sequences and (
+ not c.default.optional or not compiler.dialect.sequences_optional
+ ):
+ accumulated_bind_names: Set[str] = set()
+ values.append(
+ (
+ c,
+ compiler.preparer.format_column(c),
+ compiler.process(
+ c.default,
+ accumulate_bind_names=accumulated_bind_names,
+ **kw,
+ ),
+ accumulated_bind_names,
+ )
+ )
+ if implicit_return_defaults and c in implicit_return_defaults:
+ compiler.implicit_returning.append(c)
+ elif not c.primary_key:
+ compiler.postfetch.append(c)
+ elif c.default.is_clause_element:
+ accumulated_bind_names = set()
+ values.append(
+ (
+ c,
+ compiler.preparer.format_column(c),
+ compiler.process(
+ c.default.arg.self_group(),
+ accumulate_bind_names=accumulated_bind_names,
+ **kw,
+ ),
+ accumulated_bind_names,
+ )
+ )
+
+ if implicit_return_defaults and c in implicit_return_defaults:
+ compiler.implicit_returning.append(c)
+ elif not c.primary_key:
+ # don't add primary key column to postfetch
+ compiler.postfetch.append(c)
+ else:
+ values.append(
+ (
+ c,
+ compiler.preparer.format_column(c),
+ _create_insert_prefetch_bind_param(compiler, c, **kw),
+ (c.key,),
+ )
+ )
+
+
+def _append_param_insert_select_hasdefault(
+ compiler: SQLCompiler,
+ stmt: ValuesBase,
+ c: ColumnClause[Any],
+ values: List[_CrudParamElementSQLExpr],
+ kw: Dict[str, Any],
+) -> None:
+ if default_is_sequence(c.default):
+ if compiler.dialect.supports_sequences and (
+ not c.default.optional or not compiler.dialect.sequences_optional
+ ):
+ values.append(
+ (
+ c,
+ compiler.preparer.format_column(c),
+ c.default.next_value(),
+ (),
+ )
+ )
+ elif default_is_clause_element(c.default):
+ values.append(
+ (
+ c,
+ compiler.preparer.format_column(c),
+ c.default.arg.self_group(),
+ (),
+ )
+ )
+ else:
+ values.append(
+ (
+ c,
+ compiler.preparer.format_column(c),
+ _create_insert_prefetch_bind_param(
+ compiler, c, process=False, **kw
+ ),
+ (c.key,),
+ )
+ )
+
+
+def _append_param_update(
+ compiler, compile_state, stmt, c, implicit_return_defaults, values, kw
+):
+ include_table = compile_state.include_table_with_column_exprs
+ if c.onupdate is not None and not c.onupdate.is_sequence:
+ if c.onupdate.is_clause_element:
+ values.append(
+ (
+ c,
+ compiler.preparer.format_column(
+ c,
+ use_table=include_table,
+ ),
+ compiler.process(c.onupdate.arg.self_group(), **kw),
+ (),
+ )
+ )
+ if implicit_return_defaults and c in implicit_return_defaults:
+ compiler.implicit_returning.append(c)
+ else:
+ compiler.postfetch.append(c)
+ else:
+ values.append(
+ (
+ c,
+ compiler.preparer.format_column(
+ c,
+ use_table=include_table,
+ ),
+ _create_update_prefetch_bind_param(compiler, c, **kw),
+ (c.key,),
+ )
+ )
+ elif c.server_onupdate is not None:
+ if implicit_return_defaults and c in implicit_return_defaults:
+ compiler.implicit_returning.append(c)
+ else:
+ compiler.postfetch.append(c)
+ elif (
+ implicit_return_defaults
+ and (stmt._return_defaults_columns or not stmt._return_defaults)
+ and c in implicit_return_defaults
+ ):
+ compiler.implicit_returning.append(c)
+
+
+@overload
+def _create_insert_prefetch_bind_param(
+ compiler: SQLCompiler,
+ c: ColumnElement[Any],
+ process: Literal[True] = ...,
+ **kw: Any,
+) -> str: ...
+
+
+@overload
+def _create_insert_prefetch_bind_param(
+ compiler: SQLCompiler,
+ c: ColumnElement[Any],
+ process: Literal[False],
+ **kw: Any,
+) -> elements.BindParameter[Any]: ...
+
+
+def _create_insert_prefetch_bind_param(
+ compiler: SQLCompiler,
+ c: ColumnElement[Any],
+ process: bool = True,
+ name: Optional[str] = None,
+ **kw: Any,
+) -> Union[elements.BindParameter[Any], str]:
+ param = _create_bind_param(
+ compiler, c, None, process=process, name=name, **kw
+ )
+ compiler.insert_prefetch.append(c) # type: ignore
+ return param
+
+
+@overload
+def _create_update_prefetch_bind_param(
+ compiler: SQLCompiler,
+ c: ColumnElement[Any],
+ process: Literal[True] = ...,
+ **kw: Any,
+) -> str: ...
+
+
+@overload
+def _create_update_prefetch_bind_param(
+ compiler: SQLCompiler,
+ c: ColumnElement[Any],
+ process: Literal[False],
+ **kw: Any,
+) -> elements.BindParameter[Any]: ...
+
+
+def _create_update_prefetch_bind_param(
+ compiler: SQLCompiler,
+ c: ColumnElement[Any],
+ process: bool = True,
+ name: Optional[str] = None,
+ **kw: Any,
+) -> Union[elements.BindParameter[Any], str]:
+ param = _create_bind_param(
+ compiler, c, None, process=process, name=name, **kw
+ )
+ compiler.update_prefetch.append(c) # type: ignore
+ return param
+
+
+class _multiparam_column(elements.ColumnElement[Any]):
+ _is_multiparam_column = True
+
+ def __init__(self, original, index):
+ self.index = index
+ self.key = "%s_m%d" % (original.key, index + 1)
+ self.original = original
+ self.default = original.default
+ self.type = original.type
+
+ def compare(self, other, **kw):
+ raise NotImplementedError()
+
+ def _copy_internals(self, other, **kw):
+ raise NotImplementedError()
+
+ def __eq__(self, other):
+ return (
+ isinstance(other, _multiparam_column)
+ and other.key == self.key
+ and other.original == self.original
+ )
+
+ @util.memoized_property
+ def _default_description_tuple(self) -> _DefaultDescriptionTuple:
+ """used by default.py -> _process_execute_defaults()"""
+
+ return _DefaultDescriptionTuple._from_column_default(self.default)
+
+ @util.memoized_property
+ def _onupdate_description_tuple(self) -> _DefaultDescriptionTuple:
+ """used by default.py -> _process_execute_defaults()"""
+
+ return _DefaultDescriptionTuple._from_column_default(self.onupdate)
+
+
+def _process_multiparam_default_bind(
+ compiler: SQLCompiler,
+ stmt: ValuesBase,
+ c: KeyedColumnElement[Any],
+ index: int,
+ kw: Dict[str, Any],
+) -> str:
+ if not c.default:
+ raise exc.CompileError(
+ "INSERT value for column %s is explicitly rendered as a bound"
+ "parameter in the VALUES clause; "
+ "a Python-side value or SQL expression is required" % c
+ )
+ elif default_is_clause_element(c.default):
+ return compiler.process(c.default.arg.self_group(), **kw)
+ elif c.default.is_sequence:
+ # these conditions would have been established
+ # by append_param_insert_(?:hasdefault|pk_returning|pk_no_returning)
+ # in order for us to be here, so these don't need to be
+ # checked
+ # assert compiler.dialect.supports_sequences and (
+ # not c.default.optional
+ # or not compiler.dialect.sequences_optional
+ # )
+ return compiler.process(c.default, **kw)
+ else:
+ col = _multiparam_column(c, index)
+ assert isinstance(stmt, dml.Insert)
+ return _create_insert_prefetch_bind_param(
+ compiler, col, process=True, **kw
+ )
+
+
+def _get_update_multitable_params(
+ compiler,
+ stmt,
+ compile_state,
+ stmt_parameter_tuples,
+ check_columns,
+ _col_bind_name,
+ _getattr_col_key,
+ values,
+ kw,
+):
+ normalized_params = {
+ coercions.expect(roles.DMLColumnRole, c): param
+ for c, param in stmt_parameter_tuples or ()
+ }
+
+ include_table = compile_state.include_table_with_column_exprs
+
+ affected_tables = set()
+ for t in compile_state._extra_froms:
+ for c in t.c:
+ if c in normalized_params:
+ affected_tables.add(t)
+ check_columns[_getattr_col_key(c)] = c
+ value = normalized_params[c]
+
+ col_value = compiler.process(c, include_table=include_table)
+ if coercions._is_literal(value):
+ value = _create_bind_param(
+ compiler,
+ c,
+ value,
+ required=value is REQUIRED,
+ name=_col_bind_name(c),
+ **kw, # TODO: no test coverage for literal binds here
+ )
+ accumulated_bind_names: Iterable[str] = (c.key,)
+ elif value._is_bind_parameter:
+ cbn = _col_bind_name(c)
+ value = _handle_values_anonymous_param(
+ compiler, c, value, name=cbn, **kw
+ )
+ accumulated_bind_names = (cbn,)
+ else:
+ compiler.postfetch.append(c)
+ value = compiler.process(value.self_group(), **kw)
+ accumulated_bind_names = ()
+ values.append((c, col_value, value, accumulated_bind_names))
+ # determine tables which are actually to be updated - process onupdate
+ # and server_onupdate for these
+ for t in affected_tables:
+ for c in t.c:
+ if c in normalized_params:
+ continue
+ elif c.onupdate is not None and not c.onupdate.is_sequence:
+ if c.onupdate.is_clause_element:
+ values.append(
+ (
+ c,
+ compiler.process(c, include_table=include_table),
+ compiler.process(
+ c.onupdate.arg.self_group(), **kw
+ ),
+ (),
+ )
+ )
+ compiler.postfetch.append(c)
+ else:
+ values.append(
+ (
+ c,
+ compiler.process(c, include_table=include_table),
+ _create_update_prefetch_bind_param(
+ compiler, c, name=_col_bind_name(c), **kw
+ ),
+ (c.key,),
+ )
+ )
+ elif c.server_onupdate is not None:
+ compiler.postfetch.append(c)
+
+
+def _extend_values_for_multiparams(
+ compiler: SQLCompiler,
+ stmt: ValuesBase,
+ compile_state: DMLState,
+ initial_values: Sequence[_CrudParamElementStr],
+ _column_as_key: Callable[..., str],
+ kw: Dict[str, Any],
+) -> List[Sequence[_CrudParamElementStr]]:
+ values_0 = initial_values
+ values = [initial_values]
+
+ mp = compile_state._multi_parameters
+ assert mp is not None
+ for i, row in enumerate(mp[1:]):
+ extension: List[_CrudParamElementStr] = []
+
+ row = {_column_as_key(key): v for key, v in row.items()}
+
+ for col, col_expr, param, accumulated_names in values_0:
+ if col.key in row:
+ key = col.key
+
+ if coercions._is_literal(row[key]):
+ new_param = _create_bind_param(
+ compiler,
+ col,
+ row[key],
+ name="%s_m%d" % (col.key, i + 1),
+ **kw,
+ )
+ else:
+ new_param = compiler.process(row[key].self_group(), **kw)
+ else:
+ new_param = _process_multiparam_default_bind(
+ compiler, stmt, col, i, kw
+ )
+
+ extension.append((col, col_expr, new_param, accumulated_names))
+
+ values.append(extension)
+
+ return values
+
+
+def _get_stmt_parameter_tuples_params(
+ compiler,
+ compile_state,
+ parameters,
+ stmt_parameter_tuples,
+ _column_as_key,
+ values,
+ kw,
+):
+ for k, v in stmt_parameter_tuples:
+ colkey = _column_as_key(k)
+ if colkey is not None:
+ parameters.setdefault(colkey, v)
+ else:
+ # a non-Column expression on the left side;
+ # add it to values() in an "as-is" state,
+ # coercing right side to bound param
+
+ # note one of the main use cases for this is array slice
+ # updates on PostgreSQL, as the left side is also an expression.
+
+ col_expr = compiler.process(
+ k, include_table=compile_state.include_table_with_column_exprs
+ )
+
+ if coercions._is_literal(v):
+ v = compiler.process(
+ elements.BindParameter(None, v, type_=k.type), **kw
+ )
+ else:
+ if v._is_bind_parameter and v.type._isnull:
+ # either unique parameter, or other bound parameters that
+ # were passed in directly
+ # set type to that of the column unconditionally
+ v = v._with_binary_element_type(k.type)
+
+ v = compiler.process(v.self_group(), **kw)
+
+ # TODO: not sure if accumulated_bind_names applies here
+ values.append((k, col_expr, v, ()))
+
+
+def _get_returning_modifiers(compiler, stmt, compile_state, toplevel):
+ """determines RETURNING strategy, if any, for the statement.
+
+ This is where it's determined what we need to fetch from the
+ INSERT or UPDATE statement after it's invoked.
+
+ """
+
+ dialect = compiler.dialect
+
+ need_pks = (
+ toplevel
+ and _compile_state_isinsert(compile_state)
+ and not stmt._inline
+ and (
+ not compiler.for_executemany
+ or (dialect.insert_executemany_returning and stmt._return_defaults)
+ )
+ and not stmt._returning
+ # and (not stmt._returning or stmt._return_defaults)
+ and not compile_state._has_multi_parameters
+ )
+
+ # check if we have access to simple cursor.lastrowid. we can use that
+ # after the INSERT if that's all we need.
+ postfetch_lastrowid = (
+ need_pks
+ and dialect.postfetch_lastrowid
+ and stmt.table._autoincrement_column is not None
+ )
+
+ # see if we want to add RETURNING to an INSERT in order to get
+ # primary key columns back. This would be instead of postfetch_lastrowid
+ # if that's set.
+ implicit_returning = (
+ # statement itself can veto it
+ need_pks
+ # the dialect can veto it if it just doesnt support RETURNING
+ # with INSERT
+ and dialect.insert_returning
+ # user-defined implicit_returning on Table can veto it
+ and compile_state._primary_table.implicit_returning
+ # the compile_state can veto it (SQlite uses this to disable
+ # RETURNING for an ON CONFLICT insert, as SQLite does not return
+ # for rows that were updated, which is wrong)
+ and compile_state._supports_implicit_returning
+ and (
+ # since we support MariaDB and SQLite which also support lastrowid,
+ # decide if we should use lastrowid or RETURNING. for insert
+ # that didnt call return_defaults() and has just one set of
+ # parameters, we can use lastrowid. this is more "traditional"
+ # and a lot of weird use cases are supported by it.
+ # SQLite lastrowid times 3x faster than returning,
+ # Mariadb lastrowid 2x faster than returning
+ (not postfetch_lastrowid or dialect.favor_returning_over_lastrowid)
+ or compile_state._has_multi_parameters
+ or stmt._return_defaults
+ )
+ )
+ if implicit_returning:
+ postfetch_lastrowid = False
+
+ if _compile_state_isinsert(compile_state):
+ should_implicit_return_defaults = (
+ implicit_returning and stmt._return_defaults
+ )
+ explicit_returning = (
+ should_implicit_return_defaults
+ or stmt._returning
+ or stmt._supplemental_returning
+ )
+ use_insertmanyvalues = (
+ toplevel
+ and compiler.for_executemany
+ and dialect.use_insertmanyvalues
+ and (
+ explicit_returning or dialect.use_insertmanyvalues_wo_returning
+ )
+ )
+
+ use_sentinel_columns = None
+ if (
+ use_insertmanyvalues
+ and explicit_returning
+ and stmt._sort_by_parameter_order
+ ):
+ use_sentinel_columns = compiler._get_sentinel_column_for_table(
+ stmt.table
+ )
+
+ elif compile_state.isupdate:
+ should_implicit_return_defaults = (
+ stmt._return_defaults
+ and compile_state._primary_table.implicit_returning
+ and compile_state._supports_implicit_returning
+ and dialect.update_returning
+ )
+ use_insertmanyvalues = False
+ use_sentinel_columns = None
+ elif compile_state.isdelete:
+ should_implicit_return_defaults = (
+ stmt._return_defaults
+ and compile_state._primary_table.implicit_returning
+ and compile_state._supports_implicit_returning
+ and dialect.delete_returning
+ )
+ use_insertmanyvalues = False
+ use_sentinel_columns = None
+ else:
+ should_implicit_return_defaults = False # pragma: no cover
+ use_insertmanyvalues = False
+ use_sentinel_columns = None
+
+ if should_implicit_return_defaults:
+ if not stmt._return_defaults_columns:
+ # TODO: this is weird. See #9685 where we have to
+ # take an extra step to prevent this from happening. why
+ # would this ever be *all* columns? but if we set to blank, then
+ # that seems to break things also in the ORM. So we should
+ # try to clean this up and figure out what return_defaults
+ # needs to do w/ the ORM etc. here
+ implicit_return_defaults = set(stmt.table.c)
+ else:
+ implicit_return_defaults = set(stmt._return_defaults_columns)
+ else:
+ implicit_return_defaults = None
+
+ return (
+ need_pks,
+ implicit_returning or should_implicit_return_defaults,
+ implicit_return_defaults,
+ postfetch_lastrowid,
+ use_insertmanyvalues,
+ use_sentinel_columns,
+ )
+
+
+def _warn_pk_with_no_anticipated_value(c):
+ msg = (
+ "Column '%s.%s' is marked as a member of the "
+ "primary key for table '%s', "
+ "but has no Python-side or server-side default generator indicated, "
+ "nor does it indicate 'autoincrement=True' or 'nullable=True', "
+ "and no explicit value is passed. "
+ "Primary key columns typically may not store NULL."
+ % (c.table.fullname, c.name, c.table.fullname)
+ )
+ if len(c.table.primary_key) > 1:
+ msg += (
+ " Note that as of SQLAlchemy 1.1, 'autoincrement=True' must be "
+ "indicated explicitly for composite (e.g. multicolumn) primary "
+ "keys if AUTO_INCREMENT/SERIAL/IDENTITY "
+ "behavior is expected for one of the columns in the primary key. "
+ "CREATE TABLE statements are impacted by this change as well on "
+ "most backends."
+ )
+ util.warn(msg)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/ddl.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/ddl.py
new file mode 100644
index 0000000..d9e3f67
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/ddl.py
@@ -0,0 +1,1378 @@
+# sql/ddl.py
+# Copyright (C) 2009-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+# mypy: allow-untyped-defs, allow-untyped-calls
+
+"""
+Provides the hierarchy of DDL-defining schema items as well as routines
+to invoke them for a create/drop call.
+
+"""
+from __future__ import annotations
+
+import contextlib
+import typing
+from typing import Any
+from typing import Callable
+from typing import Iterable
+from typing import List
+from typing import Optional
+from typing import Sequence as typing_Sequence
+from typing import Tuple
+
+from . import roles
+from .base import _generative
+from .base import Executable
+from .base import SchemaVisitor
+from .elements import ClauseElement
+from .. import exc
+from .. import util
+from ..util import topological
+from ..util.typing import Protocol
+from ..util.typing import Self
+
+if typing.TYPE_CHECKING:
+ from .compiler import Compiled
+ from .compiler import DDLCompiler
+ from .elements import BindParameter
+ from .schema import Constraint
+ from .schema import ForeignKeyConstraint
+ from .schema import SchemaItem
+ from .schema import Sequence
+ from .schema import Table
+ from .selectable import TableClause
+ from ..engine.base import Connection
+ from ..engine.interfaces import CacheStats
+ from ..engine.interfaces import CompiledCacheType
+ from ..engine.interfaces import Dialect
+ from ..engine.interfaces import SchemaTranslateMapType
+
+
+class BaseDDLElement(ClauseElement):
+ """The root of DDL constructs, including those that are sub-elements
+ within the "create table" and other processes.
+
+ .. versionadded:: 2.0
+
+ """
+
+ _hierarchy_supports_caching = False
+ """disable cache warnings for all _DDLCompiles subclasses. """
+
+ def _compiler(self, dialect, **kw):
+ """Return a compiler appropriate for this ClauseElement, given a
+ Dialect."""
+
+ return dialect.ddl_compiler(dialect, self, **kw)
+
+ def _compile_w_cache(
+ self,
+ dialect: Dialect,
+ *,
+ compiled_cache: Optional[CompiledCacheType],
+ column_keys: List[str],
+ for_executemany: bool = False,
+ schema_translate_map: Optional[SchemaTranslateMapType] = None,
+ **kw: Any,
+ ) -> Tuple[
+ Compiled, Optional[typing_Sequence[BindParameter[Any]]], CacheStats
+ ]:
+ raise NotImplementedError()
+
+
+class DDLIfCallable(Protocol):
+ def __call__(
+ self,
+ ddl: BaseDDLElement,
+ target: SchemaItem,
+ bind: Optional[Connection],
+ tables: Optional[List[Table]] = None,
+ state: Optional[Any] = None,
+ *,
+ dialect: Dialect,
+ compiler: Optional[DDLCompiler] = ...,
+ checkfirst: bool,
+ ) -> bool: ...
+
+
+class DDLIf(typing.NamedTuple):
+ dialect: Optional[str]
+ callable_: Optional[DDLIfCallable]
+ state: Optional[Any]
+
+ def _should_execute(
+ self,
+ ddl: BaseDDLElement,
+ target: SchemaItem,
+ bind: Optional[Connection],
+ compiler: Optional[DDLCompiler] = None,
+ **kw: Any,
+ ) -> bool:
+ if bind is not None:
+ dialect = bind.dialect
+ elif compiler is not None:
+ dialect = compiler.dialect
+ else:
+ assert False, "compiler or dialect is required"
+
+ if isinstance(self.dialect, str):
+ if self.dialect != dialect.name:
+ return False
+ elif isinstance(self.dialect, (tuple, list, set)):
+ if dialect.name not in self.dialect:
+ return False
+ if self.callable_ is not None and not self.callable_(
+ ddl,
+ target,
+ bind,
+ state=self.state,
+ dialect=dialect,
+ compiler=compiler,
+ **kw,
+ ):
+ return False
+
+ return True
+
+
+class ExecutableDDLElement(roles.DDLRole, Executable, BaseDDLElement):
+ """Base class for standalone executable DDL expression constructs.
+
+ This class is the base for the general purpose :class:`.DDL` class,
+ as well as the various create/drop clause constructs such as
+ :class:`.CreateTable`, :class:`.DropTable`, :class:`.AddConstraint`,
+ etc.
+
+ .. versionchanged:: 2.0 :class:`.ExecutableDDLElement` is renamed from
+ :class:`.DDLElement`, which still exists for backwards compatibility.
+
+ :class:`.ExecutableDDLElement` integrates closely with SQLAlchemy events,
+ introduced in :ref:`event_toplevel`. An instance of one is
+ itself an event receiving callable::
+
+ event.listen(
+ users,
+ 'after_create',
+ AddConstraint(constraint).execute_if(dialect='postgresql')
+ )
+
+ .. seealso::
+
+ :class:`.DDL`
+
+ :class:`.DDLEvents`
+
+ :ref:`event_toplevel`
+
+ :ref:`schema_ddl_sequences`
+
+ """
+
+ _ddl_if: Optional[DDLIf] = None
+ target: Optional[SchemaItem] = None
+
+ def _execute_on_connection(
+ self, connection, distilled_params, execution_options
+ ):
+ return connection._execute_ddl(
+ self, distilled_params, execution_options
+ )
+
+ @_generative
+ def against(self, target: SchemaItem) -> Self:
+ """Return a copy of this :class:`_schema.ExecutableDDLElement` which
+ will include the given target.
+
+ This essentially applies the given item to the ``.target`` attribute of
+ the returned :class:`_schema.ExecutableDDLElement` object. This target
+ is then usable by event handlers and compilation routines in order to
+ provide services such as tokenization of a DDL string in terms of a
+ particular :class:`_schema.Table`.
+
+ When a :class:`_schema.ExecutableDDLElement` object is established as
+ an event handler for the :meth:`_events.DDLEvents.before_create` or
+ :meth:`_events.DDLEvents.after_create` events, and the event then
+ occurs for a given target such as a :class:`_schema.Constraint` or
+ :class:`_schema.Table`, that target is established with a copy of the
+ :class:`_schema.ExecutableDDLElement` object using this method, which
+ then proceeds to the :meth:`_schema.ExecutableDDLElement.execute`
+ method in order to invoke the actual DDL instruction.
+
+ :param target: a :class:`_schema.SchemaItem` that will be the subject
+ of a DDL operation.
+
+ :return: a copy of this :class:`_schema.ExecutableDDLElement` with the
+ ``.target`` attribute assigned to the given
+ :class:`_schema.SchemaItem`.
+
+ .. seealso::
+
+ :class:`_schema.DDL` - uses tokenization against the "target" when
+ processing the DDL string.
+
+ """
+ self.target = target
+ return self
+
+ @_generative
+ def execute_if(
+ self,
+ dialect: Optional[str] = None,
+ callable_: Optional[DDLIfCallable] = None,
+ state: Optional[Any] = None,
+ ) -> Self:
+ r"""Return a callable that will execute this
+ :class:`_ddl.ExecutableDDLElement` conditionally within an event
+ handler.
+
+ Used to provide a wrapper for event listening::
+
+ event.listen(
+ metadata,
+ 'before_create',
+ DDL("my_ddl").execute_if(dialect='postgresql')
+ )
+
+ :param dialect: May be a string or tuple of strings.
+ If a string, it will be compared to the name of the
+ executing database dialect::
+
+ DDL('something').execute_if(dialect='postgresql')
+
+ If a tuple, specifies multiple dialect names::
+
+ DDL('something').execute_if(dialect=('postgresql', 'mysql'))
+
+ :param callable\_: A callable, which will be invoked with
+ three positional arguments as well as optional keyword
+ arguments:
+
+ :ddl:
+ This DDL element.
+
+ :target:
+ The :class:`_schema.Table` or :class:`_schema.MetaData`
+ object which is the
+ target of this event. May be None if the DDL is executed
+ explicitly.
+
+ :bind:
+ The :class:`_engine.Connection` being used for DDL execution.
+ May be None if this construct is being created inline within
+ a table, in which case ``compiler`` will be present.
+
+ :tables:
+ Optional keyword argument - a list of Table objects which are to
+ be created/ dropped within a MetaData.create_all() or drop_all()
+ method call.
+
+ :dialect: keyword argument, but always present - the
+ :class:`.Dialect` involved in the operation.
+
+ :compiler: keyword argument. Will be ``None`` for an engine
+ level DDL invocation, but will refer to a :class:`.DDLCompiler`
+ if this DDL element is being created inline within a table.
+
+ :state:
+ Optional keyword argument - will be the ``state`` argument
+ passed to this function.
+
+ :checkfirst:
+ Keyword argument, will be True if the 'checkfirst' flag was
+ set during the call to ``create()``, ``create_all()``,
+ ``drop()``, ``drop_all()``.
+
+ If the callable returns a True value, the DDL statement will be
+ executed.
+
+ :param state: any value which will be passed to the callable\_
+ as the ``state`` keyword argument.
+
+ .. seealso::
+
+ :meth:`.SchemaItem.ddl_if`
+
+ :class:`.DDLEvents`
+
+ :ref:`event_toplevel`
+
+ """
+ self._ddl_if = DDLIf(dialect, callable_, state)
+ return self
+
+ def _should_execute(self, target, bind, **kw):
+ if self._ddl_if is None:
+ return True
+ else:
+ return self._ddl_if._should_execute(self, target, bind, **kw)
+
+ def _invoke_with(self, bind):
+ if self._should_execute(self.target, bind):
+ return bind.execute(self)
+
+ def __call__(self, target, bind, **kw):
+ """Execute the DDL as a ddl_listener."""
+
+ self.against(target)._invoke_with(bind)
+
+ def _generate(self):
+ s = self.__class__.__new__(self.__class__)
+ s.__dict__ = self.__dict__.copy()
+ return s
+
+
+DDLElement = ExecutableDDLElement
+""":class:`.DDLElement` is renamed to :class:`.ExecutableDDLElement`."""
+
+
+class DDL(ExecutableDDLElement):
+ """A literal DDL statement.
+
+ Specifies literal SQL DDL to be executed by the database. DDL objects
+ function as DDL event listeners, and can be subscribed to those events
+ listed in :class:`.DDLEvents`, using either :class:`_schema.Table` or
+ :class:`_schema.MetaData` objects as targets.
+ Basic templating support allows
+ a single DDL instance to handle repetitive tasks for multiple tables.
+
+ Examples::
+
+ from sqlalchemy import event, DDL
+
+ tbl = Table('users', metadata, Column('uid', Integer))
+ event.listen(tbl, 'before_create', DDL('DROP TRIGGER users_trigger'))
+
+ spow = DDL('ALTER TABLE %(table)s SET secretpowers TRUE')
+ event.listen(tbl, 'after_create', spow.execute_if(dialect='somedb'))
+
+ drop_spow = DDL('ALTER TABLE users SET secretpowers FALSE')
+ connection.execute(drop_spow)
+
+ When operating on Table events, the following ``statement``
+ string substitutions are available::
+
+ %(table)s - the Table name, with any required quoting applied
+ %(schema)s - the schema name, with any required quoting applied
+ %(fullname)s - the Table name including schema, quoted if needed
+
+ The DDL's "context", if any, will be combined with the standard
+ substitutions noted above. Keys present in the context will override
+ the standard substitutions.
+
+ """
+
+ __visit_name__ = "ddl"
+
+ def __init__(self, statement, context=None):
+ """Create a DDL statement.
+
+ :param statement:
+ A string or unicode string to be executed. Statements will be
+ processed with Python's string formatting operator using
+ a fixed set of string substitutions, as well as additional
+ substitutions provided by the optional :paramref:`.DDL.context`
+ parameter.
+
+ A literal '%' in a statement must be escaped as '%%'.
+
+ SQL bind parameters are not available in DDL statements.
+
+ :param context:
+ Optional dictionary, defaults to None. These values will be
+ available for use in string substitutions on the DDL statement.
+
+ .. seealso::
+
+ :class:`.DDLEvents`
+
+ :ref:`event_toplevel`
+
+ """
+
+ if not isinstance(statement, str):
+ raise exc.ArgumentError(
+ "Expected a string or unicode SQL statement, got '%r'"
+ % statement
+ )
+
+ self.statement = statement
+ self.context = context or {}
+
+ def __repr__(self):
+ parts = [repr(self.statement)]
+ if self.context:
+ parts.append(f"context={self.context}")
+
+ return "<%s@%s; %s>" % (
+ type(self).__name__,
+ id(self),
+ ", ".join(parts),
+ )
+
+
+class _CreateDropBase(ExecutableDDLElement):
+ """Base class for DDL constructs that represent CREATE and DROP or
+ equivalents.
+
+ The common theme of _CreateDropBase is a single
+ ``element`` attribute which refers to the element
+ to be created or dropped.
+
+ """
+
+ def __init__(
+ self,
+ element,
+ ):
+ self.element = self.target = element
+ self._ddl_if = getattr(element, "_ddl_if", None)
+
+ @property
+ def stringify_dialect(self):
+ return self.element.create_drop_stringify_dialect
+
+ def _create_rule_disable(self, compiler):
+ """Allow disable of _create_rule using a callable.
+
+ Pass to _create_rule using
+ util.portable_instancemethod(self._create_rule_disable)
+ to retain serializability.
+
+ """
+ return False
+
+
+class _CreateBase(_CreateDropBase):
+ def __init__(self, element, if_not_exists=False):
+ super().__init__(element)
+ self.if_not_exists = if_not_exists
+
+
+class _DropBase(_CreateDropBase):
+ def __init__(self, element, if_exists=False):
+ super().__init__(element)
+ self.if_exists = if_exists
+
+
+class CreateSchema(_CreateBase):
+ """Represent a CREATE SCHEMA statement.
+
+ The argument here is the string name of the schema.
+
+ """
+
+ __visit_name__ = "create_schema"
+
+ stringify_dialect = "default"
+
+ def __init__(
+ self,
+ name,
+ if_not_exists=False,
+ ):
+ """Create a new :class:`.CreateSchema` construct."""
+
+ super().__init__(element=name, if_not_exists=if_not_exists)
+
+
+class DropSchema(_DropBase):
+ """Represent a DROP SCHEMA statement.
+
+ The argument here is the string name of the schema.
+
+ """
+
+ __visit_name__ = "drop_schema"
+
+ stringify_dialect = "default"
+
+ def __init__(
+ self,
+ name,
+ cascade=False,
+ if_exists=False,
+ ):
+ """Create a new :class:`.DropSchema` construct."""
+
+ super().__init__(element=name, if_exists=if_exists)
+ self.cascade = cascade
+
+
+class CreateTable(_CreateBase):
+ """Represent a CREATE TABLE statement."""
+
+ __visit_name__ = "create_table"
+
+ def __init__(
+ self,
+ element: Table,
+ include_foreign_key_constraints: Optional[
+ typing_Sequence[ForeignKeyConstraint]
+ ] = None,
+ if_not_exists: bool = False,
+ ):
+ """Create a :class:`.CreateTable` construct.
+
+ :param element: a :class:`_schema.Table` that's the subject
+ of the CREATE
+ :param on: See the description for 'on' in :class:`.DDL`.
+ :param include_foreign_key_constraints: optional sequence of
+ :class:`_schema.ForeignKeyConstraint` objects that will be included
+ inline within the CREATE construct; if omitted, all foreign key
+ constraints that do not specify use_alter=True are included.
+
+ :param if_not_exists: if True, an IF NOT EXISTS operator will be
+ applied to the construct.
+
+ .. versionadded:: 1.4.0b2
+
+ """
+ super().__init__(element, if_not_exists=if_not_exists)
+ self.columns = [CreateColumn(column) for column in element.columns]
+ self.include_foreign_key_constraints = include_foreign_key_constraints
+
+
+class _DropView(_DropBase):
+ """Semi-public 'DROP VIEW' construct.
+
+ Used by the test suite for dialect-agnostic drops of views.
+ This object will eventually be part of a public "view" API.
+
+ """
+
+ __visit_name__ = "drop_view"
+
+
+class CreateConstraint(BaseDDLElement):
+ def __init__(self, element: Constraint):
+ self.element = element
+
+
+class CreateColumn(BaseDDLElement):
+ """Represent a :class:`_schema.Column`
+ as rendered in a CREATE TABLE statement,
+ via the :class:`.CreateTable` construct.
+
+ This is provided to support custom column DDL within the generation
+ of CREATE TABLE statements, by using the
+ compiler extension documented in :ref:`sqlalchemy.ext.compiler_toplevel`
+ to extend :class:`.CreateColumn`.
+
+ Typical integration is to examine the incoming :class:`_schema.Column`
+ object, and to redirect compilation if a particular flag or condition
+ is found::
+
+ from sqlalchemy import schema
+ from sqlalchemy.ext.compiler import compiles
+
+ @compiles(schema.CreateColumn)
+ def compile(element, compiler, **kw):
+ column = element.element
+
+ if "special" not in column.info:
+ return compiler.visit_create_column(element, **kw)
+
+ text = "%s SPECIAL DIRECTIVE %s" % (
+ column.name,
+ compiler.type_compiler.process(column.type)
+ )
+ default = compiler.get_column_default_string(column)
+ if default is not None:
+ text += " DEFAULT " + default
+
+ if not column.nullable:
+ text += " NOT NULL"
+
+ if column.constraints:
+ text += " ".join(
+ compiler.process(const)
+ for const in column.constraints)
+ return text
+
+ The above construct can be applied to a :class:`_schema.Table`
+ as follows::
+
+ from sqlalchemy import Table, Metadata, Column, Integer, String
+ from sqlalchemy import schema
+
+ metadata = MetaData()
+
+ table = Table('mytable', MetaData(),
+ Column('x', Integer, info={"special":True}, primary_key=True),
+ Column('y', String(50)),
+ Column('z', String(20), info={"special":True})
+ )
+
+ metadata.create_all(conn)
+
+ Above, the directives we've added to the :attr:`_schema.Column.info`
+ collection
+ will be detected by our custom compilation scheme::
+
+ CREATE TABLE mytable (
+ x SPECIAL DIRECTIVE INTEGER NOT NULL,
+ y VARCHAR(50),
+ z SPECIAL DIRECTIVE VARCHAR(20),
+ PRIMARY KEY (x)
+ )
+
+ The :class:`.CreateColumn` construct can also be used to skip certain
+ columns when producing a ``CREATE TABLE``. This is accomplished by
+ creating a compilation rule that conditionally returns ``None``.
+ This is essentially how to produce the same effect as using the
+ ``system=True`` argument on :class:`_schema.Column`, which marks a column
+ as an implicitly-present "system" column.
+
+ For example, suppose we wish to produce a :class:`_schema.Table`
+ which skips
+ rendering of the PostgreSQL ``xmin`` column against the PostgreSQL
+ backend, but on other backends does render it, in anticipation of a
+ triggered rule. A conditional compilation rule could skip this name only
+ on PostgreSQL::
+
+ from sqlalchemy.schema import CreateColumn
+
+ @compiles(CreateColumn, "postgresql")
+ def skip_xmin(element, compiler, **kw):
+ if element.element.name == 'xmin':
+ return None
+ else:
+ return compiler.visit_create_column(element, **kw)
+
+
+ my_table = Table('mytable', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('xmin', Integer)
+ )
+
+ Above, a :class:`.CreateTable` construct will generate a ``CREATE TABLE``
+ which only includes the ``id`` column in the string; the ``xmin`` column
+ will be omitted, but only against the PostgreSQL backend.
+
+ """
+
+ __visit_name__ = "create_column"
+
+ def __init__(self, element):
+ self.element = element
+
+
+class DropTable(_DropBase):
+ """Represent a DROP TABLE statement."""
+
+ __visit_name__ = "drop_table"
+
+ def __init__(self, element: Table, if_exists: bool = False):
+ """Create a :class:`.DropTable` construct.
+
+ :param element: a :class:`_schema.Table` that's the subject
+ of the DROP.
+ :param on: See the description for 'on' in :class:`.DDL`.
+ :param if_exists: if True, an IF EXISTS operator will be applied to the
+ construct.
+
+ .. versionadded:: 1.4.0b2
+
+ """
+ super().__init__(element, if_exists=if_exists)
+
+
+class CreateSequence(_CreateBase):
+ """Represent a CREATE SEQUENCE statement."""
+
+ __visit_name__ = "create_sequence"
+
+ def __init__(self, element: Sequence, if_not_exists: bool = False):
+ super().__init__(element, if_not_exists=if_not_exists)
+
+
+class DropSequence(_DropBase):
+ """Represent a DROP SEQUENCE statement."""
+
+ __visit_name__ = "drop_sequence"
+
+ def __init__(self, element: Sequence, if_exists: bool = False):
+ super().__init__(element, if_exists=if_exists)
+
+
+class CreateIndex(_CreateBase):
+ """Represent a CREATE INDEX statement."""
+
+ __visit_name__ = "create_index"
+
+ def __init__(self, element, if_not_exists=False):
+ """Create a :class:`.Createindex` construct.
+
+ :param element: a :class:`_schema.Index` that's the subject
+ of the CREATE.
+ :param if_not_exists: if True, an IF NOT EXISTS operator will be
+ applied to the construct.
+
+ .. versionadded:: 1.4.0b2
+
+ """
+ super().__init__(element, if_not_exists=if_not_exists)
+
+
+class DropIndex(_DropBase):
+ """Represent a DROP INDEX statement."""
+
+ __visit_name__ = "drop_index"
+
+ def __init__(self, element, if_exists=False):
+ """Create a :class:`.DropIndex` construct.
+
+ :param element: a :class:`_schema.Index` that's the subject
+ of the DROP.
+ :param if_exists: if True, an IF EXISTS operator will be applied to the
+ construct.
+
+ .. versionadded:: 1.4.0b2
+
+ """
+ super().__init__(element, if_exists=if_exists)
+
+
+class AddConstraint(_CreateBase):
+ """Represent an ALTER TABLE ADD CONSTRAINT statement."""
+
+ __visit_name__ = "add_constraint"
+
+ def __init__(self, element):
+ super().__init__(element)
+ element._create_rule = util.portable_instancemethod(
+ self._create_rule_disable
+ )
+
+
+class DropConstraint(_DropBase):
+ """Represent an ALTER TABLE DROP CONSTRAINT statement."""
+
+ __visit_name__ = "drop_constraint"
+
+ def __init__(self, element, cascade=False, if_exists=False, **kw):
+ self.cascade = cascade
+ super().__init__(element, if_exists=if_exists, **kw)
+ element._create_rule = util.portable_instancemethod(
+ self._create_rule_disable
+ )
+
+
+class SetTableComment(_CreateDropBase):
+ """Represent a COMMENT ON TABLE IS statement."""
+
+ __visit_name__ = "set_table_comment"
+
+
+class DropTableComment(_CreateDropBase):
+ """Represent a COMMENT ON TABLE '' statement.
+
+ Note this varies a lot across database backends.
+
+ """
+
+ __visit_name__ = "drop_table_comment"
+
+
+class SetColumnComment(_CreateDropBase):
+ """Represent a COMMENT ON COLUMN IS statement."""
+
+ __visit_name__ = "set_column_comment"
+
+
+class DropColumnComment(_CreateDropBase):
+ """Represent a COMMENT ON COLUMN IS NULL statement."""
+
+ __visit_name__ = "drop_column_comment"
+
+
+class SetConstraintComment(_CreateDropBase):
+ """Represent a COMMENT ON CONSTRAINT IS statement."""
+
+ __visit_name__ = "set_constraint_comment"
+
+
+class DropConstraintComment(_CreateDropBase):
+ """Represent a COMMENT ON CONSTRAINT IS NULL statement."""
+
+ __visit_name__ = "drop_constraint_comment"
+
+
+class InvokeDDLBase(SchemaVisitor):
+ def __init__(self, connection):
+ self.connection = connection
+
+ @contextlib.contextmanager
+ def with_ddl_events(self, target, **kw):
+ """helper context manager that will apply appropriate DDL events
+ to a CREATE or DROP operation."""
+
+ raise NotImplementedError()
+
+
+class InvokeCreateDDLBase(InvokeDDLBase):
+ @contextlib.contextmanager
+ def with_ddl_events(self, target, **kw):
+ """helper context manager that will apply appropriate DDL events
+ to a CREATE or DROP operation."""
+
+ target.dispatch.before_create(
+ target, self.connection, _ddl_runner=self, **kw
+ )
+ yield
+ target.dispatch.after_create(
+ target, self.connection, _ddl_runner=self, **kw
+ )
+
+
+class InvokeDropDDLBase(InvokeDDLBase):
+ @contextlib.contextmanager
+ def with_ddl_events(self, target, **kw):
+ """helper context manager that will apply appropriate DDL events
+ to a CREATE or DROP operation."""
+
+ target.dispatch.before_drop(
+ target, self.connection, _ddl_runner=self, **kw
+ )
+ yield
+ target.dispatch.after_drop(
+ target, self.connection, _ddl_runner=self, **kw
+ )
+
+
+class SchemaGenerator(InvokeCreateDDLBase):
+ def __init__(
+ self, dialect, connection, checkfirst=False, tables=None, **kwargs
+ ):
+ super().__init__(connection, **kwargs)
+ self.checkfirst = checkfirst
+ self.tables = tables
+ self.preparer = dialect.identifier_preparer
+ self.dialect = dialect
+ self.memo = {}
+
+ def _can_create_table(self, table):
+ self.dialect.validate_identifier(table.name)
+ effective_schema = self.connection.schema_for_object(table)
+ if effective_schema:
+ self.dialect.validate_identifier(effective_schema)
+ return not self.checkfirst or not self.dialect.has_table(
+ self.connection, table.name, schema=effective_schema
+ )
+
+ def _can_create_index(self, index):
+ effective_schema = self.connection.schema_for_object(index.table)
+ if effective_schema:
+ self.dialect.validate_identifier(effective_schema)
+ return not self.checkfirst or not self.dialect.has_index(
+ self.connection,
+ index.table.name,
+ index.name,
+ schema=effective_schema,
+ )
+
+ def _can_create_sequence(self, sequence):
+ effective_schema = self.connection.schema_for_object(sequence)
+
+ return self.dialect.supports_sequences and (
+ (not self.dialect.sequences_optional or not sequence.optional)
+ and (
+ not self.checkfirst
+ or not self.dialect.has_sequence(
+ self.connection, sequence.name, schema=effective_schema
+ )
+ )
+ )
+
+ def visit_metadata(self, metadata):
+ if self.tables is not None:
+ tables = self.tables
+ else:
+ tables = list(metadata.tables.values())
+
+ collection = sort_tables_and_constraints(
+ [t for t in tables if self._can_create_table(t)]
+ )
+
+ seq_coll = [
+ s
+ for s in metadata._sequences.values()
+ if s.column is None and self._can_create_sequence(s)
+ ]
+
+ event_collection = [t for (t, fks) in collection if t is not None]
+
+ with self.with_ddl_events(
+ metadata,
+ tables=event_collection,
+ checkfirst=self.checkfirst,
+ ):
+ for seq in seq_coll:
+ self.traverse_single(seq, create_ok=True)
+
+ for table, fkcs in collection:
+ if table is not None:
+ self.traverse_single(
+ table,
+ create_ok=True,
+ include_foreign_key_constraints=fkcs,
+ _is_metadata_operation=True,
+ )
+ else:
+ for fkc in fkcs:
+ self.traverse_single(fkc)
+
+ def visit_table(
+ self,
+ table,
+ create_ok=False,
+ include_foreign_key_constraints=None,
+ _is_metadata_operation=False,
+ ):
+ if not create_ok and not self._can_create_table(table):
+ return
+
+ with self.with_ddl_events(
+ table,
+ checkfirst=self.checkfirst,
+ _is_metadata_operation=_is_metadata_operation,
+ ):
+ for column in table.columns:
+ if column.default is not None:
+ self.traverse_single(column.default)
+
+ if not self.dialect.supports_alter:
+ # e.g., don't omit any foreign key constraints
+ include_foreign_key_constraints = None
+
+ CreateTable(
+ table,
+ include_foreign_key_constraints=(
+ include_foreign_key_constraints
+ ),
+ )._invoke_with(self.connection)
+
+ if hasattr(table, "indexes"):
+ for index in table.indexes:
+ self.traverse_single(index, create_ok=True)
+
+ if (
+ self.dialect.supports_comments
+ and not self.dialect.inline_comments
+ ):
+ if table.comment is not None:
+ SetTableComment(table)._invoke_with(self.connection)
+
+ for column in table.columns:
+ if column.comment is not None:
+ SetColumnComment(column)._invoke_with(self.connection)
+
+ if self.dialect.supports_constraint_comments:
+ for constraint in table.constraints:
+ if constraint.comment is not None:
+ self.connection.execute(
+ SetConstraintComment(constraint)
+ )
+
+ def visit_foreign_key_constraint(self, constraint):
+ if not self.dialect.supports_alter:
+ return
+
+ with self.with_ddl_events(constraint):
+ AddConstraint(constraint)._invoke_with(self.connection)
+
+ def visit_sequence(self, sequence, create_ok=False):
+ if not create_ok and not self._can_create_sequence(sequence):
+ return
+ with self.with_ddl_events(sequence):
+ CreateSequence(sequence)._invoke_with(self.connection)
+
+ def visit_index(self, index, create_ok=False):
+ if not create_ok and not self._can_create_index(index):
+ return
+ with self.with_ddl_events(index):
+ CreateIndex(index)._invoke_with(self.connection)
+
+
+class SchemaDropper(InvokeDropDDLBase):
+ def __init__(
+ self, dialect, connection, checkfirst=False, tables=None, **kwargs
+ ):
+ super().__init__(connection, **kwargs)
+ self.checkfirst = checkfirst
+ self.tables = tables
+ self.preparer = dialect.identifier_preparer
+ self.dialect = dialect
+ self.memo = {}
+
+ def visit_metadata(self, metadata):
+ if self.tables is not None:
+ tables = self.tables
+ else:
+ tables = list(metadata.tables.values())
+
+ try:
+ unsorted_tables = [t for t in tables if self._can_drop_table(t)]
+ collection = list(
+ reversed(
+ sort_tables_and_constraints(
+ unsorted_tables,
+ filter_fn=lambda constraint: (
+ False
+ if not self.dialect.supports_alter
+ or constraint.name is None
+ else None
+ ),
+ )
+ )
+ )
+ except exc.CircularDependencyError as err2:
+ if not self.dialect.supports_alter:
+ util.warn(
+ "Can't sort tables for DROP; an "
+ "unresolvable foreign key "
+ "dependency exists between tables: %s; and backend does "
+ "not support ALTER. To restore at least a partial sort, "
+ "apply use_alter=True to ForeignKey and "
+ "ForeignKeyConstraint "
+ "objects involved in the cycle to mark these as known "
+ "cycles that will be ignored."
+ % (", ".join(sorted([t.fullname for t in err2.cycles])))
+ )
+ collection = [(t, ()) for t in unsorted_tables]
+ else:
+ raise exc.CircularDependencyError(
+ err2.args[0],
+ err2.cycles,
+ err2.edges,
+ msg="Can't sort tables for DROP; an "
+ "unresolvable foreign key "
+ "dependency exists between tables: %s. Please ensure "
+ "that the ForeignKey and ForeignKeyConstraint objects "
+ "involved in the cycle have "
+ "names so that they can be dropped using "
+ "DROP CONSTRAINT."
+ % (", ".join(sorted([t.fullname for t in err2.cycles]))),
+ ) from err2
+
+ seq_coll = [
+ s
+ for s in metadata._sequences.values()
+ if self._can_drop_sequence(s)
+ ]
+
+ event_collection = [t for (t, fks) in collection if t is not None]
+
+ with self.with_ddl_events(
+ metadata,
+ tables=event_collection,
+ checkfirst=self.checkfirst,
+ ):
+ for table, fkcs in collection:
+ if table is not None:
+ self.traverse_single(
+ table,
+ drop_ok=True,
+ _is_metadata_operation=True,
+ _ignore_sequences=seq_coll,
+ )
+ else:
+ for fkc in fkcs:
+ self.traverse_single(fkc)
+
+ for seq in seq_coll:
+ self.traverse_single(seq, drop_ok=seq.column is None)
+
+ def _can_drop_table(self, table):
+ self.dialect.validate_identifier(table.name)
+ effective_schema = self.connection.schema_for_object(table)
+ if effective_schema:
+ self.dialect.validate_identifier(effective_schema)
+ return not self.checkfirst or self.dialect.has_table(
+ self.connection, table.name, schema=effective_schema
+ )
+
+ def _can_drop_index(self, index):
+ effective_schema = self.connection.schema_for_object(index.table)
+ if effective_schema:
+ self.dialect.validate_identifier(effective_schema)
+ return not self.checkfirst or self.dialect.has_index(
+ self.connection,
+ index.table.name,
+ index.name,
+ schema=effective_schema,
+ )
+
+ def _can_drop_sequence(self, sequence):
+ effective_schema = self.connection.schema_for_object(sequence)
+ return self.dialect.supports_sequences and (
+ (not self.dialect.sequences_optional or not sequence.optional)
+ and (
+ not self.checkfirst
+ or self.dialect.has_sequence(
+ self.connection, sequence.name, schema=effective_schema
+ )
+ )
+ )
+
+ def visit_index(self, index, drop_ok=False):
+ if not drop_ok and not self._can_drop_index(index):
+ return
+
+ with self.with_ddl_events(index):
+ DropIndex(index)(index, self.connection)
+
+ def visit_table(
+ self,
+ table,
+ drop_ok=False,
+ _is_metadata_operation=False,
+ _ignore_sequences=(),
+ ):
+ if not drop_ok and not self._can_drop_table(table):
+ return
+
+ with self.with_ddl_events(
+ table,
+ checkfirst=self.checkfirst,
+ _is_metadata_operation=_is_metadata_operation,
+ ):
+ DropTable(table)._invoke_with(self.connection)
+
+ # traverse client side defaults which may refer to server-side
+ # sequences. noting that some of these client side defaults may
+ # also be set up as server side defaults
+ # (see https://docs.sqlalchemy.org/en/
+ # latest/core/defaults.html
+ # #associating-a-sequence-as-the-server-side-
+ # default), so have to be dropped after the table is dropped.
+ for column in table.columns:
+ if (
+ column.default is not None
+ and column.default not in _ignore_sequences
+ ):
+ self.traverse_single(column.default)
+
+ def visit_foreign_key_constraint(self, constraint):
+ if not self.dialect.supports_alter:
+ return
+ with self.with_ddl_events(constraint):
+ DropConstraint(constraint)._invoke_with(self.connection)
+
+ def visit_sequence(self, sequence, drop_ok=False):
+ if not drop_ok and not self._can_drop_sequence(sequence):
+ return
+ with self.with_ddl_events(sequence):
+ DropSequence(sequence)._invoke_with(self.connection)
+
+
+def sort_tables(
+ tables: Iterable[TableClause],
+ skip_fn: Optional[Callable[[ForeignKeyConstraint], bool]] = None,
+ extra_dependencies: Optional[
+ typing_Sequence[Tuple[TableClause, TableClause]]
+ ] = None,
+) -> List[Table]:
+ """Sort a collection of :class:`_schema.Table` objects based on
+ dependency.
+
+ This is a dependency-ordered sort which will emit :class:`_schema.Table`
+ objects such that they will follow their dependent :class:`_schema.Table`
+ objects.
+ Tables are dependent on another based on the presence of
+ :class:`_schema.ForeignKeyConstraint`
+ objects as well as explicit dependencies
+ added by :meth:`_schema.Table.add_is_dependent_on`.
+
+ .. warning::
+
+ The :func:`._schema.sort_tables` function cannot by itself
+ accommodate automatic resolution of dependency cycles between
+ tables, which are usually caused by mutually dependent foreign key
+ constraints. When these cycles are detected, the foreign keys
+ of these tables are omitted from consideration in the sort.
+ A warning is emitted when this condition occurs, which will be an
+ exception raise in a future release. Tables which are not part
+ of the cycle will still be returned in dependency order.
+
+ To resolve these cycles, the
+ :paramref:`_schema.ForeignKeyConstraint.use_alter` parameter may be
+ applied to those constraints which create a cycle. Alternatively,
+ the :func:`_schema.sort_tables_and_constraints` function will
+ automatically return foreign key constraints in a separate
+ collection when cycles are detected so that they may be applied
+ to a schema separately.
+
+ .. versionchanged:: 1.3.17 - a warning is emitted when
+ :func:`_schema.sort_tables` cannot perform a proper sort due to
+ cyclical dependencies. This will be an exception in a future
+ release. Additionally, the sort will continue to return
+ other tables not involved in the cycle in dependency order
+ which was not the case previously.
+
+ :param tables: a sequence of :class:`_schema.Table` objects.
+
+ :param skip_fn: optional callable which will be passed a
+ :class:`_schema.ForeignKeyConstraint` object; if it returns True, this
+ constraint will not be considered as a dependency. Note this is
+ **different** from the same parameter in
+ :func:`.sort_tables_and_constraints`, which is
+ instead passed the owning :class:`_schema.ForeignKeyConstraint` object.
+
+ :param extra_dependencies: a sequence of 2-tuples of tables which will
+ also be considered as dependent on each other.
+
+ .. seealso::
+
+ :func:`.sort_tables_and_constraints`
+
+ :attr:`_schema.MetaData.sorted_tables` - uses this function to sort
+
+
+ """
+
+ if skip_fn is not None:
+ fixed_skip_fn = skip_fn
+
+ def _skip_fn(fkc):
+ for fk in fkc.elements:
+ if fixed_skip_fn(fk):
+ return True
+ else:
+ return None
+
+ else:
+ _skip_fn = None # type: ignore
+
+ return [
+ t
+ for (t, fkcs) in sort_tables_and_constraints(
+ tables,
+ filter_fn=_skip_fn,
+ extra_dependencies=extra_dependencies,
+ _warn_for_cycles=True,
+ )
+ if t is not None
+ ]
+
+
+def sort_tables_and_constraints(
+ tables, filter_fn=None, extra_dependencies=None, _warn_for_cycles=False
+):
+ """Sort a collection of :class:`_schema.Table` /
+ :class:`_schema.ForeignKeyConstraint`
+ objects.
+
+ This is a dependency-ordered sort which will emit tuples of
+ ``(Table, [ForeignKeyConstraint, ...])`` such that each
+ :class:`_schema.Table` follows its dependent :class:`_schema.Table`
+ objects.
+ Remaining :class:`_schema.ForeignKeyConstraint`
+ objects that are separate due to
+ dependency rules not satisfied by the sort are emitted afterwards
+ as ``(None, [ForeignKeyConstraint ...])``.
+
+ Tables are dependent on another based on the presence of
+ :class:`_schema.ForeignKeyConstraint` objects, explicit dependencies
+ added by :meth:`_schema.Table.add_is_dependent_on`,
+ as well as dependencies
+ stated here using the :paramref:`~.sort_tables_and_constraints.skip_fn`
+ and/or :paramref:`~.sort_tables_and_constraints.extra_dependencies`
+ parameters.
+
+ :param tables: a sequence of :class:`_schema.Table` objects.
+
+ :param filter_fn: optional callable which will be passed a
+ :class:`_schema.ForeignKeyConstraint` object,
+ and returns a value based on
+ whether this constraint should definitely be included or excluded as
+ an inline constraint, or neither. If it returns False, the constraint
+ will definitely be included as a dependency that cannot be subject
+ to ALTER; if True, it will **only** be included as an ALTER result at
+ the end. Returning None means the constraint is included in the
+ table-based result unless it is detected as part of a dependency cycle.
+
+ :param extra_dependencies: a sequence of 2-tuples of tables which will
+ also be considered as dependent on each other.
+
+ .. seealso::
+
+ :func:`.sort_tables`
+
+
+ """
+
+ fixed_dependencies = set()
+ mutable_dependencies = set()
+
+ if extra_dependencies is not None:
+ fixed_dependencies.update(extra_dependencies)
+
+ remaining_fkcs = set()
+ for table in tables:
+ for fkc in table.foreign_key_constraints:
+ if fkc.use_alter is True:
+ remaining_fkcs.add(fkc)
+ continue
+
+ if filter_fn:
+ filtered = filter_fn(fkc)
+
+ if filtered is True:
+ remaining_fkcs.add(fkc)
+ continue
+
+ dependent_on = fkc.referred_table
+ if dependent_on is not table:
+ mutable_dependencies.add((dependent_on, table))
+
+ fixed_dependencies.update(
+ (parent, table) for parent in table._extra_dependencies
+ )
+
+ try:
+ candidate_sort = list(
+ topological.sort(
+ fixed_dependencies.union(mutable_dependencies),
+ tables,
+ )
+ )
+ except exc.CircularDependencyError as err:
+ if _warn_for_cycles:
+ util.warn(
+ "Cannot correctly sort tables; there are unresolvable cycles "
+ 'between tables "%s", which is usually caused by mutually '
+ "dependent foreign key constraints. Foreign key constraints "
+ "involving these tables will not be considered; this warning "
+ "may raise an error in a future release."
+ % (", ".join(sorted(t.fullname for t in err.cycles)),)
+ )
+ for edge in err.edges:
+ if edge in mutable_dependencies:
+ table = edge[1]
+ if table not in err.cycles:
+ continue
+ can_remove = [
+ fkc
+ for fkc in table.foreign_key_constraints
+ if filter_fn is None or filter_fn(fkc) is not False
+ ]
+ remaining_fkcs.update(can_remove)
+ for fkc in can_remove:
+ dependent_on = fkc.referred_table
+ if dependent_on is not table:
+ mutable_dependencies.discard((dependent_on, table))
+ candidate_sort = list(
+ topological.sort(
+ fixed_dependencies.union(mutable_dependencies),
+ tables,
+ )
+ )
+
+ return [
+ (table, table.foreign_key_constraints.difference(remaining_fkcs))
+ for table in candidate_sort
+ ] + [(None, list(remaining_fkcs))]
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/default_comparator.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/default_comparator.py
new file mode 100644
index 0000000..76131bc
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/default_comparator.py
@@ -0,0 +1,552 @@
+# sql/default_comparator.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+"""Default implementation of SQL comparison operations.
+"""
+
+from __future__ import annotations
+
+import typing
+from typing import Any
+from typing import Callable
+from typing import Dict
+from typing import NoReturn
+from typing import Optional
+from typing import Tuple
+from typing import Type
+from typing import Union
+
+from . import coercions
+from . import operators
+from . import roles
+from . import type_api
+from .elements import and_
+from .elements import BinaryExpression
+from .elements import ClauseElement
+from .elements import CollationClause
+from .elements import CollectionAggregate
+from .elements import ExpressionClauseList
+from .elements import False_
+from .elements import Null
+from .elements import OperatorExpression
+from .elements import or_
+from .elements import True_
+from .elements import UnaryExpression
+from .operators import OperatorType
+from .. import exc
+from .. import util
+
+_T = typing.TypeVar("_T", bound=Any)
+
+if typing.TYPE_CHECKING:
+ from .elements import ColumnElement
+ from .operators import custom_op
+ from .type_api import TypeEngine
+
+
+def _boolean_compare(
+ expr: ColumnElement[Any],
+ op: OperatorType,
+ obj: Any,
+ *,
+ negate_op: Optional[OperatorType] = None,
+ reverse: bool = False,
+ _python_is_types: Tuple[Type[Any], ...] = (type(None), bool),
+ result_type: Optional[TypeEngine[bool]] = None,
+ **kwargs: Any,
+) -> OperatorExpression[bool]:
+ if result_type is None:
+ result_type = type_api.BOOLEANTYPE
+
+ if isinstance(obj, _python_is_types + (Null, True_, False_)):
+ # allow x ==/!= True/False to be treated as a literal.
+ # this comes out to "== / != true/false" or "1/0" if those
+ # constants aren't supported and works on all platforms
+ if op in (operators.eq, operators.ne) and isinstance(
+ obj, (bool, True_, False_)
+ ):
+ return OperatorExpression._construct_for_op(
+ expr,
+ coercions.expect(roles.ConstExprRole, obj),
+ op,
+ type_=result_type,
+ negate=negate_op,
+ modifiers=kwargs,
+ )
+ elif op in (
+ operators.is_distinct_from,
+ operators.is_not_distinct_from,
+ ):
+ return OperatorExpression._construct_for_op(
+ expr,
+ coercions.expect(roles.ConstExprRole, obj),
+ op,
+ type_=result_type,
+ negate=negate_op,
+ modifiers=kwargs,
+ )
+ elif expr._is_collection_aggregate:
+ obj = coercions.expect(
+ roles.ConstExprRole, element=obj, operator=op, expr=expr
+ )
+ else:
+ # all other None uses IS, IS NOT
+ if op in (operators.eq, operators.is_):
+ return OperatorExpression._construct_for_op(
+ expr,
+ coercions.expect(roles.ConstExprRole, obj),
+ operators.is_,
+ negate=operators.is_not,
+ type_=result_type,
+ )
+ elif op in (operators.ne, operators.is_not):
+ return OperatorExpression._construct_for_op(
+ expr,
+ coercions.expect(roles.ConstExprRole, obj),
+ operators.is_not,
+ negate=operators.is_,
+ type_=result_type,
+ )
+ else:
+ raise exc.ArgumentError(
+ "Only '=', '!=', 'is_()', 'is_not()', "
+ "'is_distinct_from()', 'is_not_distinct_from()' "
+ "operators can be used with None/True/False"
+ )
+ else:
+ obj = coercions.expect(
+ roles.BinaryElementRole, element=obj, operator=op, expr=expr
+ )
+
+ if reverse:
+ return OperatorExpression._construct_for_op(
+ obj,
+ expr,
+ op,
+ type_=result_type,
+ negate=negate_op,
+ modifiers=kwargs,
+ )
+ else:
+ return OperatorExpression._construct_for_op(
+ expr,
+ obj,
+ op,
+ type_=result_type,
+ negate=negate_op,
+ modifiers=kwargs,
+ )
+
+
+def _custom_op_operate(
+ expr: ColumnElement[Any],
+ op: custom_op[Any],
+ obj: Any,
+ reverse: bool = False,
+ result_type: Optional[TypeEngine[Any]] = None,
+ **kw: Any,
+) -> ColumnElement[Any]:
+ if result_type is None:
+ if op.return_type:
+ result_type = op.return_type
+ elif op.is_comparison:
+ result_type = type_api.BOOLEANTYPE
+
+ return _binary_operate(
+ expr, op, obj, reverse=reverse, result_type=result_type, **kw
+ )
+
+
+def _binary_operate(
+ expr: ColumnElement[Any],
+ op: OperatorType,
+ obj: roles.BinaryElementRole[Any],
+ *,
+ reverse: bool = False,
+ result_type: Optional[TypeEngine[_T]] = None,
+ **kw: Any,
+) -> OperatorExpression[_T]:
+ coerced_obj = coercions.expect(
+ roles.BinaryElementRole, obj, expr=expr, operator=op
+ )
+
+ if reverse:
+ left, right = coerced_obj, expr
+ else:
+ left, right = expr, coerced_obj
+
+ if result_type is None:
+ op, result_type = left.comparator._adapt_expression(
+ op, right.comparator
+ )
+
+ return OperatorExpression._construct_for_op(
+ left, right, op, type_=result_type, modifiers=kw
+ )
+
+
+def _conjunction_operate(
+ expr: ColumnElement[Any], op: OperatorType, other: Any, **kw: Any
+) -> ColumnElement[Any]:
+ if op is operators.and_:
+ return and_(expr, other)
+ elif op is operators.or_:
+ return or_(expr, other)
+ else:
+ raise NotImplementedError()
+
+
+def _scalar(
+ expr: ColumnElement[Any],
+ op: OperatorType,
+ fn: Callable[[ColumnElement[Any]], ColumnElement[Any]],
+ **kw: Any,
+) -> ColumnElement[Any]:
+ return fn(expr)
+
+
+def _in_impl(
+ expr: ColumnElement[Any],
+ op: OperatorType,
+ seq_or_selectable: ClauseElement,
+ negate_op: OperatorType,
+ **kw: Any,
+) -> ColumnElement[Any]:
+ seq_or_selectable = coercions.expect(
+ roles.InElementRole, seq_or_selectable, expr=expr, operator=op
+ )
+ if "in_ops" in seq_or_selectable._annotations:
+ op, negate_op = seq_or_selectable._annotations["in_ops"]
+
+ return _boolean_compare(
+ expr, op, seq_or_selectable, negate_op=negate_op, **kw
+ )
+
+
+def _getitem_impl(
+ expr: ColumnElement[Any], op: OperatorType, other: Any, **kw: Any
+) -> ColumnElement[Any]:
+ if (
+ isinstance(expr.type, type_api.INDEXABLE)
+ or isinstance(expr.type, type_api.TypeDecorator)
+ and isinstance(expr.type.impl_instance, type_api.INDEXABLE)
+ ):
+ other = coercions.expect(
+ roles.BinaryElementRole, other, expr=expr, operator=op
+ )
+ return _binary_operate(expr, op, other, **kw)
+ else:
+ _unsupported_impl(expr, op, other, **kw)
+
+
+def _unsupported_impl(
+ expr: ColumnElement[Any], op: OperatorType, *arg: Any, **kw: Any
+) -> NoReturn:
+ raise NotImplementedError(
+ "Operator '%s' is not supported on this expression" % op.__name__
+ )
+
+
+def _inv_impl(
+ expr: ColumnElement[Any], op: OperatorType, **kw: Any
+) -> ColumnElement[Any]:
+ """See :meth:`.ColumnOperators.__inv__`."""
+
+ # undocumented element currently used by the ORM for
+ # relationship.contains()
+ if hasattr(expr, "negation_clause"):
+ return expr.negation_clause
+ else:
+ return expr._negate()
+
+
+def _neg_impl(
+ expr: ColumnElement[Any], op: OperatorType, **kw: Any
+) -> ColumnElement[Any]:
+ """See :meth:`.ColumnOperators.__neg__`."""
+ return UnaryExpression(expr, operator=operators.neg, type_=expr.type)
+
+
+def _bitwise_not_impl(
+ expr: ColumnElement[Any], op: OperatorType, **kw: Any
+) -> ColumnElement[Any]:
+ """See :meth:`.ColumnOperators.bitwise_not`."""
+
+ return UnaryExpression(
+ expr, operator=operators.bitwise_not_op, type_=expr.type
+ )
+
+
+def _match_impl(
+ expr: ColumnElement[Any], op: OperatorType, other: Any, **kw: Any
+) -> ColumnElement[Any]:
+ """See :meth:`.ColumnOperators.match`."""
+
+ return _boolean_compare(
+ expr,
+ operators.match_op,
+ coercions.expect(
+ roles.BinaryElementRole,
+ other,
+ expr=expr,
+ operator=operators.match_op,
+ ),
+ result_type=type_api.MATCHTYPE,
+ negate_op=(
+ operators.not_match_op
+ if op is operators.match_op
+ else operators.match_op
+ ),
+ **kw,
+ )
+
+
+def _distinct_impl(
+ expr: ColumnElement[Any], op: OperatorType, **kw: Any
+) -> ColumnElement[Any]:
+ """See :meth:`.ColumnOperators.distinct`."""
+ return UnaryExpression(
+ expr, operator=operators.distinct_op, type_=expr.type
+ )
+
+
+def _between_impl(
+ expr: ColumnElement[Any],
+ op: OperatorType,
+ cleft: Any,
+ cright: Any,
+ **kw: Any,
+) -> ColumnElement[Any]:
+ """See :meth:`.ColumnOperators.between`."""
+ return BinaryExpression(
+ expr,
+ ExpressionClauseList._construct_for_list(
+ operators.and_,
+ type_api.NULLTYPE,
+ coercions.expect(
+ roles.BinaryElementRole,
+ cleft,
+ expr=expr,
+ operator=operators.and_,
+ ),
+ coercions.expect(
+ roles.BinaryElementRole,
+ cright,
+ expr=expr,
+ operator=operators.and_,
+ ),
+ group=False,
+ ),
+ op,
+ negate=(
+ operators.not_between_op
+ if op is operators.between_op
+ else operators.between_op
+ ),
+ modifiers=kw,
+ )
+
+
+def _collate_impl(
+ expr: ColumnElement[str], op: OperatorType, collation: str, **kw: Any
+) -> ColumnElement[str]:
+ return CollationClause._create_collation_expression(expr, collation)
+
+
+def _regexp_match_impl(
+ expr: ColumnElement[str],
+ op: OperatorType,
+ pattern: Any,
+ flags: Optional[str],
+ **kw: Any,
+) -> ColumnElement[Any]:
+ return BinaryExpression(
+ expr,
+ coercions.expect(
+ roles.BinaryElementRole,
+ pattern,
+ expr=expr,
+ operator=operators.comma_op,
+ ),
+ op,
+ negate=operators.not_regexp_match_op,
+ modifiers={"flags": flags},
+ )
+
+
+def _regexp_replace_impl(
+ expr: ColumnElement[Any],
+ op: OperatorType,
+ pattern: Any,
+ replacement: Any,
+ flags: Optional[str],
+ **kw: Any,
+) -> ColumnElement[Any]:
+ return BinaryExpression(
+ expr,
+ ExpressionClauseList._construct_for_list(
+ operators.comma_op,
+ type_api.NULLTYPE,
+ coercions.expect(
+ roles.BinaryElementRole,
+ pattern,
+ expr=expr,
+ operator=operators.comma_op,
+ ),
+ coercions.expect(
+ roles.BinaryElementRole,
+ replacement,
+ expr=expr,
+ operator=operators.comma_op,
+ ),
+ group=False,
+ ),
+ op,
+ modifiers={"flags": flags},
+ )
+
+
+# a mapping of operators with the method they use, along with
+# additional keyword arguments to be passed
+operator_lookup: Dict[
+ str,
+ Tuple[
+ Callable[..., ColumnElement[Any]],
+ util.immutabledict[
+ str, Union[OperatorType, Callable[..., ColumnElement[Any]]]
+ ],
+ ],
+] = {
+ "and_": (_conjunction_operate, util.EMPTY_DICT),
+ "or_": (_conjunction_operate, util.EMPTY_DICT),
+ "inv": (_inv_impl, util.EMPTY_DICT),
+ "add": (_binary_operate, util.EMPTY_DICT),
+ "mul": (_binary_operate, util.EMPTY_DICT),
+ "sub": (_binary_operate, util.EMPTY_DICT),
+ "div": (_binary_operate, util.EMPTY_DICT),
+ "mod": (_binary_operate, util.EMPTY_DICT),
+ "bitwise_xor_op": (_binary_operate, util.EMPTY_DICT),
+ "bitwise_or_op": (_binary_operate, util.EMPTY_DICT),
+ "bitwise_and_op": (_binary_operate, util.EMPTY_DICT),
+ "bitwise_not_op": (_bitwise_not_impl, util.EMPTY_DICT),
+ "bitwise_lshift_op": (_binary_operate, util.EMPTY_DICT),
+ "bitwise_rshift_op": (_binary_operate, util.EMPTY_DICT),
+ "truediv": (_binary_operate, util.EMPTY_DICT),
+ "floordiv": (_binary_operate, util.EMPTY_DICT),
+ "custom_op": (_custom_op_operate, util.EMPTY_DICT),
+ "json_path_getitem_op": (_binary_operate, util.EMPTY_DICT),
+ "json_getitem_op": (_binary_operate, util.EMPTY_DICT),
+ "concat_op": (_binary_operate, util.EMPTY_DICT),
+ "any_op": (
+ _scalar,
+ util.immutabledict({"fn": CollectionAggregate._create_any}),
+ ),
+ "all_op": (
+ _scalar,
+ util.immutabledict({"fn": CollectionAggregate._create_all}),
+ ),
+ "lt": (_boolean_compare, util.immutabledict({"negate_op": operators.ge})),
+ "le": (_boolean_compare, util.immutabledict({"negate_op": operators.gt})),
+ "ne": (_boolean_compare, util.immutabledict({"negate_op": operators.eq})),
+ "gt": (_boolean_compare, util.immutabledict({"negate_op": operators.le})),
+ "ge": (_boolean_compare, util.immutabledict({"negate_op": operators.lt})),
+ "eq": (_boolean_compare, util.immutabledict({"negate_op": operators.ne})),
+ "is_distinct_from": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.is_not_distinct_from}),
+ ),
+ "is_not_distinct_from": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.is_distinct_from}),
+ ),
+ "like_op": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.not_like_op}),
+ ),
+ "ilike_op": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.not_ilike_op}),
+ ),
+ "not_like_op": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.like_op}),
+ ),
+ "not_ilike_op": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.ilike_op}),
+ ),
+ "contains_op": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.not_contains_op}),
+ ),
+ "icontains_op": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.not_icontains_op}),
+ ),
+ "startswith_op": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.not_startswith_op}),
+ ),
+ "istartswith_op": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.not_istartswith_op}),
+ ),
+ "endswith_op": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.not_endswith_op}),
+ ),
+ "iendswith_op": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.not_iendswith_op}),
+ ),
+ "desc_op": (
+ _scalar,
+ util.immutabledict({"fn": UnaryExpression._create_desc}),
+ ),
+ "asc_op": (
+ _scalar,
+ util.immutabledict({"fn": UnaryExpression._create_asc}),
+ ),
+ "nulls_first_op": (
+ _scalar,
+ util.immutabledict({"fn": UnaryExpression._create_nulls_first}),
+ ),
+ "nulls_last_op": (
+ _scalar,
+ util.immutabledict({"fn": UnaryExpression._create_nulls_last}),
+ ),
+ "in_op": (
+ _in_impl,
+ util.immutabledict({"negate_op": operators.not_in_op}),
+ ),
+ "not_in_op": (
+ _in_impl,
+ util.immutabledict({"negate_op": operators.in_op}),
+ ),
+ "is_": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.is_}),
+ ),
+ "is_not": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.is_not}),
+ ),
+ "collate": (_collate_impl, util.EMPTY_DICT),
+ "match_op": (_match_impl, util.EMPTY_DICT),
+ "not_match_op": (_match_impl, util.EMPTY_DICT),
+ "distinct_op": (_distinct_impl, util.EMPTY_DICT),
+ "between_op": (_between_impl, util.EMPTY_DICT),
+ "not_between_op": (_between_impl, util.EMPTY_DICT),
+ "neg": (_neg_impl, util.EMPTY_DICT),
+ "getitem": (_getitem_impl, util.EMPTY_DICT),
+ "lshift": (_unsupported_impl, util.EMPTY_DICT),
+ "rshift": (_unsupported_impl, util.EMPTY_DICT),
+ "contains": (_unsupported_impl, util.EMPTY_DICT),
+ "regexp_match_op": (_regexp_match_impl, util.EMPTY_DICT),
+ "not_regexp_match_op": (_regexp_match_impl, util.EMPTY_DICT),
+ "regexp_replace_op": (_regexp_replace_impl, util.EMPTY_DICT),
+}
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/dml.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/dml.py
new file mode 100644
index 0000000..779be1d
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/dml.py
@@ -0,0 +1,1817 @@
+# sql/dml.py
+# Copyright (C) 2009-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+"""
+Provide :class:`_expression.Insert`, :class:`_expression.Update` and
+:class:`_expression.Delete`.
+
+"""
+from __future__ import annotations
+
+import collections.abc as collections_abc
+import operator
+from typing import Any
+from typing import cast
+from typing import Dict
+from typing import Iterable
+from typing import List
+from typing import MutableMapping
+from typing import NoReturn
+from typing import Optional
+from typing import overload
+from typing import Sequence
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from . import coercions
+from . import roles
+from . import util as sql_util
+from ._typing import _TP
+from ._typing import _unexpected_kw
+from ._typing import is_column_element
+from ._typing import is_named_from_clause
+from .base import _entity_namespace_key
+from .base import _exclusive_against
+from .base import _from_objects
+from .base import _generative
+from .base import _select_iterables
+from .base import ColumnCollection
+from .base import CompileState
+from .base import DialectKWArgs
+from .base import Executable
+from .base import Generative
+from .base import HasCompileState
+from .elements import BooleanClauseList
+from .elements import ClauseElement
+from .elements import ColumnClause
+from .elements import ColumnElement
+from .elements import Null
+from .selectable import Alias
+from .selectable import ExecutableReturnsRows
+from .selectable import FromClause
+from .selectable import HasCTE
+from .selectable import HasPrefixes
+from .selectable import Join
+from .selectable import SelectLabelStyle
+from .selectable import TableClause
+from .selectable import TypedReturnsRows
+from .sqltypes import NullType
+from .visitors import InternalTraversal
+from .. import exc
+from .. import util
+from ..util.typing import Self
+from ..util.typing import TypeGuard
+
+if TYPE_CHECKING:
+ from ._typing import _ColumnExpressionArgument
+ from ._typing import _ColumnsClauseArgument
+ from ._typing import _DMLColumnArgument
+ from ._typing import _DMLColumnKeyMapping
+ from ._typing import _DMLTableArgument
+ from ._typing import _T0 # noqa
+ from ._typing import _T1 # noqa
+ from ._typing import _T2 # noqa
+ from ._typing import _T3 # noqa
+ from ._typing import _T4 # noqa
+ from ._typing import _T5 # noqa
+ from ._typing import _T6 # noqa
+ from ._typing import _T7 # noqa
+ from ._typing import _TypedColumnClauseArgument as _TCCA # noqa
+ from .base import ReadOnlyColumnCollection
+ from .compiler import SQLCompiler
+ from .elements import KeyedColumnElement
+ from .selectable import _ColumnsClauseElement
+ from .selectable import _SelectIterable
+ from .selectable import Select
+ from .selectable import Selectable
+
+ def isupdate(dml: DMLState) -> TypeGuard[UpdateDMLState]: ...
+
+ def isdelete(dml: DMLState) -> TypeGuard[DeleteDMLState]: ...
+
+ def isinsert(dml: DMLState) -> TypeGuard[InsertDMLState]: ...
+
+else:
+ isupdate = operator.attrgetter("isupdate")
+ isdelete = operator.attrgetter("isdelete")
+ isinsert = operator.attrgetter("isinsert")
+
+
+_T = TypeVar("_T", bound=Any)
+
+_DMLColumnElement = Union[str, ColumnClause[Any]]
+_DMLTableElement = Union[TableClause, Alias, Join]
+
+
+class DMLState(CompileState):
+ _no_parameters = True
+ _dict_parameters: Optional[MutableMapping[_DMLColumnElement, Any]] = None
+ _multi_parameters: Optional[
+ List[MutableMapping[_DMLColumnElement, Any]]
+ ] = None
+ _ordered_values: Optional[List[Tuple[_DMLColumnElement, Any]]] = None
+ _parameter_ordering: Optional[List[_DMLColumnElement]] = None
+ _primary_table: FromClause
+ _supports_implicit_returning = True
+
+ isupdate = False
+ isdelete = False
+ isinsert = False
+
+ statement: UpdateBase
+
+ def __init__(
+ self, statement: UpdateBase, compiler: SQLCompiler, **kw: Any
+ ):
+ raise NotImplementedError()
+
+ @classmethod
+ def get_entity_description(cls, statement: UpdateBase) -> Dict[str, Any]:
+ return {
+ "name": (
+ statement.table.name
+ if is_named_from_clause(statement.table)
+ else None
+ ),
+ "table": statement.table,
+ }
+
+ @classmethod
+ def get_returning_column_descriptions(
+ cls, statement: UpdateBase
+ ) -> List[Dict[str, Any]]:
+ return [
+ {
+ "name": c.key,
+ "type": c.type,
+ "expr": c,
+ }
+ for c in statement._all_selected_columns
+ ]
+
+ @property
+ def dml_table(self) -> _DMLTableElement:
+ return self.statement.table
+
+ if TYPE_CHECKING:
+
+ @classmethod
+ def get_plugin_class(cls, statement: Executable) -> Type[DMLState]: ...
+
+ @classmethod
+ def _get_multi_crud_kv_pairs(
+ cls,
+ statement: UpdateBase,
+ multi_kv_iterator: Iterable[Dict[_DMLColumnArgument, Any]],
+ ) -> List[Dict[_DMLColumnElement, Any]]:
+ return [
+ {
+ coercions.expect(roles.DMLColumnRole, k): v
+ for k, v in mapping.items()
+ }
+ for mapping in multi_kv_iterator
+ ]
+
+ @classmethod
+ def _get_crud_kv_pairs(
+ cls,
+ statement: UpdateBase,
+ kv_iterator: Iterable[Tuple[_DMLColumnArgument, Any]],
+ needs_to_be_cacheable: bool,
+ ) -> List[Tuple[_DMLColumnElement, Any]]:
+ return [
+ (
+ coercions.expect(roles.DMLColumnRole, k),
+ (
+ v
+ if not needs_to_be_cacheable
+ else coercions.expect(
+ roles.ExpressionElementRole,
+ v,
+ type_=NullType(),
+ is_crud=True,
+ )
+ ),
+ )
+ for k, v in kv_iterator
+ ]
+
+ def _make_extra_froms(
+ self, statement: DMLWhereBase
+ ) -> Tuple[FromClause, List[FromClause]]:
+ froms: List[FromClause] = []
+
+ all_tables = list(sql_util.tables_from_leftmost(statement.table))
+ primary_table = all_tables[0]
+ seen = {primary_table}
+
+ consider = statement._where_criteria
+ if self._dict_parameters:
+ consider += tuple(self._dict_parameters.values())
+
+ for crit in consider:
+ for item in _from_objects(crit):
+ if not seen.intersection(item._cloned_set):
+ froms.append(item)
+ seen.update(item._cloned_set)
+
+ froms.extend(all_tables[1:])
+ return primary_table, froms
+
+ def _process_values(self, statement: ValuesBase) -> None:
+ if self._no_parameters:
+ self._dict_parameters = statement._values
+ self._no_parameters = False
+
+ def _process_select_values(self, statement: ValuesBase) -> None:
+ assert statement._select_names is not None
+ parameters: MutableMapping[_DMLColumnElement, Any] = {
+ name: Null() for name in statement._select_names
+ }
+
+ if self._no_parameters:
+ self._no_parameters = False
+ self._dict_parameters = parameters
+ else:
+ # this condition normally not reachable as the Insert
+ # does not allow this construction to occur
+ assert False, "This statement already has parameters"
+
+ def _no_multi_values_supported(self, statement: ValuesBase) -> NoReturn:
+ raise exc.InvalidRequestError(
+ "%s construct does not support "
+ "multiple parameter sets." % statement.__visit_name__.upper()
+ )
+
+ def _cant_mix_formats_error(self) -> NoReturn:
+ raise exc.InvalidRequestError(
+ "Can't mix single and multiple VALUES "
+ "formats in one INSERT statement; one style appends to a "
+ "list while the other replaces values, so the intent is "
+ "ambiguous."
+ )
+
+
+@CompileState.plugin_for("default", "insert")
+class InsertDMLState(DMLState):
+ isinsert = True
+
+ include_table_with_column_exprs = False
+
+ _has_multi_parameters = False
+
+ def __init__(
+ self,
+ statement: Insert,
+ compiler: SQLCompiler,
+ disable_implicit_returning: bool = False,
+ **kw: Any,
+ ):
+ self.statement = statement
+ self._primary_table = statement.table
+
+ if disable_implicit_returning:
+ self._supports_implicit_returning = False
+
+ self.isinsert = True
+ if statement._select_names:
+ self._process_select_values(statement)
+ if statement._values is not None:
+ self._process_values(statement)
+ if statement._multi_values:
+ self._process_multi_values(statement)
+
+ @util.memoized_property
+ def _insert_col_keys(self) -> List[str]:
+ # this is also done in crud.py -> _key_getters_for_crud_column
+ return [
+ coercions.expect(roles.DMLColumnRole, col, as_key=True)
+ for col in self._dict_parameters or ()
+ ]
+
+ def _process_values(self, statement: ValuesBase) -> None:
+ if self._no_parameters:
+ self._has_multi_parameters = False
+ self._dict_parameters = statement._values
+ self._no_parameters = False
+ elif self._has_multi_parameters:
+ self._cant_mix_formats_error()
+
+ def _process_multi_values(self, statement: ValuesBase) -> None:
+ for parameters in statement._multi_values:
+ multi_parameters: List[MutableMapping[_DMLColumnElement, Any]] = [
+ (
+ {
+ c.key: value
+ for c, value in zip(statement.table.c, parameter_set)
+ }
+ if isinstance(parameter_set, collections_abc.Sequence)
+ else parameter_set
+ )
+ for parameter_set in parameters
+ ]
+
+ if self._no_parameters:
+ self._no_parameters = False
+ self._has_multi_parameters = True
+ self._multi_parameters = multi_parameters
+ self._dict_parameters = self._multi_parameters[0]
+ elif not self._has_multi_parameters:
+ self._cant_mix_formats_error()
+ else:
+ assert self._multi_parameters
+ self._multi_parameters.extend(multi_parameters)
+
+
+@CompileState.plugin_for("default", "update")
+class UpdateDMLState(DMLState):
+ isupdate = True
+
+ include_table_with_column_exprs = False
+
+ def __init__(self, statement: Update, compiler: SQLCompiler, **kw: Any):
+ self.statement = statement
+
+ self.isupdate = True
+ if statement._ordered_values is not None:
+ self._process_ordered_values(statement)
+ elif statement._values is not None:
+ self._process_values(statement)
+ elif statement._multi_values:
+ self._no_multi_values_supported(statement)
+ t, ef = self._make_extra_froms(statement)
+ self._primary_table = t
+ self._extra_froms = ef
+
+ self.is_multitable = mt = ef
+ self.include_table_with_column_exprs = bool(
+ mt and compiler.render_table_with_column_in_update_from
+ )
+
+ def _process_ordered_values(self, statement: ValuesBase) -> None:
+ parameters = statement._ordered_values
+
+ if self._no_parameters:
+ self._no_parameters = False
+ assert parameters is not None
+ self._dict_parameters = dict(parameters)
+ self._ordered_values = parameters
+ self._parameter_ordering = [key for key, value in parameters]
+ else:
+ raise exc.InvalidRequestError(
+ "Can only invoke ordered_values() once, and not mixed "
+ "with any other values() call"
+ )
+
+
+@CompileState.plugin_for("default", "delete")
+class DeleteDMLState(DMLState):
+ isdelete = True
+
+ def __init__(self, statement: Delete, compiler: SQLCompiler, **kw: Any):
+ self.statement = statement
+
+ self.isdelete = True
+ t, ef = self._make_extra_froms(statement)
+ self._primary_table = t
+ self._extra_froms = ef
+ self.is_multitable = ef
+
+
+class UpdateBase(
+ roles.DMLRole,
+ HasCTE,
+ HasCompileState,
+ DialectKWArgs,
+ HasPrefixes,
+ Generative,
+ ExecutableReturnsRows,
+ ClauseElement,
+):
+ """Form the base for ``INSERT``, ``UPDATE``, and ``DELETE`` statements."""
+
+ __visit_name__ = "update_base"
+
+ _hints: util.immutabledict[Tuple[_DMLTableElement, str], str] = (
+ util.EMPTY_DICT
+ )
+ named_with_column = False
+
+ _label_style: SelectLabelStyle = (
+ SelectLabelStyle.LABEL_STYLE_DISAMBIGUATE_ONLY
+ )
+ table: _DMLTableElement
+
+ _return_defaults = False
+ _return_defaults_columns: Optional[Tuple[_ColumnsClauseElement, ...]] = (
+ None
+ )
+ _supplemental_returning: Optional[Tuple[_ColumnsClauseElement, ...]] = None
+ _returning: Tuple[_ColumnsClauseElement, ...] = ()
+
+ is_dml = True
+
+ def _generate_fromclause_column_proxies(
+ self, fromclause: FromClause
+ ) -> None:
+ fromclause._columns._populate_separate_keys(
+ col._make_proxy(fromclause)
+ for col in self._all_selected_columns
+ if is_column_element(col)
+ )
+
+ def params(self, *arg: Any, **kw: Any) -> NoReturn:
+ """Set the parameters for the statement.
+
+ This method raises ``NotImplementedError`` on the base class,
+ and is overridden by :class:`.ValuesBase` to provide the
+ SET/VALUES clause of UPDATE and INSERT.
+
+ """
+ raise NotImplementedError(
+ "params() is not supported for INSERT/UPDATE/DELETE statements."
+ " To set the values for an INSERT or UPDATE statement, use"
+ " stmt.values(**parameters)."
+ )
+
+ @_generative
+ def with_dialect_options(self, **opt: Any) -> Self:
+ """Add dialect options to this INSERT/UPDATE/DELETE object.
+
+ e.g.::
+
+ upd = table.update().dialect_options(mysql_limit=10)
+
+ .. versionadded: 1.4 - this method supersedes the dialect options
+ associated with the constructor.
+
+
+ """
+ self._validate_dialect_kwargs(opt)
+ return self
+
+ @_generative
+ def return_defaults(
+ self,
+ *cols: _DMLColumnArgument,
+ supplemental_cols: Optional[Iterable[_DMLColumnArgument]] = None,
+ sort_by_parameter_order: bool = False,
+ ) -> Self:
+ """Make use of a :term:`RETURNING` clause for the purpose
+ of fetching server-side expressions and defaults, for supporting
+ backends only.
+
+ .. deepalchemy::
+
+ The :meth:`.UpdateBase.return_defaults` method is used by the ORM
+ for its internal work in fetching newly generated primary key
+ and server default values, in particular to provide the underyling
+ implementation of the :paramref:`_orm.Mapper.eager_defaults`
+ ORM feature as well as to allow RETURNING support with bulk
+ ORM inserts. Its behavior is fairly idiosyncratic
+ and is not really intended for general use. End users should
+ stick with using :meth:`.UpdateBase.returning` in order to
+ add RETURNING clauses to their INSERT, UPDATE and DELETE
+ statements.
+
+ Normally, a single row INSERT statement will automatically populate the
+ :attr:`.CursorResult.inserted_primary_key` attribute when executed,
+ which stores the primary key of the row that was just inserted in the
+ form of a :class:`.Row` object with column names as named tuple keys
+ (and the :attr:`.Row._mapping` view fully populated as well). The
+ dialect in use chooses the strategy to use in order to populate this
+ data; if it was generated using server-side defaults and / or SQL
+ expressions, dialect-specific approaches such as ``cursor.lastrowid``
+ or ``RETURNING`` are typically used to acquire the new primary key
+ value.
+
+ However, when the statement is modified by calling
+ :meth:`.UpdateBase.return_defaults` before executing the statement,
+ additional behaviors take place **only** for backends that support
+ RETURNING and for :class:`.Table` objects that maintain the
+ :paramref:`.Table.implicit_returning` parameter at its default value of
+ ``True``. In these cases, when the :class:`.CursorResult` is returned
+ from the statement's execution, not only will
+ :attr:`.CursorResult.inserted_primary_key` be populated as always, the
+ :attr:`.CursorResult.returned_defaults` attribute will also be
+ populated with a :class:`.Row` named-tuple representing the full range
+ of server generated
+ values from that single row, including values for any columns that
+ specify :paramref:`_schema.Column.server_default` or which make use of
+ :paramref:`_schema.Column.default` using a SQL expression.
+
+ When invoking INSERT statements with multiple rows using
+ :ref:`insertmanyvalues <engine_insertmanyvalues>`, the
+ :meth:`.UpdateBase.return_defaults` modifier will have the effect of
+ the :attr:`_engine.CursorResult.inserted_primary_key_rows` and
+ :attr:`_engine.CursorResult.returned_defaults_rows` attributes being
+ fully populated with lists of :class:`.Row` objects representing newly
+ inserted primary key values as well as newly inserted server generated
+ values for each row inserted. The
+ :attr:`.CursorResult.inserted_primary_key` and
+ :attr:`.CursorResult.returned_defaults` attributes will also continue
+ to be populated with the first row of these two collections.
+
+ If the backend does not support RETURNING or the :class:`.Table` in use
+ has disabled :paramref:`.Table.implicit_returning`, then no RETURNING
+ clause is added and no additional data is fetched, however the
+ INSERT, UPDATE or DELETE statement proceeds normally.
+
+ E.g.::
+
+ stmt = table.insert().values(data='newdata').return_defaults()
+
+ result = connection.execute(stmt)
+
+ server_created_at = result.returned_defaults['created_at']
+
+ When used against an UPDATE statement
+ :meth:`.UpdateBase.return_defaults` instead looks for columns that
+ include :paramref:`_schema.Column.onupdate` or
+ :paramref:`_schema.Column.server_onupdate` parameters assigned, when
+ constructing the columns that will be included in the RETURNING clause
+ by default if explicit columns were not specified. When used against a
+ DELETE statement, no columns are included in RETURNING by default, they
+ instead must be specified explicitly as there are no columns that
+ normally change values when a DELETE statement proceeds.
+
+ .. versionadded:: 2.0 :meth:`.UpdateBase.return_defaults` is supported
+ for DELETE statements also and has been moved from
+ :class:`.ValuesBase` to :class:`.UpdateBase`.
+
+ The :meth:`.UpdateBase.return_defaults` method is mutually exclusive
+ against the :meth:`.UpdateBase.returning` method and errors will be
+ raised during the SQL compilation process if both are used at the same
+ time on one statement. The RETURNING clause of the INSERT, UPDATE or
+ DELETE statement is therefore controlled by only one of these methods
+ at a time.
+
+ The :meth:`.UpdateBase.return_defaults` method differs from
+ :meth:`.UpdateBase.returning` in these ways:
+
+ 1. :meth:`.UpdateBase.return_defaults` method causes the
+ :attr:`.CursorResult.returned_defaults` collection to be populated
+ with the first row from the RETURNING result. This attribute is not
+ populated when using :meth:`.UpdateBase.returning`.
+
+ 2. :meth:`.UpdateBase.return_defaults` is compatible with existing
+ logic used to fetch auto-generated primary key values that are then
+ populated into the :attr:`.CursorResult.inserted_primary_key`
+ attribute. By contrast, using :meth:`.UpdateBase.returning` will
+ have the effect of the :attr:`.CursorResult.inserted_primary_key`
+ attribute being left unpopulated.
+
+ 3. :meth:`.UpdateBase.return_defaults` can be called against any
+ backend. Backends that don't support RETURNING will skip the usage
+ of the feature, rather than raising an exception, *unless*
+ ``supplemental_cols`` is passed. The return value
+ of :attr:`_engine.CursorResult.returned_defaults` will be ``None``
+ for backends that don't support RETURNING or for which the target
+ :class:`.Table` sets :paramref:`.Table.implicit_returning` to
+ ``False``.
+
+ 4. An INSERT statement invoked with executemany() is supported if the
+ backend database driver supports the
+ :ref:`insertmanyvalues <engine_insertmanyvalues>`
+ feature which is now supported by most SQLAlchemy-included backends.
+ When executemany is used, the
+ :attr:`_engine.CursorResult.returned_defaults_rows` and
+ :attr:`_engine.CursorResult.inserted_primary_key_rows` accessors
+ will return the inserted defaults and primary keys.
+
+ .. versionadded:: 1.4 Added
+ :attr:`_engine.CursorResult.returned_defaults_rows` and
+ :attr:`_engine.CursorResult.inserted_primary_key_rows` accessors.
+ In version 2.0, the underlying implementation which fetches and
+ populates the data for these attributes was generalized to be
+ supported by most backends, whereas in 1.4 they were only
+ supported by the ``psycopg2`` driver.
+
+
+ :param cols: optional list of column key names or
+ :class:`_schema.Column` that acts as a filter for those columns that
+ will be fetched.
+ :param supplemental_cols: optional list of RETURNING expressions,
+ in the same form as one would pass to the
+ :meth:`.UpdateBase.returning` method. When present, the additional
+ columns will be included in the RETURNING clause, and the
+ :class:`.CursorResult` object will be "rewound" when returned, so
+ that methods like :meth:`.CursorResult.all` will return new rows
+ mostly as though the statement used :meth:`.UpdateBase.returning`
+ directly. However, unlike when using :meth:`.UpdateBase.returning`
+ directly, the **order of the columns is undefined**, so can only be
+ targeted using names or :attr:`.Row._mapping` keys; they cannot
+ reliably be targeted positionally.
+
+ .. versionadded:: 2.0
+
+ :param sort_by_parameter_order: for a batch INSERT that is being
+ executed against multiple parameter sets, organize the results of
+ RETURNING so that the returned rows correspond to the order of
+ parameter sets passed in. This applies only to an :term:`executemany`
+ execution for supporting dialects and typically makes use of the
+ :term:`insertmanyvalues` feature.
+
+ .. versionadded:: 2.0.10
+
+ .. seealso::
+
+ :ref:`engine_insertmanyvalues_returning_order` - background on
+ sorting of RETURNING rows for bulk INSERT
+
+ .. seealso::
+
+ :meth:`.UpdateBase.returning`
+
+ :attr:`_engine.CursorResult.returned_defaults`
+
+ :attr:`_engine.CursorResult.returned_defaults_rows`
+
+ :attr:`_engine.CursorResult.inserted_primary_key`
+
+ :attr:`_engine.CursorResult.inserted_primary_key_rows`
+
+ """
+
+ if self._return_defaults:
+ # note _return_defaults_columns = () means return all columns,
+ # so if we have been here before, only update collection if there
+ # are columns in the collection
+ if self._return_defaults_columns and cols:
+ self._return_defaults_columns = tuple(
+ util.OrderedSet(self._return_defaults_columns).union(
+ coercions.expect(roles.ColumnsClauseRole, c)
+ for c in cols
+ )
+ )
+ else:
+ # set for all columns
+ self._return_defaults_columns = ()
+ else:
+ self._return_defaults_columns = tuple(
+ coercions.expect(roles.ColumnsClauseRole, c) for c in cols
+ )
+ self._return_defaults = True
+ if sort_by_parameter_order:
+ if not self.is_insert:
+ raise exc.ArgumentError(
+ "The 'sort_by_parameter_order' argument to "
+ "return_defaults() only applies to INSERT statements"
+ )
+ self._sort_by_parameter_order = True
+ if supplemental_cols:
+ # uniquifying while also maintaining order (the maintain of order
+ # is for test suites but also for vertical splicing
+ supplemental_col_tup = (
+ coercions.expect(roles.ColumnsClauseRole, c)
+ for c in supplemental_cols
+ )
+
+ if self._supplemental_returning is None:
+ self._supplemental_returning = tuple(
+ util.unique_list(supplemental_col_tup)
+ )
+ else:
+ self._supplemental_returning = tuple(
+ util.unique_list(
+ self._supplemental_returning
+ + tuple(supplemental_col_tup)
+ )
+ )
+
+ return self
+
+ @_generative
+ def returning(
+ self,
+ *cols: _ColumnsClauseArgument[Any],
+ sort_by_parameter_order: bool = False,
+ **__kw: Any,
+ ) -> UpdateBase:
+ r"""Add a :term:`RETURNING` or equivalent clause to this statement.
+
+ e.g.:
+
+ .. sourcecode:: pycon+sql
+
+ >>> stmt = (
+ ... table.update()
+ ... .where(table.c.data == "value")
+ ... .values(status="X")
+ ... .returning(table.c.server_flag, table.c.updated_timestamp)
+ ... )
+ >>> print(stmt)
+ {printsql}UPDATE some_table SET status=:status
+ WHERE some_table.data = :data_1
+ RETURNING some_table.server_flag, some_table.updated_timestamp
+
+ The method may be invoked multiple times to add new entries to the
+ list of expressions to be returned.
+
+ .. versionadded:: 1.4.0b2 The method may be invoked multiple times to
+ add new entries to the list of expressions to be returned.
+
+ The given collection of column expressions should be derived from the
+ table that is the target of the INSERT, UPDATE, or DELETE. While
+ :class:`_schema.Column` objects are typical, the elements can also be
+ expressions:
+
+ .. sourcecode:: pycon+sql
+
+ >>> stmt = table.insert().returning(
+ ... (table.c.first_name + " " + table.c.last_name).label("fullname")
+ ... )
+ >>> print(stmt)
+ {printsql}INSERT INTO some_table (first_name, last_name)
+ VALUES (:first_name, :last_name)
+ RETURNING some_table.first_name || :first_name_1 || some_table.last_name AS fullname
+
+ Upon compilation, a RETURNING clause, or database equivalent,
+ will be rendered within the statement. For INSERT and UPDATE,
+ the values are the newly inserted/updated values. For DELETE,
+ the values are those of the rows which were deleted.
+
+ Upon execution, the values of the columns to be returned are made
+ available via the result set and can be iterated using
+ :meth:`_engine.CursorResult.fetchone` and similar.
+ For DBAPIs which do not
+ natively support returning values (i.e. cx_oracle), SQLAlchemy will
+ approximate this behavior at the result level so that a reasonable
+ amount of behavioral neutrality is provided.
+
+ Note that not all databases/DBAPIs
+ support RETURNING. For those backends with no support,
+ an exception is raised upon compilation and/or execution.
+ For those who do support it, the functionality across backends
+ varies greatly, including restrictions on executemany()
+ and other statements which return multiple rows. Please
+ read the documentation notes for the database in use in
+ order to determine the availability of RETURNING.
+
+ :param \*cols: series of columns, SQL expressions, or whole tables
+ entities to be returned.
+ :param sort_by_parameter_order: for a batch INSERT that is being
+ executed against multiple parameter sets, organize the results of
+ RETURNING so that the returned rows correspond to the order of
+ parameter sets passed in. This applies only to an :term:`executemany`
+ execution for supporting dialects and typically makes use of the
+ :term:`insertmanyvalues` feature.
+
+ .. versionadded:: 2.0.10
+
+ .. seealso::
+
+ :ref:`engine_insertmanyvalues_returning_order` - background on
+ sorting of RETURNING rows for bulk INSERT (Core level discussion)
+
+ :ref:`orm_queryguide_bulk_insert_returning_ordered` - example of
+ use with :ref:`orm_queryguide_bulk_insert` (ORM level discussion)
+
+ .. seealso::
+
+ :meth:`.UpdateBase.return_defaults` - an alternative method tailored
+ towards efficient fetching of server-side defaults and triggers
+ for single-row INSERTs or UPDATEs.
+
+ :ref:`tutorial_insert_returning` - in the :ref:`unified_tutorial`
+
+ """ # noqa: E501
+ if __kw:
+ raise _unexpected_kw("UpdateBase.returning()", __kw)
+ if self._return_defaults:
+ raise exc.InvalidRequestError(
+ "return_defaults() is already configured on this statement"
+ )
+ self._returning += tuple(
+ coercions.expect(roles.ColumnsClauseRole, c) for c in cols
+ )
+ if sort_by_parameter_order:
+ if not self.is_insert:
+ raise exc.ArgumentError(
+ "The 'sort_by_parameter_order' argument to returning() "
+ "only applies to INSERT statements"
+ )
+ self._sort_by_parameter_order = True
+ return self
+
+ def corresponding_column(
+ self, column: KeyedColumnElement[Any], require_embedded: bool = False
+ ) -> Optional[ColumnElement[Any]]:
+ return self.exported_columns.corresponding_column(
+ column, require_embedded=require_embedded
+ )
+
+ @util.ro_memoized_property
+ def _all_selected_columns(self) -> _SelectIterable:
+ return [c for c in _select_iterables(self._returning)]
+
+ @util.ro_memoized_property
+ def exported_columns(
+ self,
+ ) -> ReadOnlyColumnCollection[Optional[str], ColumnElement[Any]]:
+ """Return the RETURNING columns as a column collection for this
+ statement.
+
+ .. versionadded:: 1.4
+
+ """
+ return ColumnCollection(
+ (c.key, c)
+ for c in self._all_selected_columns
+ if is_column_element(c)
+ ).as_readonly()
+
+ @_generative
+ def with_hint(
+ self,
+ text: str,
+ selectable: Optional[_DMLTableArgument] = None,
+ dialect_name: str = "*",
+ ) -> Self:
+ """Add a table hint for a single table to this
+ INSERT/UPDATE/DELETE statement.
+
+ .. note::
+
+ :meth:`.UpdateBase.with_hint` currently applies only to
+ Microsoft SQL Server. For MySQL INSERT/UPDATE/DELETE hints, use
+ :meth:`.UpdateBase.prefix_with`.
+
+ The text of the hint is rendered in the appropriate
+ location for the database backend in use, relative
+ to the :class:`_schema.Table` that is the subject of this
+ statement, or optionally to that of the given
+ :class:`_schema.Table` passed as the ``selectable`` argument.
+
+ The ``dialect_name`` option will limit the rendering of a particular
+ hint to a particular backend. Such as, to add a hint
+ that only takes effect for SQL Server::
+
+ mytable.insert().with_hint("WITH (PAGLOCK)", dialect_name="mssql")
+
+ :param text: Text of the hint.
+ :param selectable: optional :class:`_schema.Table` that specifies
+ an element of the FROM clause within an UPDATE or DELETE
+ to be the subject of the hint - applies only to certain backends.
+ :param dialect_name: defaults to ``*``, if specified as the name
+ of a particular dialect, will apply these hints only when
+ that dialect is in use.
+ """
+ if selectable is None:
+ selectable = self.table
+ else:
+ selectable = coercions.expect(roles.DMLTableRole, selectable)
+ self._hints = self._hints.union({(selectable, dialect_name): text})
+ return self
+
+ @property
+ def entity_description(self) -> Dict[str, Any]:
+ """Return a :term:`plugin-enabled` description of the table and/or
+ entity which this DML construct is operating against.
+
+ This attribute is generally useful when using the ORM, as an
+ extended structure which includes information about mapped
+ entities is returned. The section :ref:`queryguide_inspection`
+ contains more background.
+
+ For a Core statement, the structure returned by this accessor
+ is derived from the :attr:`.UpdateBase.table` attribute, and
+ refers to the :class:`.Table` being inserted, updated, or deleted::
+
+ >>> stmt = insert(user_table)
+ >>> stmt.entity_description
+ {
+ "name": "user_table",
+ "table": Table("user_table", ...)
+ }
+
+ .. versionadded:: 1.4.33
+
+ .. seealso::
+
+ :attr:`.UpdateBase.returning_column_descriptions`
+
+ :attr:`.Select.column_descriptions` - entity information for
+ a :func:`.select` construct
+
+ :ref:`queryguide_inspection` - ORM background
+
+ """
+ meth = DMLState.get_plugin_class(self).get_entity_description
+ return meth(self)
+
+ @property
+ def returning_column_descriptions(self) -> List[Dict[str, Any]]:
+ """Return a :term:`plugin-enabled` description of the columns
+ which this DML construct is RETURNING against, in other words
+ the expressions established as part of :meth:`.UpdateBase.returning`.
+
+ This attribute is generally useful when using the ORM, as an
+ extended structure which includes information about mapped
+ entities is returned. The section :ref:`queryguide_inspection`
+ contains more background.
+
+ For a Core statement, the structure returned by this accessor is
+ derived from the same objects that are returned by the
+ :attr:`.UpdateBase.exported_columns` accessor::
+
+ >>> stmt = insert(user_table).returning(user_table.c.id, user_table.c.name)
+ >>> stmt.entity_description
+ [
+ {
+ "name": "id",
+ "type": Integer,
+ "expr": Column("id", Integer(), table=<user>, ...)
+ },
+ {
+ "name": "name",
+ "type": String(),
+ "expr": Column("name", String(), table=<user>, ...)
+ },
+ ]
+
+ .. versionadded:: 1.4.33
+
+ .. seealso::
+
+ :attr:`.UpdateBase.entity_description`
+
+ :attr:`.Select.column_descriptions` - entity information for
+ a :func:`.select` construct
+
+ :ref:`queryguide_inspection` - ORM background
+
+ """ # noqa: E501
+ meth = DMLState.get_plugin_class(
+ self
+ ).get_returning_column_descriptions
+ return meth(self)
+
+
+class ValuesBase(UpdateBase):
+ """Supplies support for :meth:`.ValuesBase.values` to
+ INSERT and UPDATE constructs."""
+
+ __visit_name__ = "values_base"
+
+ _supports_multi_parameters = False
+
+ select: Optional[Select[Any]] = None
+ """SELECT statement for INSERT .. FROM SELECT"""
+
+ _post_values_clause: Optional[ClauseElement] = None
+ """used by extensions to Insert etc. to add additional syntacitcal
+ constructs, e.g. ON CONFLICT etc."""
+
+ _values: Optional[util.immutabledict[_DMLColumnElement, Any]] = None
+ _multi_values: Tuple[
+ Union[
+ Sequence[Dict[_DMLColumnElement, Any]],
+ Sequence[Sequence[Any]],
+ ],
+ ...,
+ ] = ()
+
+ _ordered_values: Optional[List[Tuple[_DMLColumnElement, Any]]] = None
+
+ _select_names: Optional[List[str]] = None
+ _inline: bool = False
+
+ def __init__(self, table: _DMLTableArgument):
+ self.table = coercions.expect(
+ roles.DMLTableRole, table, apply_propagate_attrs=self
+ )
+
+ @_generative
+ @_exclusive_against(
+ "_select_names",
+ "_ordered_values",
+ msgs={
+ "_select_names": "This construct already inserts from a SELECT",
+ "_ordered_values": "This statement already has ordered "
+ "values present",
+ },
+ )
+ def values(
+ self,
+ *args: Union[
+ _DMLColumnKeyMapping[Any],
+ Sequence[Any],
+ ],
+ **kwargs: Any,
+ ) -> Self:
+ r"""Specify a fixed VALUES clause for an INSERT statement, or the SET
+ clause for an UPDATE.
+
+ Note that the :class:`_expression.Insert` and
+ :class:`_expression.Update`
+ constructs support
+ per-execution time formatting of the VALUES and/or SET clauses,
+ based on the arguments passed to :meth:`_engine.Connection.execute`.
+ However, the :meth:`.ValuesBase.values` method can be used to "fix" a
+ particular set of parameters into the statement.
+
+ Multiple calls to :meth:`.ValuesBase.values` will produce a new
+ construct, each one with the parameter list modified to include
+ the new parameters sent. In the typical case of a single
+ dictionary of parameters, the newly passed keys will replace
+ the same keys in the previous construct. In the case of a list-based
+ "multiple values" construct, each new list of values is extended
+ onto the existing list of values.
+
+ :param \**kwargs: key value pairs representing the string key
+ of a :class:`_schema.Column`
+ mapped to the value to be rendered into the
+ VALUES or SET clause::
+
+ users.insert().values(name="some name")
+
+ users.update().where(users.c.id==5).values(name="some name")
+
+ :param \*args: As an alternative to passing key/value parameters,
+ a dictionary, tuple, or list of dictionaries or tuples can be passed
+ as a single positional argument in order to form the VALUES or
+ SET clause of the statement. The forms that are accepted vary
+ based on whether this is an :class:`_expression.Insert` or an
+ :class:`_expression.Update` construct.
+
+ For either an :class:`_expression.Insert` or
+ :class:`_expression.Update`
+ construct, a single dictionary can be passed, which works the same as
+ that of the kwargs form::
+
+ users.insert().values({"name": "some name"})
+
+ users.update().values({"name": "some new name"})
+
+ Also for either form but more typically for the
+ :class:`_expression.Insert` construct, a tuple that contains an
+ entry for every column in the table is also accepted::
+
+ users.insert().values((5, "some name"))
+
+ The :class:`_expression.Insert` construct also supports being
+ passed a list of dictionaries or full-table-tuples, which on the
+ server will render the less common SQL syntax of "multiple values" -
+ this syntax is supported on backends such as SQLite, PostgreSQL,
+ MySQL, but not necessarily others::
+
+ users.insert().values([
+ {"name": "some name"},
+ {"name": "some other name"},
+ {"name": "yet another name"},
+ ])
+
+ The above form would render a multiple VALUES statement similar to::
+
+ INSERT INTO users (name) VALUES
+ (:name_1),
+ (:name_2),
+ (:name_3)
+
+ It is essential to note that **passing multiple values is
+ NOT the same as using traditional executemany() form**. The above
+ syntax is a **special** syntax not typically used. To emit an
+ INSERT statement against multiple rows, the normal method is
+ to pass a multiple values list to the
+ :meth:`_engine.Connection.execute`
+ method, which is supported by all database backends and is generally
+ more efficient for a very large number of parameters.
+
+ .. seealso::
+
+ :ref:`tutorial_multiple_parameters` - an introduction to
+ the traditional Core method of multiple parameter set
+ invocation for INSERTs and other statements.
+
+ The UPDATE construct also supports rendering the SET parameters
+ in a specific order. For this feature refer to the
+ :meth:`_expression.Update.ordered_values` method.
+
+ .. seealso::
+
+ :meth:`_expression.Update.ordered_values`
+
+
+ """
+ if args:
+ # positional case. this is currently expensive. we don't
+ # yet have positional-only args so we have to check the length.
+ # then we need to check multiparams vs. single dictionary.
+ # since the parameter format is needed in order to determine
+ # a cache key, we need to determine this up front.
+ arg = args[0]
+
+ if kwargs:
+ raise exc.ArgumentError(
+ "Can't pass positional and kwargs to values() "
+ "simultaneously"
+ )
+ elif len(args) > 1:
+ raise exc.ArgumentError(
+ "Only a single dictionary/tuple or list of "
+ "dictionaries/tuples is accepted positionally."
+ )
+
+ elif isinstance(arg, collections_abc.Sequence):
+ if arg and isinstance(arg[0], dict):
+ multi_kv_generator = DMLState.get_plugin_class(
+ self
+ )._get_multi_crud_kv_pairs
+ self._multi_values += (multi_kv_generator(self, arg),)
+ return self
+
+ if arg and isinstance(arg[0], (list, tuple)):
+ self._multi_values += (arg,)
+ return self
+
+ if TYPE_CHECKING:
+ # crud.py raises during compilation if this is not the
+ # case
+ assert isinstance(self, Insert)
+
+ # tuple values
+ arg = {c.key: value for c, value in zip(self.table.c, arg)}
+
+ else:
+ # kwarg path. this is the most common path for non-multi-params
+ # so this is fairly quick.
+ arg = cast("Dict[_DMLColumnArgument, Any]", kwargs)
+ if args:
+ raise exc.ArgumentError(
+ "Only a single dictionary/tuple or list of "
+ "dictionaries/tuples is accepted positionally."
+ )
+
+ # for top level values(), convert literals to anonymous bound
+ # parameters at statement construction time, so that these values can
+ # participate in the cache key process like any other ClauseElement.
+ # crud.py now intercepts bound parameters with unique=True from here
+ # and ensures they get the "crud"-style name when rendered.
+
+ kv_generator = DMLState.get_plugin_class(self)._get_crud_kv_pairs
+ coerced_arg = dict(kv_generator(self, arg.items(), True))
+ if self._values:
+ self._values = self._values.union(coerced_arg)
+ else:
+ self._values = util.immutabledict(coerced_arg)
+ return self
+
+
+class Insert(ValuesBase):
+ """Represent an INSERT construct.
+
+ The :class:`_expression.Insert` object is created using the
+ :func:`_expression.insert()` function.
+
+ """
+
+ __visit_name__ = "insert"
+
+ _supports_multi_parameters = True
+
+ select = None
+ include_insert_from_select_defaults = False
+
+ _sort_by_parameter_order: bool = False
+
+ is_insert = True
+
+ table: TableClause
+
+ _traverse_internals = (
+ [
+ ("table", InternalTraversal.dp_clauseelement),
+ ("_inline", InternalTraversal.dp_boolean),
+ ("_select_names", InternalTraversal.dp_string_list),
+ ("_values", InternalTraversal.dp_dml_values),
+ ("_multi_values", InternalTraversal.dp_dml_multi_values),
+ ("select", InternalTraversal.dp_clauseelement),
+ ("_post_values_clause", InternalTraversal.dp_clauseelement),
+ ("_returning", InternalTraversal.dp_clauseelement_tuple),
+ ("_hints", InternalTraversal.dp_table_hint_list),
+ ("_return_defaults", InternalTraversal.dp_boolean),
+ (
+ "_return_defaults_columns",
+ InternalTraversal.dp_clauseelement_tuple,
+ ),
+ ("_sort_by_parameter_order", InternalTraversal.dp_boolean),
+ ]
+ + HasPrefixes._has_prefixes_traverse_internals
+ + DialectKWArgs._dialect_kwargs_traverse_internals
+ + Executable._executable_traverse_internals
+ + HasCTE._has_ctes_traverse_internals
+ )
+
+ def __init__(self, table: _DMLTableArgument):
+ super().__init__(table)
+
+ @_generative
+ def inline(self) -> Self:
+ """Make this :class:`_expression.Insert` construct "inline" .
+
+ When set, no attempt will be made to retrieve the
+ SQL-generated default values to be provided within the statement;
+ in particular,
+ this allows SQL expressions to be rendered 'inline' within the
+ statement without the need to pre-execute them beforehand; for
+ backends that support "returning", this turns off the "implicit
+ returning" feature for the statement.
+
+
+ .. versionchanged:: 1.4 the :paramref:`_expression.Insert.inline`
+ parameter
+ is now superseded by the :meth:`_expression.Insert.inline` method.
+
+ """
+ self._inline = True
+ return self
+
+ @_generative
+ def from_select(
+ self,
+ names: Sequence[_DMLColumnArgument],
+ select: Selectable,
+ include_defaults: bool = True,
+ ) -> Self:
+ """Return a new :class:`_expression.Insert` construct which represents
+ an ``INSERT...FROM SELECT`` statement.
+
+ e.g.::
+
+ sel = select(table1.c.a, table1.c.b).where(table1.c.c > 5)
+ ins = table2.insert().from_select(['a', 'b'], sel)
+
+ :param names: a sequence of string column names or
+ :class:`_schema.Column`
+ objects representing the target columns.
+ :param select: a :func:`_expression.select` construct,
+ :class:`_expression.FromClause`
+ or other construct which resolves into a
+ :class:`_expression.FromClause`,
+ such as an ORM :class:`_query.Query` object, etc. The order of
+ columns returned from this FROM clause should correspond to the
+ order of columns sent as the ``names`` parameter; while this
+ is not checked before passing along to the database, the database
+ would normally raise an exception if these column lists don't
+ correspond.
+ :param include_defaults: if True, non-server default values and
+ SQL expressions as specified on :class:`_schema.Column` objects
+ (as documented in :ref:`metadata_defaults_toplevel`) not
+ otherwise specified in the list of names will be rendered
+ into the INSERT and SELECT statements, so that these values are also
+ included in the data to be inserted.
+
+ .. note:: A Python-side default that uses a Python callable function
+ will only be invoked **once** for the whole statement, and **not
+ per row**.
+
+ """
+
+ if self._values:
+ raise exc.InvalidRequestError(
+ "This construct already inserts value expressions"
+ )
+
+ self._select_names = [
+ coercions.expect(roles.DMLColumnRole, name, as_key=True)
+ for name in names
+ ]
+ self._inline = True
+ self.include_insert_from_select_defaults = include_defaults
+ self.select = coercions.expect(roles.DMLSelectRole, select)
+ return self
+
+ if TYPE_CHECKING:
+ # START OVERLOADED FUNCTIONS self.returning ReturningInsert 1-8 ", *, sort_by_parameter_order: bool = False" # noqa: E501
+
+ # code within this block is **programmatically,
+ # statically generated** by tools/generate_tuple_map_overloads.py
+
+ @overload
+ def returning(
+ self, __ent0: _TCCA[_T0], *, sort_by_parameter_order: bool = False
+ ) -> ReturningInsert[Tuple[_T0]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ *,
+ sort_by_parameter_order: bool = False,
+ ) -> ReturningInsert[Tuple[_T0, _T1]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ *,
+ sort_by_parameter_order: bool = False,
+ ) -> ReturningInsert[Tuple[_T0, _T1, _T2]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ *,
+ sort_by_parameter_order: bool = False,
+ ) -> ReturningInsert[Tuple[_T0, _T1, _T2, _T3]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ *,
+ sort_by_parameter_order: bool = False,
+ ) -> ReturningInsert[Tuple[_T0, _T1, _T2, _T3, _T4]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ *,
+ sort_by_parameter_order: bool = False,
+ ) -> ReturningInsert[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ __ent6: _TCCA[_T6],
+ *,
+ sort_by_parameter_order: bool = False,
+ ) -> ReturningInsert[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ __ent6: _TCCA[_T6],
+ __ent7: _TCCA[_T7],
+ *,
+ sort_by_parameter_order: bool = False,
+ ) -> ReturningInsert[
+ Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]
+ ]: ...
+
+ # END OVERLOADED FUNCTIONS self.returning
+
+ @overload
+ def returning(
+ self,
+ *cols: _ColumnsClauseArgument[Any],
+ sort_by_parameter_order: bool = False,
+ **__kw: Any,
+ ) -> ReturningInsert[Any]: ...
+
+ def returning(
+ self,
+ *cols: _ColumnsClauseArgument[Any],
+ sort_by_parameter_order: bool = False,
+ **__kw: Any,
+ ) -> ReturningInsert[Any]: ...
+
+
+class ReturningInsert(Insert, TypedReturnsRows[_TP]):
+ """Typing-only class that establishes a generic type form of
+ :class:`.Insert` which tracks returned column types.
+
+ This datatype is delivered when calling the
+ :meth:`.Insert.returning` method.
+
+ .. versionadded:: 2.0
+
+ """
+
+
+class DMLWhereBase:
+ table: _DMLTableElement
+ _where_criteria: Tuple[ColumnElement[Any], ...] = ()
+
+ @_generative
+ def where(self, *whereclause: _ColumnExpressionArgument[bool]) -> Self:
+ """Return a new construct with the given expression(s) added to
+ its WHERE clause, joined to the existing clause via AND, if any.
+
+ Both :meth:`_dml.Update.where` and :meth:`_dml.Delete.where`
+ support multiple-table forms, including database-specific
+ ``UPDATE...FROM`` as well as ``DELETE..USING``. For backends that
+ don't have multiple-table support, a backend agnostic approach
+ to using multiple tables is to make use of correlated subqueries.
+ See the linked tutorial sections below for examples.
+
+ .. seealso::
+
+ :ref:`tutorial_correlated_updates`
+
+ :ref:`tutorial_update_from`
+
+ :ref:`tutorial_multi_table_deletes`
+
+ """
+
+ for criterion in whereclause:
+ where_criteria: ColumnElement[Any] = coercions.expect(
+ roles.WhereHavingRole, criterion, apply_propagate_attrs=self
+ )
+ self._where_criteria += (where_criteria,)
+ return self
+
+ def filter(self, *criteria: roles.ExpressionElementRole[Any]) -> Self:
+ """A synonym for the :meth:`_dml.DMLWhereBase.where` method.
+
+ .. versionadded:: 1.4
+
+ """
+
+ return self.where(*criteria)
+
+ def _filter_by_zero(self) -> _DMLTableElement:
+ return self.table
+
+ def filter_by(self, **kwargs: Any) -> Self:
+ r"""apply the given filtering criterion as a WHERE clause
+ to this select.
+
+ """
+ from_entity = self._filter_by_zero()
+
+ clauses = [
+ _entity_namespace_key(from_entity, key) == value
+ for key, value in kwargs.items()
+ ]
+ return self.filter(*clauses)
+
+ @property
+ def whereclause(self) -> Optional[ColumnElement[Any]]:
+ """Return the completed WHERE clause for this :class:`.DMLWhereBase`
+ statement.
+
+ This assembles the current collection of WHERE criteria
+ into a single :class:`_expression.BooleanClauseList` construct.
+
+
+ .. versionadded:: 1.4
+
+ """
+
+ return BooleanClauseList._construct_for_whereclause(
+ self._where_criteria
+ )
+
+
+class Update(DMLWhereBase, ValuesBase):
+ """Represent an Update construct.
+
+ The :class:`_expression.Update` object is created using the
+ :func:`_expression.update()` function.
+
+ """
+
+ __visit_name__ = "update"
+
+ is_update = True
+
+ _traverse_internals = (
+ [
+ ("table", InternalTraversal.dp_clauseelement),
+ ("_where_criteria", InternalTraversal.dp_clauseelement_tuple),
+ ("_inline", InternalTraversal.dp_boolean),
+ ("_ordered_values", InternalTraversal.dp_dml_ordered_values),
+ ("_values", InternalTraversal.dp_dml_values),
+ ("_returning", InternalTraversal.dp_clauseelement_tuple),
+ ("_hints", InternalTraversal.dp_table_hint_list),
+ ("_return_defaults", InternalTraversal.dp_boolean),
+ (
+ "_return_defaults_columns",
+ InternalTraversal.dp_clauseelement_tuple,
+ ),
+ ]
+ + HasPrefixes._has_prefixes_traverse_internals
+ + DialectKWArgs._dialect_kwargs_traverse_internals
+ + Executable._executable_traverse_internals
+ + HasCTE._has_ctes_traverse_internals
+ )
+
+ def __init__(self, table: _DMLTableArgument):
+ super().__init__(table)
+
+ @_generative
+ def ordered_values(self, *args: Tuple[_DMLColumnArgument, Any]) -> Self:
+ """Specify the VALUES clause of this UPDATE statement with an explicit
+ parameter ordering that will be maintained in the SET clause of the
+ resulting UPDATE statement.
+
+ E.g.::
+
+ stmt = table.update().ordered_values(
+ ("name", "ed"), ("ident", "foo")
+ )
+
+ .. seealso::
+
+ :ref:`tutorial_parameter_ordered_updates` - full example of the
+ :meth:`_expression.Update.ordered_values` method.
+
+ .. versionchanged:: 1.4 The :meth:`_expression.Update.ordered_values`
+ method
+ supersedes the
+ :paramref:`_expression.update.preserve_parameter_order`
+ parameter, which will be removed in SQLAlchemy 2.0.
+
+ """
+ if self._values:
+ raise exc.ArgumentError(
+ "This statement already has values present"
+ )
+ elif self._ordered_values:
+ raise exc.ArgumentError(
+ "This statement already has ordered values present"
+ )
+
+ kv_generator = DMLState.get_plugin_class(self)._get_crud_kv_pairs
+ self._ordered_values = kv_generator(self, args, True)
+ return self
+
+ @_generative
+ def inline(self) -> Self:
+ """Make this :class:`_expression.Update` construct "inline" .
+
+ When set, SQL defaults present on :class:`_schema.Column`
+ objects via the
+ ``default`` keyword will be compiled 'inline' into the statement and
+ not pre-executed. This means that their values will not be available
+ in the dictionary returned from
+ :meth:`_engine.CursorResult.last_updated_params`.
+
+ .. versionchanged:: 1.4 the :paramref:`_expression.update.inline`
+ parameter
+ is now superseded by the :meth:`_expression.Update.inline` method.
+
+ """
+ self._inline = True
+ return self
+
+ if TYPE_CHECKING:
+ # START OVERLOADED FUNCTIONS self.returning ReturningUpdate 1-8
+
+ # code within this block is **programmatically,
+ # statically generated** by tools/generate_tuple_map_overloads.py
+
+ @overload
+ def returning(
+ self, __ent0: _TCCA[_T0]
+ ) -> ReturningUpdate[Tuple[_T0]]: ...
+
+ @overload
+ def returning(
+ self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1]
+ ) -> ReturningUpdate[Tuple[_T0, _T1]]: ...
+
+ @overload
+ def returning(
+ self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2]
+ ) -> ReturningUpdate[Tuple[_T0, _T1, _T2]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ ) -> ReturningUpdate[Tuple[_T0, _T1, _T2, _T3]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ ) -> ReturningUpdate[Tuple[_T0, _T1, _T2, _T3, _T4]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ ) -> ReturningUpdate[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ __ent6: _TCCA[_T6],
+ ) -> ReturningUpdate[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ __ent6: _TCCA[_T6],
+ __ent7: _TCCA[_T7],
+ ) -> ReturningUpdate[
+ Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]
+ ]: ...
+
+ # END OVERLOADED FUNCTIONS self.returning
+
+ @overload
+ def returning(
+ self, *cols: _ColumnsClauseArgument[Any], **__kw: Any
+ ) -> ReturningUpdate[Any]: ...
+
+ def returning(
+ self, *cols: _ColumnsClauseArgument[Any], **__kw: Any
+ ) -> ReturningUpdate[Any]: ...
+
+
+class ReturningUpdate(Update, TypedReturnsRows[_TP]):
+ """Typing-only class that establishes a generic type form of
+ :class:`.Update` which tracks returned column types.
+
+ This datatype is delivered when calling the
+ :meth:`.Update.returning` method.
+
+ .. versionadded:: 2.0
+
+ """
+
+
+class Delete(DMLWhereBase, UpdateBase):
+ """Represent a DELETE construct.
+
+ The :class:`_expression.Delete` object is created using the
+ :func:`_expression.delete()` function.
+
+ """
+
+ __visit_name__ = "delete"
+
+ is_delete = True
+
+ _traverse_internals = (
+ [
+ ("table", InternalTraversal.dp_clauseelement),
+ ("_where_criteria", InternalTraversal.dp_clauseelement_tuple),
+ ("_returning", InternalTraversal.dp_clauseelement_tuple),
+ ("_hints", InternalTraversal.dp_table_hint_list),
+ ]
+ + HasPrefixes._has_prefixes_traverse_internals
+ + DialectKWArgs._dialect_kwargs_traverse_internals
+ + Executable._executable_traverse_internals
+ + HasCTE._has_ctes_traverse_internals
+ )
+
+ def __init__(self, table: _DMLTableArgument):
+ self.table = coercions.expect(
+ roles.DMLTableRole, table, apply_propagate_attrs=self
+ )
+
+ if TYPE_CHECKING:
+ # START OVERLOADED FUNCTIONS self.returning ReturningDelete 1-8
+
+ # code within this block is **programmatically,
+ # statically generated** by tools/generate_tuple_map_overloads.py
+
+ @overload
+ def returning(
+ self, __ent0: _TCCA[_T0]
+ ) -> ReturningDelete[Tuple[_T0]]: ...
+
+ @overload
+ def returning(
+ self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1]
+ ) -> ReturningDelete[Tuple[_T0, _T1]]: ...
+
+ @overload
+ def returning(
+ self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2]
+ ) -> ReturningDelete[Tuple[_T0, _T1, _T2]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ ) -> ReturningDelete[Tuple[_T0, _T1, _T2, _T3]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ ) -> ReturningDelete[Tuple[_T0, _T1, _T2, _T3, _T4]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ ) -> ReturningDelete[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ __ent6: _TCCA[_T6],
+ ) -> ReturningDelete[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]: ...
+
+ @overload
+ def returning(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ __ent6: _TCCA[_T6],
+ __ent7: _TCCA[_T7],
+ ) -> ReturningDelete[
+ Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]
+ ]: ...
+
+ # END OVERLOADED FUNCTIONS self.returning
+
+ @overload
+ def returning(
+ self, *cols: _ColumnsClauseArgument[Any], **__kw: Any
+ ) -> ReturningDelete[Any]: ...
+
+ def returning(
+ self, *cols: _ColumnsClauseArgument[Any], **__kw: Any
+ ) -> ReturningDelete[Any]: ...
+
+
+class ReturningDelete(Update, TypedReturnsRows[_TP]):
+ """Typing-only class that establishes a generic type form of
+ :class:`.Delete` which tracks returned column types.
+
+ This datatype is delivered when calling the
+ :meth:`.Delete.returning` method.
+
+ .. versionadded:: 2.0
+
+ """
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/elements.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/elements.py
new file mode 100644
index 0000000..bafb5c7
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/elements.py
@@ -0,0 +1,5405 @@
+# sql/elements.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+# mypy: allow-untyped-defs, allow-untyped-calls
+
+"""Core SQL expression elements, including :class:`_expression.ClauseElement`,
+:class:`_expression.ColumnElement`, and derived classes.
+
+"""
+
+from __future__ import annotations
+
+from decimal import Decimal
+from enum import IntEnum
+import itertools
+import operator
+import re
+import typing
+from typing import AbstractSet
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import Dict
+from typing import FrozenSet
+from typing import Generic
+from typing import Iterable
+from typing import Iterator
+from typing import List
+from typing import Mapping
+from typing import Optional
+from typing import overload
+from typing import Sequence
+from typing import Set
+from typing import Tuple as typing_Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from . import coercions
+from . import operators
+from . import roles
+from . import traversals
+from . import type_api
+from ._typing import has_schema_attr
+from ._typing import is_named_from_clause
+from ._typing import is_quoted_name
+from ._typing import is_tuple_type
+from .annotation import Annotated
+from .annotation import SupportsWrappingAnnotations
+from .base import _clone
+from .base import _expand_cloned
+from .base import _generative
+from .base import _NoArg
+from .base import Executable
+from .base import Generative
+from .base import HasMemoized
+from .base import Immutable
+from .base import NO_ARG
+from .base import SingletonConstant
+from .cache_key import MemoizedHasCacheKey
+from .cache_key import NO_CACHE
+from .coercions import _document_text_coercion # noqa
+from .operators import ColumnOperators
+from .traversals import HasCopyInternals
+from .visitors import cloned_traverse
+from .visitors import ExternallyTraversible
+from .visitors import InternalTraversal
+from .visitors import traverse
+from .visitors import Visitable
+from .. import exc
+from .. import inspection
+from .. import util
+from ..util import HasMemoized_ro_memoized_attribute
+from ..util import TypingOnly
+from ..util.typing import Literal
+from ..util.typing import Self
+
+if typing.TYPE_CHECKING:
+ from ._typing import _ByArgument
+ from ._typing import _ColumnExpressionArgument
+ from ._typing import _ColumnExpressionOrStrLabelArgument
+ from ._typing import _HasDialect
+ from ._typing import _InfoType
+ from ._typing import _PropagateAttrsType
+ from ._typing import _TypeEngineArgument
+ from .cache_key import _CacheKeyTraversalType
+ from .cache_key import CacheKey
+ from .compiler import Compiled
+ from .compiler import SQLCompiler
+ from .functions import FunctionElement
+ from .operators import OperatorType
+ from .schema import Column
+ from .schema import DefaultGenerator
+ from .schema import FetchedValue
+ from .schema import ForeignKey
+ from .selectable import _SelectIterable
+ from .selectable import FromClause
+ from .selectable import NamedFromClause
+ from .selectable import TextualSelect
+ from .sqltypes import TupleType
+ from .type_api import TypeEngine
+ from .visitors import _CloneCallableType
+ from .visitors import _TraverseInternalsType
+ from .visitors import anon_map
+ from ..engine import Connection
+ from ..engine import Dialect
+ from ..engine.interfaces import _CoreMultiExecuteParams
+ from ..engine.interfaces import CacheStats
+ from ..engine.interfaces import CompiledCacheType
+ from ..engine.interfaces import CoreExecuteOptionsParameter
+ from ..engine.interfaces import SchemaTranslateMapType
+ from ..engine.result import Result
+
+_NUMERIC = Union[float, Decimal]
+_NUMBER = Union[float, int, Decimal]
+
+_T = TypeVar("_T", bound="Any")
+_T_co = TypeVar("_T_co", bound=Any, covariant=True)
+_OPT = TypeVar("_OPT", bound="Any")
+_NT = TypeVar("_NT", bound="_NUMERIC")
+
+_NMT = TypeVar("_NMT", bound="_NUMBER")
+
+
+@overload
+def literal(
+ value: Any,
+ type_: _TypeEngineArgument[_T],
+ literal_execute: bool = False,
+) -> BindParameter[_T]: ...
+
+
+@overload
+def literal(
+ value: _T,
+ type_: None = None,
+ literal_execute: bool = False,
+) -> BindParameter[_T]: ...
+
+
+@overload
+def literal(
+ value: Any,
+ type_: Optional[_TypeEngineArgument[Any]] = None,
+ literal_execute: bool = False,
+) -> BindParameter[Any]: ...
+
+
+def literal(
+ value: Any,
+ type_: Optional[_TypeEngineArgument[Any]] = None,
+ literal_execute: bool = False,
+) -> BindParameter[Any]:
+ r"""Return a literal clause, bound to a bind parameter.
+
+ Literal clauses are created automatically when non-
+ :class:`_expression.ClauseElement` objects (such as strings, ints, dates,
+ etc.) are
+ used in a comparison operation with a :class:`_expression.ColumnElement`
+ subclass,
+ such as a :class:`~sqlalchemy.schema.Column` object. Use this function
+ to force the generation of a literal clause, which will be created as a
+ :class:`BindParameter` with a bound value.
+
+ :param value: the value to be bound. Can be any Python object supported by
+ the underlying DB-API, or is translatable via the given type argument.
+
+ :param type\_: an optional :class:`~sqlalchemy.types.TypeEngine` which will
+ provide bind-parameter translation for this literal.
+
+ :param literal_execute: optional bool, when True, the SQL engine will
+ attempt to render the bound value directly in the SQL statement at
+ execution time rather than providing as a parameter value.
+
+ .. versionadded:: 2.0
+
+ """
+ return coercions.expect(
+ roles.LiteralValueRole,
+ value,
+ type_=type_,
+ literal_execute=literal_execute,
+ )
+
+
+def literal_column(
+ text: str, type_: Optional[_TypeEngineArgument[_T]] = None
+) -> ColumnClause[_T]:
+ r"""Produce a :class:`.ColumnClause` object that has the
+ :paramref:`_expression.column.is_literal` flag set to True.
+
+ :func:`_expression.literal_column` is similar to
+ :func:`_expression.column`, except that
+ it is more often used as a "standalone" column expression that renders
+ exactly as stated; while :func:`_expression.column`
+ stores a string name that
+ will be assumed to be part of a table and may be quoted as such,
+ :func:`_expression.literal_column` can be that,
+ or any other arbitrary column-oriented
+ expression.
+
+ :param text: the text of the expression; can be any SQL expression.
+ Quoting rules will not be applied. To specify a column-name expression
+ which should be subject to quoting rules, use the :func:`column`
+ function.
+
+ :param type\_: an optional :class:`~sqlalchemy.types.TypeEngine`
+ object which will
+ provide result-set translation and additional expression semantics for
+ this column. If left as ``None`` the type will be :class:`.NullType`.
+
+ .. seealso::
+
+ :func:`_expression.column`
+
+ :func:`_expression.text`
+
+ :ref:`tutorial_select_arbitrary_text`
+
+ """
+ return ColumnClause(text, type_=type_, is_literal=True)
+
+
+class CompilerElement(Visitable):
+ """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: Optional[_HasDialect] = None,
+ dialect: Optional[Dialect] = None,
+ **kw: Any,
+ ) -> Compiled:
+ """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 :class:`.Connection` or :class:`.Engine` which
+ can provide a :class:`.Dialect` in order to generate a
+ :class:`.Compiled` object. If the ``bind`` and
+ ``dialect`` parameters are both omitted, a default SQL compiler
+ is used.
+
+ :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 :class:`.Dialect` instance which can generate
+ a :class:`.Compiled` object. This argument takes precedence over
+ the ``bind`` argument.
+
+ :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}))
+
+ .. seealso::
+
+ :ref:`faq_sql_expression_string`
+
+ """
+
+ if dialect is None:
+ if bind:
+ dialect = bind.dialect
+ elif 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: Dialect, **kw: Any) -> Compiled:
+ """Return a compiler appropriate for this ClauseElement, given a
+ Dialect."""
+
+ if TYPE_CHECKING:
+ assert isinstance(self, ClauseElement)
+ return dialect.statement_compiler(dialect, self, **kw)
+
+ def __str__(self) -> str:
+ return str(self.compile())
+
+
+@inspection._self_inspects
+class ClauseElement(
+ SupportsWrappingAnnotations,
+ MemoizedHasCacheKey,
+ HasCopyInternals,
+ ExternallyTraversible,
+ CompilerElement,
+):
+ """Base class for elements of a programmatically constructed SQL
+ expression.
+
+ """
+
+ __visit_name__ = "clause"
+
+ if TYPE_CHECKING:
+
+ @util.memoized_property
+ def _propagate_attrs(self) -> _PropagateAttrsType:
+ """like annotations, however these propagate outwards liberally
+ as SQL constructs are built, and are set up at construction time.
+
+ """
+ ...
+
+ else:
+ _propagate_attrs = util.EMPTY_DICT
+
+ @util.ro_memoized_property
+ def description(self) -> Optional[str]:
+ return None
+
+ _is_clone_of: Optional[Self] = None
+
+ is_clause_element = True
+ is_selectable = False
+ is_dml = False
+ _is_column_element = False
+ _is_keyed_column_element = False
+ _is_table = False
+ _gen_static_annotations_cache_key = False
+ _is_textual = False
+ _is_from_clause = False
+ _is_returns_rows = False
+ _is_text_clause = False
+ _is_from_container = False
+ _is_select_container = False
+ _is_select_base = False
+ _is_select_statement = False
+ _is_bind_parameter = False
+ _is_clause_list = False
+ _is_lambda_element = False
+ _is_singleton_constant = False
+ _is_immutable = False
+ _is_star = False
+
+ @property
+ def _order_by_label_element(self) -> Optional[Label[Any]]:
+ return None
+
+ _cache_key_traversal: _CacheKeyTraversalType = None
+
+ negation_clause: ColumnElement[bool]
+
+ if typing.TYPE_CHECKING:
+
+ def get_children(
+ self, *, omit_attrs: typing_Tuple[str, ...] = ..., **kw: Any
+ ) -> Iterable[ClauseElement]: ...
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return []
+
+ def _set_propagate_attrs(self, values: Mapping[str, Any]) -> Self:
+ # usually, self._propagate_attrs is empty here. one case where it's
+ # not is a subquery against ORM select, that is then pulled as a
+ # property of an aliased class. should all be good
+
+ # assert not self._propagate_attrs
+
+ self._propagate_attrs = util.immutabledict(values)
+ return self
+
+ def _clone(self, **kw: Any) -> Self:
+ """Create a shallow copy of this ClauseElement.
+
+ This method may be used by a generative API. Its also used as
+ part of the "deep" copy afforded by a traversal that combines
+ the _copy_internals() method.
+
+ """
+
+ skip = self._memoized_keys
+ c = self.__class__.__new__(self.__class__)
+
+ if skip:
+ # ensure this iteration remains atomic
+ c.__dict__ = {
+ k: v for k, v in self.__dict__.copy().items() if k not in skip
+ }
+ else:
+ c.__dict__ = self.__dict__.copy()
+
+ # this is a marker that helps to "equate" clauses to each other
+ # when a Select returns its list of FROM clauses. the cloning
+ # process leaves around a lot of remnants of the previous clause
+ # typically in the form of column expressions still attached to the
+ # old table.
+ cc = self._is_clone_of
+ c._is_clone_of = cc if cc is not None else self
+ return c
+
+ def _negate_in_binary(self, negated_op, original_op):
+ """a hook to allow the right side of a binary expression to respond
+ to a negation of the binary expression.
+
+ Used for the special case of expanding bind parameter with IN.
+
+ """
+ return self
+
+ def _with_binary_element_type(self, type_):
+ """in the context of binary expression, convert the type of this
+ object to the one given.
+
+ applies only to :class:`_expression.ColumnElement` classes.
+
+ """
+ return self
+
+ @property
+ def _constructor(self):
+ """return the 'constructor' for this ClauseElement.
+
+ This is for the purposes for creating a new object of
+ this type. Usually, its just the element's __class__.
+ However, the "Annotated" version of the object overrides
+ to return the class of its proxied element.
+
+ """
+ return self.__class__
+
+ @HasMemoized.memoized_attribute
+ def _cloned_set(self):
+ """Return the set consisting all cloned ancestors of this
+ ClauseElement.
+
+ Includes this ClauseElement. This accessor tends to be used for
+ FromClause objects to identify 'equivalent' FROM clauses, regardless
+ of transformative operations.
+
+ """
+ s = util.column_set()
+ f: Optional[ClauseElement] = self
+
+ # note this creates a cycle, asserted in test_memusage. however,
+ # turning this into a plain @property adds tends of thousands of method
+ # calls to Core / ORM performance tests, so the small overhead
+ # introduced by the relatively small amount of short term cycles
+ # produced here is preferable
+ while f is not None:
+ s.add(f)
+ f = f._is_clone_of
+ return s
+
+ def _de_clone(self):
+ while self._is_clone_of is not None:
+ self = self._is_clone_of
+ return self
+
+ @property
+ def entity_namespace(self):
+ raise AttributeError(
+ "This SQL expression has no entity namespace "
+ "with which to filter from."
+ )
+
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ d.pop("_is_clone_of", None)
+ d.pop("_generate_cache_key", None)
+ return d
+
+ def _execute_on_connection(
+ self,
+ connection: Connection,
+ distilled_params: _CoreMultiExecuteParams,
+ execution_options: CoreExecuteOptionsParameter,
+ ) -> Result[Any]:
+ if self.supports_execution:
+ if TYPE_CHECKING:
+ assert isinstance(self, Executable)
+ return connection._execute_clauseelement(
+ self, distilled_params, execution_options
+ )
+ else:
+ raise exc.ObjectNotExecutableError(self)
+
+ def _execute_on_scalar(
+ self,
+ connection: Connection,
+ distilled_params: _CoreMultiExecuteParams,
+ execution_options: CoreExecuteOptionsParameter,
+ ) -> Any:
+ """an additional hook for subclasses to provide a different
+ implementation for connection.scalar() vs. connection.execute().
+
+ .. versionadded:: 2.0
+
+ """
+ return self._execute_on_connection(
+ connection, distilled_params, execution_options
+ ).scalar()
+
+ def _get_embedded_bindparams(self) -> Sequence[BindParameter[Any]]:
+ """Return the list of :class:`.BindParameter` objects embedded in the
+ object.
+
+ This accomplishes the same purpose as ``visitors.traverse()`` or
+ similar would provide, however by making use of the cache key
+ it takes advantage of memoization of the key to result in fewer
+ net method calls, assuming the statement is also going to be
+ executed.
+
+ """
+
+ key = self._generate_cache_key()
+ if key is None:
+ bindparams: List[BindParameter[Any]] = []
+
+ traverse(self, {}, {"bindparam": bindparams.append})
+ return bindparams
+
+ else:
+ return key.bindparams
+
+ def unique_params(
+ self,
+ __optionaldict: Optional[Dict[str, Any]] = None,
+ **kwargs: Any,
+ ) -> Self:
+ """Return a copy with :func:`_expression.bindparam` elements
+ replaced.
+
+ Same functionality as :meth:`_expression.ClauseElement.params`,
+ except adds `unique=True`
+ to affected bind parameters so that multiple statements can be
+ used.
+
+ """
+ return self._replace_params(True, __optionaldict, kwargs)
+
+ def params(
+ self,
+ __optionaldict: Optional[Mapping[str, Any]] = None,
+ **kwargs: Any,
+ ) -> Self:
+ """Return a copy with :func:`_expression.bindparam` elements
+ replaced.
+
+ Returns a copy of this ClauseElement with
+ :func:`_expression.bindparam`
+ elements replaced with values taken from the given dictionary::
+
+ >>> clause = column('x') + bindparam('foo')
+ >>> print(clause.compile().params)
+ {'foo':None}
+ >>> print(clause.params({'foo':7}).compile().params)
+ {'foo':7}
+
+ """
+ return self._replace_params(False, __optionaldict, kwargs)
+
+ def _replace_params(
+ self,
+ unique: bool,
+ optionaldict: Optional[Mapping[str, Any]],
+ kwargs: Dict[str, Any],
+ ) -> Self:
+ if optionaldict:
+ kwargs.update(optionaldict)
+
+ def visit_bindparam(bind: BindParameter[Any]) -> None:
+ if bind.key in kwargs:
+ bind.value = kwargs[bind.key]
+ bind.required = False
+ if unique:
+ bind._convert_to_unique()
+
+ return cloned_traverse(
+ self,
+ {"maintain_key": True, "detect_subquery_cols": True},
+ {"bindparam": visit_bindparam},
+ )
+
+ def compare(self, other: ClauseElement, **kw: Any) -> bool:
+ r"""Compare this :class:`_expression.ClauseElement` to
+ the given :class:`_expression.ClauseElement`.
+
+ Subclasses should override the default behavior, which is a
+ straight identity comparison.
+
+ \**kw are arguments consumed by subclass ``compare()`` methods and
+ may be used to modify the criteria for comparison
+ (see :class:`_expression.ColumnElement`).
+
+ """
+ return traversals.compare(self, other, **kw)
+
+ def self_group(
+ self, against: Optional[OperatorType] = None
+ ) -> ClauseElement:
+ """Apply a 'grouping' to this :class:`_expression.ClauseElement`.
+
+ This method is overridden by subclasses to return a "grouping"
+ construct, i.e. parenthesis. In particular it's used by "binary"
+ expressions to provide a grouping around themselves when placed into a
+ larger expression, as well as by :func:`_expression.select`
+ constructs when placed into the FROM clause of another
+ :func:`_expression.select`. (Note that subqueries should be
+ normally created using the :meth:`_expression.Select.alias` method,
+ as many
+ platforms require nested SELECT statements to be named).
+
+ As expressions are composed together, the application of
+ :meth:`self_group` is automatic - end-user code should never
+ need to use this method directly. Note that SQLAlchemy's
+ clause constructs take operator precedence into account -
+ so parenthesis might not be needed, for example, in
+ an expression like ``x OR (y AND z)`` - AND takes precedence
+ over OR.
+
+ The base :meth:`self_group` method of
+ :class:`_expression.ClauseElement`
+ just returns self.
+ """
+ return self
+
+ def _ungroup(self) -> ClauseElement:
+ """Return this :class:`_expression.ClauseElement`
+ without any groupings.
+ """
+
+ return self
+
+ def _compile_w_cache(
+ self,
+ dialect: Dialect,
+ *,
+ compiled_cache: Optional[CompiledCacheType],
+ column_keys: List[str],
+ for_executemany: bool = False,
+ schema_translate_map: Optional[SchemaTranslateMapType] = None,
+ **kw: Any,
+ ) -> typing_Tuple[
+ Compiled, Optional[Sequence[BindParameter[Any]]], CacheStats
+ ]:
+ elem_cache_key: Optional[CacheKey]
+
+ if compiled_cache is not None and dialect._supports_statement_cache:
+ elem_cache_key = self._generate_cache_key()
+ else:
+ elem_cache_key = None
+
+ if elem_cache_key is not None:
+ if TYPE_CHECKING:
+ assert compiled_cache is not None
+
+ cache_key, extracted_params = elem_cache_key
+ key = (
+ dialect,
+ cache_key,
+ tuple(column_keys),
+ bool(schema_translate_map),
+ for_executemany,
+ )
+ compiled_sql = compiled_cache.get(key)
+
+ if compiled_sql is None:
+ cache_hit = dialect.CACHE_MISS
+ compiled_sql = self._compiler(
+ dialect,
+ cache_key=elem_cache_key,
+ column_keys=column_keys,
+ for_executemany=for_executemany,
+ schema_translate_map=schema_translate_map,
+ **kw,
+ )
+ compiled_cache[key] = compiled_sql
+ else:
+ cache_hit = dialect.CACHE_HIT
+ else:
+ extracted_params = None
+ compiled_sql = self._compiler(
+ dialect,
+ cache_key=elem_cache_key,
+ column_keys=column_keys,
+ for_executemany=for_executemany,
+ schema_translate_map=schema_translate_map,
+ **kw,
+ )
+
+ 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
+
+ def __invert__(self):
+ # undocumented element currently used by the ORM for
+ # relationship.contains()
+ if hasattr(self, "negation_clause"):
+ return self.negation_clause
+ else:
+ return self._negate()
+
+ def _negate(self) -> ClauseElement:
+ grouped = self.self_group(against=operators.inv)
+ assert isinstance(grouped, ColumnElement)
+ return UnaryExpression(grouped, operator=operators.inv)
+
+ def __bool__(self):
+ raise TypeError("Boolean value of this clause is not defined")
+
+ def __repr__(self):
+ friendly = self.description
+ if friendly is None:
+ return object.__repr__(self)
+ else:
+ return "<%s.%s at 0x%x; %s>" % (
+ self.__module__,
+ self.__class__.__name__,
+ id(self),
+ friendly,
+ )
+
+
+class DQLDMLClauseElement(ClauseElement):
+ """represents a :class:`.ClauseElement` that compiles to a DQL or DML
+ expression, not DDL.
+
+ .. versionadded:: 2.0
+
+ """
+
+ if typing.TYPE_CHECKING:
+
+ def _compiler(self, dialect: Dialect, **kw: Any) -> SQLCompiler:
+ """Return a compiler appropriate for this ClauseElement, given a
+ Dialect."""
+ ...
+
+ def compile( # noqa: A001
+ self,
+ bind: Optional[_HasDialect] = None,
+ dialect: Optional[Dialect] = None,
+ **kw: Any,
+ ) -> SQLCompiler: ...
+
+
+class CompilerColumnElement(
+ roles.DMLColumnRole,
+ roles.DDLConstraintColumnRole,
+ roles.ColumnsClauseRole,
+ CompilerElement,
+):
+ """A compiler-only column element used for ad-hoc string compilations.
+
+ .. versionadded:: 2.0
+
+ """
+
+ __slots__ = ()
+
+ _propagate_attrs = util.EMPTY_DICT
+ _is_collection_aggregate = False
+
+
+# SQLCoreOperations should be suiting the ExpressionElementRole
+# and ColumnsClauseRole. however the MRO issues become too elaborate
+# at the moment.
+class SQLCoreOperations(Generic[_T_co], ColumnOperators, TypingOnly):
+ __slots__ = ()
+
+ # annotations for comparison methods
+ # these are from operators->Operators / ColumnOperators,
+ # redefined with the specific types returned by ColumnElement hierarchies
+ if typing.TYPE_CHECKING:
+
+ @util.non_memoized_property
+ def _propagate_attrs(self) -> _PropagateAttrsType: ...
+
+ def operate(
+ self, op: OperatorType, *other: Any, **kwargs: Any
+ ) -> ColumnElement[Any]: ...
+
+ def reverse_operate(
+ self, op: OperatorType, other: Any, **kwargs: Any
+ ) -> ColumnElement[Any]: ...
+
+ @overload
+ def op(
+ self,
+ opstring: str,
+ precedence: int = ...,
+ is_comparison: bool = ...,
+ *,
+ return_type: _TypeEngineArgument[_OPT],
+ python_impl: Optional[Callable[..., Any]] = None,
+ ) -> Callable[[Any], BinaryExpression[_OPT]]: ...
+
+ @overload
+ def op(
+ self,
+ opstring: str,
+ precedence: int = ...,
+ is_comparison: bool = ...,
+ return_type: Optional[_TypeEngineArgument[Any]] = ...,
+ python_impl: Optional[Callable[..., Any]] = ...,
+ ) -> Callable[[Any], BinaryExpression[Any]]: ...
+
+ def op(
+ self,
+ opstring: str,
+ precedence: int = 0,
+ is_comparison: bool = False,
+ return_type: Optional[_TypeEngineArgument[Any]] = None,
+ python_impl: Optional[Callable[..., Any]] = None,
+ ) -> Callable[[Any], BinaryExpression[Any]]: ...
+
+ def bool_op(
+ self,
+ opstring: str,
+ precedence: int = 0,
+ python_impl: Optional[Callable[..., Any]] = None,
+ ) -> Callable[[Any], BinaryExpression[bool]]: ...
+
+ def __and__(self, other: Any) -> BooleanClauseList: ...
+
+ def __or__(self, other: Any) -> BooleanClauseList: ...
+
+ def __invert__(self) -> ColumnElement[_T_co]: ...
+
+ def __lt__(self, other: Any) -> ColumnElement[bool]: ...
+
+ def __le__(self, other: Any) -> ColumnElement[bool]: ...
+
+ # declare also that this class has an hash method otherwise
+ # it may be assumed to be None by type checkers since the
+ # object defines __eq__ and python sets it to None in that case:
+ # https://docs.python.org/3/reference/datamodel.html#object.__hash__
+ def __hash__(self) -> int: ...
+
+ def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
+ ...
+
+ def __ne__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
+ ...
+
+ def is_distinct_from(self, other: Any) -> ColumnElement[bool]: ...
+
+ def is_not_distinct_from(self, other: Any) -> ColumnElement[bool]: ...
+
+ def __gt__(self, other: Any) -> ColumnElement[bool]: ...
+
+ def __ge__(self, other: Any) -> ColumnElement[bool]: ...
+
+ def __neg__(self) -> UnaryExpression[_T_co]: ...
+
+ def __contains__(self, other: Any) -> ColumnElement[bool]: ...
+
+ def __getitem__(self, index: Any) -> ColumnElement[Any]: ...
+
+ @overload
+ def __lshift__(self: _SQO[int], other: Any) -> ColumnElement[int]: ...
+
+ @overload
+ def __lshift__(self, other: Any) -> ColumnElement[Any]: ...
+
+ def __lshift__(self, other: Any) -> ColumnElement[Any]: ...
+
+ @overload
+ def __rshift__(self: _SQO[int], other: Any) -> ColumnElement[int]: ...
+
+ @overload
+ def __rshift__(self, other: Any) -> ColumnElement[Any]: ...
+
+ def __rshift__(self, other: Any) -> ColumnElement[Any]: ...
+
+ @overload
+ def concat(self: _SQO[str], other: Any) -> ColumnElement[str]: ...
+
+ @overload
+ def concat(self, other: Any) -> ColumnElement[Any]: ...
+
+ def concat(self, other: Any) -> ColumnElement[Any]: ...
+
+ def like(
+ self, other: Any, escape: Optional[str] = None
+ ) -> BinaryExpression[bool]: ...
+
+ def ilike(
+ self, other: Any, escape: Optional[str] = None
+ ) -> BinaryExpression[bool]: ...
+
+ def bitwise_xor(self, other: Any) -> BinaryExpression[Any]: ...
+
+ def bitwise_or(self, other: Any) -> BinaryExpression[Any]: ...
+
+ def bitwise_and(self, other: Any) -> BinaryExpression[Any]: ...
+
+ def bitwise_not(self) -> UnaryExpression[_T_co]: ...
+
+ def bitwise_lshift(self, other: Any) -> BinaryExpression[Any]: ...
+
+ def bitwise_rshift(self, other: Any) -> BinaryExpression[Any]: ...
+
+ def in_(
+ self,
+ other: Union[
+ Iterable[Any], BindParameter[Any], roles.InElementRole
+ ],
+ ) -> BinaryExpression[bool]: ...
+
+ def not_in(
+ self,
+ other: Union[
+ Iterable[Any], BindParameter[Any], roles.InElementRole
+ ],
+ ) -> BinaryExpression[bool]: ...
+
+ def notin_(
+ self,
+ other: Union[
+ Iterable[Any], BindParameter[Any], roles.InElementRole
+ ],
+ ) -> BinaryExpression[bool]: ...
+
+ def not_like(
+ self, other: Any, escape: Optional[str] = None
+ ) -> BinaryExpression[bool]: ...
+
+ def notlike(
+ self, other: Any, escape: Optional[str] = None
+ ) -> BinaryExpression[bool]: ...
+
+ def not_ilike(
+ self, other: Any, escape: Optional[str] = None
+ ) -> BinaryExpression[bool]: ...
+
+ def notilike(
+ self, other: Any, escape: Optional[str] = None
+ ) -> BinaryExpression[bool]: ...
+
+ def is_(self, other: Any) -> BinaryExpression[bool]: ...
+
+ def is_not(self, other: Any) -> BinaryExpression[bool]: ...
+
+ def isnot(self, other: Any) -> BinaryExpression[bool]: ...
+
+ def startswith(
+ self,
+ other: Any,
+ escape: Optional[str] = None,
+ autoescape: bool = False,
+ ) -> ColumnElement[bool]: ...
+
+ def istartswith(
+ self,
+ other: Any,
+ escape: Optional[str] = None,
+ autoescape: bool = False,
+ ) -> ColumnElement[bool]: ...
+
+ def endswith(
+ self,
+ other: Any,
+ escape: Optional[str] = None,
+ autoescape: bool = False,
+ ) -> ColumnElement[bool]: ...
+
+ def iendswith(
+ self,
+ other: Any,
+ escape: Optional[str] = None,
+ autoescape: bool = False,
+ ) -> ColumnElement[bool]: ...
+
+ def contains(self, other: Any, **kw: Any) -> ColumnElement[bool]: ...
+
+ def icontains(self, other: Any, **kw: Any) -> ColumnElement[bool]: ...
+
+ def match(self, other: Any, **kwargs: Any) -> ColumnElement[bool]: ...
+
+ def regexp_match(
+ self, pattern: Any, flags: Optional[str] = None
+ ) -> ColumnElement[bool]: ...
+
+ def regexp_replace(
+ self, pattern: Any, replacement: Any, flags: Optional[str] = None
+ ) -> ColumnElement[str]: ...
+
+ def desc(self) -> UnaryExpression[_T_co]: ...
+
+ def asc(self) -> UnaryExpression[_T_co]: ...
+
+ def nulls_first(self) -> UnaryExpression[_T_co]: ...
+
+ def nullsfirst(self) -> UnaryExpression[_T_co]: ...
+
+ def nulls_last(self) -> UnaryExpression[_T_co]: ...
+
+ def nullslast(self) -> UnaryExpression[_T_co]: ...
+
+ def collate(self, collation: str) -> CollationClause: ...
+
+ def between(
+ self, cleft: Any, cright: Any, symmetric: bool = False
+ ) -> BinaryExpression[bool]: ...
+
+ def distinct(self: _SQO[_T_co]) -> UnaryExpression[_T_co]: ...
+
+ def any_(self) -> CollectionAggregate[Any]: ...
+
+ def all_(self) -> CollectionAggregate[Any]: ...
+
+ # numeric overloads. These need more tweaking
+ # in particular they all need to have a variant for Optiona[_T]
+ # because Optional only applies to the data side, not the expression
+ # side
+
+ @overload
+ def __add__(
+ self: _SQO[_NMT],
+ other: Any,
+ ) -> ColumnElement[_NMT]: ...
+
+ @overload
+ def __add__(
+ self: _SQO[str],
+ other: Any,
+ ) -> ColumnElement[str]: ...
+
+ def __add__(self, other: Any) -> ColumnElement[Any]: ...
+
+ @overload
+ def __radd__(self: _SQO[_NMT], other: Any) -> ColumnElement[_NMT]: ...
+
+ @overload
+ def __radd__(self: _SQO[str], other: Any) -> ColumnElement[str]: ...
+
+ def __radd__(self, other: Any) -> ColumnElement[Any]: ...
+
+ @overload
+ def __sub__(
+ self: _SQO[_NMT],
+ other: Any,
+ ) -> ColumnElement[_NMT]: ...
+
+ @overload
+ def __sub__(self, other: Any) -> ColumnElement[Any]: ...
+
+ def __sub__(self, other: Any) -> ColumnElement[Any]: ...
+
+ @overload
+ def __rsub__(
+ self: _SQO[_NMT],
+ other: Any,
+ ) -> ColumnElement[_NMT]: ...
+
+ @overload
+ def __rsub__(self, other: Any) -> ColumnElement[Any]: ...
+
+ def __rsub__(self, other: Any) -> ColumnElement[Any]: ...
+
+ @overload
+ def __mul__(
+ self: _SQO[_NMT],
+ other: Any,
+ ) -> ColumnElement[_NMT]: ...
+
+ @overload
+ def __mul__(self, other: Any) -> ColumnElement[Any]: ...
+
+ def __mul__(self, other: Any) -> ColumnElement[Any]: ...
+
+ @overload
+ def __rmul__(
+ self: _SQO[_NMT],
+ other: Any,
+ ) -> ColumnElement[_NMT]: ...
+
+ @overload
+ def __rmul__(self, other: Any) -> ColumnElement[Any]: ...
+
+ def __rmul__(self, other: Any) -> ColumnElement[Any]: ...
+
+ @overload
+ def __mod__(self: _SQO[_NMT], other: Any) -> ColumnElement[_NMT]: ...
+
+ @overload
+ def __mod__(self, other: Any) -> ColumnElement[Any]: ...
+
+ def __mod__(self, other: Any) -> ColumnElement[Any]: ...
+
+ @overload
+ def __rmod__(self: _SQO[_NMT], other: Any) -> ColumnElement[_NMT]: ...
+
+ @overload
+ def __rmod__(self, other: Any) -> ColumnElement[Any]: ...
+
+ def __rmod__(self, other: Any) -> ColumnElement[Any]: ...
+
+ @overload
+ def __truediv__(
+ self: _SQO[int], other: Any
+ ) -> ColumnElement[_NUMERIC]: ...
+
+ @overload
+ def __truediv__(self: _SQO[_NT], other: Any) -> ColumnElement[_NT]: ...
+
+ @overload
+ def __truediv__(self, other: Any) -> ColumnElement[Any]: ...
+
+ def __truediv__(self, other: Any) -> ColumnElement[Any]: ...
+
+ @overload
+ def __rtruediv__(
+ self: _SQO[_NMT], other: Any
+ ) -> ColumnElement[_NUMERIC]: ...
+
+ @overload
+ def __rtruediv__(self, other: Any) -> ColumnElement[Any]: ...
+
+ def __rtruediv__(self, other: Any) -> ColumnElement[Any]: ...
+
+ @overload
+ def __floordiv__(
+ self: _SQO[_NMT], other: Any
+ ) -> ColumnElement[_NMT]: ...
+
+ @overload
+ def __floordiv__(self, other: Any) -> ColumnElement[Any]: ...
+
+ def __floordiv__(self, other: Any) -> ColumnElement[Any]: ...
+
+ @overload
+ def __rfloordiv__(
+ self: _SQO[_NMT], other: Any
+ ) -> ColumnElement[_NMT]: ...
+
+ @overload
+ def __rfloordiv__(self, other: Any) -> ColumnElement[Any]: ...
+
+ def __rfloordiv__(self, other: Any) -> ColumnElement[Any]: ...
+
+
+class SQLColumnExpression(
+ SQLCoreOperations[_T_co], roles.ExpressionElementRole[_T_co], TypingOnly
+):
+ """A type that may be used to indicate any SQL column element or object
+ that acts in place of one.
+
+ :class:`.SQLColumnExpression` is a base of
+ :class:`.ColumnElement`, as well as within the bases of ORM elements
+ such as :class:`.InstrumentedAttribute`, and may be used in :pep:`484`
+ typing to indicate arguments or return values that should behave
+ as column expressions.
+
+ .. versionadded:: 2.0.0b4
+
+
+ """
+
+ __slots__ = ()
+
+
+_SQO = SQLCoreOperations
+
+
+class ColumnElement(
+ roles.ColumnArgumentOrKeyRole,
+ roles.StatementOptionRole,
+ roles.WhereHavingRole,
+ roles.BinaryElementRole[_T],
+ roles.OrderByRole,
+ roles.ColumnsClauseRole,
+ roles.LimitOffsetRole,
+ roles.DMLColumnRole,
+ roles.DDLConstraintColumnRole,
+ roles.DDLExpressionRole,
+ SQLColumnExpression[_T],
+ DQLDMLClauseElement,
+):
+ """Represent a column-oriented SQL expression suitable for usage in the
+ "columns" clause, WHERE clause etc. of a statement.
+
+ While the most familiar kind of :class:`_expression.ColumnElement` is the
+ :class:`_schema.Column` object, :class:`_expression.ColumnElement`
+ serves as the basis
+ for any unit that may be present in a SQL expression, including
+ the expressions themselves, SQL functions, bound parameters,
+ literal expressions, keywords such as ``NULL``, etc.
+ :class:`_expression.ColumnElement`
+ is the ultimate base class for all such elements.
+
+ A wide variety of SQLAlchemy Core functions work at the SQL expression
+ level, and are intended to accept instances of
+ :class:`_expression.ColumnElement` as
+ arguments. These functions will typically document that they accept a
+ "SQL expression" as an argument. What this means in terms of SQLAlchemy
+ usually refers to an input which is either already in the form of a
+ :class:`_expression.ColumnElement` object,
+ or a value which can be **coerced** into
+ one. The coercion rules followed by most, but not all, SQLAlchemy Core
+ functions with regards to SQL expressions are as follows:
+
+ * a literal Python value, such as a string, integer or floating
+ point value, boolean, datetime, ``Decimal`` object, or virtually
+ any other Python object, will be coerced into a "literal bound
+ value". This generally means that a :func:`.bindparam` will be
+ produced featuring the given value embedded into the construct; the
+ resulting :class:`.BindParameter` object is an instance of
+ :class:`_expression.ColumnElement`.
+ The Python value will ultimately be sent
+ to the DBAPI at execution time as a parameterized argument to the
+ ``execute()`` or ``executemany()`` methods, after SQLAlchemy
+ type-specific converters (e.g. those provided by any associated
+ :class:`.TypeEngine` objects) are applied to the value.
+
+ * any special object value, typically ORM-level constructs, which
+ feature an accessor called ``__clause_element__()``. The Core
+ expression system looks for this method when an object of otherwise
+ unknown type is passed to a function that is looking to coerce the
+ argument into a :class:`_expression.ColumnElement` and sometimes a
+ :class:`_expression.SelectBase` expression.
+ It is used within the ORM to
+ convert from ORM-specific objects like mapped classes and
+ mapped attributes into Core expression objects.
+
+ * The Python ``None`` value is typically interpreted as ``NULL``,
+ which in SQLAlchemy Core produces an instance of :func:`.null`.
+
+ A :class:`_expression.ColumnElement` provides the ability to generate new
+ :class:`_expression.ColumnElement`
+ objects using Python expressions. This means that Python operators
+ such as ``==``, ``!=`` and ``<`` are overloaded to mimic SQL operations,
+ and allow the instantiation of further :class:`_expression.ColumnElement`
+ instances
+ which are composed from other, more fundamental
+ :class:`_expression.ColumnElement`
+ objects. For example, two :class:`.ColumnClause` objects can be added
+ together with the addition operator ``+`` to produce
+ a :class:`.BinaryExpression`.
+ Both :class:`.ColumnClause` and :class:`.BinaryExpression` are subclasses
+ of :class:`_expression.ColumnElement`:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy.sql import column
+ >>> column('a') + column('b')
+ <sqlalchemy.sql.expression.BinaryExpression object at 0x101029dd0>
+ >>> print(column('a') + column('b'))
+ {printsql}a + b
+
+ .. seealso::
+
+ :class:`_schema.Column`
+
+ :func:`_expression.column`
+
+ """
+
+ __visit_name__ = "column_element"
+
+ primary_key: bool = False
+ _is_clone_of: Optional[ColumnElement[_T]]
+ _is_column_element = True
+ _insert_sentinel: bool = False
+ _omit_from_statements = False
+ _is_collection_aggregate = False
+
+ foreign_keys: AbstractSet[ForeignKey] = frozenset()
+
+ @util.memoized_property
+ def _proxies(self) -> List[ColumnElement[Any]]:
+ return []
+
+ @util.non_memoized_property
+ def _tq_label(self) -> Optional[str]:
+ """The named label that can be used to target
+ this column in a result set in a "table qualified" context.
+
+ This label is almost always the label used when
+ rendering <expr> AS <label> in a SELECT statement when using
+ the LABEL_STYLE_TABLENAME_PLUS_COL label style, which is what the
+ legacy ORM ``Query`` object uses as well.
+
+ For a regular Column bound to a Table, this is typically the label
+ <tablename>_<columnname>. For other constructs, different rules
+ may apply, such as anonymized labels and others.
+
+ .. versionchanged:: 1.4.21 renamed from ``._label``
+
+ """
+ return None
+
+ key: Optional[str] = None
+ """The 'key' that in some circumstances refers to this object in a
+ Python namespace.
+
+ This typically refers to the "key" of the column as present in the
+ ``.c`` collection of a selectable, e.g. ``sometable.c["somekey"]`` would
+ return a :class:`_schema.Column` with a ``.key`` of "somekey".
+
+ """
+
+ @HasMemoized.memoized_attribute
+ def _tq_key_label(self) -> Optional[str]:
+ """A label-based version of 'key' that in some circumstances refers
+ to this object in a Python namespace.
+
+
+ _tq_key_label comes into play when a select() statement is constructed
+ with apply_labels(); in this case, all Column objects in the ``.c``
+ collection are rendered as <tablename>_<columnname> in SQL; this is
+ essentially the value of ._label. But to locate those columns in the
+ ``.c`` collection, the name is along the lines of <tablename>_<key>;
+ that's the typical value of .key_label.
+
+ .. versionchanged:: 1.4.21 renamed from ``._key_label``
+
+ """
+ return self._proxy_key
+
+ @property
+ def _key_label(self) -> Optional[str]:
+ """legacy; renamed to _tq_key_label"""
+ return self._tq_key_label
+
+ @property
+ def _label(self) -> Optional[str]:
+ """legacy; renamed to _tq_label"""
+ return self._tq_label
+
+ @property
+ def _non_anon_label(self) -> Optional[str]:
+ """the 'name' that naturally applies this element when rendered in
+ SQL.
+
+ Concretely, this is the "name" of a column or a label in a
+ SELECT statement; ``<columnname>`` and ``<labelname>`` below::
+
+ SELECT <columnmame> FROM table
+
+ SELECT column AS <labelname> FROM table
+
+ Above, the two names noted will be what's present in the DBAPI
+ ``cursor.description`` as the names.
+
+ If this attribute returns ``None``, it means that the SQL element as
+ written does not have a 100% fully predictable "name" that would appear
+ in the ``cursor.description``. Examples include SQL functions, CAST
+ functions, etc. While such things do return names in
+ ``cursor.description``, they are only predictable on a
+ database-specific basis; e.g. an expression like ``MAX(table.col)`` may
+ appear as the string ``max`` on one database (like PostgreSQL) or may
+ appear as the whole expression ``max(table.col)`` on SQLite.
+
+ The default implementation looks for a ``.name`` attribute on the
+ object, as has been the precedent established in SQLAlchemy for many
+ years. An exception is made on the ``FunctionElement`` subclass
+ so that the return value is always ``None``.
+
+ .. versionadded:: 1.4.21
+
+
+
+ """
+ return getattr(self, "name", None)
+
+ _render_label_in_columns_clause = True
+ """A flag used by select._columns_plus_names that helps to determine
+ we are actually going to render in terms of "SELECT <col> AS <label>".
+ This flag can be returned as False for some Column objects that want
+ to be rendered as simple "SELECT <col>"; typically columns that don't have
+ any parent table and are named the same as what the label would be
+ in any case.
+
+ """
+
+ _allow_label_resolve = True
+ """A flag that can be flipped to prevent a column from being resolvable
+ by string label name.
+
+ The joined eager loader strategy in the ORM uses this, for example.
+
+ """
+
+ _is_implicitly_boolean = False
+
+ _alt_names: Sequence[str] = ()
+
+ @overload
+ def self_group(
+ self: ColumnElement[_T], against: Optional[OperatorType] = None
+ ) -> ColumnElement[_T]: ...
+
+ @overload
+ def self_group(
+ self: ColumnElement[Any], against: Optional[OperatorType] = None
+ ) -> ColumnElement[Any]: ...
+
+ def self_group(
+ self, against: Optional[OperatorType] = None
+ ) -> ColumnElement[Any]:
+ if (
+ against in (operators.and_, operators.or_, operators._asbool)
+ and self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity
+ ):
+ return AsBoolean(self, operators.is_true, operators.is_false)
+ elif against in (operators.any_op, operators.all_op):
+ return Grouping(self)
+ else:
+ return self
+
+ @overload
+ def _negate(self: ColumnElement[bool]) -> ColumnElement[bool]: ...
+
+ @overload
+ def _negate(self: ColumnElement[_T]) -> ColumnElement[_T]: ...
+
+ def _negate(self) -> ColumnElement[Any]:
+ if self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity:
+ return AsBoolean(self, operators.is_false, operators.is_true)
+ else:
+ grouped = self.self_group(against=operators.inv)
+ assert isinstance(grouped, ColumnElement)
+ return UnaryExpression(
+ grouped, operator=operators.inv, wraps_column_expression=True
+ )
+
+ type: TypeEngine[_T]
+
+ if not TYPE_CHECKING:
+
+ @util.memoized_property
+ def type(self) -> TypeEngine[_T]: # noqa: A001
+ # used for delayed setup of
+ # type_api
+ return type_api.NULLTYPE
+
+ @HasMemoized.memoized_attribute
+ def comparator(self) -> TypeEngine.Comparator[_T]:
+ try:
+ comparator_factory = self.type.comparator_factory
+ except AttributeError as err:
+ raise TypeError(
+ "Object %r associated with '.type' attribute "
+ "is not a TypeEngine class or object" % self.type
+ ) from err
+ else:
+ return comparator_factory(self)
+
+ def __setstate__(self, state):
+ self.__dict__.update(state)
+
+ def __getattr__(self, key: str) -> Any:
+ try:
+ return getattr(self.comparator, key)
+ except AttributeError as err:
+ raise AttributeError(
+ "Neither %r object nor %r object has an attribute %r"
+ % (
+ type(self).__name__,
+ type(self.comparator).__name__,
+ key,
+ )
+ ) from err
+
+ def operate(
+ self,
+ op: operators.OperatorType,
+ *other: Any,
+ **kwargs: Any,
+ ) -> ColumnElement[Any]:
+ return op(self.comparator, *other, **kwargs) # type: ignore[no-any-return] # noqa: E501
+
+ def reverse_operate(
+ self, op: operators.OperatorType, other: Any, **kwargs: Any
+ ) -> ColumnElement[Any]:
+ return op(other, self.comparator, **kwargs) # type: ignore[no-any-return] # noqa: E501
+
+ def _bind_param(
+ self,
+ operator: operators.OperatorType,
+ obj: Any,
+ type_: Optional[TypeEngine[_T]] = None,
+ expanding: bool = False,
+ ) -> BindParameter[_T]:
+ return BindParameter(
+ None,
+ obj,
+ _compared_to_operator=operator,
+ type_=type_,
+ _compared_to_type=self.type,
+ unique=True,
+ expanding=expanding,
+ )
+
+ @property
+ def expression(self) -> ColumnElement[Any]:
+ """Return a column expression.
+
+ Part of the inspection interface; returns self.
+
+ """
+ return self
+
+ @property
+ def _select_iterable(self) -> _SelectIterable:
+ return (self,)
+
+ @util.memoized_property
+ def base_columns(self) -> FrozenSet[ColumnElement[Any]]:
+ return frozenset(c for c in self.proxy_set if not c._proxies)
+
+ @util.memoized_property
+ def proxy_set(self) -> FrozenSet[ColumnElement[Any]]:
+ """set of all columns we are proxying
+
+ as of 2.0 this is explicitly deannotated columns. previously it was
+ effectively deannotated columns but wasn't enforced. annotated
+ columns should basically not go into sets if at all possible because
+ their hashing behavior is very non-performant.
+
+ """
+ return frozenset([self._deannotate()]).union(
+ itertools.chain(*[c.proxy_set for c in self._proxies])
+ )
+
+ @util.memoized_property
+ def _expanded_proxy_set(self) -> FrozenSet[ColumnElement[Any]]:
+ return frozenset(_expand_cloned(self.proxy_set))
+
+ def _uncached_proxy_list(self) -> List[ColumnElement[Any]]:
+ """An 'uncached' version of proxy set.
+
+ This list includes annotated columns which perform very poorly in
+ set operations.
+
+ """
+
+ return [self] + list(
+ itertools.chain(*[c._uncached_proxy_list() for c in self._proxies])
+ )
+
+ def shares_lineage(self, othercolumn: ColumnElement[Any]) -> bool:
+ """Return True if the given :class:`_expression.ColumnElement`
+ has a common ancestor to this :class:`_expression.ColumnElement`."""
+
+ return bool(self.proxy_set.intersection(othercolumn.proxy_set))
+
+ def _compare_name_for_result(self, other: ColumnElement[Any]) -> bool:
+ """Return True if the given column element compares to this one
+ when targeting within a result row."""
+
+ return (
+ hasattr(other, "name")
+ and hasattr(self, "name")
+ and other.name == self.name
+ )
+
+ @HasMemoized.memoized_attribute
+ def _proxy_key(self) -> Optional[str]:
+ if self._annotations and "proxy_key" in self._annotations:
+ return cast(str, self._annotations["proxy_key"])
+
+ name = self.key
+ if not name:
+ # there's a bit of a seeming contradiction which is that the
+ # "_non_anon_label" of a column can in fact be an
+ # "_anonymous_label"; this is when it's on a column that is
+ # proxying for an anonymous expression in a subquery.
+ name = self._non_anon_label
+
+ if isinstance(name, _anonymous_label):
+ return None
+ else:
+ return name
+
+ @HasMemoized.memoized_attribute
+ def _expression_label(self) -> Optional[str]:
+ """a suggested label to use in the case that the column has no name,
+ which should be used if possible as the explicit 'AS <label>'
+ where this expression would normally have an anon label.
+
+ this is essentially mostly what _proxy_key does except it returns
+ None if the column has a normal name that can be used.
+
+ """
+
+ if getattr(self, "name", None) is not None:
+ return None
+ elif self._annotations and "proxy_key" in self._annotations:
+ return cast(str, self._annotations["proxy_key"])
+ else:
+ return None
+
+ def _make_proxy(
+ self,
+ selectable: FromClause,
+ *,
+ name: Optional[str] = None,
+ key: Optional[str] = None,
+ name_is_truncatable: bool = False,
+ compound_select_cols: Optional[Sequence[ColumnElement[Any]]] = None,
+ **kw: Any,
+ ) -> typing_Tuple[str, ColumnClause[_T]]:
+ """Create a new :class:`_expression.ColumnElement` representing this
+ :class:`_expression.ColumnElement` as it appears in the select list of
+ a descending selectable.
+
+ """
+ if name is None:
+ name = self._anon_name_label
+ if key is None:
+ key = self._proxy_key
+ else:
+ key = name
+
+ assert key is not None
+
+ co: ColumnClause[_T] = ColumnClause(
+ (
+ coercions.expect(roles.TruncatedLabelRole, name)
+ if name_is_truncatable
+ else name
+ ),
+ type_=getattr(self, "type", None),
+ _selectable=selectable,
+ )
+
+ co._propagate_attrs = selectable._propagate_attrs
+ if compound_select_cols:
+ co._proxies = list(compound_select_cols)
+ else:
+ co._proxies = [self]
+ if selectable._is_clone_of is not None:
+ co._is_clone_of = selectable._is_clone_of.columns.get(key)
+ return key, co
+
+ def cast(self, type_: _TypeEngineArgument[_OPT]) -> Cast[_OPT]:
+ """Produce a type cast, i.e. ``CAST(<expression> AS <type>)``.
+
+ This is a shortcut to the :func:`_expression.cast` function.
+
+ .. seealso::
+
+ :ref:`tutorial_casts`
+
+ :func:`_expression.cast`
+
+ :func:`_expression.type_coerce`
+
+ """
+ return Cast(self, type_)
+
+ def label(self, name: Optional[str]) -> Label[_T]:
+ """Produce a column label, i.e. ``<columnname> AS <name>``.
+
+ This is a shortcut to the :func:`_expression.label` function.
+
+ If 'name' is ``None``, an anonymous label name will be generated.
+
+ """
+ return Label(name, self, self.type)
+
+ def _anon_label(
+ self, seed: Optional[str], add_hash: Optional[int] = None
+ ) -> _anonymous_label:
+ while self._is_clone_of is not None:
+ self = self._is_clone_of
+
+ # as of 1.4 anonymous label for ColumnElement uses hash(), not id(),
+ # as the identifier, because a column and its annotated version are
+ # the same thing in a SQL statement
+ hash_value = hash(self)
+
+ if add_hash:
+ # this path is used for disambiguating anon labels that would
+ # otherwise be the same name for the same element repeated.
+ # an additional numeric value is factored in for each label.
+
+ # shift hash(self) (which is id(self), typically 8 byte integer)
+ # 16 bits leftward. fill extra add_hash on right
+ assert add_hash < (2 << 15)
+ assert seed
+ hash_value = (hash_value << 16) | add_hash
+
+ # extra underscore is added for labels with extra hash
+ # values, to isolate the "deduped anon" namespace from the
+ # regular namespace. eliminates chance of these
+ # manufactured hash values overlapping with regular ones for some
+ # undefined python interpreter
+ seed = seed + "_"
+
+ if isinstance(seed, _anonymous_label):
+ return _anonymous_label.safe_construct(
+ hash_value, "", enclosing_label=seed
+ )
+
+ return _anonymous_label.safe_construct(hash_value, seed or "anon")
+
+ @util.memoized_property
+ def _anon_name_label(self) -> str:
+ """Provides a constant 'anonymous label' for this ColumnElement.
+
+ This is a label() expression which will be named at compile time.
+ The same label() is returned each time ``anon_label`` is called so
+ that expressions can reference ``anon_label`` multiple times,
+ producing the same label name at compile time.
+
+ The compiler uses this function automatically at compile time
+ for expressions that are known to be 'unnamed' like binary
+ expressions and function calls.
+
+ .. versionchanged:: 1.4.9 - this attribute was not intended to be
+ public and is renamed to _anon_name_label. anon_name exists
+ for backwards compat
+
+ """
+ name = getattr(self, "name", None)
+ return self._anon_label(name)
+
+ @util.memoized_property
+ def _anon_key_label(self) -> _anonymous_label:
+ """Provides a constant 'anonymous key label' for this ColumnElement.
+
+ Compare to ``anon_label``, except that the "key" of the column,
+ if available, is used to generate the label.
+
+ This is used when a deduplicating key is placed into the columns
+ collection of a selectable.
+
+ .. versionchanged:: 1.4.9 - this attribute was not intended to be
+ public and is renamed to _anon_key_label. anon_key_label exists
+ for backwards compat
+
+ """
+ return self._anon_label(self._proxy_key)
+
+ @property
+ @util.deprecated(
+ "1.4",
+ "The :attr:`_expression.ColumnElement.anon_label` attribute is now "
+ "private, and the public accessor is deprecated.",
+ )
+ def anon_label(self) -> str:
+ return self._anon_name_label
+
+ @property
+ @util.deprecated(
+ "1.4",
+ "The :attr:`_expression.ColumnElement.anon_key_label` attribute is "
+ "now private, and the public accessor is deprecated.",
+ )
+ def anon_key_label(self) -> str:
+ return self._anon_key_label
+
+ def _dedupe_anon_label_idx(self, idx: int) -> str:
+ """label to apply to a column that is anon labeled, but repeated
+ in the SELECT, so that we have to make an "extra anon" label that
+ disambiguates it from the previous appearance.
+
+ these labels come out like "foo_bar_id__1" and have double underscores
+ in them.
+
+ """
+ label = getattr(self, "name", None)
+
+ # current convention is that if the element doesn't have a
+ # ".name" (usually because it is not NamedColumn), we try to
+ # use a "table qualified" form for the "dedupe anon" label,
+ # based on the notion that a label like
+ # "CAST(casttest.v1 AS DECIMAL) AS casttest_v1__1" looks better than
+ # "CAST(casttest.v1 AS DECIMAL) AS anon__1"
+
+ if label is None:
+ return self._dedupe_anon_tq_label_idx(idx)
+ else:
+ return self._anon_label(label, add_hash=idx)
+
+ @util.memoized_property
+ def _anon_tq_label(self) -> _anonymous_label:
+ return self._anon_label(getattr(self, "_tq_label", None))
+
+ @util.memoized_property
+ def _anon_tq_key_label(self) -> _anonymous_label:
+ return self._anon_label(getattr(self, "_tq_key_label", None))
+
+ def _dedupe_anon_tq_label_idx(self, idx: int) -> _anonymous_label:
+ label = getattr(self, "_tq_label", None) or "anon"
+
+ return self._anon_label(label, add_hash=idx)
+
+
+class KeyedColumnElement(ColumnElement[_T]):
+ """ColumnElement where ``.key`` is non-None."""
+
+ _is_keyed_column_element = True
+
+ key: str
+
+
+class WrapsColumnExpression(ColumnElement[_T]):
+ """Mixin that defines a :class:`_expression.ColumnElement`
+ as a wrapper with special
+ labeling behavior for an expression that already has a name.
+
+ .. versionadded:: 1.4
+
+ .. seealso::
+
+ :ref:`change_4449`
+
+
+ """
+
+ @property
+ def wrapped_column_expression(self) -> ColumnElement[_T]:
+ raise NotImplementedError()
+
+ @util.non_memoized_property
+ def _tq_label(self) -> Optional[str]:
+ wce = self.wrapped_column_expression
+ if hasattr(wce, "_tq_label"):
+ return wce._tq_label
+ else:
+ return None
+
+ @property
+ def _label(self) -> Optional[str]:
+ return self._tq_label
+
+ @property
+ def _non_anon_label(self) -> Optional[str]:
+ return None
+
+ @util.non_memoized_property
+ def _anon_name_label(self) -> str:
+ wce = self.wrapped_column_expression
+
+ # this logic tries to get the WrappedColumnExpression to render
+ # with "<expr> AS <name>", where "<name>" is the natural name
+ # within the expression itself. e.g. "CAST(table.foo) AS foo".
+ if not wce._is_text_clause:
+ nal = wce._non_anon_label
+ if nal:
+ return nal
+ elif hasattr(wce, "_anon_name_label"):
+ return wce._anon_name_label
+ return super()._anon_name_label
+
+ def _dedupe_anon_label_idx(self, idx: int) -> str:
+ wce = self.wrapped_column_expression
+ nal = wce._non_anon_label
+ if nal:
+ return self._anon_label(nal + "_")
+ else:
+ return self._dedupe_anon_tq_label_idx(idx)
+
+ @property
+ def _proxy_key(self):
+ wce = self.wrapped_column_expression
+
+ if not wce._is_text_clause:
+ return wce._proxy_key
+ return super()._proxy_key
+
+
+class BindParameter(roles.InElementRole, KeyedColumnElement[_T]):
+ r"""Represent a "bound expression".
+
+ :class:`.BindParameter` is invoked explicitly using the
+ :func:`.bindparam` function, as in::
+
+ from sqlalchemy import bindparam
+
+ stmt = select(users_table).where(
+ users_table.c.name == bindparam("username")
+ )
+
+ Detailed discussion of how :class:`.BindParameter` is used is
+ at :func:`.bindparam`.
+
+ .. seealso::
+
+ :func:`.bindparam`
+
+ """
+
+ __visit_name__ = "bindparam"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("key", InternalTraversal.dp_anon_name),
+ ("type", InternalTraversal.dp_type),
+ ("callable", InternalTraversal.dp_plain_dict),
+ ("value", InternalTraversal.dp_plain_obj),
+ ("literal_execute", InternalTraversal.dp_boolean),
+ ]
+
+ key: str
+ type: TypeEngine[_T]
+ value: Optional[_T]
+
+ _is_crud = False
+ _is_bind_parameter = True
+ _key_is_anon = False
+
+ # bindparam implements its own _gen_cache_key() method however
+ # we check subclasses for this flag, else no cache key is generated
+ inherit_cache = True
+
+ def __init__(
+ self,
+ key: Optional[str],
+ value: Any = _NoArg.NO_ARG,
+ type_: Optional[_TypeEngineArgument[_T]] = None,
+ unique: bool = False,
+ required: Union[bool, Literal[_NoArg.NO_ARG]] = _NoArg.NO_ARG,
+ quote: Optional[bool] = None,
+ callable_: Optional[Callable[[], Any]] = None,
+ expanding: bool = False,
+ isoutparam: bool = False,
+ literal_execute: bool = False,
+ _compared_to_operator: Optional[OperatorType] = None,
+ _compared_to_type: Optional[TypeEngine[Any]] = None,
+ _is_crud: bool = False,
+ ):
+ if required is _NoArg.NO_ARG:
+ required = value is _NoArg.NO_ARG and callable_ is None
+ if value is _NoArg.NO_ARG:
+ value = None
+
+ if quote is not None:
+ key = quoted_name.construct(key, quote)
+
+ if unique:
+ self.key = _anonymous_label.safe_construct(
+ id(self),
+ (
+ key
+ if key is not None
+ and not isinstance(key, _anonymous_label)
+ else "param"
+ ),
+ sanitize_key=True,
+ )
+ self._key_is_anon = True
+ elif key:
+ self.key = key
+ else:
+ self.key = _anonymous_label.safe_construct(id(self), "param")
+ self._key_is_anon = True
+
+ # identifying key that won't change across
+ # clones, used to identify the bind's logical
+ # identity
+ self._identifying_key = self.key
+
+ # key that was passed in the first place, used to
+ # generate new keys
+ self._orig_key = key or "param"
+
+ self.unique = unique
+ self.value = value
+ self.callable = callable_
+ self.isoutparam = isoutparam
+ self.required = required
+
+ # indicate an "expanding" parameter; the compiler sets this
+ # automatically in the compiler _render_in_expr_w_bindparam method
+ # for an IN expression
+ self.expanding = expanding
+
+ # this is another hint to help w/ expanding and is typically
+ # set in the compiler _render_in_expr_w_bindparam method for an
+ # IN expression
+ self.expand_op = None
+
+ self.literal_execute = literal_execute
+ if _is_crud:
+ self._is_crud = True
+
+ if type_ is None:
+ if expanding:
+ if value:
+ check_value = value[0]
+ else:
+ check_value = type_api._NO_VALUE_IN_LIST
+ else:
+ check_value = value
+ if _compared_to_type is not None:
+ self.type = _compared_to_type.coerce_compared_value(
+ _compared_to_operator, check_value
+ )
+ else:
+ self.type = type_api._resolve_value_to_type(check_value)
+ elif isinstance(type_, type):
+ self.type = type_()
+ elif is_tuple_type(type_):
+ if value:
+ if expanding:
+ check_value = value[0]
+ else:
+ check_value = value
+ cast("BindParameter[typing_Tuple[Any, ...]]", self).type = (
+ type_._resolve_values_to_types(check_value)
+ )
+ else:
+ cast("BindParameter[typing_Tuple[Any, ...]]", self).type = (
+ type_
+ )
+ else:
+ self.type = type_
+
+ def _with_value(self, value, maintain_key=False, required=NO_ARG):
+ """Return a copy of this :class:`.BindParameter` with the given value
+ set.
+ """
+ cloned = self._clone(maintain_key=maintain_key)
+ cloned.value = value
+ cloned.callable = None
+ cloned.required = required if required is not NO_ARG else self.required
+ if cloned.type is type_api.NULLTYPE:
+ cloned.type = type_api._resolve_value_to_type(value)
+ return cloned
+
+ @property
+ def effective_value(self) -> Optional[_T]:
+ """Return the value of this bound parameter,
+ taking into account if the ``callable`` parameter
+ was set.
+
+ The ``callable`` value will be evaluated
+ and returned if present, else ``value``.
+
+ """
+ if self.callable:
+ # TODO: set up protocol for bind parameter callable
+ return self.callable() # type: ignore
+ else:
+ return self.value
+
+ def render_literal_execute(self) -> BindParameter[_T]:
+ """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`
+
+ """
+ c = ClauseElement._clone(self)
+ c.literal_execute = True
+ return c
+
+ def _negate_in_binary(self, negated_op, original_op):
+ if self.expand_op is original_op:
+ bind = self._clone()
+ bind.expand_op = negated_op
+ return bind
+ else:
+ return self
+
+ def _with_binary_element_type(self, type_):
+ c = ClauseElement._clone(self)
+ c.type = type_
+ return c
+
+ def _clone(self, maintain_key: bool = False, **kw: Any) -> Self:
+ c = ClauseElement._clone(self, **kw)
+ # ensure all the BindParameter objects stay in cloned set.
+ # in #7823, we changed "clone" so that a clone only keeps a reference
+ # to the "original" element, since for column correspondence, that's
+ # all we need. However, for BindParam, _cloned_set is used by
+ # the "cache key bind match" lookup, which means if any of those
+ # interim BindParameter objects became part of a cache key in the
+ # cache, we need it. So here, make sure all clones keep carrying
+ # forward.
+ c._cloned_set.update(self._cloned_set)
+ if not maintain_key and self.unique:
+ c.key = _anonymous_label.safe_construct(
+ id(c), c._orig_key or "param", sanitize_key=True
+ )
+ return c
+
+ def _gen_cache_key(self, anon_map, bindparams):
+ _gen_cache_ok = self.__class__.__dict__.get("inherit_cache", False)
+
+ if not _gen_cache_ok:
+ if anon_map is not None:
+ anon_map[NO_CACHE] = True
+ return None
+
+ id_, found = anon_map.get_anon(self)
+ if found:
+ return (id_, self.__class__)
+
+ if bindparams is not None:
+ bindparams.append(self)
+
+ return (
+ id_,
+ self.__class__,
+ self.type._static_cache_key,
+ self.key % anon_map if self._key_is_anon else self.key,
+ self.literal_execute,
+ )
+
+ def _convert_to_unique(self):
+ if not self.unique:
+ self.unique = True
+ self.key = _anonymous_label.safe_construct(
+ id(self), self._orig_key or "param", sanitize_key=True
+ )
+
+ def __getstate__(self):
+ """execute a deferred value for serialization purposes."""
+
+ d = self.__dict__.copy()
+ v = self.value
+ if self.callable:
+ v = self.callable()
+ d["callable"] = None
+ d["value"] = v
+ return d
+
+ def __setstate__(self, state):
+ if state.get("unique", False):
+ state["key"] = _anonymous_label.safe_construct(
+ id(self), state.get("_orig_key", "param"), sanitize_key=True
+ )
+ self.__dict__.update(state)
+
+ def __repr__(self):
+ return "%s(%r, %r, type_=%r)" % (
+ self.__class__.__name__,
+ self.key,
+ self.value,
+ self.type,
+ )
+
+
+class TypeClause(DQLDMLClauseElement):
+ """Handle a type keyword in a SQL statement.
+
+ Used by the ``Case`` statement.
+
+ """
+
+ __visit_name__ = "typeclause"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("type", InternalTraversal.dp_type)
+ ]
+
+ def __init__(self, type_):
+ self.type = type_
+
+
+class TextClause(
+ roles.DDLConstraintColumnRole,
+ roles.DDLExpressionRole,
+ roles.StatementOptionRole,
+ roles.WhereHavingRole,
+ roles.OrderByRole,
+ roles.FromClauseRole,
+ roles.SelectStatementRole,
+ roles.InElementRole,
+ Generative,
+ Executable,
+ DQLDMLClauseElement,
+ roles.BinaryElementRole[Any],
+ inspection.Inspectable["TextClause"],
+):
+ """Represent a literal SQL text fragment.
+
+ E.g.::
+
+ from sqlalchemy import text
+
+ t = text("SELECT * FROM users")
+ result = connection.execute(t)
+
+
+ The :class:`_expression.TextClause` construct is produced using the
+ :func:`_expression.text`
+ function; see that function for full documentation.
+
+ .. seealso::
+
+ :func:`_expression.text`
+
+ """
+
+ __visit_name__ = "textclause"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("_bindparams", InternalTraversal.dp_string_clauseelement_dict),
+ ("text", InternalTraversal.dp_string),
+ ]
+
+ _is_text_clause = True
+
+ _is_textual = True
+
+ _bind_params_regex = re.compile(r"(?<![:\w\x5c]):(\w+)(?!:)", re.UNICODE)
+ _is_implicitly_boolean = False
+
+ _render_label_in_columns_clause = False
+
+ _omit_from_statements = False
+
+ _is_collection_aggregate = False
+
+ @property
+ def _hide_froms(self) -> Iterable[FromClause]:
+ return ()
+
+ def __and__(self, other):
+ # support use in select.where(), query.filter()
+ return and_(self, other)
+
+ @property
+ def _select_iterable(self) -> _SelectIterable:
+ return (self,)
+
+ # help in those cases where text() is
+ # interpreted in a column expression situation
+ key: Optional[str] = None
+ _label: Optional[str] = None
+
+ _allow_label_resolve = False
+
+ @property
+ def _is_star(self):
+ return self.text == "*"
+
+ def __init__(self, text: str):
+ self._bindparams: Dict[str, BindParameter[Any]] = {}
+
+ def repl(m):
+ self._bindparams[m.group(1)] = BindParameter(m.group(1))
+ return ":%s" % m.group(1)
+
+ # scan the string and search for bind parameter names, add them
+ # to the list of bindparams
+ self.text = self._bind_params_regex.sub(repl, text)
+
+ @_generative
+ def bindparams(
+ self,
+ *binds: BindParameter[Any],
+ **names_to_values: Any,
+ ) -> Self:
+ """Establish the values and/or types of bound parameters within
+ this :class:`_expression.TextClause` construct.
+
+ Given a text construct such as::
+
+ from sqlalchemy import text
+ stmt = text("SELECT id, name FROM user WHERE name=:name "
+ "AND timestamp=:timestamp")
+
+ the :meth:`_expression.TextClause.bindparams`
+ method can be used to establish
+ the initial value of ``:name`` and ``:timestamp``,
+ using simple keyword arguments::
+
+ stmt = stmt.bindparams(name='jack',
+ timestamp=datetime.datetime(2012, 10, 8, 15, 12, 5))
+
+ Where above, new :class:`.BindParameter` objects
+ will be generated with the names ``name`` and ``timestamp``, and
+ values of ``jack`` and ``datetime.datetime(2012, 10, 8, 15, 12, 5)``,
+ respectively. The types will be
+ inferred from the values given, in this case :class:`.String` and
+ :class:`.DateTime`.
+
+ When specific typing behavior is needed, the positional ``*binds``
+ argument can be used in which to specify :func:`.bindparam` constructs
+ directly. These constructs must include at least the ``key``
+ argument, then an optional value and type::
+
+ from sqlalchemy import bindparam
+ stmt = stmt.bindparams(
+ bindparam('name', value='jack', type_=String),
+ bindparam('timestamp', type_=DateTime)
+ )
+
+ Above, we specified the type of :class:`.DateTime` for the
+ ``timestamp`` bind, and the type of :class:`.String` for the ``name``
+ bind. In the case of ``name`` we also set the default value of
+ ``"jack"``.
+
+ Additional bound parameters can be supplied at statement execution
+ time, e.g.::
+
+ result = connection.execute(stmt,
+ timestamp=datetime.datetime(2012, 10, 8, 15, 12, 5))
+
+ The :meth:`_expression.TextClause.bindparams`
+ method can be called repeatedly,
+ where it will re-use existing :class:`.BindParameter` objects to add
+ new information. For example, we can call
+ :meth:`_expression.TextClause.bindparams`
+ first with typing information, and a
+ second time with value information, and it will be combined::
+
+ stmt = text("SELECT id, name FROM user WHERE name=:name "
+ "AND timestamp=:timestamp")
+ stmt = stmt.bindparams(
+ bindparam('name', type_=String),
+ bindparam('timestamp', type_=DateTime)
+ )
+ stmt = stmt.bindparams(
+ name='jack',
+ timestamp=datetime.datetime(2012, 10, 8, 15, 12, 5)
+ )
+
+ The :meth:`_expression.TextClause.bindparams`
+ method also supports the concept of
+ **unique** bound parameters. These are parameters that are
+ "uniquified" on name at statement compilation time, so that multiple
+ :func:`_expression.text`
+ constructs may be combined together without the names
+ conflicting. To use this feature, specify the
+ :paramref:`.BindParameter.unique` flag on each :func:`.bindparam`
+ object::
+
+ stmt1 = text("select id from table where name=:name").bindparams(
+ bindparam("name", value='name1', unique=True)
+ )
+ stmt2 = text("select id from table where name=:name").bindparams(
+ bindparam("name", value='name2', unique=True)
+ )
+
+ union = union_all(
+ stmt1.columns(column("id")),
+ stmt2.columns(column("id"))
+ )
+
+ The above statement will render as::
+
+ select id from table where name=:name_1
+ UNION ALL select id from table where name=:name_2
+
+ .. versionadded:: 1.3.11 Added support for the
+ :paramref:`.BindParameter.unique` flag to work with
+ :func:`_expression.text`
+ constructs.
+
+ """
+ self._bindparams = new_params = self._bindparams.copy()
+
+ for bind in binds:
+ try:
+ # the regex used for text() currently will not match
+ # a unique/anonymous key in any case, so use the _orig_key
+ # so that a text() construct can support unique parameters
+ existing = new_params[bind._orig_key]
+ except KeyError as err:
+ raise exc.ArgumentError(
+ "This text() construct doesn't define a "
+ "bound parameter named %r" % bind._orig_key
+ ) from err
+ else:
+ new_params[existing._orig_key] = bind
+
+ for key, value in names_to_values.items():
+ try:
+ existing = new_params[key]
+ except KeyError as err:
+ raise exc.ArgumentError(
+ "This text() construct doesn't define a "
+ "bound parameter named %r" % key
+ ) from err
+ else:
+ new_params[key] = existing._with_value(value, required=False)
+ return self
+
+ @util.preload_module("sqlalchemy.sql.selectable")
+ def columns(
+ self, *cols: _ColumnExpressionArgument[Any], **types: TypeEngine[Any]
+ ) -> TextualSelect:
+ r"""Turn this :class:`_expression.TextClause` object into a
+ :class:`_expression.TextualSelect`
+ object that serves the same role as a SELECT
+ statement.
+
+ The :class:`_expression.TextualSelect` is part of the
+ :class:`_expression.SelectBase`
+ hierarchy and can be embedded into another statement by using the
+ :meth:`_expression.TextualSelect.subquery` method to produce a
+ :class:`.Subquery`
+ object, which can then be SELECTed from.
+
+ This function essentially bridges the gap between an entirely
+ textual SELECT statement and the SQL expression language concept
+ of a "selectable"::
+
+ from sqlalchemy.sql import column, text
+
+ stmt = text("SELECT id, name FROM some_table")
+ stmt = stmt.columns(column('id'), column('name')).subquery('st')
+
+ stmt = select(mytable).\
+ select_from(
+ mytable.join(stmt, mytable.c.name == stmt.c.name)
+ ).where(stmt.c.id > 5)
+
+ Above, we pass a series of :func:`_expression.column` elements to the
+ :meth:`_expression.TextClause.columns` method positionally. These
+ :func:`_expression.column`
+ elements now become first class elements upon the
+ :attr:`_expression.TextualSelect.selected_columns` column collection,
+ which then
+ become part of the :attr:`.Subquery.c` collection after
+ :meth:`_expression.TextualSelect.subquery` is invoked.
+
+ The column expressions we pass to
+ :meth:`_expression.TextClause.columns` may
+ also be typed; when we do so, these :class:`.TypeEngine` objects become
+ the effective return type of the column, so that SQLAlchemy's
+ result-set-processing systems may be used on the return values.
+ This is often needed for types such as date or boolean types, as well
+ as for unicode processing on some dialect configurations::
+
+ stmt = text("SELECT id, name, timestamp FROM some_table")
+ stmt = stmt.columns(
+ column('id', Integer),
+ column('name', Unicode),
+ column('timestamp', DateTime)
+ )
+
+ for id, name, timestamp in connection.execute(stmt):
+ print(id, name, timestamp)
+
+ As a shortcut to the above syntax, keyword arguments referring to
+ types alone may be used, if only type conversion is needed::
+
+ stmt = text("SELECT id, name, timestamp FROM some_table")
+ stmt = stmt.columns(
+ id=Integer,
+ name=Unicode,
+ timestamp=DateTime
+ )
+
+ for id, name, timestamp in connection.execute(stmt):
+ print(id, name, timestamp)
+
+ The positional form of :meth:`_expression.TextClause.columns`
+ also provides the
+ unique feature of **positional column targeting**, which is
+ particularly useful when using the ORM with complex textual queries. If
+ we specify the columns from our model to
+ :meth:`_expression.TextClause.columns`,
+ the result set will match to those columns positionally, meaning the
+ name or origin of the column in the textual SQL doesn't matter::
+
+ stmt = text("SELECT users.id, addresses.id, users.id, "
+ "users.name, addresses.email_address AS email "
+ "FROM users JOIN addresses ON users.id=addresses.user_id "
+ "WHERE users.id = 1").columns(
+ User.id,
+ Address.id,
+ Address.user_id,
+ User.name,
+ Address.email_address
+ )
+
+ query = session.query(User).from_statement(stmt).options(
+ contains_eager(User.addresses))
+
+ The :meth:`_expression.TextClause.columns` method provides a direct
+ route to calling :meth:`_expression.FromClause.subquery` as well as
+ :meth:`_expression.SelectBase.cte`
+ against a textual SELECT statement::
+
+ stmt = stmt.columns(id=Integer, name=String).cte('st')
+
+ stmt = select(sometable).where(sometable.c.id == stmt.c.id)
+
+ :param \*cols: A series of :class:`_expression.ColumnElement` objects,
+ typically
+ :class:`_schema.Column` objects from a :class:`_schema.Table`
+ or ORM level
+ column-mapped attributes, representing a set of columns that this
+ textual string will SELECT from.
+
+ :param \**types: A mapping of string names to :class:`.TypeEngine`
+ type objects indicating the datatypes to use for names that are
+ SELECTed from the textual string. Prefer to use the ``*cols``
+ argument as it also indicates positional ordering.
+
+ """
+ selectable = util.preloaded.sql_selectable
+
+ input_cols: List[NamedColumn[Any]] = [
+ coercions.expect(roles.LabeledColumnExprRole, col) for col in cols
+ ]
+
+ positional_input_cols = [
+ (
+ ColumnClause(col.key, types.pop(col.key))
+ if col.key in types
+ else col
+ )
+ for col in input_cols
+ ]
+ keyed_input_cols: List[NamedColumn[Any]] = [
+ ColumnClause(key, type_) for key, type_ in types.items()
+ ]
+
+ elem = selectable.TextualSelect.__new__(selectable.TextualSelect)
+ elem._init(
+ self,
+ positional_input_cols + keyed_input_cols,
+ positional=bool(positional_input_cols) and not keyed_input_cols,
+ )
+ return elem
+
+ @property
+ def type(self) -> TypeEngine[Any]:
+ return type_api.NULLTYPE
+
+ @property
+ def comparator(self):
+ # TODO: this seems wrong, it seems like we might not
+ # be using this method.
+ return self.type.comparator_factory(self) # type: ignore
+
+ def self_group(self, against=None):
+ if against is operators.in_op:
+ return Grouping(self)
+ else:
+ return self
+
+
+class Null(SingletonConstant, roles.ConstExprRole[None], ColumnElement[None]):
+ """Represent the NULL keyword in a SQL statement.
+
+ :class:`.Null` is accessed as a constant via the
+ :func:`.null` function.
+
+ """
+
+ __visit_name__ = "null"
+
+ _traverse_internals: _TraverseInternalsType = []
+ _singleton: Null
+
+ if not TYPE_CHECKING:
+
+ @util.memoized_property
+ def type(self) -> TypeEngine[_T]: # noqa: A001
+ return type_api.NULLTYPE
+
+ @classmethod
+ def _instance(cls) -> Null:
+ """Return a constant :class:`.Null` construct."""
+
+ return Null._singleton
+
+
+Null._create_singleton()
+
+
+class False_(
+ SingletonConstant, roles.ConstExprRole[bool], ColumnElement[bool]
+):
+ """Represent the ``false`` keyword, or equivalent, in a SQL statement.
+
+ :class:`.False_` is accessed as a constant via the
+ :func:`.false` function.
+
+ """
+
+ __visit_name__ = "false"
+ _traverse_internals: _TraverseInternalsType = []
+ _singleton: False_
+
+ if not TYPE_CHECKING:
+
+ @util.memoized_property
+ def type(self) -> TypeEngine[_T]: # noqa: A001
+ return type_api.BOOLEANTYPE
+
+ def _negate(self) -> True_:
+ return True_._singleton
+
+ @classmethod
+ def _instance(cls) -> False_:
+ return False_._singleton
+
+
+False_._create_singleton()
+
+
+class True_(SingletonConstant, roles.ConstExprRole[bool], ColumnElement[bool]):
+ """Represent the ``true`` keyword, or equivalent, in a SQL statement.
+
+ :class:`.True_` is accessed as a constant via the
+ :func:`.true` function.
+
+ """
+
+ __visit_name__ = "true"
+
+ _traverse_internals: _TraverseInternalsType = []
+ _singleton: True_
+
+ if not TYPE_CHECKING:
+
+ @util.memoized_property
+ def type(self) -> TypeEngine[_T]: # noqa: A001
+ return type_api.BOOLEANTYPE
+
+ def _negate(self) -> False_:
+ return False_._singleton
+
+ @classmethod
+ def _ifnone(
+ cls, other: Optional[ColumnElement[Any]]
+ ) -> ColumnElement[Any]:
+ if other is None:
+ return cls._instance()
+ else:
+ return other
+
+ @classmethod
+ def _instance(cls) -> True_:
+ return True_._singleton
+
+
+True_._create_singleton()
+
+
+class ClauseList(
+ roles.InElementRole,
+ roles.OrderByRole,
+ roles.ColumnsClauseRole,
+ roles.DMLColumnRole,
+ DQLDMLClauseElement,
+):
+ """Describe a list of clauses, separated by an operator.
+
+ By default, is comma-separated, such as a column listing.
+
+ """
+
+ __visit_name__ = "clauselist"
+
+ # this is used only by the ORM in a legacy use case for
+ # composite attributes
+ _is_clause_list = True
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("clauses", InternalTraversal.dp_clauseelement_list),
+ ("operator", InternalTraversal.dp_operator),
+ ]
+
+ clauses: List[ColumnElement[Any]]
+
+ def __init__(
+ self,
+ *clauses: _ColumnExpressionArgument[Any],
+ operator: OperatorType = operators.comma_op,
+ group: bool = True,
+ group_contents: bool = True,
+ _literal_as_text_role: Type[roles.SQLRole] = roles.WhereHavingRole,
+ ):
+ self.operator = operator
+ self.group = group
+ self.group_contents = group_contents
+ clauses_iterator: Iterable[_ColumnExpressionArgument[Any]] = clauses
+ text_converter_role: Type[roles.SQLRole] = _literal_as_text_role
+ self._text_converter_role = text_converter_role
+
+ if self.group_contents:
+ self.clauses = [
+ coercions.expect(
+ text_converter_role, clause, apply_propagate_attrs=self
+ ).self_group(against=self.operator)
+ for clause in clauses_iterator
+ ]
+ else:
+ self.clauses = [
+ coercions.expect(
+ text_converter_role, clause, apply_propagate_attrs=self
+ )
+ for clause in clauses_iterator
+ ]
+ self._is_implicitly_boolean = operators.is_boolean(self.operator)
+
+ @classmethod
+ def _construct_raw(
+ cls,
+ operator: OperatorType,
+ clauses: Optional[Sequence[ColumnElement[Any]]] = None,
+ ) -> ClauseList:
+ self = cls.__new__(cls)
+ self.clauses = list(clauses) if clauses else []
+ self.group = True
+ self.operator = operator
+ self.group_contents = True
+ self._is_implicitly_boolean = False
+ return self
+
+ def __iter__(self) -> Iterator[ColumnElement[Any]]:
+ return iter(self.clauses)
+
+ def __len__(self) -> int:
+ return len(self.clauses)
+
+ @property
+ def _select_iterable(self) -> _SelectIterable:
+ return itertools.chain.from_iterable(
+ [elem._select_iterable for elem in self.clauses]
+ )
+
+ def append(self, clause):
+ if self.group_contents:
+ self.clauses.append(
+ coercions.expect(self._text_converter_role, clause).self_group(
+ against=self.operator
+ )
+ )
+ else:
+ self.clauses.append(
+ coercions.expect(self._text_converter_role, clause)
+ )
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return list(itertools.chain(*[c._from_objects for c in self.clauses]))
+
+ def self_group(self, against=None):
+ if self.group and operators.is_precedent(self.operator, against):
+ return Grouping(self)
+ else:
+ return self
+
+
+class OperatorExpression(ColumnElement[_T]):
+ """base for expressions that contain an operator and operands
+
+ .. versionadded:: 2.0
+
+ """
+
+ operator: OperatorType
+ type: TypeEngine[_T]
+
+ group: bool = True
+
+ @property
+ def is_comparison(self):
+ return operators.is_comparison(self.operator)
+
+ def self_group(self, against=None):
+ if (
+ self.group
+ and operators.is_precedent(self.operator, against)
+ or (
+ # a negate against a non-boolean operator
+ # doesn't make too much sense but we should
+ # group for that
+ against is operators.inv
+ and not operators.is_boolean(self.operator)
+ )
+ ):
+ return Grouping(self)
+ else:
+ return self
+
+ @property
+ def _flattened_operator_clauses(
+ self,
+ ) -> typing_Tuple[ColumnElement[Any], ...]:
+ raise NotImplementedError()
+
+ @classmethod
+ def _construct_for_op(
+ cls,
+ left: ColumnElement[Any],
+ right: ColumnElement[Any],
+ op: OperatorType,
+ *,
+ type_: TypeEngine[_T],
+ negate: Optional[OperatorType] = None,
+ modifiers: Optional[Mapping[str, Any]] = None,
+ ) -> OperatorExpression[_T]:
+ if operators.is_associative(op):
+ assert (
+ negate is None
+ ), f"negate not supported for associative operator {op}"
+
+ multi = False
+ if getattr(
+ left, "operator", None
+ ) is op and type_._compare_type_affinity(left.type):
+ multi = True
+ left_flattened = left._flattened_operator_clauses
+ else:
+ left_flattened = (left,)
+
+ if getattr(
+ right, "operator", None
+ ) is op and type_._compare_type_affinity(right.type):
+ multi = True
+ right_flattened = right._flattened_operator_clauses
+ else:
+ right_flattened = (right,)
+
+ if multi:
+ return ExpressionClauseList._construct_for_list(
+ op,
+ type_,
+ *(left_flattened + right_flattened),
+ )
+
+ if right._is_collection_aggregate:
+ negate = None
+
+ return BinaryExpression(
+ left, right, op, type_=type_, negate=negate, modifiers=modifiers
+ )
+
+
+class ExpressionClauseList(OperatorExpression[_T]):
+ """Describe a list of clauses, separated by an operator,
+ in a column expression context.
+
+ :class:`.ExpressionClauseList` differs from :class:`.ClauseList` in that
+ it represents a column-oriented DQL expression only, not an open ended
+ list of anything comma separated.
+
+ .. versionadded:: 2.0
+
+ """
+
+ __visit_name__ = "expression_clauselist"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("clauses", InternalTraversal.dp_clauseelement_tuple),
+ ("operator", InternalTraversal.dp_operator),
+ ]
+
+ clauses: typing_Tuple[ColumnElement[Any], ...]
+
+ group: bool
+
+ def __init__(
+ self,
+ operator: OperatorType,
+ *clauses: _ColumnExpressionArgument[Any],
+ type_: Optional[_TypeEngineArgument[_T]] = None,
+ ):
+ self.operator = operator
+
+ self.clauses = tuple(
+ coercions.expect(
+ roles.ExpressionElementRole, clause, apply_propagate_attrs=self
+ )
+ for clause in clauses
+ )
+ self._is_implicitly_boolean = operators.is_boolean(self.operator)
+ self.type = type_api.to_instance(type_) # type: ignore
+
+ @property
+ def _flattened_operator_clauses(
+ self,
+ ) -> typing_Tuple[ColumnElement[Any], ...]:
+ return self.clauses
+
+ def __iter__(self) -> Iterator[ColumnElement[Any]]:
+ return iter(self.clauses)
+
+ def __len__(self) -> int:
+ return len(self.clauses)
+
+ @property
+ def _select_iterable(self) -> _SelectIterable:
+ return (self,)
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return list(itertools.chain(*[c._from_objects for c in self.clauses]))
+
+ def _append_inplace(self, clause: ColumnElement[Any]) -> None:
+ self.clauses += (clause,)
+
+ @classmethod
+ def _construct_for_list(
+ cls,
+ operator: OperatorType,
+ type_: TypeEngine[_T],
+ *clauses: ColumnElement[Any],
+ group: bool = True,
+ ) -> ExpressionClauseList[_T]:
+ self = cls.__new__(cls)
+ self.group = group
+ if group:
+ self.clauses = tuple(
+ c.self_group(against=operator) for c in clauses
+ )
+ else:
+ self.clauses = clauses
+ self.operator = operator
+ self.type = type_
+ return self
+
+ def _negate(self) -> Any:
+ grouped = self.self_group(against=operators.inv)
+ assert isinstance(grouped, ColumnElement)
+ return UnaryExpression(
+ grouped, operator=operators.inv, wraps_column_expression=True
+ )
+
+
+class BooleanClauseList(ExpressionClauseList[bool]):
+ __visit_name__ = "expression_clauselist"
+ inherit_cache = True
+
+ def __init__(self, *arg, **kw):
+ raise NotImplementedError(
+ "BooleanClauseList has a private constructor"
+ )
+
+ @classmethod
+ def _process_clauses_for_boolean(
+ cls,
+ operator: OperatorType,
+ continue_on: Any,
+ skip_on: Any,
+ clauses: Iterable[ColumnElement[Any]],
+ ) -> typing_Tuple[int, List[ColumnElement[Any]]]:
+ has_continue_on = None
+
+ convert_clauses = []
+
+ against = operators._asbool
+ lcc = 0
+
+ for clause in clauses:
+ if clause is continue_on:
+ # instance of continue_on, like and_(x, y, True, z), store it
+ # if we didn't find one already, we will use it if there
+ # are no other expressions here.
+ has_continue_on = clause
+ elif clause is skip_on:
+ # instance of skip_on, e.g. and_(x, y, False, z), cancels
+ # the rest out
+ convert_clauses = [clause]
+ lcc = 1
+ break
+ else:
+ if not lcc:
+ lcc = 1
+ else:
+ against = operator
+ # technically this would be len(convert_clauses) + 1
+ # however this only needs to indicate "greater than one"
+ lcc = 2
+ convert_clauses.append(clause)
+
+ if not convert_clauses and has_continue_on is not None:
+ convert_clauses = [has_continue_on]
+ lcc = 1
+
+ return lcc, [c.self_group(against=against) for c in convert_clauses]
+
+ @classmethod
+ def _construct(
+ cls,
+ operator: OperatorType,
+ continue_on: Any,
+ skip_on: Any,
+ initial_clause: Any = _NoArg.NO_ARG,
+ *clauses: Any,
+ **kw: Any,
+ ) -> ColumnElement[Any]:
+ if initial_clause is _NoArg.NO_ARG:
+ # no elements period. deprecated use case. return an empty
+ # ClauseList construct that generates nothing unless it has
+ # elements added to it.
+ name = operator.__name__
+
+ util.warn_deprecated(
+ f"Invoking {name}() without arguments is deprecated, and "
+ f"will be disallowed in a future release. For an empty "
+ f"""{name}() construct, use '{name}({
+ 'true()' if continue_on is True_._singleton else 'false()'
+ }, *args)' """
+ f"""or '{name}({
+ 'True' if continue_on is True_._singleton else 'False'
+ }, *args)'.""",
+ version="1.4",
+ )
+ return cls._construct_raw(operator)
+
+ lcc, convert_clauses = cls._process_clauses_for_boolean(
+ operator,
+ continue_on,
+ skip_on,
+ [
+ coercions.expect(roles.WhereHavingRole, clause)
+ for clause in util.coerce_generator_arg(
+ (initial_clause,) + clauses
+ )
+ ],
+ )
+
+ if lcc > 1:
+ # multiple elements. Return regular BooleanClauseList
+ # which will link elements against the operator.
+
+ flattened_clauses = itertools.chain.from_iterable(
+ (
+ (c for c in to_flat._flattened_operator_clauses)
+ if getattr(to_flat, "operator", None) is operator
+ else (to_flat,)
+ )
+ for to_flat in convert_clauses
+ )
+
+ return cls._construct_raw(operator, flattened_clauses) # type: ignore # noqa: E501
+ else:
+ assert lcc
+ # just one element. return it as a single boolean element,
+ # not a list and discard the operator.
+ return convert_clauses[0]
+
+ @classmethod
+ def _construct_for_whereclause(
+ cls, clauses: Iterable[ColumnElement[Any]]
+ ) -> Optional[ColumnElement[bool]]:
+ operator, continue_on, skip_on = (
+ operators.and_,
+ True_._singleton,
+ False_._singleton,
+ )
+
+ lcc, convert_clauses = cls._process_clauses_for_boolean(
+ operator,
+ continue_on,
+ skip_on,
+ clauses, # these are assumed to be coerced already
+ )
+
+ if lcc > 1:
+ # multiple elements. Return regular BooleanClauseList
+ # which will link elements against the operator.
+ return cls._construct_raw(operator, convert_clauses)
+ elif lcc == 1:
+ # just one element. return it as a single boolean element,
+ # not a list and discard the operator.
+ return convert_clauses[0]
+ else:
+ return None
+
+ @classmethod
+ def _construct_raw(
+ cls,
+ operator: OperatorType,
+ clauses: Optional[Sequence[ColumnElement[Any]]] = None,
+ ) -> BooleanClauseList:
+ self = cls.__new__(cls)
+ self.clauses = tuple(clauses) if clauses else ()
+ self.group = True
+ self.operator = operator
+ self.type = type_api.BOOLEANTYPE
+ self._is_implicitly_boolean = True
+ return self
+
+ @classmethod
+ def and_(
+ cls,
+ initial_clause: Union[
+ Literal[True], _ColumnExpressionArgument[bool], _NoArg
+ ] = _NoArg.NO_ARG,
+ *clauses: _ColumnExpressionArgument[bool],
+ ) -> ColumnElement[bool]:
+ r"""Produce a conjunction of expressions joined by ``AND``.
+
+ See :func:`_sql.and_` for full documentation.
+ """
+ return cls._construct(
+ operators.and_,
+ True_._singleton,
+ False_._singleton,
+ initial_clause,
+ *clauses,
+ )
+
+ @classmethod
+ def or_(
+ cls,
+ initial_clause: Union[
+ Literal[False], _ColumnExpressionArgument[bool], _NoArg
+ ] = _NoArg.NO_ARG,
+ *clauses: _ColumnExpressionArgument[bool],
+ ) -> ColumnElement[bool]:
+ """Produce a conjunction of expressions joined by ``OR``.
+
+ See :func:`_sql.or_` for full documentation.
+ """
+ return cls._construct(
+ operators.or_,
+ False_._singleton,
+ True_._singleton,
+ initial_clause,
+ *clauses,
+ )
+
+ @property
+ def _select_iterable(self) -> _SelectIterable:
+ return (self,)
+
+ def self_group(self, against=None):
+ if not self.clauses:
+ return self
+ else:
+ return super().self_group(against=against)
+
+
+and_ = BooleanClauseList.and_
+or_ = BooleanClauseList.or_
+
+
+class Tuple(ClauseList, ColumnElement[typing_Tuple[Any, ...]]):
+ """Represent a SQL tuple."""
+
+ __visit_name__ = "tuple"
+
+ _traverse_internals: _TraverseInternalsType = (
+ ClauseList._traverse_internals + []
+ )
+
+ type: TupleType
+
+ @util.preload_module("sqlalchemy.sql.sqltypes")
+ def __init__(
+ self,
+ *clauses: _ColumnExpressionArgument[Any],
+ types: Optional[Sequence[_TypeEngineArgument[Any]]] = None,
+ ):
+ sqltypes = util.preloaded.sql_sqltypes
+
+ if types is None:
+ init_clauses: List[ColumnElement[Any]] = [
+ coercions.expect(roles.ExpressionElementRole, c)
+ for c in clauses
+ ]
+ else:
+ if len(types) != len(clauses):
+ raise exc.ArgumentError(
+ "Wrong number of elements for %d-tuple: %r "
+ % (len(types), clauses)
+ )
+ init_clauses = [
+ coercions.expect(
+ roles.ExpressionElementRole,
+ c,
+ type_=typ if not typ._isnull else None,
+ )
+ for typ, c in zip(types, clauses)
+ ]
+
+ self.type = sqltypes.TupleType(*[arg.type for arg in init_clauses])
+ super().__init__(*init_clauses)
+
+ @property
+ def _select_iterable(self) -> _SelectIterable:
+ return (self,)
+
+ def _bind_param(self, operator, obj, type_=None, expanding=False):
+ if expanding:
+ return BindParameter(
+ None,
+ value=obj,
+ _compared_to_operator=operator,
+ unique=True,
+ expanding=True,
+ type_=type_,
+ _compared_to_type=self.type,
+ )
+ else:
+ return Tuple(
+ *[
+ BindParameter(
+ None,
+ o,
+ _compared_to_operator=operator,
+ _compared_to_type=compared_to_type,
+ unique=True,
+ type_=type_,
+ )
+ for o, compared_to_type in zip(obj, self.type.types)
+ ]
+ )
+
+ def self_group(self, against=None):
+ # Tuple is parenthesized by definition.
+ return self
+
+
+class Case(ColumnElement[_T]):
+ """Represent a ``CASE`` expression.
+
+ :class:`.Case` is produced using the :func:`.case` factory function,
+ as in::
+
+ from sqlalchemy import case
+
+ stmt = select(users_table).\
+ where(
+ case(
+ (users_table.c.name == 'wendy', 'W'),
+ (users_table.c.name == 'jack', 'J'),
+ else_='E'
+ )
+ )
+
+ Details on :class:`.Case` usage is at :func:`.case`.
+
+ .. seealso::
+
+ :func:`.case`
+
+ """
+
+ __visit_name__ = "case"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("value", InternalTraversal.dp_clauseelement),
+ ("whens", InternalTraversal.dp_clauseelement_tuples),
+ ("else_", InternalTraversal.dp_clauseelement),
+ ]
+
+ # for case(), the type is derived from the whens. so for the moment
+ # users would have to cast() the case to get a specific type
+
+ whens: List[typing_Tuple[ColumnElement[bool], ColumnElement[_T]]]
+ else_: Optional[ColumnElement[_T]]
+ value: Optional[ColumnElement[Any]]
+
+ def __init__(
+ self,
+ *whens: Union[
+ typing_Tuple[_ColumnExpressionArgument[bool], Any],
+ Mapping[Any, Any],
+ ],
+ value: Optional[Any] = None,
+ else_: Optional[Any] = None,
+ ):
+ new_whens: Iterable[Any] = coercions._expression_collection_was_a_list(
+ "whens", "case", whens
+ )
+ try:
+ new_whens = util.dictlike_iteritems(new_whens)
+ except TypeError:
+ pass
+
+ self.whens = [
+ (
+ coercions.expect(
+ roles.ExpressionElementRole,
+ c,
+ apply_propagate_attrs=self,
+ ).self_group(),
+ coercions.expect(roles.ExpressionElementRole, r),
+ )
+ for (c, r) in new_whens
+ ]
+
+ if value is None:
+ self.value = None
+ else:
+ self.value = coercions.expect(roles.ExpressionElementRole, value)
+
+ if else_ is not None:
+ self.else_ = coercions.expect(roles.ExpressionElementRole, else_)
+ else:
+ self.else_ = None
+
+ type_ = next(
+ (
+ then.type
+ # Iterate `whens` in reverse to match previous behaviour
+ # where type of final element took priority
+ for *_, then in reversed(self.whens)
+ if not then.type._isnull
+ ),
+ self.else_.type if self.else_ is not None else type_api.NULLTYPE,
+ )
+ self.type = cast(_T, type_)
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return list(
+ itertools.chain(*[x._from_objects for x in self.get_children()])
+ )
+
+
+class Cast(WrapsColumnExpression[_T]):
+ """Represent a ``CAST`` expression.
+
+ :class:`.Cast` is produced using the :func:`.cast` factory function,
+ as in::
+
+ from sqlalchemy import cast, Numeric
+
+ stmt = select(cast(product_table.c.unit_price, Numeric(10, 4)))
+
+ Details on :class:`.Cast` usage is at :func:`.cast`.
+
+ .. seealso::
+
+ :ref:`tutorial_casts`
+
+ :func:`.cast`
+
+ :func:`.try_cast`
+
+ :func:`.type_coerce` - an alternative to CAST that coerces the type
+ on the Python side only, which is often sufficient to generate the
+ correct SQL and data coercion.
+
+ """
+
+ __visit_name__ = "cast"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("clause", InternalTraversal.dp_clauseelement),
+ ("type", InternalTraversal.dp_type),
+ ]
+
+ clause: ColumnElement[Any]
+ type: TypeEngine[_T]
+ typeclause: TypeClause
+
+ def __init__(
+ self,
+ expression: _ColumnExpressionArgument[Any],
+ type_: _TypeEngineArgument[_T],
+ ):
+ self.type = type_api.to_instance(type_)
+ self.clause = coercions.expect(
+ roles.ExpressionElementRole,
+ expression,
+ type_=self.type,
+ apply_propagate_attrs=self,
+ )
+ self.typeclause = TypeClause(self.type)
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return self.clause._from_objects
+
+ @property
+ def wrapped_column_expression(self):
+ return self.clause
+
+
+class TryCast(Cast[_T]):
+ """Represent a TRY_CAST expression.
+
+ Details on :class:`.TryCast` usage is at :func:`.try_cast`.
+
+ .. seealso::
+
+ :func:`.try_cast`
+
+ :ref:`tutorial_casts`
+ """
+
+ __visit_name__ = "try_cast"
+ inherit_cache = True
+
+
+class TypeCoerce(WrapsColumnExpression[_T]):
+ """Represent a Python-side type-coercion wrapper.
+
+ :class:`.TypeCoerce` supplies the :func:`_expression.type_coerce`
+ function; see that function for usage details.
+
+ .. seealso::
+
+ :func:`_expression.type_coerce`
+
+ :func:`.cast`
+
+ """
+
+ __visit_name__ = "type_coerce"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("clause", InternalTraversal.dp_clauseelement),
+ ("type", InternalTraversal.dp_type),
+ ]
+
+ clause: ColumnElement[Any]
+ type: TypeEngine[_T]
+
+ def __init__(
+ self,
+ expression: _ColumnExpressionArgument[Any],
+ type_: _TypeEngineArgument[_T],
+ ):
+ self.type = type_api.to_instance(type_)
+ self.clause = coercions.expect(
+ roles.ExpressionElementRole,
+ expression,
+ type_=self.type,
+ apply_propagate_attrs=self,
+ )
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return self.clause._from_objects
+
+ @HasMemoized.memoized_attribute
+ def typed_expression(self):
+ if isinstance(self.clause, BindParameter):
+ bp = self.clause._clone()
+ bp.type = self.type
+ return bp
+ else:
+ return self.clause
+
+ @property
+ def wrapped_column_expression(self):
+ return self.clause
+
+ def self_group(self, against=None):
+ grouped = self.clause.self_group(against=against)
+ if grouped is not self.clause:
+ return TypeCoerce(grouped, self.type)
+ else:
+ return self
+
+
+class Extract(ColumnElement[int]):
+ """Represent a SQL EXTRACT clause, ``extract(field FROM expr)``."""
+
+ __visit_name__ = "extract"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("expr", InternalTraversal.dp_clauseelement),
+ ("field", InternalTraversal.dp_string),
+ ]
+
+ expr: ColumnElement[Any]
+ field: str
+
+ def __init__(self, field: str, expr: _ColumnExpressionArgument[Any]):
+ self.type = type_api.INTEGERTYPE
+ self.field = field
+ self.expr = coercions.expect(roles.ExpressionElementRole, expr)
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return self.expr._from_objects
+
+
+class _label_reference(ColumnElement[_T]):
+ """Wrap a column expression as it appears in a 'reference' context.
+
+ This expression is any that includes an _order_by_label_element,
+ which is a Label, or a DESC / ASC construct wrapping a Label.
+
+ The production of _label_reference() should occur when an expression
+ is added to this context; this includes the ORDER BY or GROUP BY of a
+ SELECT statement, as well as a few other places, such as the ORDER BY
+ within an OVER clause.
+
+ """
+
+ __visit_name__ = "label_reference"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("element", InternalTraversal.dp_clauseelement)
+ ]
+
+ element: ColumnElement[_T]
+
+ def __init__(self, element: ColumnElement[_T]):
+ self.element = element
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return []
+
+
+class _textual_label_reference(ColumnElement[Any]):
+ __visit_name__ = "textual_label_reference"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("element", InternalTraversal.dp_string)
+ ]
+
+ def __init__(self, element: str):
+ self.element = element
+
+ @util.memoized_property
+ def _text_clause(self) -> TextClause:
+ return TextClause(self.element)
+
+
+class UnaryExpression(ColumnElement[_T]):
+ """Define a 'unary' expression.
+
+ A unary expression has a single column expression
+ and an operator. The operator can be placed on the left
+ (where it is called the 'operator') or right (where it is called the
+ 'modifier') of the column expression.
+
+ :class:`.UnaryExpression` is the basis for several unary operators
+ including those used by :func:`.desc`, :func:`.asc`, :func:`.distinct`,
+ :func:`.nulls_first` and :func:`.nulls_last`.
+
+ """
+
+ __visit_name__ = "unary"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("element", InternalTraversal.dp_clauseelement),
+ ("operator", InternalTraversal.dp_operator),
+ ("modifier", InternalTraversal.dp_operator),
+ ]
+
+ element: ClauseElement
+
+ def __init__(
+ self,
+ element: ColumnElement[Any],
+ operator: Optional[OperatorType] = None,
+ modifier: Optional[OperatorType] = None,
+ type_: Optional[_TypeEngineArgument[_T]] = None,
+ wraps_column_expression: bool = False,
+ ):
+ self.operator = operator
+ self.modifier = modifier
+ self._propagate_attrs = element._propagate_attrs
+ self.element = element.self_group(
+ against=self.operator or self.modifier
+ )
+
+ # if type is None, we get NULLTYPE, which is our _T. But I don't
+ # know how to get the overloads to express that correctly
+ self.type = type_api.to_instance(type_) # type: ignore
+
+ self.wraps_column_expression = wraps_column_expression
+
+ @classmethod
+ def _create_nulls_first(
+ cls,
+ column: _ColumnExpressionArgument[_T],
+ ) -> UnaryExpression[_T]:
+ return UnaryExpression(
+ coercions.expect(roles.ByOfRole, column),
+ modifier=operators.nulls_first_op,
+ wraps_column_expression=False,
+ )
+
+ @classmethod
+ def _create_nulls_last(
+ cls,
+ column: _ColumnExpressionArgument[_T],
+ ) -> UnaryExpression[_T]:
+ return UnaryExpression(
+ coercions.expect(roles.ByOfRole, column),
+ modifier=operators.nulls_last_op,
+ wraps_column_expression=False,
+ )
+
+ @classmethod
+ def _create_desc(
+ cls, column: _ColumnExpressionOrStrLabelArgument[_T]
+ ) -> UnaryExpression[_T]:
+ return UnaryExpression(
+ coercions.expect(roles.ByOfRole, column),
+ modifier=operators.desc_op,
+ wraps_column_expression=False,
+ )
+
+ @classmethod
+ def _create_asc(
+ cls,
+ column: _ColumnExpressionOrStrLabelArgument[_T],
+ ) -> UnaryExpression[_T]:
+ return UnaryExpression(
+ coercions.expect(roles.ByOfRole, column),
+ modifier=operators.asc_op,
+ wraps_column_expression=False,
+ )
+
+ @classmethod
+ def _create_distinct(
+ cls,
+ expr: _ColumnExpressionArgument[_T],
+ ) -> UnaryExpression[_T]:
+ col_expr: ColumnElement[_T] = coercions.expect(
+ roles.ExpressionElementRole, expr
+ )
+ return UnaryExpression(
+ col_expr,
+ operator=operators.distinct_op,
+ type_=col_expr.type,
+ wraps_column_expression=False,
+ )
+
+ @classmethod
+ def _create_bitwise_not(
+ cls,
+ expr: _ColumnExpressionArgument[_T],
+ ) -> UnaryExpression[_T]:
+ col_expr: ColumnElement[_T] = coercions.expect(
+ roles.ExpressionElementRole, expr
+ )
+ return UnaryExpression(
+ col_expr,
+ operator=operators.bitwise_not_op,
+ type_=col_expr.type,
+ wraps_column_expression=False,
+ )
+
+ @property
+ def _order_by_label_element(self) -> Optional[Label[Any]]:
+ if self.modifier in (operators.desc_op, operators.asc_op):
+ return self.element._order_by_label_element
+ else:
+ return None
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return self.element._from_objects
+
+ def _negate(self):
+ if self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity:
+ return UnaryExpression(
+ self.self_group(against=operators.inv),
+ operator=operators.inv,
+ type_=type_api.BOOLEANTYPE,
+ wraps_column_expression=self.wraps_column_expression,
+ )
+ else:
+ return ClauseElement._negate(self)
+
+ def self_group(self, against=None):
+ if self.operator and operators.is_precedent(self.operator, against):
+ return Grouping(self)
+ else:
+ return self
+
+
+class CollectionAggregate(UnaryExpression[_T]):
+ """Forms the basis for right-hand collection operator modifiers
+ ANY and ALL.
+
+ The ANY and ALL keywords are available in different ways on different
+ backends. On PostgreSQL, they only work for an ARRAY type. On
+ MySQL, they only work for subqueries.
+
+ """
+
+ inherit_cache = True
+ _is_collection_aggregate = True
+
+ @classmethod
+ def _create_any(
+ cls, expr: _ColumnExpressionArgument[_T]
+ ) -> CollectionAggregate[bool]:
+ col_expr: ColumnElement[_T] = coercions.expect(
+ roles.ExpressionElementRole,
+ expr,
+ )
+ col_expr = col_expr.self_group()
+ return CollectionAggregate(
+ col_expr,
+ operator=operators.any_op,
+ type_=type_api.BOOLEANTYPE,
+ wraps_column_expression=False,
+ )
+
+ @classmethod
+ def _create_all(
+ cls, expr: _ColumnExpressionArgument[_T]
+ ) -> CollectionAggregate[bool]:
+ col_expr: ColumnElement[_T] = coercions.expect(
+ roles.ExpressionElementRole,
+ expr,
+ )
+ col_expr = col_expr.self_group()
+ return CollectionAggregate(
+ col_expr,
+ operator=operators.all_op,
+ type_=type_api.BOOLEANTYPE,
+ wraps_column_expression=False,
+ )
+
+ # operate and reverse_operate are hardwired to
+ # dispatch onto the type comparator directly, so that we can
+ # ensure "reversed" behavior.
+ def operate(self, op, *other, **kwargs):
+ if not operators.is_comparison(op):
+ raise exc.ArgumentError(
+ "Only comparison operators may be used with ANY/ALL"
+ )
+ kwargs["reverse"] = True
+ return self.comparator.operate(operators.mirror(op), *other, **kwargs)
+
+ def reverse_operate(self, op, other, **kwargs):
+ # comparison operators should never call reverse_operate
+ assert not operators.is_comparison(op)
+ raise exc.ArgumentError(
+ "Only comparison operators may be used with ANY/ALL"
+ )
+
+
+class AsBoolean(WrapsColumnExpression[bool], UnaryExpression[bool]):
+ inherit_cache = True
+
+ def __init__(self, element, operator, negate):
+ self.element = element
+ self.type = type_api.BOOLEANTYPE
+ self.operator = operator
+ self.negate = negate
+ self.modifier = None
+ self.wraps_column_expression = True
+ self._is_implicitly_boolean = element._is_implicitly_boolean
+
+ @property
+ def wrapped_column_expression(self):
+ return self.element
+
+ def self_group(self, against=None):
+ return self
+
+ def _negate(self):
+ if isinstance(self.element, (True_, False_)):
+ return self.element._negate()
+ else:
+ return AsBoolean(self.element, self.negate, self.operator)
+
+
+class BinaryExpression(OperatorExpression[_T]):
+ """Represent an expression that is ``LEFT <operator> RIGHT``.
+
+ A :class:`.BinaryExpression` is generated automatically
+ whenever two column expressions are used in a Python binary expression:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy.sql import column
+ >>> column('a') + column('b')
+ <sqlalchemy.sql.expression.BinaryExpression object at 0x101029dd0>
+ >>> print(column('a') + column('b'))
+ {printsql}a + b
+
+ """
+
+ __visit_name__ = "binary"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("left", InternalTraversal.dp_clauseelement),
+ ("right", InternalTraversal.dp_clauseelement),
+ ("operator", InternalTraversal.dp_operator),
+ ("negate", InternalTraversal.dp_operator),
+ ("modifiers", InternalTraversal.dp_plain_dict),
+ (
+ "type",
+ InternalTraversal.dp_type,
+ ),
+ ]
+
+ _cache_key_traversal = [
+ ("left", InternalTraversal.dp_clauseelement),
+ ("right", InternalTraversal.dp_clauseelement),
+ ("operator", InternalTraversal.dp_operator),
+ ("modifiers", InternalTraversal.dp_plain_dict),
+ # "type" affects JSON CAST operators, so while redundant in most cases,
+ # is needed for that one
+ (
+ "type",
+ InternalTraversal.dp_type,
+ ),
+ ]
+
+ _is_implicitly_boolean = True
+ """Indicates that any database will know this is a boolean expression
+ even if the database does not have an explicit boolean datatype.
+
+ """
+
+ modifiers: Optional[Mapping[str, Any]]
+
+ left: ColumnElement[Any]
+ right: ColumnElement[Any]
+
+ def __init__(
+ self,
+ left: ColumnElement[Any],
+ right: ColumnElement[Any],
+ operator: OperatorType,
+ type_: Optional[_TypeEngineArgument[_T]] = None,
+ negate: Optional[OperatorType] = None,
+ modifiers: Optional[Mapping[str, Any]] = None,
+ ):
+ # allow compatibility with libraries that
+ # refer to BinaryExpression directly and pass strings
+ if isinstance(operator, str):
+ operator = operators.custom_op(operator)
+ self._orig = (left.__hash__(), right.__hash__())
+ self._propagate_attrs = left._propagate_attrs or right._propagate_attrs
+ self.left = left.self_group(against=operator)
+ self.right = right.self_group(against=operator)
+ self.operator = operator
+
+ # if type is None, we get NULLTYPE, which is our _T. But I don't
+ # know how to get the overloads to express that correctly
+ self.type = type_api.to_instance(type_) # type: ignore
+
+ self.negate = negate
+ self._is_implicitly_boolean = operators.is_boolean(operator)
+
+ if modifiers is None:
+ self.modifiers = {}
+ else:
+ self.modifiers = modifiers
+
+ @property
+ def _flattened_operator_clauses(
+ self,
+ ) -> typing_Tuple[ColumnElement[Any], ...]:
+ return (self.left, self.right)
+
+ def __bool__(self):
+ """Implement Python-side "bool" for BinaryExpression as a
+ simple "identity" check for the left and right attributes,
+ if the operator is "eq" or "ne". Otherwise the expression
+ continues to not support "bool" like all other column expressions.
+
+ The rationale here is so that ColumnElement objects can be hashable.
+ What? Well, suppose you do this::
+
+ c1, c2 = column('x'), column('y')
+ s1 = set([c1, c2])
+
+ We do that **a lot**, columns inside of sets is an extremely basic
+ thing all over the ORM for example.
+
+ So what happens if we do this? ::
+
+ c1 in s1
+
+ Hashing means it will normally use ``__hash__()`` of the object,
+ but in case of hash collision, it's going to also do ``c1 == c1``
+ and/or ``c1 == c2`` inside. Those operations need to return a
+ True/False value. But because we override ``==`` and ``!=``, they're
+ going to get a BinaryExpression. Hence we implement ``__bool__`` here
+ so that these comparisons behave in this particular context mostly
+ like regular object comparisons. Thankfully Python is OK with
+ that! Otherwise we'd have to use special set classes for columns
+ (which we used to do, decades ago).
+
+ """
+ if self.operator in (operators.eq, operators.ne):
+ # this is using the eq/ne operator given int hash values,
+ # rather than Operator, so that "bool" can be based on
+ # identity
+ return self.operator(*self._orig) # type: ignore
+ else:
+ raise TypeError("Boolean value of this clause is not defined")
+
+ if typing.TYPE_CHECKING:
+
+ def __invert__(
+ self: BinaryExpression[_T],
+ ) -> BinaryExpression[_T]: ...
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return self.left._from_objects + self.right._from_objects
+
+ def _negate(self):
+ if self.negate is not None:
+ return BinaryExpression(
+ self.left,
+ self.right._negate_in_binary(self.negate, self.operator),
+ self.negate,
+ negate=self.operator,
+ type_=self.type,
+ modifiers=self.modifiers,
+ )
+ else:
+ return self.self_group()._negate()
+
+
+class Slice(ColumnElement[Any]):
+ """Represent SQL for a Python array-slice object.
+
+ This is not a specific SQL construct at this level, but
+ may be interpreted by specific dialects, e.g. PostgreSQL.
+
+ """
+
+ __visit_name__ = "slice"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("start", InternalTraversal.dp_clauseelement),
+ ("stop", InternalTraversal.dp_clauseelement),
+ ("step", InternalTraversal.dp_clauseelement),
+ ]
+
+ def __init__(self, start, stop, step, _name=None):
+ self.start = coercions.expect(
+ roles.ExpressionElementRole,
+ start,
+ name=_name,
+ type_=type_api.INTEGERTYPE,
+ )
+ self.stop = coercions.expect(
+ roles.ExpressionElementRole,
+ stop,
+ name=_name,
+ type_=type_api.INTEGERTYPE,
+ )
+ self.step = coercions.expect(
+ roles.ExpressionElementRole,
+ step,
+ name=_name,
+ type_=type_api.INTEGERTYPE,
+ )
+ self.type = type_api.NULLTYPE
+
+ def self_group(self, against=None):
+ assert against is operator.getitem
+ return self
+
+
+class IndexExpression(BinaryExpression[Any]):
+ """Represent the class of expressions that are like an "index"
+ operation."""
+
+ inherit_cache = True
+
+
+class GroupedElement(DQLDMLClauseElement):
+ """Represent any parenthesized expression"""
+
+ __visit_name__ = "grouping"
+
+ element: ClauseElement
+
+ def self_group(self, against=None):
+ return self
+
+ def _ungroup(self):
+ return self.element._ungroup()
+
+
+class Grouping(GroupedElement, ColumnElement[_T]):
+ """Represent a grouping within a column expression"""
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("element", InternalTraversal.dp_clauseelement),
+ ("type", InternalTraversal.dp_type),
+ ]
+
+ _cache_key_traversal = [
+ ("element", InternalTraversal.dp_clauseelement),
+ ]
+
+ element: Union[TextClause, ClauseList, ColumnElement[_T]]
+
+ def __init__(
+ self, element: Union[TextClause, ClauseList, ColumnElement[_T]]
+ ):
+ self.element = element
+
+ # nulltype assignment issue
+ self.type = getattr(element, "type", type_api.NULLTYPE) # type: ignore
+ self._propagate_attrs = element._propagate_attrs
+
+ def _with_binary_element_type(self, type_):
+ return self.__class__(self.element._with_binary_element_type(type_))
+
+ @util.memoized_property
+ def _is_implicitly_boolean(self):
+ return self.element._is_implicitly_boolean
+
+ @util.non_memoized_property
+ def _tq_label(self) -> Optional[str]:
+ return (
+ getattr(self.element, "_tq_label", None) or self._anon_name_label
+ )
+
+ @util.non_memoized_property
+ def _proxies(self) -> List[ColumnElement[Any]]:
+ if isinstance(self.element, ColumnElement):
+ return [self.element]
+ else:
+ return []
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return self.element._from_objects
+
+ def __getattr__(self, attr):
+ return getattr(self.element, attr)
+
+ def __getstate__(self):
+ return {"element": self.element, "type": self.type}
+
+ def __setstate__(self, state):
+ self.element = state["element"]
+ self.type = state["type"]
+
+
+class _OverrideBinds(Grouping[_T]):
+ """used by cache_key->_apply_params_to_element to allow compilation /
+ execution of a SQL element that's been cached, using an alternate set of
+ bound parameter values.
+
+ This is used by the ORM to swap new parameter values into expressions
+ that are embedded into loader options like with_expression(),
+ selectinload(). Previously, this task was accomplished using the
+ .params() method which would perform a deep-copy instead. This deep
+ copy proved to be too expensive for more complex expressions.
+
+ See #11085
+
+ """
+
+ __visit_name__ = "override_binds"
+
+ def __init__(
+ self,
+ element: ColumnElement[_T],
+ bindparams: Sequence[BindParameter[Any]],
+ replaces_params: Sequence[BindParameter[Any]],
+ ):
+ self.element = element
+ self.translate = {
+ k.key: v.value for k, v in zip(replaces_params, bindparams)
+ }
+
+ def _gen_cache_key(
+ self, anon_map: anon_map, bindparams: List[BindParameter[Any]]
+ ) -> Optional[typing_Tuple[Any, ...]]:
+ """generate a cache key for the given element, substituting its bind
+ values for the translation values present."""
+
+ existing_bps: List[BindParameter[Any]] = []
+ ck = self.element._gen_cache_key(anon_map, existing_bps)
+
+ bindparams.extend(
+ (
+ bp._with_value(
+ self.translate[bp.key], maintain_key=True, required=False
+ )
+ if bp.key in self.translate
+ else bp
+ )
+ for bp in existing_bps
+ )
+
+ return ck
+
+
+class _OverRange(IntEnum):
+ RANGE_UNBOUNDED = 0
+ RANGE_CURRENT = 1
+
+
+RANGE_UNBOUNDED = _OverRange.RANGE_UNBOUNDED
+RANGE_CURRENT = _OverRange.RANGE_CURRENT
+
+
+class Over(ColumnElement[_T]):
+ """Represent an OVER clause.
+
+ This is a special operator against a so-called
+ "window" function, as well as any aggregate function,
+ which produces results relative to the result set
+ itself. Most modern SQL backends now support window functions.
+
+ """
+
+ __visit_name__ = "over"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("element", InternalTraversal.dp_clauseelement),
+ ("order_by", InternalTraversal.dp_clauseelement),
+ ("partition_by", InternalTraversal.dp_clauseelement),
+ ("range_", InternalTraversal.dp_plain_obj),
+ ("rows", InternalTraversal.dp_plain_obj),
+ ]
+
+ order_by: Optional[ClauseList] = None
+ partition_by: Optional[ClauseList] = None
+
+ element: ColumnElement[_T]
+ """The underlying expression object to which this :class:`.Over`
+ object refers."""
+
+ range_: Optional[typing_Tuple[int, int]]
+
+ def __init__(
+ self,
+ element: ColumnElement[_T],
+ partition_by: Optional[_ByArgument] = None,
+ order_by: Optional[_ByArgument] = None,
+ range_: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
+ rows: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
+ ):
+ self.element = element
+ if order_by is not None:
+ self.order_by = ClauseList(
+ *util.to_list(order_by), _literal_as_text_role=roles.ByOfRole
+ )
+ if partition_by is not None:
+ self.partition_by = ClauseList(
+ *util.to_list(partition_by),
+ _literal_as_text_role=roles.ByOfRole,
+ )
+
+ if range_:
+ self.range_ = self._interpret_range(range_)
+ if rows:
+ raise exc.ArgumentError(
+ "'range_' and 'rows' are mutually exclusive"
+ )
+ else:
+ self.rows = None
+ elif rows:
+ self.rows = self._interpret_range(rows)
+ self.range_ = None
+ else:
+ self.rows = self.range_ = None
+
+ def __reduce__(self):
+ return self.__class__, (
+ self.element,
+ self.partition_by,
+ self.order_by,
+ self.range_,
+ self.rows,
+ )
+
+ def _interpret_range(
+ self, range_: typing_Tuple[Optional[int], Optional[int]]
+ ) -> typing_Tuple[int, int]:
+ if not isinstance(range_, tuple) or len(range_) != 2:
+ raise exc.ArgumentError("2-tuple expected for range/rows")
+
+ lower: int
+ upper: int
+
+ if range_[0] is None:
+ lower = RANGE_UNBOUNDED
+ else:
+ try:
+ lower = int(range_[0])
+ except ValueError as err:
+ raise exc.ArgumentError(
+ "Integer or None expected for range value"
+ ) from err
+ else:
+ if lower == 0:
+ lower = RANGE_CURRENT
+
+ if range_[1] is None:
+ upper = RANGE_UNBOUNDED
+ else:
+ try:
+ upper = int(range_[1])
+ except ValueError as err:
+ raise exc.ArgumentError(
+ "Integer or None expected for range value"
+ ) from err
+ else:
+ if upper == 0:
+ upper = RANGE_CURRENT
+
+ return lower, upper
+
+ if not TYPE_CHECKING:
+
+ @util.memoized_property
+ def type(self) -> TypeEngine[_T]: # noqa: A001
+ return self.element.type
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return list(
+ itertools.chain(
+ *[
+ c._from_objects
+ for c in (self.element, self.partition_by, self.order_by)
+ if c is not None
+ ]
+ )
+ )
+
+
+class WithinGroup(ColumnElement[_T]):
+ """Represent a WITHIN GROUP (ORDER BY) clause.
+
+ This is a special operator against so-called
+ "ordered set aggregate" and "hypothetical
+ set aggregate" functions, including ``percentile_cont()``,
+ ``rank()``, ``dense_rank()``, etc.
+
+ It's supported only by certain database backends, such as PostgreSQL,
+ Oracle and MS SQL Server.
+
+ The :class:`.WithinGroup` construct extracts its type from the
+ method :meth:`.FunctionElement.within_group_type`. If this returns
+ ``None``, the function's ``.type`` is used.
+
+ """
+
+ __visit_name__ = "withingroup"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("element", InternalTraversal.dp_clauseelement),
+ ("order_by", InternalTraversal.dp_clauseelement),
+ ]
+
+ order_by: Optional[ClauseList] = None
+
+ def __init__(
+ self,
+ element: FunctionElement[_T],
+ *order_by: _ColumnExpressionArgument[Any],
+ ):
+ self.element = element
+ if order_by is not None:
+ self.order_by = ClauseList(
+ *util.to_list(order_by), _literal_as_text_role=roles.ByOfRole
+ )
+
+ def __reduce__(self):
+ return self.__class__, (self.element,) + (
+ tuple(self.order_by) if self.order_by is not None else ()
+ )
+
+ def over(self, partition_by=None, order_by=None, range_=None, rows=None):
+ """Produce an OVER clause against this :class:`.WithinGroup`
+ construct.
+
+ This function has the same signature as that of
+ :meth:`.FunctionElement.over`.
+
+ """
+ return Over(
+ self,
+ partition_by=partition_by,
+ order_by=order_by,
+ range_=range_,
+ rows=rows,
+ )
+
+ if not TYPE_CHECKING:
+
+ @util.memoized_property
+ def type(self) -> TypeEngine[_T]: # noqa: A001
+ wgt = self.element.within_group_type(self)
+ if wgt is not None:
+ return wgt
+ else:
+ return self.element.type
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return list(
+ itertools.chain(
+ *[
+ c._from_objects
+ for c in (self.element, self.order_by)
+ if c is not None
+ ]
+ )
+ )
+
+
+class FunctionFilter(ColumnElement[_T]):
+ """Represent a function FILTER clause.
+
+ This is a special operator against aggregate and window functions,
+ which controls which rows are passed to it.
+ It's supported only by certain database backends.
+
+ Invocation of :class:`.FunctionFilter` is via
+ :meth:`.FunctionElement.filter`::
+
+ func.count(1).filter(True)
+
+ .. seealso::
+
+ :meth:`.FunctionElement.filter`
+
+ """
+
+ __visit_name__ = "funcfilter"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("func", InternalTraversal.dp_clauseelement),
+ ("criterion", InternalTraversal.dp_clauseelement),
+ ]
+
+ criterion: Optional[ColumnElement[bool]] = None
+
+ def __init__(
+ self,
+ func: FunctionElement[_T],
+ *criterion: _ColumnExpressionArgument[bool],
+ ):
+ self.func = func
+ self.filter(*criterion)
+
+ def filter(self, *criterion: _ColumnExpressionArgument[bool]) -> Self:
+ """Produce an additional FILTER against the function.
+
+ This method adds additional criteria to the initial criteria
+ set up by :meth:`.FunctionElement.filter`.
+
+ Multiple criteria are joined together at SQL render time
+ via ``AND``.
+
+
+ """
+
+ for crit in list(criterion):
+ crit = coercions.expect(roles.WhereHavingRole, crit)
+
+ if self.criterion is not None:
+ self.criterion = self.criterion & crit
+ else:
+ self.criterion = crit
+
+ return self
+
+ def over(
+ self,
+ partition_by: Optional[
+ Union[
+ Iterable[_ColumnExpressionArgument[Any]],
+ _ColumnExpressionArgument[Any],
+ ]
+ ] = None,
+ order_by: Optional[
+ Union[
+ Iterable[_ColumnExpressionArgument[Any]],
+ _ColumnExpressionArgument[Any],
+ ]
+ ] = None,
+ range_: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
+ rows: Optional[typing_Tuple[Optional[int], Optional[int]]] = None,
+ ) -> Over[_T]:
+ """Produce an OVER clause against this filtered function.
+
+ Used against aggregate or so-called "window" functions,
+ for database backends that support window functions.
+
+ The expression::
+
+ func.rank().filter(MyClass.y > 5).over(order_by='x')
+
+ is shorthand for::
+
+ from sqlalchemy import over, funcfilter
+ over(funcfilter(func.rank(), MyClass.y > 5), order_by='x')
+
+ See :func:`_expression.over` for a full description.
+
+ """
+ return Over(
+ self,
+ partition_by=partition_by,
+ order_by=order_by,
+ range_=range_,
+ rows=rows,
+ )
+
+ def self_group(
+ self, against: Optional[OperatorType] = None
+ ) -> Union[Self, Grouping[_T]]:
+ if operators.is_precedent(operators.filter_op, against):
+ return Grouping(self)
+ else:
+ return self
+
+ if not TYPE_CHECKING:
+
+ @util.memoized_property
+ def type(self) -> TypeEngine[_T]: # noqa: A001
+ return self.func.type
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return list(
+ itertools.chain(
+ *[
+ c._from_objects
+ for c in (self.func, self.criterion)
+ if c is not None
+ ]
+ )
+ )
+
+
+class NamedColumn(KeyedColumnElement[_T]):
+ is_literal = False
+ table: Optional[FromClause] = None
+ name: str
+ key: str
+
+ def _compare_name_for_result(self, other):
+ return (hasattr(other, "name") and self.name == other.name) or (
+ hasattr(other, "_label") and self._label == other._label
+ )
+
+ @util.ro_memoized_property
+ def description(self) -> str:
+ return self.name
+
+ @HasMemoized.memoized_attribute
+ def _tq_key_label(self):
+ """table qualified label based on column key.
+
+ for table-bound columns this is <tablename>_<column key/proxy key>;
+
+ all other expressions it resolves to key/proxy key.
+
+ """
+ proxy_key = self._proxy_key
+ if proxy_key and proxy_key != self.name:
+ return self._gen_tq_label(proxy_key)
+ else:
+ return self._tq_label
+
+ @HasMemoized.memoized_attribute
+ def _tq_label(self) -> Optional[str]:
+ """table qualified label based on column name.
+
+ for table-bound columns this is <tablename>_<columnname>; all other
+ expressions it resolves to .name.
+
+ """
+ return self._gen_tq_label(self.name)
+
+ @HasMemoized.memoized_attribute
+ def _render_label_in_columns_clause(self):
+ return True
+
+ @HasMemoized.memoized_attribute
+ def _non_anon_label(self):
+ return self.name
+
+ def _gen_tq_label(
+ self, name: str, dedupe_on_key: bool = True
+ ) -> Optional[str]:
+ return name
+
+ def _bind_param(
+ self,
+ operator: OperatorType,
+ obj: Any,
+ type_: Optional[TypeEngine[_T]] = None,
+ expanding: bool = False,
+ ) -> BindParameter[_T]:
+ return BindParameter(
+ self.key,
+ obj,
+ _compared_to_operator=operator,
+ _compared_to_type=self.type,
+ type_=type_,
+ unique=True,
+ expanding=expanding,
+ )
+
+ def _make_proxy(
+ self,
+ selectable: FromClause,
+ *,
+ name: Optional[str] = None,
+ key: Optional[str] = None,
+ name_is_truncatable: bool = False,
+ compound_select_cols: Optional[Sequence[ColumnElement[Any]]] = None,
+ disallow_is_literal: bool = False,
+ **kw: Any,
+ ) -> typing_Tuple[str, ColumnClause[_T]]:
+ c = ColumnClause(
+ (
+ coercions.expect(roles.TruncatedLabelRole, name or self.name)
+ if name_is_truncatable
+ else (name or self.name)
+ ),
+ type_=self.type,
+ _selectable=selectable,
+ is_literal=False,
+ )
+
+ c._propagate_attrs = selectable._propagate_attrs
+ if name is None:
+ c.key = self.key
+ if compound_select_cols:
+ c._proxies = list(compound_select_cols)
+ else:
+ c._proxies = [self]
+
+ if selectable._is_clone_of is not None:
+ c._is_clone_of = selectable._is_clone_of.columns.get(c.key)
+ return c.key, c
+
+
+class Label(roles.LabeledColumnExprRole[_T], NamedColumn[_T]):
+ """Represents a column label (AS).
+
+ Represent a label, as typically applied to any column-level
+ element using the ``AS`` sql keyword.
+
+ """
+
+ __visit_name__ = "label"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("name", InternalTraversal.dp_anon_name),
+ ("type", InternalTraversal.dp_type),
+ ("_element", InternalTraversal.dp_clauseelement),
+ ]
+
+ _cache_key_traversal = [
+ ("name", InternalTraversal.dp_anon_name),
+ ("_element", InternalTraversal.dp_clauseelement),
+ ]
+
+ _element: ColumnElement[_T]
+ name: str
+
+ def __init__(
+ self,
+ name: Optional[str],
+ element: _ColumnExpressionArgument[_T],
+ type_: Optional[_TypeEngineArgument[_T]] = None,
+ ):
+ orig_element = element
+ element = coercions.expect(
+ roles.ExpressionElementRole,
+ element,
+ apply_propagate_attrs=self,
+ )
+ while isinstance(element, Label):
+ # TODO: this is only covered in test_text.py, but nothing
+ # fails if it's removed. determine rationale
+ element = element.element
+
+ if name:
+ self.name = name
+ else:
+ self.name = _anonymous_label.safe_construct(
+ id(self), getattr(element, "name", "anon")
+ )
+ if isinstance(orig_element, Label):
+ # TODO: no coverage for this block, again would be in
+ # test_text.py where the resolve_label concept is important
+ self._resolve_label = orig_element._label
+
+ self.key = self._tq_label = self._tq_key_label = self.name
+ self._element = element
+
+ self.type = (
+ type_api.to_instance(type_)
+ if type_ is not None
+ else self._element.type
+ )
+
+ self._proxies = [element]
+
+ def __reduce__(self):
+ return self.__class__, (self.name, self._element, self.type)
+
+ @HasMemoized.memoized_attribute
+ def _render_label_in_columns_clause(self):
+ return True
+
+ def _bind_param(self, operator, obj, type_=None, expanding=False):
+ return BindParameter(
+ None,
+ obj,
+ _compared_to_operator=operator,
+ type_=type_,
+ _compared_to_type=self.type,
+ unique=True,
+ expanding=expanding,
+ )
+
+ @util.memoized_property
+ def _is_implicitly_boolean(self):
+ return self.element._is_implicitly_boolean
+
+ @HasMemoized.memoized_attribute
+ def _allow_label_resolve(self):
+ return self.element._allow_label_resolve
+
+ @property
+ def _order_by_label_element(self):
+ return self
+
+ @HasMemoized.memoized_attribute
+ def element(self) -> ColumnElement[_T]:
+ return self._element.self_group(against=operators.as_)
+
+ def self_group(self, against=None):
+ return self._apply_to_inner(self._element.self_group, against=against)
+
+ def _negate(self):
+ return self._apply_to_inner(self._element._negate)
+
+ def _apply_to_inner(self, fn, *arg, **kw):
+ sub_element = fn(*arg, **kw)
+ if sub_element is not self._element:
+ return Label(self.name, sub_element, type_=self.type)
+ else:
+ return self
+
+ @property
+ def primary_key(self):
+ return self.element.primary_key
+
+ @property
+ def foreign_keys(self):
+ return self.element.foreign_keys
+
+ def _copy_internals(
+ self,
+ *,
+ clone: _CloneCallableType = _clone,
+ anonymize_labels: bool = False,
+ **kw: Any,
+ ) -> None:
+ self._reset_memoizations()
+ self._element = clone(self._element, **kw)
+ if anonymize_labels:
+ self.name = _anonymous_label.safe_construct(
+ id(self), getattr(self.element, "name", "anon")
+ )
+ self.key = self._tq_label = self._tq_key_label = self.name
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return self.element._from_objects
+
+ def _make_proxy(
+ self,
+ selectable: FromClause,
+ *,
+ name: Optional[str] = None,
+ compound_select_cols: Optional[Sequence[ColumnElement[Any]]] = None,
+ **kw: Any,
+ ) -> typing_Tuple[str, ColumnClause[_T]]:
+ name = self.name if not name else name
+
+ key, e = self.element._make_proxy(
+ selectable,
+ name=name,
+ disallow_is_literal=True,
+ name_is_truncatable=isinstance(name, _truncated_label),
+ compound_select_cols=compound_select_cols,
+ )
+
+ # there was a note here to remove this assertion, which was here
+ # to determine if we later could support a use case where
+ # the key and name of a label are separate. But I don't know what
+ # that case was. For now, this is an unexpected case that occurs
+ # when a label name conflicts with other columns and select()
+ # is attempting to disambiguate an explicit label, which is not what
+ # the user would want. See issue #6090.
+ if key != self.name and not isinstance(self.name, _anonymous_label):
+ raise exc.InvalidRequestError(
+ "Label name %s is being renamed to an anonymous label due "
+ "to disambiguation "
+ "which is not supported right now. Please use unique names "
+ "for explicit labels." % (self.name)
+ )
+
+ e._propagate_attrs = selectable._propagate_attrs
+ e._proxies.append(self)
+ if self.type is not None:
+ e.type = self.type
+
+ return self.key, e
+
+
+class ColumnClause(
+ roles.DDLReferredColumnRole,
+ roles.LabeledColumnExprRole[_T],
+ roles.StrAsPlainColumnRole,
+ Immutable,
+ NamedColumn[_T],
+):
+ """Represents a column expression from any textual string.
+
+ The :class:`.ColumnClause`, a lightweight analogue to the
+ :class:`_schema.Column` class, is typically invoked using the
+ :func:`_expression.column` function, as in::
+
+ from sqlalchemy import column
+
+ id, name = column("id"), column("name")
+ stmt = select(id, name).select_from("user")
+
+ The above statement would produce SQL like::
+
+ SELECT id, name FROM user
+
+ :class:`.ColumnClause` is the immediate superclass of the schema-specific
+ :class:`_schema.Column` object. While the :class:`_schema.Column`
+ class has all the
+ same capabilities as :class:`.ColumnClause`, the :class:`.ColumnClause`
+ class is usable by itself in those cases where behavioral requirements
+ are limited to simple SQL expression generation. The object has none of
+ the associations with schema-level metadata or with execution-time
+ behavior that :class:`_schema.Column` does,
+ so in that sense is a "lightweight"
+ version of :class:`_schema.Column`.
+
+ Full details on :class:`.ColumnClause` usage is at
+ :func:`_expression.column`.
+
+ .. seealso::
+
+ :func:`_expression.column`
+
+ :class:`_schema.Column`
+
+ """
+
+ table: Optional[FromClause]
+ is_literal: bool
+
+ __visit_name__ = "column"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("name", InternalTraversal.dp_anon_name),
+ ("type", InternalTraversal.dp_type),
+ ("table", InternalTraversal.dp_clauseelement),
+ ("is_literal", InternalTraversal.dp_boolean),
+ ]
+
+ onupdate: Optional[DefaultGenerator] = None
+ default: Optional[DefaultGenerator] = None
+ server_default: Optional[FetchedValue] = None
+ server_onupdate: Optional[FetchedValue] = None
+
+ _is_multiparam_column = False
+
+ @property
+ def _is_star(self):
+ return self.is_literal and self.name == "*"
+
+ def __init__(
+ self,
+ text: str,
+ type_: Optional[_TypeEngineArgument[_T]] = None,
+ is_literal: bool = False,
+ _selectable: Optional[FromClause] = None,
+ ):
+ self.key = self.name = text
+ self.table = _selectable
+
+ # if type is None, we get NULLTYPE, which is our _T. But I don't
+ # know how to get the overloads to express that correctly
+ self.type = type_api.to_instance(type_) # type: ignore
+
+ self.is_literal = is_literal
+
+ def get_children(self, *, column_tables=False, **kw):
+ # override base get_children() to not return the Table
+ # or selectable that is parent to this column. Traversals
+ # expect the columns of tables and subqueries to be leaf nodes.
+ return []
+
+ @property
+ def entity_namespace(self):
+ if self.table is not None:
+ return self.table.entity_namespace
+ else:
+ return super().entity_namespace
+
+ def _clone(self, detect_subquery_cols=False, **kw):
+ if (
+ detect_subquery_cols
+ and self.table is not None
+ and self.table._is_subquery
+ ):
+ clone = kw.pop("clone")
+ table = clone(self.table, **kw)
+ new = table.c.corresponding_column(self)
+ return new
+
+ return super()._clone(**kw)
+
+ @HasMemoized_ro_memoized_attribute
+ def _from_objects(self) -> List[FromClause]:
+ t = self.table
+ if t is not None:
+ return [t]
+ else:
+ return []
+
+ @HasMemoized.memoized_attribute
+ def _render_label_in_columns_clause(self):
+ return self.table is not None
+
+ @property
+ def _ddl_label(self):
+ return self._gen_tq_label(self.name, dedupe_on_key=False)
+
+ def _compare_name_for_result(self, other):
+ if (
+ self.is_literal
+ or self.table is None
+ or self.table._is_textual
+ or not hasattr(other, "proxy_set")
+ or (
+ isinstance(other, ColumnClause)
+ and (
+ other.is_literal
+ or other.table is None
+ or other.table._is_textual
+ )
+ )
+ ):
+ return (hasattr(other, "name") and self.name == other.name) or (
+ hasattr(other, "_tq_label")
+ and self._tq_label == other._tq_label
+ )
+ else:
+ return other.proxy_set.intersection(self.proxy_set)
+
+ def _gen_tq_label(
+ self, name: str, dedupe_on_key: bool = True
+ ) -> Optional[str]:
+ """generate table-qualified label
+
+ for a table-bound column this is <tablename>_<columnname>.
+
+ used primarily for LABEL_STYLE_TABLENAME_PLUS_COL
+ as well as the .columns collection on a Join object.
+
+ """
+ label: str
+ t = self.table
+ if self.is_literal:
+ return None
+ elif t is not None and is_named_from_clause(t):
+ if has_schema_attr(t) and t.schema:
+ label = t.schema.replace(".", "_") + "_" + t.name + "_" + name
+ else:
+ assert not TYPE_CHECKING or isinstance(t, NamedFromClause)
+ label = t.name + "_" + name
+
+ # propagate name quoting rules for labels.
+ if is_quoted_name(name) and name.quote is not None:
+ if is_quoted_name(label):
+ label.quote = name.quote
+ else:
+ label = quoted_name(label, name.quote)
+ elif is_quoted_name(t.name) and t.name.quote is not None:
+ # can't get this situation to occur, so let's
+ # assert false on it for now
+ assert not isinstance(label, quoted_name)
+ label = quoted_name(label, t.name.quote)
+
+ if dedupe_on_key:
+ # ensure the label name doesn't conflict with that of an
+ # existing column. note that this implies that any Column
+ # must **not** set up its _label before its parent table has
+ # all of its other Column objects set up. There are several
+ # tables in the test suite which will fail otherwise; example:
+ # table "owner" has columns "name" and "owner_name". Therefore
+ # column owner.name cannot use the label "owner_name", it has
+ # to be "owner_name_1".
+ if label in t.c:
+ _label = label
+ counter = 1
+ while _label in t.c:
+ _label = label + "_" + str(counter)
+ counter += 1
+ label = _label
+
+ return coercions.expect(roles.TruncatedLabelRole, label)
+
+ else:
+ return name
+
+ def _make_proxy(
+ self,
+ selectable: FromClause,
+ *,
+ name: Optional[str] = None,
+ key: Optional[str] = None,
+ name_is_truncatable: bool = False,
+ compound_select_cols: Optional[Sequence[ColumnElement[Any]]] = None,
+ disallow_is_literal: bool = False,
+ **kw: Any,
+ ) -> typing_Tuple[str, ColumnClause[_T]]:
+ # the "is_literal" flag normally should never be propagated; a proxied
+ # column is always a SQL identifier and never the actual expression
+ # being evaluated. however, there is a case where the "is_literal" flag
+ # might be used to allow the given identifier to have a fixed quoting
+ # pattern already, so maintain the flag for the proxy unless a
+ # :class:`.Label` object is creating the proxy. See [ticket:4730].
+ is_literal = (
+ not disallow_is_literal
+ and self.is_literal
+ and (
+ # note this does not accommodate for quoted_name differences
+ # right now
+ name is None
+ or name == self.name
+ )
+ )
+ c = self._constructor(
+ (
+ coercions.expect(roles.TruncatedLabelRole, name or self.name)
+ if name_is_truncatable
+ else (name or self.name)
+ ),
+ type_=self.type,
+ _selectable=selectable,
+ is_literal=is_literal,
+ )
+ c._propagate_attrs = selectable._propagate_attrs
+ if name is None:
+ c.key = self.key
+ if compound_select_cols:
+ c._proxies = list(compound_select_cols)
+ else:
+ c._proxies = [self]
+
+ if selectable._is_clone_of is not None:
+ c._is_clone_of = selectable._is_clone_of.columns.get(c.key)
+ return c.key, c
+
+
+class TableValuedColumn(NamedColumn[_T]):
+ __visit_name__ = "table_valued_column"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("name", InternalTraversal.dp_anon_name),
+ ("type", InternalTraversal.dp_type),
+ ("scalar_alias", InternalTraversal.dp_clauseelement),
+ ]
+
+ def __init__(self, scalar_alias: NamedFromClause, type_: TypeEngine[_T]):
+ self.scalar_alias = scalar_alias
+ self.key = self.name = scalar_alias.name
+ self.type = type_
+
+ def _copy_internals(
+ self, clone: _CloneCallableType = _clone, **kw: Any
+ ) -> None:
+ self.scalar_alias = clone(self.scalar_alias, **kw)
+ self.key = self.name = self.scalar_alias.name
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return [self.scalar_alias]
+
+
+class CollationClause(ColumnElement[str]):
+ __visit_name__ = "collation"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("collation", InternalTraversal.dp_string)
+ ]
+
+ @classmethod
+ def _create_collation_expression(
+ cls, expression: _ColumnExpressionArgument[str], collation: str
+ ) -> BinaryExpression[str]:
+ expr = coercions.expect(roles.ExpressionElementRole[str], expression)
+ return BinaryExpression(
+ expr,
+ CollationClause(collation),
+ operators.collate,
+ type_=expr.type,
+ )
+
+ def __init__(self, collation):
+ self.collation = collation
+
+
+class _IdentifiedClause(Executable, ClauseElement):
+ __visit_name__ = "identified"
+
+ def __init__(self, ident):
+ self.ident = ident
+
+
+class SavepointClause(_IdentifiedClause):
+ __visit_name__ = "savepoint"
+ inherit_cache = False
+
+
+class RollbackToSavepointClause(_IdentifiedClause):
+ __visit_name__ = "rollback_to_savepoint"
+ inherit_cache = False
+
+
+class ReleaseSavepointClause(_IdentifiedClause):
+ __visit_name__ = "release_savepoint"
+ inherit_cache = False
+
+
+class quoted_name(util.MemoizedSlots, str):
+ """Represent a SQL identifier combined with quoting preferences.
+
+ :class:`.quoted_name` is a Python unicode/str subclass which
+ represents a particular identifier name along with a
+ ``quote`` flag. This ``quote`` flag, when set to
+ ``True`` or ``False``, overrides automatic quoting behavior
+ for this identifier in order to either unconditionally quote
+ or to not quote the name. If left at its default of ``None``,
+ quoting behavior is applied to the identifier on a per-backend basis
+ based on an examination of the token itself.
+
+ A :class:`.quoted_name` object with ``quote=True`` is also
+ prevented from being modified in the case of a so-called
+ "name normalize" option. Certain database backends, such as
+ Oracle, Firebird, and DB2 "normalize" case-insensitive names
+ as uppercase. The SQLAlchemy dialects for these backends
+ convert from SQLAlchemy's lower-case-means-insensitive convention
+ to the upper-case-means-insensitive conventions of those backends.
+ The ``quote=True`` flag here will prevent this conversion from occurring
+ to support an identifier that's quoted as all lower case against
+ such a backend.
+
+ The :class:`.quoted_name` object is normally created automatically
+ when specifying the name for key schema constructs such as
+ :class:`_schema.Table`, :class:`_schema.Column`, and others.
+ The class can also be
+ passed explicitly as the name to any function that receives a name which
+ can be quoted. Such as to use the :meth:`_engine.Engine.has_table`
+ method with
+ an unconditionally quoted name::
+
+ from sqlalchemy import create_engine
+ from sqlalchemy import inspect
+ from sqlalchemy.sql import quoted_name
+
+ engine = create_engine("oracle+cx_oracle://some_dsn")
+ print(inspect(engine).has_table(quoted_name("some_table", True)))
+
+ The above logic will run the "has table" logic against the Oracle backend,
+ passing the name exactly as ``"some_table"`` without converting to
+ upper case.
+
+ .. versionchanged:: 1.2 The :class:`.quoted_name` construct is now
+ importable from ``sqlalchemy.sql``, in addition to the previous
+ location of ``sqlalchemy.sql.elements``.
+
+ """
+
+ __slots__ = "quote", "lower", "upper"
+
+ quote: Optional[bool]
+
+ @overload
+ @classmethod
+ def construct(cls, value: str, quote: Optional[bool]) -> quoted_name: ...
+
+ @overload
+ @classmethod
+ def construct(cls, value: None, quote: Optional[bool]) -> None: ...
+
+ @classmethod
+ def construct(
+ cls, value: Optional[str], quote: Optional[bool]
+ ) -> Optional[quoted_name]:
+ if value is None:
+ return None
+ else:
+ return quoted_name(value, quote)
+
+ def __new__(cls, value: str, quote: Optional[bool]) -> quoted_name:
+ assert (
+ value is not None
+ ), "use quoted_name.construct() for None passthrough"
+ if isinstance(value, cls) and (quote is None or value.quote == quote):
+ return value
+ self = super().__new__(cls, value)
+
+ self.quote = quote
+ return self
+
+ def __reduce__(self):
+ return quoted_name, (str(self), self.quote)
+
+ def _memoized_method_lower(self):
+ if self.quote:
+ return self
+ else:
+ return str(self).lower()
+
+ def _memoized_method_upper(self):
+ if self.quote:
+ return self
+ else:
+ return str(self).upper()
+
+
+def _find_columns(clause: ClauseElement) -> Set[ColumnClause[Any]]:
+ """locate Column objects within the given expression."""
+
+ cols: Set[ColumnClause[Any]] = set()
+ traverse(clause, {}, {"column": cols.add})
+ return cols
+
+
+def _type_from_args(args: Sequence[ColumnElement[_T]]) -> TypeEngine[_T]:
+ for a in args:
+ if not a.type._isnull:
+ return a.type
+ else:
+ return type_api.NULLTYPE # type: ignore
+
+
+def _corresponding_column_or_error(fromclause, column, require_embedded=False):
+ c = fromclause.corresponding_column(
+ column, require_embedded=require_embedded
+ )
+ if c is None:
+ raise exc.InvalidRequestError(
+ "Given column '%s', attached to table '%s', "
+ "failed to locate a corresponding column from table '%s'"
+ % (column, getattr(column, "table", None), fromclause.description)
+ )
+ return c
+
+
+class _memoized_property_but_not_nulltype(
+ util.memoized_property["TypeEngine[_T]"]
+):
+ """memoized property, but dont memoize NullType"""
+
+ def __get__(self, obj, cls):
+ if obj is None:
+ return self
+ result = self.fget(obj)
+ if not result._isnull:
+ obj.__dict__[self.__name__] = result
+ return result
+
+
+class AnnotatedColumnElement(Annotated):
+ _Annotated__element: ColumnElement[Any]
+
+ def __init__(self, element, values):
+ Annotated.__init__(self, element, values)
+ for attr in (
+ "comparator",
+ "_proxy_key",
+ "_tq_key_label",
+ "_tq_label",
+ "_non_anon_label",
+ "type",
+ ):
+ self.__dict__.pop(attr, None)
+ for attr in ("name", "key", "table"):
+ if self.__dict__.get(attr, False) is None:
+ self.__dict__.pop(attr)
+
+ def _with_annotations(self, values):
+ clone = super()._with_annotations(values)
+ clone.__dict__.pop("comparator", None)
+ return clone
+
+ @util.memoized_property
+ def name(self):
+ """pull 'name' from parent, if not present"""
+ return self._Annotated__element.name
+
+ @_memoized_property_but_not_nulltype
+ def type(self):
+ """pull 'type' from parent and don't cache if null.
+
+ type is routinely changed on existing columns within the
+ mapped_column() initialization process, and "type" is also consulted
+ during the creation of SQL expressions. Therefore it can change after
+ it was already retrieved. At the same time we don't want annotated
+ objects having overhead when expressions are produced, so continue
+ to memoize, but only when we have a non-null type.
+
+ """
+ return self._Annotated__element.type
+
+ @util.memoized_property
+ def table(self):
+ """pull 'table' from parent, if not present"""
+ return self._Annotated__element.table
+
+ @util.memoized_property
+ def key(self):
+ """pull 'key' from parent, if not present"""
+ return self._Annotated__element.key
+
+ @util.memoized_property
+ def info(self) -> _InfoType:
+ if TYPE_CHECKING:
+ assert isinstance(self._Annotated__element, Column)
+ return self._Annotated__element.info
+
+ @util.memoized_property
+ def _anon_name_label(self) -> str:
+ return self._Annotated__element._anon_name_label
+
+
+class _truncated_label(quoted_name):
+ """A unicode subclass used to identify symbolic "
+ "names that may require truncation."""
+
+ __slots__ = ()
+
+ def __new__(cls, value: str, quote: Optional[bool] = None) -> Any:
+ quote = getattr(value, "quote", quote)
+ # return super(_truncated_label, cls).__new__(cls, value, quote, True)
+ return super().__new__(cls, value, quote)
+
+ def __reduce__(self) -> Any:
+ return self.__class__, (str(self), self.quote)
+
+ def apply_map(self, map_: Mapping[str, Any]) -> str:
+ return self
+
+
+class conv(_truncated_label):
+ """Mark a string indicating that a name has already been converted
+ by a naming convention.
+
+ This is a string subclass that indicates a name that should not be
+ subject to any further naming conventions.
+
+ E.g. when we create a :class:`.Constraint` using a naming convention
+ as follows::
+
+ m = MetaData(naming_convention={
+ "ck": "ck_%(table_name)s_%(constraint_name)s"
+ })
+ t = Table('t', m, Column('x', Integer),
+ CheckConstraint('x > 5', name='x5'))
+
+ The name of the above constraint will be rendered as ``"ck_t_x5"``.
+ That is, the existing name ``x5`` is used in the naming convention as the
+ ``constraint_name`` token.
+
+ In some situations, such as in migration scripts, we may be rendering
+ the above :class:`.CheckConstraint` with a name that's already been
+ converted. In order to make sure the name isn't double-modified, the
+ new name is applied using the :func:`_schema.conv` marker. We can
+ use this explicitly as follows::
+
+
+ m = MetaData(naming_convention={
+ "ck": "ck_%(table_name)s_%(constraint_name)s"
+ })
+ t = Table('t', m, Column('x', Integer),
+ CheckConstraint('x > 5', name=conv('ck_t_x5')))
+
+ Where above, the :func:`_schema.conv` marker indicates that the constraint
+ name here is final, and the name will render as ``"ck_t_x5"`` and not
+ ``"ck_t_ck_t_x5"``
+
+ .. seealso::
+
+ :ref:`constraint_naming_conventions`
+
+ """
+
+ __slots__ = ()
+
+
+# for backwards compatibility in case
+# someone is re-implementing the
+# _truncated_identifier() sequence in a custom
+# compiler
+_generated_label = _truncated_label
+
+
+class _anonymous_label(_truncated_label):
+ """A unicode subclass used to identify anonymously
+ generated names."""
+
+ __slots__ = ()
+
+ @classmethod
+ def safe_construct(
+ cls,
+ seed: int,
+ body: str,
+ enclosing_label: Optional[str] = None,
+ sanitize_key: bool = False,
+ ) -> _anonymous_label:
+ # need to escape chars that interfere with format
+ # strings in any case, issue #8724
+ body = re.sub(r"[%\(\) \$]+", "_", body)
+
+ if sanitize_key:
+ # sanitize_key is then an extra step used by BindParameter
+ body = body.strip("_")
+
+ label = "%%(%d %s)s" % (seed, body.replace("%", "%%"))
+ if enclosing_label:
+ label = "%s%s" % (enclosing_label, label)
+
+ return _anonymous_label(label)
+
+ def __add__(self, other):
+ if "%" in other and not isinstance(other, _anonymous_label):
+ other = str(other).replace("%", "%%")
+ else:
+ other = str(other)
+
+ return _anonymous_label(
+ quoted_name(
+ str.__add__(self, other),
+ self.quote,
+ )
+ )
+
+ def __radd__(self, other):
+ if "%" in other and not isinstance(other, _anonymous_label):
+ other = str(other).replace("%", "%%")
+ else:
+ other = str(other)
+
+ return _anonymous_label(
+ quoted_name(
+ str.__add__(other, self),
+ self.quote,
+ )
+ )
+
+ def apply_map(self, map_):
+ if self.quote is not None:
+ # preserve quoting only if necessary
+ return quoted_name(self % map_, self.quote)
+ else:
+ # else skip the constructor call
+ return self % map_
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/events.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/events.py
new file mode 100644
index 0000000..1a6a9a6
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/events.py
@@ -0,0 +1,455 @@
+# sql/events.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+from __future__ import annotations
+
+from typing import Any
+from typing import TYPE_CHECKING
+
+from .base import SchemaEventTarget
+from .. import event
+
+if TYPE_CHECKING:
+ from .schema import Column
+ from .schema import Constraint
+ from .schema import SchemaItem
+ from .schema import Table
+ from ..engine.base import Connection
+ from ..engine.interfaces import ReflectedColumn
+ from ..engine.reflection import Inspector
+
+
+class DDLEvents(event.Events[SchemaEventTarget]):
+ """
+ Define event listeners for schema objects,
+ that is, :class:`.SchemaItem` and other :class:`.SchemaEventTarget`
+ subclasses, including :class:`_schema.MetaData`, :class:`_schema.Table`,
+ :class:`_schema.Column`, etc.
+
+ **Create / Drop Events**
+
+ Events emitted when CREATE and DROP commands are emitted to the database.
+ The event hooks in this category include :meth:`.DDLEvents.before_create`,
+ :meth:`.DDLEvents.after_create`, :meth:`.DDLEvents.before_drop`, and
+ :meth:`.DDLEvents.after_drop`.
+
+ These events are emitted when using schema-level methods such as
+ :meth:`.MetaData.create_all` and :meth:`.MetaData.drop_all`. Per-object
+ create/drop methods such as :meth:`.Table.create`, :meth:`.Table.drop`,
+ :meth:`.Index.create` are also included, as well as dialect-specific
+ methods such as :meth:`_postgresql.ENUM.create`.
+
+ .. versionadded:: 2.0 :class:`.DDLEvents` event hooks now take place
+ for non-table objects including constraints, indexes, and
+ dialect-specific schema types.
+
+ Event hooks may be attached directly to a :class:`_schema.Table` object or
+ to a :class:`_schema.MetaData` collection, as well as to any
+ :class:`.SchemaItem` class or object that can be individually created and
+ dropped using a distinct SQL command. Such classes include :class:`.Index`,
+ :class:`.Sequence`, and dialect-specific classes such as
+ :class:`_postgresql.ENUM`.
+
+ Example using the :meth:`.DDLEvents.after_create` event, where a custom
+ event hook will emit an ``ALTER TABLE`` command on the current connection,
+ after ``CREATE TABLE`` is emitted::
+
+ from sqlalchemy import create_engine
+ from sqlalchemy import event
+ from sqlalchemy import Table, Column, Metadata, Integer
+
+ m = MetaData()
+ some_table = Table('some_table', m, Column('data', Integer))
+
+ @event.listens_for(some_table, "after_create")
+ def after_create(target, connection, **kw):
+ connection.execute(text(
+ "ALTER TABLE %s SET name=foo_%s" % (target.name, target.name)
+ ))
+
+
+ some_engine = create_engine("postgresql://scott:tiger@host/test")
+
+ # will emit "CREATE TABLE some_table" as well as the above
+ # "ALTER TABLE" statement afterwards
+ m.create_all(some_engine)
+
+ Constraint objects such as :class:`.ForeignKeyConstraint`,
+ :class:`.UniqueConstraint`, :class:`.CheckConstraint` may also be
+ subscribed to these events, however they will **not** normally produce
+ events as these objects are usually rendered inline within an
+ enclosing ``CREATE TABLE`` statement and implicitly dropped from a
+ ``DROP TABLE`` statement.
+
+ For the :class:`.Index` construct, the event hook will be emitted
+ for ``CREATE INDEX``, however SQLAlchemy does not normally emit
+ ``DROP INDEX`` when dropping tables as this is again implicit within the
+ ``DROP TABLE`` statement.
+
+ .. versionadded:: 2.0 Support for :class:`.SchemaItem` objects
+ for create/drop events was expanded from its previous support for
+ :class:`.MetaData` and :class:`.Table` to also include
+ :class:`.Constraint` and all subclasses, :class:`.Index`,
+ :class:`.Sequence` and some type-related constructs such as
+ :class:`_postgresql.ENUM`.
+
+ .. note:: These event hooks are only emitted within the scope of
+ SQLAlchemy's create/drop methods; they are not necessarily supported
+ by tools such as `alembic <https://alembic.sqlalchemy.org>`_.
+
+
+ **Attachment Events**
+
+ Attachment events are provided to customize
+ behavior whenever a child schema element is associated
+ with a parent, such as when a :class:`_schema.Column` is associated
+ with its :class:`_schema.Table`, when a
+ :class:`_schema.ForeignKeyConstraint`
+ is associated with a :class:`_schema.Table`, etc. These events include
+ :meth:`.DDLEvents.before_parent_attach` and
+ :meth:`.DDLEvents.after_parent_attach`.
+
+ **Reflection Events**
+
+ The :meth:`.DDLEvents.column_reflect` event is used to intercept
+ and modify the in-Python definition of database columns when
+ :term:`reflection` of database tables proceeds.
+
+ **Use with Generic DDL**
+
+ DDL events integrate closely with the
+ :class:`.DDL` class and the :class:`.ExecutableDDLElement` hierarchy
+ of DDL clause constructs, which are themselves appropriate
+ as listener callables::
+
+ from sqlalchemy import DDL
+ event.listen(
+ some_table,
+ "after_create",
+ DDL("ALTER TABLE %(table)s SET name=foo_%(table)s")
+ )
+
+ **Event Propagation to MetaData Copies**
+
+ For all :class:`.DDLEvent` events, the ``propagate=True`` keyword argument
+ will ensure that a given event handler is propagated to copies of the
+ object, which are made when using the :meth:`_schema.Table.to_metadata`
+ method::
+
+ from sqlalchemy import DDL
+
+ metadata = MetaData()
+ some_table = Table("some_table", metadata, Column("data", Integer))
+
+ event.listen(
+ some_table,
+ "after_create",
+ DDL("ALTER TABLE %(table)s SET name=foo_%(table)s"),
+ propagate=True
+ )
+
+ new_metadata = MetaData()
+ new_table = some_table.to_metadata(new_metadata)
+
+ The above :class:`.DDL` object will be associated with the
+ :meth:`.DDLEvents.after_create` event for both the ``some_table`` and
+ the ``new_table`` :class:`.Table` objects.
+
+ .. seealso::
+
+ :ref:`event_toplevel`
+
+ :class:`.ExecutableDDLElement`
+
+ :class:`.DDL`
+
+ :ref:`schema_ddl_sequences`
+
+ """
+
+ _target_class_doc = "SomeSchemaClassOrObject"
+ _dispatch_target = SchemaEventTarget
+
+ def before_create(
+ self, target: SchemaEventTarget, connection: Connection, **kw: Any
+ ) -> None:
+ r"""Called before CREATE statements are emitted.
+
+ :param target: the :class:`.SchemaObject`, such as a
+ :class:`_schema.MetaData` or :class:`_schema.Table`
+ but also including all create/drop objects such as
+ :class:`.Index`, :class:`.Sequence`, etc.,
+ object which is the target of the event.
+
+ .. versionadded:: 2.0 Support for all :class:`.SchemaItem` objects
+ was added.
+
+ :param connection: the :class:`_engine.Connection` where the
+ CREATE statement or statements will be emitted.
+ :param \**kw: additional keyword arguments relevant
+ to the event. The contents of this dictionary
+ may vary across releases, and include the
+ list of tables being generated for a metadata-level
+ event, the checkfirst flag, and other
+ elements used by internal events.
+
+ :func:`.event.listen` accepts the ``propagate=True``
+ modifier for this event; when True, the listener function will
+ be established for any copies made of the target object,
+ i.e. those copies that are generated when
+ :meth:`_schema.Table.to_metadata` is used.
+
+ :func:`.event.listen` accepts the ``insert=True``
+ modifier for this event; when True, the listener function will
+ be prepended to the internal list of events upon discovery, and execute
+ before registered listener functions that do not pass this argument.
+
+ """
+
+ def after_create(
+ self, target: SchemaEventTarget, connection: Connection, **kw: Any
+ ) -> None:
+ r"""Called after CREATE statements are emitted.
+
+ :param target: the :class:`.SchemaObject`, such as a
+ :class:`_schema.MetaData` or :class:`_schema.Table`
+ but also including all create/drop objects such as
+ :class:`.Index`, :class:`.Sequence`, etc.,
+ object which is the target of the event.
+
+ .. versionadded:: 2.0 Support for all :class:`.SchemaItem` objects
+ was added.
+
+ :param connection: the :class:`_engine.Connection` where the
+ CREATE statement or statements have been emitted.
+ :param \**kw: additional keyword arguments relevant
+ to the event. The contents of this dictionary
+ may vary across releases, and include the
+ list of tables being generated for a metadata-level
+ event, the checkfirst flag, and other
+ elements used by internal events.
+
+ :func:`.event.listen` also accepts the ``propagate=True``
+ modifier for this event; when True, the listener function will
+ be established for any copies made of the target object,
+ i.e. those copies that are generated when
+ :meth:`_schema.Table.to_metadata` is used.
+
+ """
+
+ def before_drop(
+ self, target: SchemaEventTarget, connection: Connection, **kw: Any
+ ) -> None:
+ r"""Called before DROP statements are emitted.
+
+ :param target: the :class:`.SchemaObject`, such as a
+ :class:`_schema.MetaData` or :class:`_schema.Table`
+ but also including all create/drop objects such as
+ :class:`.Index`, :class:`.Sequence`, etc.,
+ object which is the target of the event.
+
+ .. versionadded:: 2.0 Support for all :class:`.SchemaItem` objects
+ was added.
+
+ :param connection: the :class:`_engine.Connection` where the
+ DROP statement or statements will be emitted.
+ :param \**kw: additional keyword arguments relevant
+ to the event. The contents of this dictionary
+ may vary across releases, and include the
+ list of tables being generated for a metadata-level
+ event, the checkfirst flag, and other
+ elements used by internal events.
+
+ :func:`.event.listen` also accepts the ``propagate=True``
+ modifier for this event; when True, the listener function will
+ be established for any copies made of the target object,
+ i.e. those copies that are generated when
+ :meth:`_schema.Table.to_metadata` is used.
+
+ """
+
+ def after_drop(
+ self, target: SchemaEventTarget, connection: Connection, **kw: Any
+ ) -> None:
+ r"""Called after DROP statements are emitted.
+
+ :param target: the :class:`.SchemaObject`, such as a
+ :class:`_schema.MetaData` or :class:`_schema.Table`
+ but also including all create/drop objects such as
+ :class:`.Index`, :class:`.Sequence`, etc.,
+ object which is the target of the event.
+
+ .. versionadded:: 2.0 Support for all :class:`.SchemaItem` objects
+ was added.
+
+ :param connection: the :class:`_engine.Connection` where the
+ DROP statement or statements have been emitted.
+ :param \**kw: additional keyword arguments relevant
+ to the event. The contents of this dictionary
+ may vary across releases, and include the
+ list of tables being generated for a metadata-level
+ event, the checkfirst flag, and other
+ elements used by internal events.
+
+ :func:`.event.listen` also accepts the ``propagate=True``
+ modifier for this event; when True, the listener function will
+ be established for any copies made of the target object,
+ i.e. those copies that are generated when
+ :meth:`_schema.Table.to_metadata` is used.
+
+ """
+
+ def before_parent_attach(
+ self, target: SchemaEventTarget, parent: SchemaItem
+ ) -> None:
+ """Called before a :class:`.SchemaItem` is associated with
+ a parent :class:`.SchemaItem`.
+
+ :param target: the target object
+ :param parent: the parent to which the target is being attached.
+
+ :func:`.event.listen` also accepts the ``propagate=True``
+ modifier for this event; when True, the listener function will
+ be established for any copies made of the target object,
+ i.e. those copies that are generated when
+ :meth:`_schema.Table.to_metadata` is used.
+
+ """
+
+ def after_parent_attach(
+ self, target: SchemaEventTarget, parent: SchemaItem
+ ) -> None:
+ """Called after a :class:`.SchemaItem` is associated with
+ a parent :class:`.SchemaItem`.
+
+ :param target: the target object
+ :param parent: the parent to which the target is being attached.
+
+ :func:`.event.listen` also accepts the ``propagate=True``
+ modifier for this event; when True, the listener function will
+ be established for any copies made of the target object,
+ i.e. those copies that are generated when
+ :meth:`_schema.Table.to_metadata` is used.
+
+ """
+
+ def _sa_event_column_added_to_pk_constraint(
+ self, const: Constraint, col: Column[Any]
+ ) -> None:
+ """internal event hook used for primary key naming convention
+ updates.
+
+ """
+
+ def column_reflect(
+ self, inspector: Inspector, table: Table, column_info: ReflectedColumn
+ ) -> None:
+ """Called for each unit of 'column info' retrieved when
+ a :class:`_schema.Table` is being reflected.
+
+ This event is most easily used by applying it to a specific
+ :class:`_schema.MetaData` instance, where it will take effect for
+ all :class:`_schema.Table` objects within that
+ :class:`_schema.MetaData` that undergo reflection::
+
+ metadata = MetaData()
+
+ @event.listens_for(metadata, 'column_reflect')
+ def receive_column_reflect(inspector, table, column_info):
+ # receives for all Table objects that are reflected
+ # under this MetaData
+
+
+ # will use the above event hook
+ my_table = Table("my_table", metadata, autoload_with=some_engine)
+
+
+ .. versionadded:: 1.4.0b2 The :meth:`_events.DDLEvents.column_reflect`
+ hook may now be applied to a :class:`_schema.MetaData` object as
+ well as the :class:`_schema.MetaData` class itself where it will
+ take place for all :class:`_schema.Table` objects associated with
+ the targeted :class:`_schema.MetaData`.
+
+ It may also be applied to the :class:`_schema.Table` class across
+ the board::
+
+ from sqlalchemy import Table
+
+ @event.listens_for(Table, 'column_reflect')
+ def receive_column_reflect(inspector, table, column_info):
+ # receives for all Table objects that are reflected
+
+ It can also be applied to a specific :class:`_schema.Table` at the
+ point that one is being reflected using the
+ :paramref:`_schema.Table.listeners` parameter::
+
+ t1 = Table(
+ "my_table",
+ autoload_with=some_engine,
+ listeners=[
+ ('column_reflect', receive_column_reflect)
+ ]
+ )
+
+ The dictionary of column information as returned by the
+ dialect is passed, and can be modified. The dictionary
+ is that returned in each element of the list returned
+ by :meth:`.reflection.Inspector.get_columns`:
+
+ * ``name`` - the column's name, is applied to the
+ :paramref:`_schema.Column.name` parameter
+
+ * ``type`` - the type of this column, which should be an instance
+ of :class:`~sqlalchemy.types.TypeEngine`, is applied to the
+ :paramref:`_schema.Column.type` parameter
+
+ * ``nullable`` - boolean flag if the column is NULL or NOT NULL,
+ is applied to the :paramref:`_schema.Column.nullable` parameter
+
+ * ``default`` - the column's server default value. This is
+ normally specified as a plain string SQL expression, however the
+ event can pass a :class:`.FetchedValue`, :class:`.DefaultClause`,
+ or :func:`_expression.text` object as well. Is applied to the
+ :paramref:`_schema.Column.server_default` parameter
+
+ The event is called before any action is taken against
+ this dictionary, and the contents can be modified; the following
+ additional keys may be added to the dictionary to further modify
+ how the :class:`_schema.Column` is constructed:
+
+
+ * ``key`` - the string key that will be used to access this
+ :class:`_schema.Column` in the ``.c`` collection; will be applied
+ to the :paramref:`_schema.Column.key` parameter. Is also used
+ for ORM mapping. See the section
+ :ref:`mapper_automated_reflection_schemes` for an example.
+
+ * ``quote`` - force or un-force quoting on the column name;
+ is applied to the :paramref:`_schema.Column.quote` parameter.
+
+ * ``info`` - a dictionary of arbitrary data to follow along with
+ the :class:`_schema.Column`, is applied to the
+ :paramref:`_schema.Column.info` parameter.
+
+ :func:`.event.listen` also accepts the ``propagate=True``
+ modifier for this event; when True, the listener function will
+ be established for any copies made of the target object,
+ i.e. those copies that are generated when
+ :meth:`_schema.Table.to_metadata` is used.
+
+ .. seealso::
+
+ :ref:`mapper_automated_reflection_schemes` -
+ in the ORM mapping documentation
+
+ :ref:`automap_intercepting_columns` -
+ in the :ref:`automap_toplevel` documentation
+
+ :ref:`metadata_reflection_dbagnostic_types` - in
+ the :ref:`metadata_reflection_toplevel` documentation
+
+ """
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/expression.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/expression.py
new file mode 100644
index 0000000..ba42445
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/expression.py
@@ -0,0 +1,162 @@
+# sql/expression.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+"""Defines the public namespace for SQL expression constructs.
+
+
+"""
+
+
+from __future__ import annotations
+
+from ._dml_constructors import delete as delete
+from ._dml_constructors import insert as insert
+from ._dml_constructors import update as update
+from ._elements_constructors import all_ as all_
+from ._elements_constructors import and_ as and_
+from ._elements_constructors import any_ as any_
+from ._elements_constructors import asc as asc
+from ._elements_constructors import between as between
+from ._elements_constructors import bindparam as bindparam
+from ._elements_constructors import bitwise_not as bitwise_not
+from ._elements_constructors import case as case
+from ._elements_constructors import cast as cast
+from ._elements_constructors import collate as collate
+from ._elements_constructors import column as column
+from ._elements_constructors import desc as desc
+from ._elements_constructors import distinct as distinct
+from ._elements_constructors import extract as extract
+from ._elements_constructors import false as false
+from ._elements_constructors import funcfilter as funcfilter
+from ._elements_constructors import label as label
+from ._elements_constructors import not_ as not_
+from ._elements_constructors import null as null
+from ._elements_constructors import nulls_first as nulls_first
+from ._elements_constructors import nulls_last as nulls_last
+from ._elements_constructors import or_ as or_
+from ._elements_constructors import outparam as outparam
+from ._elements_constructors import over as over
+from ._elements_constructors import text as text
+from ._elements_constructors import true as true
+from ._elements_constructors import try_cast as try_cast
+from ._elements_constructors import tuple_ as tuple_
+from ._elements_constructors import type_coerce as type_coerce
+from ._elements_constructors import within_group as within_group
+from ._selectable_constructors import alias as alias
+from ._selectable_constructors import cte as cte
+from ._selectable_constructors import except_ as except_
+from ._selectable_constructors import except_all as except_all
+from ._selectable_constructors import exists as exists
+from ._selectable_constructors import intersect as intersect
+from ._selectable_constructors import intersect_all as intersect_all
+from ._selectable_constructors import join as join
+from ._selectable_constructors import lateral as lateral
+from ._selectable_constructors import outerjoin as outerjoin
+from ._selectable_constructors import select as select
+from ._selectable_constructors import table as table
+from ._selectable_constructors import tablesample as tablesample
+from ._selectable_constructors import union as union
+from ._selectable_constructors import union_all as union_all
+from ._selectable_constructors import values as values
+from ._typing import ColumnExpressionArgument as ColumnExpressionArgument
+from .base import _from_objects as _from_objects
+from .base import _select_iterables as _select_iterables
+from .base import ColumnCollection as ColumnCollection
+from .base import Executable as Executable
+from .cache_key import CacheKey as CacheKey
+from .dml import Delete as Delete
+from .dml import Insert as Insert
+from .dml import Update as Update
+from .dml import UpdateBase as UpdateBase
+from .dml import ValuesBase as ValuesBase
+from .elements import _truncated_label as _truncated_label
+from .elements import BinaryExpression as BinaryExpression
+from .elements import BindParameter as BindParameter
+from .elements import BooleanClauseList as BooleanClauseList
+from .elements import Case as Case
+from .elements import Cast as Cast
+from .elements import ClauseElement as ClauseElement
+from .elements import ClauseList as ClauseList
+from .elements import CollectionAggregate as CollectionAggregate
+from .elements import ColumnClause as ColumnClause
+from .elements import ColumnElement as ColumnElement
+from .elements import ExpressionClauseList as ExpressionClauseList
+from .elements import Extract as Extract
+from .elements import False_ as False_
+from .elements import FunctionFilter as FunctionFilter
+from .elements import Grouping as Grouping
+from .elements import Label as Label
+from .elements import literal as literal
+from .elements import literal_column as literal_column
+from .elements import Null as Null
+from .elements import Over as Over
+from .elements import quoted_name as quoted_name
+from .elements import ReleaseSavepointClause as ReleaseSavepointClause
+from .elements import RollbackToSavepointClause as RollbackToSavepointClause
+from .elements import SavepointClause as SavepointClause
+from .elements import SQLColumnExpression as SQLColumnExpression
+from .elements import TextClause as TextClause
+from .elements import True_ as True_
+from .elements import TryCast as TryCast
+from .elements import Tuple as Tuple
+from .elements import TypeClause as TypeClause
+from .elements import TypeCoerce as TypeCoerce
+from .elements import UnaryExpression as UnaryExpression
+from .elements import WithinGroup as WithinGroup
+from .functions import func as func
+from .functions import Function as Function
+from .functions import FunctionElement as FunctionElement
+from .functions import modifier as modifier
+from .lambdas import lambda_stmt as lambda_stmt
+from .lambdas import LambdaElement as LambdaElement
+from .lambdas import StatementLambdaElement as StatementLambdaElement
+from .operators import ColumnOperators as ColumnOperators
+from .operators import custom_op as custom_op
+from .operators import Operators as Operators
+from .selectable import Alias as Alias
+from .selectable import AliasedReturnsRows as AliasedReturnsRows
+from .selectable import CompoundSelect as CompoundSelect
+from .selectable import CTE as CTE
+from .selectable import Exists as Exists
+from .selectable import FromClause as FromClause
+from .selectable import FromGrouping as FromGrouping
+from .selectable import GenerativeSelect as GenerativeSelect
+from .selectable import HasCTE as HasCTE
+from .selectable import HasPrefixes as HasPrefixes
+from .selectable import HasSuffixes as HasSuffixes
+from .selectable import Join as Join
+from .selectable import LABEL_STYLE_DEFAULT as LABEL_STYLE_DEFAULT
+from .selectable import (
+ LABEL_STYLE_DISAMBIGUATE_ONLY as LABEL_STYLE_DISAMBIGUATE_ONLY,
+)
+from .selectable import LABEL_STYLE_NONE as LABEL_STYLE_NONE
+from .selectable import (
+ LABEL_STYLE_TABLENAME_PLUS_COL as LABEL_STYLE_TABLENAME_PLUS_COL,
+)
+from .selectable import Lateral as Lateral
+from .selectable import ReturnsRows as ReturnsRows
+from .selectable import ScalarSelect as ScalarSelect
+from .selectable import ScalarValues as ScalarValues
+from .selectable import Select as Select
+from .selectable import Selectable as Selectable
+from .selectable import SelectBase as SelectBase
+from .selectable import SelectLabelStyle as SelectLabelStyle
+from .selectable import Subquery as Subquery
+from .selectable import TableClause as TableClause
+from .selectable import TableSample as TableSample
+from .selectable import TableValuedAlias as TableValuedAlias
+from .selectable import TextAsFrom as TextAsFrom
+from .selectable import TextualSelect as TextualSelect
+from .selectable import Values as Values
+from .visitors import Visitable as Visitable
+
+nullsfirst = nulls_first
+"""Synonym for the :func:`.nulls_first` function."""
+
+
+nullslast = nulls_last
+"""Synonym for the :func:`.nulls_last` function."""
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/functions.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/functions.py
new file mode 100644
index 0000000..afb2b1d
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/functions.py
@@ -0,0 +1,2052 @@
+# sql/functions.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+
+"""SQL function API, factories, and built-in functions.
+
+"""
+
+from __future__ import annotations
+
+import datetime
+import decimal
+from typing import Any
+from typing import cast
+from typing import Dict
+from typing import List
+from typing import Mapping
+from typing import Optional
+from typing import overload
+from typing import Sequence
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from . import annotation
+from . import coercions
+from . import operators
+from . import roles
+from . import schema
+from . import sqltypes
+from . import type_api
+from . import util as sqlutil
+from ._typing import is_table_value_type
+from .base import _entity_namespace
+from .base import ColumnCollection
+from .base import Executable
+from .base import Generative
+from .base import HasMemoized
+from .elements import _type_from_args
+from .elements import BinaryExpression
+from .elements import BindParameter
+from .elements import Cast
+from .elements import ClauseList
+from .elements import ColumnElement
+from .elements import Extract
+from .elements import FunctionFilter
+from .elements import Grouping
+from .elements import literal_column
+from .elements import NamedColumn
+from .elements import Over
+from .elements import WithinGroup
+from .selectable import FromClause
+from .selectable import Select
+from .selectable import TableValuedAlias
+from .sqltypes import TableValueType
+from .type_api import TypeEngine
+from .visitors import InternalTraversal
+from .. import util
+
+
+if TYPE_CHECKING:
+ from ._typing import _ByArgument
+ from ._typing import _ColumnExpressionArgument
+ from ._typing import _ColumnExpressionOrLiteralArgument
+ from ._typing import _ColumnExpressionOrStrLabelArgument
+ from ._typing import _TypeEngineArgument
+ from .base import _EntityNamespace
+ from .elements import ClauseElement
+ from .elements import KeyedColumnElement
+ from .elements import TableValuedColumn
+ from .operators import OperatorType
+ from ..engine.base import Connection
+ from ..engine.cursor import CursorResult
+ from ..engine.interfaces import _CoreMultiExecuteParams
+ from ..engine.interfaces import CoreExecuteOptionsParameter
+ from ..util.typing import Self
+
+_T = TypeVar("_T", bound=Any)
+_S = TypeVar("_S", bound=Any)
+
+_registry: util.defaultdict[str, Dict[str, Type[Function[Any]]]] = (
+ util.defaultdict(dict)
+)
+
+
+def register_function(
+ identifier: str, fn: Type[Function[Any]], package: str = "_default"
+) -> None:
+ """Associate a callable with a particular func. name.
+
+ This is normally called by GenericFunction, but is also
+ available by itself so that a non-Function construct
+ can be associated with the :data:`.func` accessor (i.e.
+ CAST, EXTRACT).
+
+ """
+ reg = _registry[package]
+
+ identifier = str(identifier).lower()
+
+ # Check if a function with the same identifier is registered.
+ if identifier in reg:
+ util.warn(
+ "The GenericFunction '{}' is already registered and "
+ "is going to be overridden.".format(identifier)
+ )
+ reg[identifier] = fn
+
+
+class FunctionElement(Executable, ColumnElement[_T], FromClause, Generative):
+ """Base for SQL function-oriented constructs.
+
+ This is a `generic type <https://peps.python.org/pep-0484/#generics>`_,
+ meaning that type checkers and IDEs can be instructed on the types to
+ expect in a :class:`_engine.Result` for this function. See
+ :class:`.GenericFunction` for an example of how this is done.
+
+ .. seealso::
+
+ :ref:`tutorial_functions` - in the :ref:`unified_tutorial`
+
+ :class:`.Function` - named SQL function.
+
+ :data:`.func` - namespace which produces registered or ad-hoc
+ :class:`.Function` instances.
+
+ :class:`.GenericFunction` - allows creation of registered function
+ types.
+
+ """
+
+ _traverse_internals = [
+ ("clause_expr", InternalTraversal.dp_clauseelement),
+ ("_with_ordinality", InternalTraversal.dp_boolean),
+ ("_table_value_type", InternalTraversal.dp_has_cache_key),
+ ]
+
+ packagenames: Tuple[str, ...] = ()
+
+ _has_args = False
+ _with_ordinality = False
+ _table_value_type: Optional[TableValueType] = None
+
+ # some attributes that are defined between both ColumnElement and
+ # FromClause are set to Any here to avoid typing errors
+ primary_key: Any
+ _is_clone_of: Any
+
+ clause_expr: Grouping[Any]
+
+ def __init__(self, *clauses: _ColumnExpressionOrLiteralArgument[Any]):
+ r"""Construct a :class:`.FunctionElement`.
+
+ :param \*clauses: list of column expressions that form the arguments
+ of the SQL function call.
+
+ :param \**kwargs: additional kwargs are typically consumed by
+ subclasses.
+
+ .. seealso::
+
+ :data:`.func`
+
+ :class:`.Function`
+
+ """
+ args: Sequence[_ColumnExpressionArgument[Any]] = [
+ coercions.expect(
+ roles.ExpressionElementRole,
+ c,
+ name=getattr(self, "name", None),
+ apply_propagate_attrs=self,
+ )
+ for c in clauses
+ ]
+ self._has_args = self._has_args or bool(args)
+ self.clause_expr = Grouping(
+ ClauseList(operator=operators.comma_op, group_contents=True, *args)
+ )
+
+ _non_anon_label = None
+
+ @property
+ def _proxy_key(self) -> Any:
+ return super()._proxy_key or getattr(self, "name", None)
+
+ def _execute_on_connection(
+ self,
+ connection: Connection,
+ distilled_params: _CoreMultiExecuteParams,
+ execution_options: CoreExecuteOptionsParameter,
+ ) -> CursorResult[Any]:
+ return connection._execute_function(
+ self, distilled_params, execution_options
+ )
+
+ def scalar_table_valued(
+ self, name: str, type_: Optional[_TypeEngineArgument[_T]] = None
+ ) -> ScalarFunctionColumn[_T]:
+ """Return a column expression that's against this
+ :class:`_functions.FunctionElement` as a scalar
+ table-valued expression.
+
+ The returned expression is similar to that returned by a single column
+ accessed off of a :meth:`_functions.FunctionElement.table_valued`
+ construct, except no FROM clause is generated; the function is rendered
+ in the similar way as a scalar subquery.
+
+ E.g.:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import func, select
+ >>> fn = func.jsonb_each("{'k', 'v'}").scalar_table_valued("key")
+ >>> print(select(fn))
+ {printsql}SELECT (jsonb_each(:jsonb_each_1)).key
+
+ .. versionadded:: 1.4.0b2
+
+ .. seealso::
+
+ :meth:`_functions.FunctionElement.table_valued`
+
+ :meth:`_functions.FunctionElement.alias`
+
+ :meth:`_functions.FunctionElement.column_valued`
+
+ """ # noqa: E501
+
+ return ScalarFunctionColumn(self, name, type_)
+
+ def table_valued(
+ self, *expr: _ColumnExpressionOrStrLabelArgument[Any], **kw: Any
+ ) -> TableValuedAlias:
+ r"""Return a :class:`_sql.TableValuedAlias` representation of this
+ :class:`_functions.FunctionElement` with table-valued expressions added.
+
+ e.g.:
+
+ .. sourcecode:: pycon+sql
+
+ >>> fn = (
+ ... func.generate_series(1, 5).
+ ... table_valued("value", "start", "stop", "step")
+ ... )
+
+ >>> print(select(fn))
+ {printsql}SELECT anon_1.value, anon_1.start, anon_1.stop, anon_1.step
+ FROM generate_series(:generate_series_1, :generate_series_2) AS anon_1{stop}
+
+ >>> print(select(fn.c.value, fn.c.stop).where(fn.c.value > 2))
+ {printsql}SELECT anon_1.value, anon_1.stop
+ FROM generate_series(:generate_series_1, :generate_series_2) AS anon_1
+ WHERE anon_1.value > :value_1{stop}
+
+ A WITH ORDINALITY expression may be generated by passing the keyword
+ argument "with_ordinality":
+
+ .. sourcecode:: pycon+sql
+
+ >>> fn = func.generate_series(4, 1, -1).table_valued("gen", with_ordinality="ordinality")
+ >>> print(select(fn))
+ {printsql}SELECT anon_1.gen, anon_1.ordinality
+ FROM generate_series(:generate_series_1, :generate_series_2, :generate_series_3) WITH ORDINALITY AS anon_1
+
+ :param \*expr: A series of string column names that will be added to the
+ ``.c`` collection of the resulting :class:`_sql.TableValuedAlias`
+ construct as columns. :func:`_sql.column` objects with or without
+ datatypes may also be used.
+
+ :param name: optional name to assign to the alias name that's generated.
+ If omitted, a unique anonymizing name is used.
+
+ :param with_ordinality: string name that when present results in the
+ ``WITH ORDINALITY`` clause being added to the alias, and the given
+ string name will be added as a column to the .c collection
+ of the resulting :class:`_sql.TableValuedAlias`.
+
+ :param joins_implicitly: when True, the table valued function may be
+ used in the FROM clause without any explicit JOIN to other tables
+ in the SQL query, and no "cartesian product" warning will be generated.
+ May be useful for SQL functions such as ``func.json_each()``.
+
+ .. versionadded:: 1.4.33
+
+ .. versionadded:: 1.4.0b2
+
+
+ .. seealso::
+
+ :ref:`tutorial_functions_table_valued` - in the :ref:`unified_tutorial`
+
+ :ref:`postgresql_table_valued` - in the :ref:`postgresql_toplevel` documentation
+
+ :meth:`_functions.FunctionElement.scalar_table_valued` - variant of
+ :meth:`_functions.FunctionElement.table_valued` which delivers the
+ complete table valued expression as a scalar column expression
+
+ :meth:`_functions.FunctionElement.column_valued`
+
+ :meth:`_sql.TableValuedAlias.render_derived` - renders the alias
+ using a derived column clause, e.g. ``AS name(col1, col2, ...)``
+
+ """ # noqa: 501
+
+ new_func = self._generate()
+
+ with_ordinality = kw.pop("with_ordinality", None)
+ joins_implicitly = kw.pop("joins_implicitly", None)
+ name = kw.pop("name", None)
+
+ if with_ordinality:
+ expr += (with_ordinality,)
+ new_func._with_ordinality = True
+
+ new_func.type = new_func._table_value_type = TableValueType(*expr)
+
+ return new_func.alias(name=name, joins_implicitly=joins_implicitly)
+
+ def column_valued(
+ self, name: Optional[str] = None, joins_implicitly: bool = False
+ ) -> TableValuedColumn[_T]:
+ """Return this :class:`_functions.FunctionElement` as a column expression that
+ selects from itself as a FROM clause.
+
+ E.g.:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import select, func
+ >>> gs = func.generate_series(1, 5, -1).column_valued()
+ >>> print(select(gs))
+ {printsql}SELECT anon_1
+ FROM generate_series(:generate_series_1, :generate_series_2, :generate_series_3) AS anon_1
+
+ This is shorthand for::
+
+ gs = func.generate_series(1, 5, -1).alias().column
+
+ :param name: optional name to assign to the alias name that's generated.
+ If omitted, a unique anonymizing name is used.
+
+ :param joins_implicitly: when True, the "table" portion of the column
+ valued function may be a member of the FROM clause without any
+ explicit JOIN to other tables in the SQL query, and no "cartesian
+ product" warning will be generated. May be useful for SQL functions
+ such as ``func.json_array_elements()``.
+
+ .. versionadded:: 1.4.46
+
+ .. seealso::
+
+ :ref:`tutorial_functions_column_valued` - in the :ref:`unified_tutorial`
+
+ :ref:`postgresql_column_valued` - in the :ref:`postgresql_toplevel` documentation
+
+ :meth:`_functions.FunctionElement.table_valued`
+
+ """ # noqa: 501
+
+ return self.alias(name=name, joins_implicitly=joins_implicitly).column
+
+ @util.ro_non_memoized_property
+ def columns(self) -> ColumnCollection[str, KeyedColumnElement[Any]]: # type: ignore[override] # noqa: E501
+ r"""The set of columns exported by this :class:`.FunctionElement`.
+
+ This is a placeholder collection that allows the function to be
+ placed in the FROM clause of a statement:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import column, select, func
+ >>> stmt = select(column('x'), column('y')).select_from(func.myfunction())
+ >>> print(stmt)
+ {printsql}SELECT x, y FROM myfunction()
+
+ The above form is a legacy feature that is now superseded by the
+ fully capable :meth:`_functions.FunctionElement.table_valued`
+ method; see that method for details.
+
+ .. seealso::
+
+ :meth:`_functions.FunctionElement.table_valued` - generates table-valued
+ SQL function expressions.
+
+ """ # noqa: E501
+ return self.c
+
+ @util.ro_memoized_property
+ def c(self) -> ColumnCollection[str, KeyedColumnElement[Any]]: # type: ignore[override] # noqa: E501
+ """synonym for :attr:`.FunctionElement.columns`."""
+
+ return ColumnCollection(
+ columns=[(col.key, col) for col in self._all_selected_columns]
+ )
+
+ @property
+ def _all_selected_columns(self) -> Sequence[KeyedColumnElement[Any]]:
+ if is_table_value_type(self.type):
+ # TODO: this might not be fully accurate
+ cols = cast(
+ "Sequence[KeyedColumnElement[Any]]", self.type._elements
+ )
+ else:
+ cols = [self.label(None)]
+
+ return cols
+
+ @property
+ def exported_columns( # type: ignore[override]
+ self,
+ ) -> ColumnCollection[str, KeyedColumnElement[Any]]:
+ return self.columns
+
+ @HasMemoized.memoized_attribute
+ def clauses(self) -> ClauseList:
+ """Return the underlying :class:`.ClauseList` which contains
+ the arguments for this :class:`.FunctionElement`.
+
+ """
+ return cast(ClauseList, self.clause_expr.element)
+
+ def over(
+ self,
+ *,
+ partition_by: Optional[_ByArgument] = None,
+ order_by: Optional[_ByArgument] = None,
+ rows: Optional[Tuple[Optional[int], Optional[int]]] = None,
+ range_: Optional[Tuple[Optional[int], Optional[int]]] = None,
+ ) -> Over[_T]:
+ """Produce an OVER clause against this function.
+
+ Used against aggregate or so-called "window" functions,
+ for database backends that support window functions.
+
+ The expression::
+
+ func.row_number().over(order_by='x')
+
+ is shorthand for::
+
+ from sqlalchemy import over
+ over(func.row_number(), order_by='x')
+
+ See :func:`_expression.over` for a full description.
+
+ .. seealso::
+
+ :func:`_expression.over`
+
+ :ref:`tutorial_window_functions` - in the :ref:`unified_tutorial`
+
+ """
+ return Over(
+ self,
+ partition_by=partition_by,
+ order_by=order_by,
+ rows=rows,
+ range_=range_,
+ )
+
+ def within_group(
+ self, *order_by: _ColumnExpressionArgument[Any]
+ ) -> WithinGroup[_T]:
+ """Produce a WITHIN GROUP (ORDER BY expr) clause against this function.
+
+ Used against so-called "ordered set aggregate" and "hypothetical
+ set aggregate" functions, including :class:`.percentile_cont`,
+ :class:`.rank`, :class:`.dense_rank`, etc.
+
+ See :func:`_expression.within_group` for a full description.
+
+ .. seealso::
+
+ :ref:`tutorial_functions_within_group` -
+ in the :ref:`unified_tutorial`
+
+
+ """
+ return WithinGroup(self, *order_by)
+
+ @overload
+ def filter(self) -> Self: ...
+
+ @overload
+ def filter(
+ self,
+ __criterion0: _ColumnExpressionArgument[bool],
+ *criterion: _ColumnExpressionArgument[bool],
+ ) -> FunctionFilter[_T]: ...
+
+ def filter(
+ self, *criterion: _ColumnExpressionArgument[bool]
+ ) -> Union[Self, FunctionFilter[_T]]:
+ """Produce a FILTER clause against this function.
+
+ Used against aggregate and window functions,
+ for database backends that support the "FILTER" clause.
+
+ The expression::
+
+ func.count(1).filter(True)
+
+ is shorthand for::
+
+ from sqlalchemy import funcfilter
+ funcfilter(func.count(1), True)
+
+ .. seealso::
+
+ :ref:`tutorial_functions_within_group` -
+ in the :ref:`unified_tutorial`
+
+ :class:`.FunctionFilter`
+
+ :func:`.funcfilter`
+
+
+ """
+ if not criterion:
+ return self
+ return FunctionFilter(self, *criterion)
+
+ def as_comparison(
+ self, left_index: int, right_index: int
+ ) -> FunctionAsBinary:
+ """Interpret this expression as a boolean comparison between two
+ values.
+
+ This method is used for an ORM use case described at
+ :ref:`relationship_custom_operator_sql_function`.
+
+ A hypothetical SQL function "is_equal()" which compares to values
+ for equality would be written in the Core expression language as::
+
+ expr = func.is_equal("a", "b")
+
+ If "is_equal()" above is comparing "a" and "b" for equality, the
+ :meth:`.FunctionElement.as_comparison` method would be invoked as::
+
+ expr = func.is_equal("a", "b").as_comparison(1, 2)
+
+ Where above, the integer value "1" refers to the first argument of the
+ "is_equal()" function and the integer value "2" refers to the second.
+
+ This would create a :class:`.BinaryExpression` that is equivalent to::
+
+ BinaryExpression("a", "b", operator=op.eq)
+
+ However, at the SQL level it would still render as
+ "is_equal('a', 'b')".
+
+ The ORM, when it loads a related object or collection, needs to be able
+ to manipulate the "left" and "right" sides of the ON clause of a JOIN
+ expression. The purpose of this method is to provide a SQL function
+ construct that can also supply this information to the ORM, when used
+ with the :paramref:`_orm.relationship.primaryjoin` parameter. The
+ return value is a containment object called :class:`.FunctionAsBinary`.
+
+ An ORM example is as follows::
+
+ class Venue(Base):
+ __tablename__ = 'venue'
+ id = Column(Integer, primary_key=True)
+ name = Column(String)
+
+ descendants = relationship(
+ "Venue",
+ primaryjoin=func.instr(
+ remote(foreign(name)), name + "/"
+ ).as_comparison(1, 2) == 1,
+ viewonly=True,
+ order_by=name
+ )
+
+ Above, the "Venue" class can load descendant "Venue" objects by
+ determining if the name of the parent Venue is contained within the
+ start of the hypothetical descendant value's name, e.g. "parent1" would
+ match up to "parent1/child1", but not to "parent2/child1".
+
+ Possible use cases include the "materialized path" example given above,
+ as well as making use of special SQL functions such as geometric
+ functions to create join conditions.
+
+ :param left_index: the integer 1-based index of the function argument
+ that serves as the "left" side of the expression.
+ :param right_index: the integer 1-based index of the function argument
+ that serves as the "right" side of the expression.
+
+ .. versionadded:: 1.3
+
+ .. seealso::
+
+ :ref:`relationship_custom_operator_sql_function` -
+ example use within the ORM
+
+ """
+ return FunctionAsBinary(self, left_index, right_index)
+
+ @property
+ def _from_objects(self) -> Any:
+ return self.clauses._from_objects
+
+ def within_group_type(
+ self, within_group: WithinGroup[_S]
+ ) -> Optional[TypeEngine[_S]]:
+ """For types that define their return type as based on the criteria
+ within a WITHIN GROUP (ORDER BY) expression, called by the
+ :class:`.WithinGroup` construct.
+
+ Returns None by default, in which case the function's normal ``.type``
+ is used.
+
+ """
+
+ return None
+
+ def alias(
+ self, name: Optional[str] = None, joins_implicitly: bool = False
+ ) -> TableValuedAlias:
+ r"""Produce a :class:`_expression.Alias` construct against this
+ :class:`.FunctionElement`.
+
+ .. tip::
+
+ The :meth:`_functions.FunctionElement.alias` method is part of the
+ mechanism by which "table valued" SQL functions are created.
+ However, most use cases are covered by higher level methods on
+ :class:`_functions.FunctionElement` including
+ :meth:`_functions.FunctionElement.table_valued`, and
+ :meth:`_functions.FunctionElement.column_valued`.
+
+ This construct wraps the function in a named alias which
+ is suitable for the FROM clause, in the style accepted for example
+ by PostgreSQL. A column expression is also provided using the
+ special ``.column`` attribute, which may
+ be used to refer to the output of the function as a scalar value
+ in the columns or where clause, for a backend such as PostgreSQL.
+
+ For a full table-valued expression, use the
+ :meth:`_functions.FunctionElement.table_valued` method first to
+ establish named columns.
+
+ e.g.:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import func, select, column
+ >>> data_view = func.unnest([1, 2, 3]).alias("data_view")
+ >>> print(select(data_view.column))
+ {printsql}SELECT data_view
+ FROM unnest(:unnest_1) AS data_view
+
+ The :meth:`_functions.FunctionElement.column_valued` method provides
+ a shortcut for the above pattern:
+
+ .. sourcecode:: pycon+sql
+
+ >>> data_view = func.unnest([1, 2, 3]).column_valued("data_view")
+ >>> print(select(data_view))
+ {printsql}SELECT data_view
+ FROM unnest(:unnest_1) AS data_view
+
+ .. versionadded:: 1.4.0b2 Added the ``.column`` accessor
+
+ :param name: alias name, will be rendered as ``AS <name>`` in the
+ FROM clause
+
+ :param joins_implicitly: when True, the table valued function may be
+ used in the FROM clause without any explicit JOIN to other tables
+ in the SQL query, and no "cartesian product" warning will be
+ generated. May be useful for SQL functions such as
+ ``func.json_each()``.
+
+ .. versionadded:: 1.4.33
+
+ .. seealso::
+
+ :ref:`tutorial_functions_table_valued` -
+ in the :ref:`unified_tutorial`
+
+ :meth:`_functions.FunctionElement.table_valued`
+
+ :meth:`_functions.FunctionElement.scalar_table_valued`
+
+ :meth:`_functions.FunctionElement.column_valued`
+
+
+ """
+
+ return TableValuedAlias._construct(
+ self,
+ name=name,
+ table_value_type=self.type,
+ joins_implicitly=joins_implicitly,
+ )
+
+ def select(self) -> Select[Tuple[_T]]:
+ """Produce a :func:`_expression.select` construct
+ against this :class:`.FunctionElement`.
+
+ This is shorthand for::
+
+ s = select(function_element)
+
+ """
+ s: Select[Any] = Select(self)
+ if self._execution_options:
+ s = s.execution_options(**self._execution_options)
+ return s
+
+ def _bind_param(
+ self,
+ operator: OperatorType,
+ obj: Any,
+ type_: Optional[TypeEngine[_T]] = None,
+ expanding: bool = False,
+ **kw: Any,
+ ) -> BindParameter[_T]:
+ return BindParameter(
+ None,
+ obj,
+ _compared_to_operator=operator,
+ _compared_to_type=self.type,
+ unique=True,
+ type_=type_,
+ expanding=expanding,
+ **kw,
+ )
+
+ def self_group(self, against: Optional[OperatorType] = None) -> ClauseElement: # type: ignore[override] # noqa E501
+ # for the moment, we are parenthesizing all array-returning
+ # expressions against getitem. This may need to be made
+ # more portable if in the future we support other DBs
+ # besides postgresql.
+ if against is operators.getitem and isinstance(
+ self.type, sqltypes.ARRAY
+ ):
+ return Grouping(self)
+ else:
+ return super().self_group(against=against)
+
+ @property
+ def entity_namespace(self) -> _EntityNamespace:
+ """overrides FromClause.entity_namespace as functions are generally
+ column expressions and not FromClauses.
+
+ """
+ # ideally functions would not be fromclauses but we failed to make
+ # this adjustment in 1.4
+ return _entity_namespace(self.clause_expr)
+
+
+class FunctionAsBinary(BinaryExpression[Any]):
+ _traverse_internals = [
+ ("sql_function", InternalTraversal.dp_clauseelement),
+ ("left_index", InternalTraversal.dp_plain_obj),
+ ("right_index", InternalTraversal.dp_plain_obj),
+ ("modifiers", InternalTraversal.dp_plain_dict),
+ ]
+
+ sql_function: FunctionElement[Any]
+ left_index: int
+ right_index: int
+
+ def _gen_cache_key(self, anon_map: Any, bindparams: Any) -> Any:
+ return ColumnElement._gen_cache_key(self, anon_map, bindparams)
+
+ def __init__(
+ self, fn: FunctionElement[Any], left_index: int, right_index: int
+ ):
+ self.sql_function = fn
+ self.left_index = left_index
+ self.right_index = right_index
+
+ self.operator = operators.function_as_comparison_op
+ self.type = sqltypes.BOOLEANTYPE
+ self.negate = None
+ self._is_implicitly_boolean = True
+ self.modifiers = {}
+
+ @property
+ def left_expr(self) -> ColumnElement[Any]:
+ return self.sql_function.clauses.clauses[self.left_index - 1]
+
+ @left_expr.setter
+ def left_expr(self, value: ColumnElement[Any]) -> None:
+ self.sql_function.clauses.clauses[self.left_index - 1] = value
+
+ @property
+ def right_expr(self) -> ColumnElement[Any]:
+ return self.sql_function.clauses.clauses[self.right_index - 1]
+
+ @right_expr.setter
+ def right_expr(self, value: ColumnElement[Any]) -> None:
+ self.sql_function.clauses.clauses[self.right_index - 1] = value
+
+ if not TYPE_CHECKING:
+ # mypy can't accommodate @property to replace an instance
+ # variable
+
+ left = left_expr
+ right = right_expr
+
+
+class ScalarFunctionColumn(NamedColumn[_T]):
+ __visit_name__ = "scalar_function_column"
+
+ _traverse_internals = [
+ ("name", InternalTraversal.dp_anon_name),
+ ("type", InternalTraversal.dp_type),
+ ("fn", InternalTraversal.dp_clauseelement),
+ ]
+
+ is_literal = False
+ table = None
+
+ def __init__(
+ self,
+ fn: FunctionElement[_T],
+ name: str,
+ type_: Optional[_TypeEngineArgument[_T]] = None,
+ ):
+ self.fn = fn
+ self.name = name
+
+ # if type is None, we get NULLTYPE, which is our _T. But I don't
+ # know how to get the overloads to express that correctly
+ self.type = type_api.to_instance(type_) # type: ignore
+
+
+class _FunctionGenerator:
+ """Generate SQL function expressions.
+
+ :data:`.func` is a special object instance which generates SQL
+ functions based on name-based attributes, e.g.:
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(func.count(1))
+ {printsql}count(:param_1)
+
+ The returned object is an instance of :class:`.Function`, and is a
+ column-oriented SQL element like any other, and is used in that way:
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(select(func.count(table.c.id)))
+ {printsql}SELECT count(sometable.id) FROM sometable
+
+ Any name can be given to :data:`.func`. If the function name is unknown to
+ SQLAlchemy, it will be rendered exactly as is. For common SQL functions
+ which SQLAlchemy is aware of, the name may be interpreted as a *generic
+ function* which will be compiled appropriately to the target database:
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(func.current_timestamp())
+ {printsql}CURRENT_TIMESTAMP
+
+ To call functions which are present in dot-separated packages,
+ specify them in the same manner:
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(func.stats.yield_curve(5, 10))
+ {printsql}stats.yield_curve(:yield_curve_1, :yield_curve_2)
+
+ SQLAlchemy can be made aware of the return type of functions to enable
+ type-specific lexical and result-based behavior. For example, to ensure
+ that a string-based function returns a Unicode value and is similarly
+ treated as a string in expressions, specify
+ :class:`~sqlalchemy.types.Unicode` as the type:
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(func.my_string(u'hi', type_=Unicode) + ' ' +
+ ... func.my_string(u'there', type_=Unicode))
+ {printsql}my_string(:my_string_1) || :my_string_2 || my_string(:my_string_3)
+
+ The object returned by a :data:`.func` call is usually an instance of
+ :class:`.Function`.
+ This object meets the "column" interface, including comparison and labeling
+ functions. The object can also be passed the :meth:`~.Connectable.execute`
+ method of a :class:`_engine.Connection` or :class:`_engine.Engine`,
+ where it will be
+ wrapped inside of a SELECT statement first::
+
+ print(connection.execute(func.current_timestamp()).scalar())
+
+ In a few exception cases, the :data:`.func` accessor
+ will redirect a name to a built-in expression such as :func:`.cast`
+ or :func:`.extract`, as these names have well-known meaning
+ but are not exactly the same as "functions" from a SQLAlchemy
+ perspective.
+
+ Functions which are interpreted as "generic" functions know how to
+ calculate their return type automatically. For a listing of known generic
+ functions, see :ref:`generic_functions`.
+
+ .. note::
+
+ The :data:`.func` construct has only limited support for calling
+ standalone "stored procedures", especially those with special
+ parameterization concerns.
+
+ See the section :ref:`stored_procedures` for details on how to use
+ the DBAPI-level ``callproc()`` method for fully traditional stored
+ procedures.
+
+ .. seealso::
+
+ :ref:`tutorial_functions` - in the :ref:`unified_tutorial`
+
+ :class:`.Function`
+
+ """ # noqa
+
+ def __init__(self, **opts: Any):
+ self.__names: List[str] = []
+ self.opts = opts
+
+ def __getattr__(self, name: str) -> _FunctionGenerator:
+ # passthru __ attributes; fixes pydoc
+ if name.startswith("__"):
+ try:
+ return self.__dict__[name] # type: ignore
+ except KeyError:
+ raise AttributeError(name)
+
+ elif name.endswith("_"):
+ name = name[0:-1]
+ f = _FunctionGenerator(**self.opts)
+ f.__names = list(self.__names) + [name]
+ return f
+
+ @overload
+ def __call__(
+ self, *c: Any, type_: _TypeEngineArgument[_T], **kwargs: Any
+ ) -> Function[_T]: ...
+
+ @overload
+ def __call__(self, *c: Any, **kwargs: Any) -> Function[Any]: ...
+
+ def __call__(self, *c: Any, **kwargs: Any) -> Function[Any]:
+ o = self.opts.copy()
+ o.update(kwargs)
+
+ tokens = len(self.__names)
+
+ if tokens == 2:
+ package, fname = self.__names
+ elif tokens == 1:
+ package, fname = "_default", self.__names[0]
+ else:
+ package = None
+
+ if package is not None:
+ func = _registry[package].get(fname.lower())
+ if func is not None:
+ return func(*c, **o)
+
+ return Function(
+ self.__names[-1], packagenames=tuple(self.__names[0:-1]), *c, **o
+ )
+
+ if TYPE_CHECKING:
+ # START GENERATED FUNCTION ACCESSORS
+
+ # code within this block is **programmatically,
+ # statically generated** by tools/generate_sql_functions.py
+
+ @property
+ def aggregate_strings(self) -> Type[aggregate_strings]: ...
+
+ @property
+ def ansifunction(self) -> Type[AnsiFunction[Any]]: ...
+
+ @property
+ def array_agg(self) -> Type[array_agg[Any]]: ...
+
+ @property
+ def cast(self) -> Type[Cast[Any]]: ...
+
+ @property
+ def char_length(self) -> Type[char_length]: ...
+
+ # set ColumnElement[_T] as a separate overload, to appease mypy
+ # which seems to not want to accept _T from _ColumnExpressionArgument.
+ # this is even if all non-generic types are removed from it, so
+ # reasons remain unclear for why this does not work
+
+ @overload
+ def coalesce(
+ self,
+ col: ColumnElement[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> coalesce[_T]: ...
+
+ @overload
+ def coalesce(
+ self,
+ col: _ColumnExpressionArgument[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> coalesce[_T]: ...
+
+ @overload
+ def coalesce(
+ self,
+ col: _ColumnExpressionOrLiteralArgument[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> coalesce[_T]: ...
+
+ def coalesce(
+ self,
+ col: _ColumnExpressionOrLiteralArgument[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> coalesce[_T]: ...
+
+ @property
+ def concat(self) -> Type[concat]: ...
+
+ @property
+ def count(self) -> Type[count]: ...
+
+ @property
+ def cube(self) -> Type[cube[Any]]: ...
+
+ @property
+ def cume_dist(self) -> Type[cume_dist]: ...
+
+ @property
+ def current_date(self) -> Type[current_date]: ...
+
+ @property
+ def current_time(self) -> Type[current_time]: ...
+
+ @property
+ def current_timestamp(self) -> Type[current_timestamp]: ...
+
+ @property
+ def current_user(self) -> Type[current_user]: ...
+
+ @property
+ def dense_rank(self) -> Type[dense_rank]: ...
+
+ @property
+ def extract(self) -> Type[Extract]: ...
+
+ @property
+ def grouping_sets(self) -> Type[grouping_sets[Any]]: ...
+
+ @property
+ def localtime(self) -> Type[localtime]: ...
+
+ @property
+ def localtimestamp(self) -> Type[localtimestamp]: ...
+
+ # set ColumnElement[_T] as a separate overload, to appease mypy
+ # which seems to not want to accept _T from _ColumnExpressionArgument.
+ # this is even if all non-generic types are removed from it, so
+ # reasons remain unclear for why this does not work
+
+ @overload
+ def max( # noqa: A001
+ self,
+ col: ColumnElement[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> max[_T]: ...
+
+ @overload
+ def max( # noqa: A001
+ self,
+ col: _ColumnExpressionArgument[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> max[_T]: ...
+
+ @overload
+ def max( # noqa: A001
+ self,
+ col: _ColumnExpressionOrLiteralArgument[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> max[_T]: ...
+
+ def max( # noqa: A001
+ self,
+ col: _ColumnExpressionOrLiteralArgument[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> max[_T]: ...
+
+ # set ColumnElement[_T] as a separate overload, to appease mypy
+ # which seems to not want to accept _T from _ColumnExpressionArgument.
+ # this is even if all non-generic types are removed from it, so
+ # reasons remain unclear for why this does not work
+
+ @overload
+ def min( # noqa: A001
+ self,
+ col: ColumnElement[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> min[_T]: ...
+
+ @overload
+ def min( # noqa: A001
+ self,
+ col: _ColumnExpressionArgument[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> min[_T]: ...
+
+ @overload
+ def min( # noqa: A001
+ self,
+ col: _ColumnExpressionOrLiteralArgument[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> min[_T]: ...
+
+ def min( # noqa: A001
+ self,
+ col: _ColumnExpressionOrLiteralArgument[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> min[_T]: ...
+
+ @property
+ def mode(self) -> Type[mode[Any]]: ...
+
+ @property
+ def next_value(self) -> Type[next_value]: ...
+
+ @property
+ def now(self) -> Type[now]: ...
+
+ @property
+ def orderedsetagg(self) -> Type[OrderedSetAgg[Any]]: ...
+
+ @property
+ def percent_rank(self) -> Type[percent_rank]: ...
+
+ @property
+ def percentile_cont(self) -> Type[percentile_cont[Any]]: ...
+
+ @property
+ def percentile_disc(self) -> Type[percentile_disc[Any]]: ...
+
+ @property
+ def random(self) -> Type[random]: ...
+
+ @property
+ def rank(self) -> Type[rank]: ...
+
+ @property
+ def rollup(self) -> Type[rollup[Any]]: ...
+
+ @property
+ def session_user(self) -> Type[session_user]: ...
+
+ # set ColumnElement[_T] as a separate overload, to appease mypy
+ # which seems to not want to accept _T from _ColumnExpressionArgument.
+ # this is even if all non-generic types are removed from it, so
+ # reasons remain unclear for why this does not work
+
+ @overload
+ def sum( # noqa: A001
+ self,
+ col: ColumnElement[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> sum[_T]: ...
+
+ @overload
+ def sum( # noqa: A001
+ self,
+ col: _ColumnExpressionArgument[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> sum[_T]: ...
+
+ @overload
+ def sum( # noqa: A001
+ self,
+ col: _ColumnExpressionOrLiteralArgument[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> sum[_T]: ...
+
+ def sum( # noqa: A001
+ self,
+ col: _ColumnExpressionOrLiteralArgument[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> sum[_T]: ...
+
+ @property
+ def sysdate(self) -> Type[sysdate]: ...
+
+ @property
+ def user(self) -> Type[user]: ...
+
+ # END GENERATED FUNCTION ACCESSORS
+
+
+func = _FunctionGenerator()
+func.__doc__ = _FunctionGenerator.__doc__
+
+modifier = _FunctionGenerator(group=False)
+
+
+class Function(FunctionElement[_T]):
+ r"""Describe a named SQL function.
+
+ The :class:`.Function` object is typically generated from the
+ :data:`.func` generation object.
+
+
+ :param \*clauses: list of column expressions that form the arguments
+ of the SQL function call.
+
+ :param type\_: optional :class:`.TypeEngine` datatype object that will be
+ used as the return value of the column expression generated by this
+ function call.
+
+ :param packagenames: a string which indicates package prefix names
+ to be prepended to the function name when the SQL is generated.
+ The :data:`.func` generator creates these when it is called using
+ dotted format, e.g.::
+
+ func.mypackage.some_function(col1, col2)
+
+ .. seealso::
+
+ :ref:`tutorial_functions` - in the :ref:`unified_tutorial`
+
+ :data:`.func` - namespace which produces registered or ad-hoc
+ :class:`.Function` instances.
+
+ :class:`.GenericFunction` - allows creation of registered function
+ types.
+
+ """
+
+ __visit_name__ = "function"
+
+ _traverse_internals = FunctionElement._traverse_internals + [
+ ("packagenames", InternalTraversal.dp_plain_obj),
+ ("name", InternalTraversal.dp_string),
+ ("type", InternalTraversal.dp_type),
+ ]
+
+ name: str
+
+ identifier: str
+
+ type: TypeEngine[_T]
+ """A :class:`_types.TypeEngine` object which refers to the SQL return
+ type represented by this SQL function.
+
+ This datatype may be configured when generating a
+ :class:`_functions.Function` object by passing the
+ :paramref:`_functions.Function.type_` parameter, e.g.::
+
+ >>> select(func.lower("some VALUE", type_=String))
+
+ The small number of built-in classes of :class:`_functions.Function` come
+ with a built-in datatype that's appropriate to the class of function and
+ its arguments. For functions that aren't known, the type defaults to the
+ "null type".
+
+ """
+
+ @overload
+ def __init__(
+ self,
+ name: str,
+ *clauses: _ColumnExpressionOrLiteralArgument[_T],
+ type_: None = ...,
+ packagenames: Optional[Tuple[str, ...]] = ...,
+ ): ...
+
+ @overload
+ def __init__(
+ self,
+ name: str,
+ *clauses: _ColumnExpressionOrLiteralArgument[Any],
+ type_: _TypeEngineArgument[_T] = ...,
+ packagenames: Optional[Tuple[str, ...]] = ...,
+ ): ...
+
+ def __init__(
+ self,
+ name: str,
+ *clauses: _ColumnExpressionOrLiteralArgument[Any],
+ type_: Optional[_TypeEngineArgument[_T]] = None,
+ packagenames: Optional[Tuple[str, ...]] = None,
+ ):
+ """Construct a :class:`.Function`.
+
+ The :data:`.func` construct is normally used to construct
+ new :class:`.Function` instances.
+
+ """
+ self.packagenames = packagenames or ()
+ self.name = name
+
+ # if type is None, we get NULLTYPE, which is our _T. But I don't
+ # know how to get the overloads to express that correctly
+ self.type = type_api.to_instance(type_) # type: ignore
+
+ FunctionElement.__init__(self, *clauses)
+
+ def _bind_param(
+ self,
+ operator: OperatorType,
+ obj: Any,
+ type_: Optional[TypeEngine[_T]] = None,
+ expanding: bool = False,
+ **kw: Any,
+ ) -> BindParameter[_T]:
+ return BindParameter(
+ self.name,
+ obj,
+ _compared_to_operator=operator,
+ _compared_to_type=self.type,
+ type_=type_,
+ unique=True,
+ expanding=expanding,
+ **kw,
+ )
+
+
+class GenericFunction(Function[_T]):
+ """Define a 'generic' function.
+
+ A generic function is a pre-established :class:`.Function`
+ class that is instantiated automatically when called
+ by name from the :data:`.func` attribute. Note that
+ calling any name from :data:`.func` has the effect that
+ a new :class:`.Function` instance is created automatically,
+ given that name. The primary use case for defining
+ a :class:`.GenericFunction` class is so that a function
+ of a particular name may be given a fixed return type.
+ It can also include custom argument parsing schemes as well
+ as additional methods.
+
+ Subclasses of :class:`.GenericFunction` are automatically
+ registered under the name of the class. For
+ example, a user-defined function ``as_utc()`` would
+ be available immediately::
+
+ from sqlalchemy.sql.functions import GenericFunction
+ from sqlalchemy.types import DateTime
+
+ class as_utc(GenericFunction):
+ type = DateTime()
+ inherit_cache = True
+
+ print(select(func.as_utc()))
+
+ User-defined generic functions can be organized into
+ packages by specifying the "package" attribute when defining
+ :class:`.GenericFunction`. Third party libraries
+ containing many functions may want to use this in order
+ to avoid name conflicts with other systems. For example,
+ if our ``as_utc()`` function were part of a package
+ "time"::
+
+ class as_utc(GenericFunction):
+ type = DateTime()
+ package = "time"
+ inherit_cache = True
+
+ The above function would be available from :data:`.func`
+ using the package name ``time``::
+
+ print(select(func.time.as_utc()))
+
+ A final option is to allow the function to be accessed
+ from one name in :data:`.func` but to render as a different name.
+ The ``identifier`` attribute will override the name used to
+ access the function as loaded from :data:`.func`, but will retain
+ the usage of ``name`` as the rendered name::
+
+ class GeoBuffer(GenericFunction):
+ type = Geometry()
+ package = "geo"
+ name = "ST_Buffer"
+ identifier = "buffer"
+ inherit_cache = True
+
+ The above function will render as follows:
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(func.geo.buffer())
+ {printsql}ST_Buffer()
+
+ The name will be rendered as is, however without quoting unless the name
+ contains special characters that require quoting. To force quoting
+ on or off for the name, use the :class:`.sqlalchemy.sql.quoted_name`
+ construct::
+
+ from sqlalchemy.sql import quoted_name
+
+ class GeoBuffer(GenericFunction):
+ type = Geometry()
+ package = "geo"
+ name = quoted_name("ST_Buffer", True)
+ identifier = "buffer"
+ inherit_cache = True
+
+ The above function will render as:
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(func.geo.buffer())
+ {printsql}"ST_Buffer"()
+
+ Type parameters for this class as a
+ `generic type <https://peps.python.org/pep-0484/#generics>`_ can be passed
+ and should match the type seen in a :class:`_engine.Result`. For example::
+
+ class as_utc(GenericFunction[datetime.datetime]):
+ type = DateTime()
+ inherit_cache = True
+
+ The above indicates that the following expression returns a ``datetime``
+ object::
+
+ connection.scalar(select(func.as_utc()))
+
+ .. versionadded:: 1.3.13 The :class:`.quoted_name` construct is now
+ recognized for quoting when used with the "name" attribute of the
+ object, so that quoting can be forced on or off for the function
+ name.
+
+
+ """
+
+ coerce_arguments = True
+ inherit_cache = True
+
+ _register: bool
+
+ name = "GenericFunction"
+
+ def __init_subclass__(cls) -> None:
+ if annotation.Annotated not in cls.__mro__:
+ cls._register_generic_function(cls.__name__, cls.__dict__)
+ super().__init_subclass__()
+
+ @classmethod
+ def _register_generic_function(
+ cls, clsname: str, clsdict: Mapping[str, Any]
+ ) -> None:
+ cls.name = name = clsdict.get("name", clsname)
+ cls.identifier = identifier = clsdict.get("identifier", name)
+ package = clsdict.get("package", "_default")
+ # legacy
+ if "__return_type__" in clsdict:
+ cls.type = clsdict["__return_type__"]
+
+ # Check _register attribute status
+ cls._register = getattr(cls, "_register", True)
+
+ # Register the function if required
+ if cls._register:
+ register_function(identifier, cls, package)
+ else:
+ # Set _register to True to register child classes by default
+ cls._register = True
+
+ def __init__(
+ self, *args: _ColumnExpressionOrLiteralArgument[Any], **kwargs: Any
+ ):
+ parsed_args = kwargs.pop("_parsed_args", None)
+ if parsed_args is None:
+ parsed_args = [
+ coercions.expect(
+ roles.ExpressionElementRole,
+ c,
+ name=self.name,
+ apply_propagate_attrs=self,
+ )
+ for c in args
+ ]
+ self._has_args = self._has_args or bool(parsed_args)
+ self.packagenames = ()
+
+ self.clause_expr = Grouping(
+ ClauseList(
+ operator=operators.comma_op, group_contents=True, *parsed_args
+ )
+ )
+
+ self.type = type_api.to_instance( # type: ignore
+ kwargs.pop("type_", None) or getattr(self, "type", None)
+ )
+
+
+register_function("cast", Cast) # type: ignore
+register_function("extract", Extract) # type: ignore
+
+
+class next_value(GenericFunction[int]):
+ """Represent the 'next value', given a :class:`.Sequence`
+ as its single argument.
+
+ Compiles into the appropriate function on each backend,
+ or will raise NotImplementedError if used on a backend
+ that does not provide support for sequences.
+
+ """
+
+ type = sqltypes.Integer()
+ name = "next_value"
+
+ _traverse_internals = [
+ ("sequence", InternalTraversal.dp_named_ddl_element)
+ ]
+
+ def __init__(self, seq: schema.Sequence, **kw: Any):
+ assert isinstance(
+ seq, schema.Sequence
+ ), "next_value() accepts a Sequence object as input."
+ self.sequence = seq
+ self.type = sqltypes.to_instance( # type: ignore
+ seq.data_type or getattr(self, "type", None)
+ )
+
+ def compare(self, other: Any, **kw: Any) -> bool:
+ return (
+ isinstance(other, next_value)
+ and self.sequence.name == other.sequence.name
+ )
+
+ @property
+ def _from_objects(self) -> Any:
+ return []
+
+
+class AnsiFunction(GenericFunction[_T]):
+ """Define a function in "ansi" format, which doesn't render parenthesis."""
+
+ inherit_cache = True
+
+ def __init__(self, *args: _ColumnExpressionArgument[Any], **kwargs: Any):
+ GenericFunction.__init__(self, *args, **kwargs)
+
+
+class ReturnTypeFromArgs(GenericFunction[_T]):
+ """Define a function whose return type is the same as its arguments."""
+
+ inherit_cache = True
+
+ # set ColumnElement[_T] as a separate overload, to appease mypy which seems
+ # to not want to accept _T from _ColumnExpressionArgument. this is even if
+ # all non-generic types are removed from it, so reasons remain unclear for
+ # why this does not work
+
+ @overload
+ def __init__(
+ self,
+ col: ColumnElement[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ): ...
+
+ @overload
+ def __init__(
+ self,
+ col: _ColumnExpressionArgument[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ): ...
+
+ @overload
+ def __init__(
+ self,
+ col: _ColumnExpressionOrLiteralArgument[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ): ...
+
+ def __init__(
+ self, *args: _ColumnExpressionOrLiteralArgument[Any], **kwargs: Any
+ ):
+ fn_args: Sequence[ColumnElement[Any]] = [
+ coercions.expect(
+ roles.ExpressionElementRole,
+ c,
+ name=self.name,
+ apply_propagate_attrs=self,
+ )
+ for c in args
+ ]
+ kwargs.setdefault("type_", _type_from_args(fn_args))
+ kwargs["_parsed_args"] = fn_args
+ super().__init__(*fn_args, **kwargs)
+
+
+class coalesce(ReturnTypeFromArgs[_T]):
+ _has_args = True
+ inherit_cache = True
+
+
+class max(ReturnTypeFromArgs[_T]): # noqa: A001
+ """The SQL MAX() aggregate function."""
+
+ inherit_cache = True
+
+
+class min(ReturnTypeFromArgs[_T]): # noqa: A001
+ """The SQL MIN() aggregate function."""
+
+ inherit_cache = True
+
+
+class sum(ReturnTypeFromArgs[_T]): # noqa: A001
+ """The SQL SUM() aggregate function."""
+
+ inherit_cache = True
+
+
+class now(GenericFunction[datetime.datetime]):
+ """The SQL now() datetime function.
+
+ SQLAlchemy dialects will usually render this particular function
+ in a backend-specific way, such as rendering it as ``CURRENT_TIMESTAMP``.
+
+ """
+
+ type = sqltypes.DateTime()
+ inherit_cache = True
+
+
+class concat(GenericFunction[str]):
+ """The SQL CONCAT() function, which concatenates strings.
+
+ E.g.:
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(select(func.concat('a', 'b')))
+ {printsql}SELECT concat(:concat_2, :concat_3) AS concat_1
+
+ String concatenation in SQLAlchemy is more commonly available using the
+ Python ``+`` operator with string datatypes, which will render a
+ backend-specific concatenation operator, such as :
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(select(literal("a") + "b"))
+ {printsql}SELECT :param_1 || :param_2 AS anon_1
+
+
+ """
+
+ type = sqltypes.String()
+ inherit_cache = True
+
+
+class char_length(GenericFunction[int]):
+ """The CHAR_LENGTH() SQL function."""
+
+ type = sqltypes.Integer()
+ inherit_cache = True
+
+ def __init__(self, arg: _ColumnExpressionArgument[str], **kw: Any):
+ # slight hack to limit to just one positional argument
+ # not sure why this one function has this special treatment
+ super().__init__(arg, **kw)
+
+
+class random(GenericFunction[float]):
+ """The RANDOM() SQL function."""
+
+ _has_args = True
+ inherit_cache = True
+
+
+class count(GenericFunction[int]):
+ r"""The ANSI COUNT aggregate function. With no arguments,
+ emits COUNT \*.
+
+ E.g.::
+
+ from sqlalchemy import func
+ from sqlalchemy import select
+ from sqlalchemy import table, column
+
+ my_table = table('some_table', column('id'))
+
+ stmt = select(func.count()).select_from(my_table)
+
+ Executing ``stmt`` would emit::
+
+ SELECT count(*) AS count_1
+ FROM some_table
+
+
+ """
+
+ type = sqltypes.Integer()
+ inherit_cache = True
+
+ def __init__(
+ self,
+ expression: Optional[_ColumnExpressionArgument[Any]] = None,
+ **kwargs: Any,
+ ):
+ if expression is None:
+ expression = literal_column("*")
+ super().__init__(expression, **kwargs)
+
+
+class current_date(AnsiFunction[datetime.date]):
+ """The CURRENT_DATE() SQL function."""
+
+ type = sqltypes.Date()
+ inherit_cache = True
+
+
+class current_time(AnsiFunction[datetime.time]):
+ """The CURRENT_TIME() SQL function."""
+
+ type = sqltypes.Time()
+ inherit_cache = True
+
+
+class current_timestamp(AnsiFunction[datetime.datetime]):
+ """The CURRENT_TIMESTAMP() SQL function."""
+
+ type = sqltypes.DateTime()
+ inherit_cache = True
+
+
+class current_user(AnsiFunction[str]):
+ """The CURRENT_USER() SQL function."""
+
+ type = sqltypes.String()
+ inherit_cache = True
+
+
+class localtime(AnsiFunction[datetime.datetime]):
+ """The localtime() SQL function."""
+
+ type = sqltypes.DateTime()
+ inherit_cache = True
+
+
+class localtimestamp(AnsiFunction[datetime.datetime]):
+ """The localtimestamp() SQL function."""
+
+ type = sqltypes.DateTime()
+ inherit_cache = True
+
+
+class session_user(AnsiFunction[str]):
+ """The SESSION_USER() SQL function."""
+
+ type = sqltypes.String()
+ inherit_cache = True
+
+
+class sysdate(AnsiFunction[datetime.datetime]):
+ """The SYSDATE() SQL function."""
+
+ type = sqltypes.DateTime()
+ inherit_cache = True
+
+
+class user(AnsiFunction[str]):
+ """The USER() SQL function."""
+
+ type = sqltypes.String()
+ inherit_cache = True
+
+
+class array_agg(GenericFunction[_T]):
+ """Support for the ARRAY_AGG function.
+
+ The ``func.array_agg(expr)`` construct returns an expression of
+ type :class:`_types.ARRAY`.
+
+ e.g.::
+
+ stmt = select(func.array_agg(table.c.values)[2:5])
+
+ .. seealso::
+
+ :func:`_postgresql.array_agg` - PostgreSQL-specific version that
+ returns :class:`_postgresql.ARRAY`, which has PG-specific operators
+ added.
+
+ """
+
+ inherit_cache = True
+
+ def __init__(self, *args: _ColumnExpressionArgument[Any], **kwargs: Any):
+ fn_args: Sequence[ColumnElement[Any]] = [
+ coercions.expect(
+ roles.ExpressionElementRole, c, apply_propagate_attrs=self
+ )
+ for c in args
+ ]
+
+ default_array_type = kwargs.pop("_default_array_type", sqltypes.ARRAY)
+ if "type_" not in kwargs:
+ type_from_args = _type_from_args(fn_args)
+ if isinstance(type_from_args, sqltypes.ARRAY):
+ kwargs["type_"] = type_from_args
+ else:
+ kwargs["type_"] = default_array_type(
+ type_from_args, dimensions=1
+ )
+ kwargs["_parsed_args"] = fn_args
+ super().__init__(*fn_args, **kwargs)
+
+
+class OrderedSetAgg(GenericFunction[_T]):
+ """Define a function where the return type is based on the sort
+ expression type as defined by the expression passed to the
+ :meth:`.FunctionElement.within_group` method."""
+
+ array_for_multi_clause = False
+ inherit_cache = True
+
+ def within_group_type(
+ self, within_group: WithinGroup[Any]
+ ) -> TypeEngine[Any]:
+ func_clauses = cast(ClauseList, self.clause_expr.element)
+ order_by: Sequence[ColumnElement[Any]] = sqlutil.unwrap_order_by(
+ within_group.order_by
+ )
+ if self.array_for_multi_clause and len(func_clauses.clauses) > 1:
+ return sqltypes.ARRAY(order_by[0].type)
+ else:
+ return order_by[0].type
+
+
+class mode(OrderedSetAgg[_T]):
+ """Implement the ``mode`` ordered-set aggregate function.
+
+ This function must be used with the :meth:`.FunctionElement.within_group`
+ modifier to supply a sort expression to operate upon.
+
+ The return type of this function is the same as the sort expression.
+
+ """
+
+ inherit_cache = True
+
+
+class percentile_cont(OrderedSetAgg[_T]):
+ """Implement the ``percentile_cont`` ordered-set aggregate function.
+
+ This function must be used with the :meth:`.FunctionElement.within_group`
+ modifier to supply a sort expression to operate upon.
+
+ The return type of this function is the same as the sort expression,
+ or if the arguments are an array, an :class:`_types.ARRAY` of the sort
+ expression's type.
+
+ """
+
+ array_for_multi_clause = True
+ inherit_cache = True
+
+
+class percentile_disc(OrderedSetAgg[_T]):
+ """Implement the ``percentile_disc`` ordered-set aggregate function.
+
+ This function must be used with the :meth:`.FunctionElement.within_group`
+ modifier to supply a sort expression to operate upon.
+
+ The return type of this function is the same as the sort expression,
+ or if the arguments are an array, an :class:`_types.ARRAY` of the sort
+ expression's type.
+
+ """
+
+ array_for_multi_clause = True
+ inherit_cache = True
+
+
+class rank(GenericFunction[int]):
+ """Implement the ``rank`` hypothetical-set aggregate function.
+
+ This function must be used with the :meth:`.FunctionElement.within_group`
+ modifier to supply a sort expression to operate upon.
+
+ The return type of this function is :class:`.Integer`.
+
+ """
+
+ type = sqltypes.Integer()
+ inherit_cache = True
+
+
+class dense_rank(GenericFunction[int]):
+ """Implement the ``dense_rank`` hypothetical-set aggregate function.
+
+ This function must be used with the :meth:`.FunctionElement.within_group`
+ modifier to supply a sort expression to operate upon.
+
+ The return type of this function is :class:`.Integer`.
+
+ """
+
+ type = sqltypes.Integer()
+ inherit_cache = True
+
+
+class percent_rank(GenericFunction[decimal.Decimal]):
+ """Implement the ``percent_rank`` hypothetical-set aggregate function.
+
+ This function must be used with the :meth:`.FunctionElement.within_group`
+ modifier to supply a sort expression to operate upon.
+
+ The return type of this function is :class:`.Numeric`.
+
+ """
+
+ type: sqltypes.Numeric[decimal.Decimal] = sqltypes.Numeric()
+ inherit_cache = True
+
+
+class cume_dist(GenericFunction[decimal.Decimal]):
+ """Implement the ``cume_dist`` hypothetical-set aggregate function.
+
+ This function must be used with the :meth:`.FunctionElement.within_group`
+ modifier to supply a sort expression to operate upon.
+
+ The return type of this function is :class:`.Numeric`.
+
+ """
+
+ type: sqltypes.Numeric[decimal.Decimal] = sqltypes.Numeric()
+ inherit_cache = True
+
+
+class cube(GenericFunction[_T]):
+ r"""Implement the ``CUBE`` grouping operation.
+
+ This function is used as part of the GROUP BY of a statement,
+ e.g. :meth:`_expression.Select.group_by`::
+
+ stmt = select(
+ func.sum(table.c.value), table.c.col_1, table.c.col_2
+ ).group_by(func.cube(table.c.col_1, table.c.col_2))
+
+ .. versionadded:: 1.2
+
+ """
+
+ _has_args = True
+ inherit_cache = True
+
+
+class rollup(GenericFunction[_T]):
+ r"""Implement the ``ROLLUP`` grouping operation.
+
+ This function is used as part of the GROUP BY of a statement,
+ e.g. :meth:`_expression.Select.group_by`::
+
+ stmt = select(
+ func.sum(table.c.value), table.c.col_1, table.c.col_2
+ ).group_by(func.rollup(table.c.col_1, table.c.col_2))
+
+ .. versionadded:: 1.2
+
+ """
+
+ _has_args = True
+ inherit_cache = True
+
+
+class grouping_sets(GenericFunction[_T]):
+ r"""Implement the ``GROUPING SETS`` grouping operation.
+
+ This function is used as part of the GROUP BY of a statement,
+ e.g. :meth:`_expression.Select.group_by`::
+
+ stmt = select(
+ func.sum(table.c.value), table.c.col_1, table.c.col_2
+ ).group_by(func.grouping_sets(table.c.col_1, table.c.col_2))
+
+ In order to group by multiple sets, use the :func:`.tuple_` construct::
+
+ from sqlalchemy import tuple_
+
+ stmt = select(
+ func.sum(table.c.value),
+ table.c.col_1, table.c.col_2,
+ table.c.col_3
+ ).group_by(
+ func.grouping_sets(
+ tuple_(table.c.col_1, table.c.col_2),
+ tuple_(table.c.value, table.c.col_3),
+ )
+ )
+
+
+ .. versionadded:: 1.2
+
+ """
+
+ _has_args = True
+ inherit_cache = True
+
+
+class aggregate_strings(GenericFunction[str]):
+ """Implement a generic string aggregation function.
+
+ This function will concatenate non-null values into a string and
+ separate the values by a delimiter.
+
+ This function is compiled on a per-backend basis, into functions
+ such as ``group_concat()``, ``string_agg()``, or ``LISTAGG()``.
+
+ e.g. Example usage with delimiter '.'::
+
+ stmt = select(func.aggregate_strings(table.c.str_col, "."))
+
+ The return type of this function is :class:`.String`.
+
+ .. versionadded: 2.0.21
+
+ """
+
+ type = sqltypes.String()
+ _has_args = True
+ inherit_cache = True
+
+ def __init__(self, clause: _ColumnExpressionArgument[Any], separator: str):
+ super().__init__(clause, separator)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/lambdas.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/lambdas.py
new file mode 100644
index 0000000..7a6b7b8
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/lambdas.py
@@ -0,0 +1,1449 @@
+# sql/lambdas.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+# mypy: allow-untyped-defs, allow-untyped-calls
+
+from __future__ import annotations
+
+import collections.abc as collections_abc
+import inspect
+import itertools
+import operator
+import threading
+import types
+from types import CodeType
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import List
+from typing import MutableMapping
+from typing import Optional
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+import weakref
+
+from . import cache_key as _cache_key
+from . import coercions
+from . import elements
+from . import roles
+from . import schema
+from . import visitors
+from .base import _clone
+from .base import Executable
+from .base import Options
+from .cache_key import CacheConst
+from .operators import ColumnOperators
+from .. import exc
+from .. import inspection
+from .. import util
+from ..util.typing import Literal
+
+
+if TYPE_CHECKING:
+ from .elements import BindParameter
+ from .elements import ClauseElement
+ from .roles import SQLRole
+ from .visitors import _CloneCallableType
+
+_LambdaCacheType = MutableMapping[
+ Tuple[Any, ...], Union["NonAnalyzedFunction", "AnalyzedFunction"]
+]
+_BoundParameterGetter = Callable[..., Any]
+
+_closure_per_cache_key: _LambdaCacheType = util.LRUCache(1000)
+
+
+_LambdaType = Callable[[], Any]
+
+_AnyLambdaType = Callable[..., Any]
+
+_StmtLambdaType = Callable[[], Any]
+
+_E = TypeVar("_E", bound=Executable)
+_StmtLambdaElementType = Callable[[_E], Any]
+
+
+class LambdaOptions(Options):
+ enable_tracking = True
+ track_closure_variables = True
+ track_on: Optional[object] = None
+ global_track_bound_values = True
+ track_bound_values = True
+ lambda_cache: Optional[_LambdaCacheType] = None
+
+
+def lambda_stmt(
+ lmb: _StmtLambdaType,
+ enable_tracking: bool = True,
+ track_closure_variables: bool = True,
+ track_on: Optional[object] = None,
+ global_track_bound_values: bool = True,
+ track_bound_values: bool = True,
+ lambda_cache: Optional[_LambdaCacheType] = None,
+) -> StatementLambdaElement:
+ """Produce a SQL statement that is cached as a lambda.
+
+ The Python code object within the lambda is scanned for both Python
+ literals that will become bound parameters as well as closure variables
+ that refer to Core or ORM constructs that may vary. The lambda itself
+ will be invoked only once per particular set of constructs detected.
+
+ E.g.::
+
+ from sqlalchemy import lambda_stmt
+
+ stmt = lambda_stmt(lambda: table.select())
+ stmt += lambda s: s.where(table.c.id == 5)
+
+ result = connection.execute(stmt)
+
+ The object returned is an instance of :class:`_sql.StatementLambdaElement`.
+
+ .. versionadded:: 1.4
+
+ :param lmb: a Python function, typically a lambda, which takes no arguments
+ and returns a SQL expression construct
+ :param enable_tracking: when False, all scanning of the given lambda for
+ changes in closure variables or bound parameters is disabled. Use for
+ a lambda that produces the identical results in all cases with no
+ parameterization.
+ :param track_closure_variables: when False, changes in closure variables
+ within the lambda will not be scanned. Use for a lambda where the
+ state of its closure variables will never change the SQL structure
+ returned by the lambda.
+ :param track_bound_values: when False, bound parameter tracking will
+ be disabled for the given lambda. Use for a lambda that either does
+ not produce any bound values, or where the initial bound values never
+ change.
+ :param global_track_bound_values: when False, bound parameter tracking
+ will be disabled for the entire statement including additional links
+ added via the :meth:`_sql.StatementLambdaElement.add_criteria` method.
+ :param lambda_cache: a dictionary or other mapping-like object where
+ information about the lambda's Python code as well as the tracked closure
+ variables in the lambda itself will be stored. Defaults
+ to a global LRU cache. This cache is independent of the "compiled_cache"
+ used by the :class:`_engine.Connection` object.
+
+ .. seealso::
+
+ :ref:`engine_lambda_caching`
+
+
+ """
+
+ return StatementLambdaElement(
+ lmb,
+ roles.StatementRole,
+ LambdaOptions(
+ enable_tracking=enable_tracking,
+ track_on=track_on,
+ track_closure_variables=track_closure_variables,
+ global_track_bound_values=global_track_bound_values,
+ track_bound_values=track_bound_values,
+ lambda_cache=lambda_cache,
+ ),
+ )
+
+
+class LambdaElement(elements.ClauseElement):
+ """A SQL construct where the state is stored as an un-invoked lambda.
+
+ The :class:`_sql.LambdaElement` is produced transparently whenever
+ passing lambda expressions into SQL constructs, such as::
+
+ stmt = select(table).where(lambda: table.c.col == parameter)
+
+ The :class:`_sql.LambdaElement` is the base of the
+ :class:`_sql.StatementLambdaElement` which represents a full statement
+ within a lambda.
+
+ .. versionadded:: 1.4
+
+ .. seealso::
+
+ :ref:`engine_lambda_caching`
+
+ """
+
+ __visit_name__ = "lambda_element"
+
+ _is_lambda_element = True
+
+ _traverse_internals = [
+ ("_resolved", visitors.InternalTraversal.dp_clauseelement)
+ ]
+
+ _transforms: Tuple[_CloneCallableType, ...] = ()
+
+ _resolved_bindparams: List[BindParameter[Any]]
+ parent_lambda: Optional[StatementLambdaElement] = None
+ closure_cache_key: Union[Tuple[Any, ...], Literal[CacheConst.NO_CACHE]]
+ role: Type[SQLRole]
+ _rec: Union[AnalyzedFunction, NonAnalyzedFunction]
+ fn: _AnyLambdaType
+ tracker_key: Tuple[CodeType, ...]
+
+ def __repr__(self):
+ return "%s(%r)" % (
+ self.__class__.__name__,
+ self.fn.__code__,
+ )
+
+ def __init__(
+ self,
+ fn: _LambdaType,
+ role: Type[SQLRole],
+ opts: Union[Type[LambdaOptions], LambdaOptions] = LambdaOptions,
+ apply_propagate_attrs: Optional[ClauseElement] = None,
+ ):
+ self.fn = fn
+ self.role = role
+ self.tracker_key = (fn.__code__,)
+ self.opts = opts
+
+ if apply_propagate_attrs is None and (role is roles.StatementRole):
+ apply_propagate_attrs = self
+
+ rec = self._retrieve_tracker_rec(fn, apply_propagate_attrs, opts)
+
+ if apply_propagate_attrs is not None:
+ propagate_attrs = rec.propagate_attrs
+ if propagate_attrs:
+ apply_propagate_attrs._propagate_attrs = propagate_attrs
+
+ def _retrieve_tracker_rec(self, fn, apply_propagate_attrs, opts):
+ lambda_cache = opts.lambda_cache
+ if lambda_cache is None:
+ lambda_cache = _closure_per_cache_key
+
+ tracker_key = self.tracker_key
+
+ fn = self.fn
+ closure = fn.__closure__
+ tracker = AnalyzedCode.get(
+ fn,
+ self,
+ opts,
+ )
+
+ bindparams: List[BindParameter[Any]]
+ self._resolved_bindparams = bindparams = []
+
+ if self.parent_lambda is not None:
+ parent_closure_cache_key = self.parent_lambda.closure_cache_key
+ else:
+ parent_closure_cache_key = ()
+
+ cache_key: Union[Tuple[Any, ...], Literal[CacheConst.NO_CACHE]]
+
+ if parent_closure_cache_key is not _cache_key.NO_CACHE:
+ anon_map = visitors.anon_map()
+ cache_key = tuple(
+ [
+ getter(closure, opts, anon_map, bindparams)
+ for getter in tracker.closure_trackers
+ ]
+ )
+
+ if _cache_key.NO_CACHE not in anon_map:
+ cache_key = parent_closure_cache_key + cache_key
+
+ self.closure_cache_key = cache_key
+
+ try:
+ rec = lambda_cache[tracker_key + cache_key]
+ except KeyError:
+ rec = None
+ else:
+ cache_key = _cache_key.NO_CACHE
+ rec = None
+
+ else:
+ cache_key = _cache_key.NO_CACHE
+ rec = None
+
+ self.closure_cache_key = cache_key
+
+ if rec is None:
+ if cache_key is not _cache_key.NO_CACHE:
+ with AnalyzedCode._generation_mutex:
+ key = tracker_key + cache_key
+ if key not in lambda_cache:
+ rec = AnalyzedFunction(
+ tracker, self, apply_propagate_attrs, fn
+ )
+ rec.closure_bindparams = bindparams
+ lambda_cache[key] = rec
+ else:
+ rec = lambda_cache[key]
+ else:
+ rec = NonAnalyzedFunction(self._invoke_user_fn(fn))
+
+ else:
+ bindparams[:] = [
+ orig_bind._with_value(new_bind.value, maintain_key=True)
+ for orig_bind, new_bind in zip(
+ rec.closure_bindparams, bindparams
+ )
+ ]
+
+ self._rec = rec
+
+ if cache_key is not _cache_key.NO_CACHE:
+ if self.parent_lambda is not None:
+ bindparams[:0] = self.parent_lambda._resolved_bindparams
+
+ lambda_element: Optional[LambdaElement] = self
+ while lambda_element is not None:
+ rec = lambda_element._rec
+ if rec.bindparam_trackers:
+ tracker_instrumented_fn = rec.tracker_instrumented_fn
+ for tracker in rec.bindparam_trackers:
+ tracker(
+ lambda_element.fn,
+ tracker_instrumented_fn,
+ bindparams,
+ )
+ lambda_element = lambda_element.parent_lambda
+
+ return rec
+
+ def __getattr__(self, key):
+ return getattr(self._rec.expected_expr, key)
+
+ @property
+ def _is_sequence(self):
+ return self._rec.is_sequence
+
+ @property
+ def _select_iterable(self):
+ if self._is_sequence:
+ return itertools.chain.from_iterable(
+ [element._select_iterable for element in self._resolved]
+ )
+
+ else:
+ return self._resolved._select_iterable
+
+ @property
+ def _from_objects(self):
+ if self._is_sequence:
+ return itertools.chain.from_iterable(
+ [element._from_objects for element in self._resolved]
+ )
+
+ else:
+ return self._resolved._from_objects
+
+ def _param_dict(self):
+ return {b.key: b.value for b in self._resolved_bindparams}
+
+ def _setup_binds_for_tracked_expr(self, expr):
+ bindparam_lookup = {b.key: b for b in self._resolved_bindparams}
+
+ def replace(
+ element: Optional[visitors.ExternallyTraversible], **kw: Any
+ ) -> Optional[visitors.ExternallyTraversible]:
+ if isinstance(element, elements.BindParameter):
+ if element.key in bindparam_lookup:
+ bind = bindparam_lookup[element.key]
+ if element.expanding:
+ bind.expanding = True
+ bind.expand_op = element.expand_op
+ bind.type = element.type
+ return bind
+
+ return None
+
+ if self._rec.is_sequence:
+ expr = [
+ visitors.replacement_traverse(sub_expr, {}, replace)
+ for sub_expr in expr
+ ]
+ elif getattr(expr, "is_clause_element", False):
+ expr = visitors.replacement_traverse(expr, {}, replace)
+
+ return expr
+
+ def _copy_internals(
+ self,
+ clone: _CloneCallableType = _clone,
+ deferred_copy_internals: Optional[_CloneCallableType] = None,
+ **kw: Any,
+ ) -> None:
+ # TODO: this needs A LOT of tests
+ self._resolved = clone(
+ self._resolved,
+ deferred_copy_internals=deferred_copy_internals,
+ **kw,
+ )
+
+ @util.memoized_property
+ def _resolved(self):
+ expr = self._rec.expected_expr
+
+ if self._resolved_bindparams:
+ expr = self._setup_binds_for_tracked_expr(expr)
+
+ return expr
+
+ def _gen_cache_key(self, anon_map, bindparams):
+ if self.closure_cache_key is _cache_key.NO_CACHE:
+ anon_map[_cache_key.NO_CACHE] = True
+ return None
+
+ cache_key = (
+ self.fn.__code__,
+ self.__class__,
+ ) + self.closure_cache_key
+
+ parent = self.parent_lambda
+
+ while parent is not None:
+ assert parent.closure_cache_key is not CacheConst.NO_CACHE
+ parent_closure_cache_key: Tuple[Any, ...] = (
+ parent.closure_cache_key
+ )
+
+ cache_key = (
+ (parent.fn.__code__,) + parent_closure_cache_key + cache_key
+ )
+
+ parent = parent.parent_lambda
+
+ if self._resolved_bindparams:
+ bindparams.extend(self._resolved_bindparams)
+ return cache_key
+
+ def _invoke_user_fn(self, fn: _AnyLambdaType, *arg: Any) -> ClauseElement:
+ return fn() # type: ignore[no-any-return]
+
+
+class DeferredLambdaElement(LambdaElement):
+ """A LambdaElement where the lambda accepts arguments and is
+ invoked within the compile phase with special context.
+
+ This lambda doesn't normally produce its real SQL expression outside of the
+ compile phase. It is passed a fixed set of initial arguments
+ so that it can generate a sample expression.
+
+ """
+
+ def __init__(
+ self,
+ fn: _AnyLambdaType,
+ role: Type[roles.SQLRole],
+ opts: Union[Type[LambdaOptions], LambdaOptions] = LambdaOptions,
+ lambda_args: Tuple[Any, ...] = (),
+ ):
+ self.lambda_args = lambda_args
+ super().__init__(fn, role, opts)
+
+ def _invoke_user_fn(self, fn, *arg):
+ return fn(*self.lambda_args)
+
+ def _resolve_with_args(self, *lambda_args: Any) -> ClauseElement:
+ assert isinstance(self._rec, AnalyzedFunction)
+ tracker_fn = self._rec.tracker_instrumented_fn
+ expr = tracker_fn(*lambda_args)
+
+ expr = coercions.expect(self.role, expr)
+
+ expr = self._setup_binds_for_tracked_expr(expr)
+
+ # this validation is getting very close, but not quite, to achieving
+ # #5767. The problem is if the base lambda uses an unnamed column
+ # as is very common with mixins, the parameter name is different
+ # and it produces a false positive; that is, for the documented case
+ # that is exactly what people will be doing, it doesn't work, so
+ # I'm not really sure how to handle this right now.
+ # expected_binds = [
+ # b._orig_key
+ # for b in self._rec.expr._generate_cache_key()[1]
+ # if b.required
+ # ]
+ # got_binds = [
+ # b._orig_key for b in expr._generate_cache_key()[1] if b.required
+ # ]
+ # if expected_binds != got_binds:
+ # raise exc.InvalidRequestError(
+ # "Lambda callable at %s produced a different set of bound "
+ # "parameters than its original run: %s"
+ # % (self.fn.__code__, ", ".join(got_binds))
+ # )
+
+ # TODO: TEST TEST TEST, this is very out there
+ for deferred_copy_internals in self._transforms:
+ expr = deferred_copy_internals(expr)
+
+ return expr # type: ignore
+
+ def _copy_internals(
+ self, clone=_clone, deferred_copy_internals=None, **kw
+ ):
+ super()._copy_internals(
+ clone=clone,
+ deferred_copy_internals=deferred_copy_internals, # **kw
+ opts=kw,
+ )
+
+ # TODO: A LOT A LOT of tests. for _resolve_with_args, we don't know
+ # our expression yet. so hold onto the replacement
+ if deferred_copy_internals:
+ self._transforms += (deferred_copy_internals,)
+
+
+class StatementLambdaElement(
+ roles.AllowsLambdaRole, LambdaElement, Executable
+):
+ """Represent a composable SQL statement as a :class:`_sql.LambdaElement`.
+
+ The :class:`_sql.StatementLambdaElement` is constructed using the
+ :func:`_sql.lambda_stmt` function::
+
+
+ from sqlalchemy import lambda_stmt
+
+ stmt = lambda_stmt(lambda: select(table))
+
+ Once constructed, additional criteria can be built onto the statement
+ by adding subsequent lambdas, which accept the existing statement
+ object as a single parameter::
+
+ stmt += lambda s: s.where(table.c.col == parameter)
+
+
+ .. versionadded:: 1.4
+
+ .. seealso::
+
+ :ref:`engine_lambda_caching`
+
+ """
+
+ if TYPE_CHECKING:
+
+ def __init__(
+ self,
+ fn: _StmtLambdaType,
+ role: Type[SQLRole],
+ opts: Union[Type[LambdaOptions], LambdaOptions] = LambdaOptions,
+ apply_propagate_attrs: Optional[ClauseElement] = None,
+ ): ...
+
+ def __add__(
+ self, other: _StmtLambdaElementType[Any]
+ ) -> StatementLambdaElement:
+ return self.add_criteria(other)
+
+ def add_criteria(
+ self,
+ other: _StmtLambdaElementType[Any],
+ enable_tracking: bool = True,
+ track_on: Optional[Any] = None,
+ track_closure_variables: bool = True,
+ track_bound_values: bool = True,
+ ) -> StatementLambdaElement:
+ """Add new criteria to this :class:`_sql.StatementLambdaElement`.
+
+ E.g.::
+
+ >>> def my_stmt(parameter):
+ ... stmt = lambda_stmt(
+ ... lambda: select(table.c.x, table.c.y),
+ ... )
+ ... stmt = stmt.add_criteria(
+ ... lambda: table.c.x > parameter
+ ... )
+ ... return stmt
+
+ The :meth:`_sql.StatementLambdaElement.add_criteria` method is
+ equivalent to using the Python addition operator to add a new
+ lambda, except that additional arguments may be added including
+ ``track_closure_values`` and ``track_on``::
+
+ >>> def my_stmt(self, foo):
+ ... stmt = lambda_stmt(
+ ... lambda: select(func.max(foo.x, foo.y)),
+ ... track_closure_variables=False
+ ... )
+ ... stmt = stmt.add_criteria(
+ ... lambda: self.where_criteria,
+ ... track_on=[self]
+ ... )
+ ... return stmt
+
+ See :func:`_sql.lambda_stmt` for a description of the parameters
+ accepted.
+
+ """
+
+ opts = self.opts + dict(
+ enable_tracking=enable_tracking,
+ track_closure_variables=track_closure_variables,
+ global_track_bound_values=self.opts.global_track_bound_values,
+ track_on=track_on,
+ track_bound_values=track_bound_values,
+ )
+
+ return LinkedLambdaElement(other, parent_lambda=self, opts=opts)
+
+ def _execute_on_connection(
+ self, connection, distilled_params, execution_options
+ ):
+ if TYPE_CHECKING:
+ assert isinstance(self._rec.expected_expr, ClauseElement)
+ if self._rec.expected_expr.supports_execution:
+ return connection._execute_clauseelement(
+ self, distilled_params, execution_options
+ )
+ else:
+ raise exc.ObjectNotExecutableError(self)
+
+ @property
+ def _proxied(self) -> Any:
+ return self._rec_expected_expr
+
+ @property
+ def _with_options(self):
+ return self._proxied._with_options
+
+ @property
+ def _effective_plugin_target(self):
+ return self._proxied._effective_plugin_target
+
+ @property
+ def _execution_options(self):
+ return self._proxied._execution_options
+
+ @property
+ def _all_selected_columns(self):
+ return self._proxied._all_selected_columns
+
+ @property
+ def is_select(self):
+ return self._proxied.is_select
+
+ @property
+ def is_update(self):
+ return self._proxied.is_update
+
+ @property
+ def is_insert(self):
+ return self._proxied.is_insert
+
+ @property
+ def is_text(self):
+ return self._proxied.is_text
+
+ @property
+ def is_delete(self):
+ return self._proxied.is_delete
+
+ @property
+ def is_dml(self):
+ return self._proxied.is_dml
+
+ def spoil(self) -> NullLambdaStatement:
+ """Return a new :class:`.StatementLambdaElement` that will run
+ all lambdas unconditionally each time.
+
+ """
+ return NullLambdaStatement(self.fn())
+
+
+class NullLambdaStatement(roles.AllowsLambdaRole, elements.ClauseElement):
+ """Provides the :class:`.StatementLambdaElement` API but does not
+ cache or analyze lambdas.
+
+ the lambdas are instead invoked immediately.
+
+ The intended use is to isolate issues that may arise when using
+ lambda statements.
+
+ """
+
+ __visit_name__ = "lambda_element"
+
+ _is_lambda_element = True
+
+ _traverse_internals = [
+ ("_resolved", visitors.InternalTraversal.dp_clauseelement)
+ ]
+
+ def __init__(self, statement):
+ self._resolved = statement
+ self._propagate_attrs = statement._propagate_attrs
+
+ def __getattr__(self, key):
+ return getattr(self._resolved, key)
+
+ def __add__(self, other):
+ statement = other(self._resolved)
+
+ return NullLambdaStatement(statement)
+
+ def add_criteria(self, other, **kw):
+ statement = other(self._resolved)
+
+ return NullLambdaStatement(statement)
+
+ def _execute_on_connection(
+ self, connection, distilled_params, execution_options
+ ):
+ if self._resolved.supports_execution:
+ return connection._execute_clauseelement(
+ self, distilled_params, execution_options
+ )
+ else:
+ raise exc.ObjectNotExecutableError(self)
+
+
+class LinkedLambdaElement(StatementLambdaElement):
+ """Represent subsequent links of a :class:`.StatementLambdaElement`."""
+
+ parent_lambda: StatementLambdaElement
+
+ def __init__(
+ self,
+ fn: _StmtLambdaElementType[Any],
+ parent_lambda: StatementLambdaElement,
+ opts: Union[Type[LambdaOptions], LambdaOptions],
+ ):
+ self.opts = opts
+ self.fn = fn
+ self.parent_lambda = parent_lambda
+
+ self.tracker_key = parent_lambda.tracker_key + (fn.__code__,)
+ self._retrieve_tracker_rec(fn, self, opts)
+ self._propagate_attrs = parent_lambda._propagate_attrs
+
+ def _invoke_user_fn(self, fn, *arg):
+ return fn(self.parent_lambda._resolved)
+
+
+class AnalyzedCode:
+ __slots__ = (
+ "track_closure_variables",
+ "track_bound_values",
+ "bindparam_trackers",
+ "closure_trackers",
+ "build_py_wrappers",
+ )
+ _fns: weakref.WeakKeyDictionary[CodeType, AnalyzedCode] = (
+ weakref.WeakKeyDictionary()
+ )
+
+ _generation_mutex = threading.RLock()
+
+ @classmethod
+ def get(cls, fn, lambda_element, lambda_kw, **kw):
+ try:
+ # TODO: validate kw haven't changed?
+ return cls._fns[fn.__code__]
+ except KeyError:
+ pass
+
+ with cls._generation_mutex:
+ # check for other thread already created object
+ if fn.__code__ in cls._fns:
+ return cls._fns[fn.__code__]
+
+ analyzed: AnalyzedCode
+ cls._fns[fn.__code__] = analyzed = AnalyzedCode(
+ fn, lambda_element, lambda_kw, **kw
+ )
+ return analyzed
+
+ def __init__(self, fn, lambda_element, opts):
+ if inspect.ismethod(fn):
+ raise exc.ArgumentError(
+ "Method %s may not be passed as a SQL expression" % fn
+ )
+ closure = fn.__closure__
+
+ self.track_bound_values = (
+ opts.track_bound_values and opts.global_track_bound_values
+ )
+ enable_tracking = opts.enable_tracking
+ track_on = opts.track_on
+ track_closure_variables = opts.track_closure_variables
+
+ self.track_closure_variables = track_closure_variables and not track_on
+
+ # a list of callables generated from _bound_parameter_getter_*
+ # functions. Each of these uses a PyWrapper object to retrieve
+ # a parameter value
+ self.bindparam_trackers = []
+
+ # a list of callables generated from _cache_key_getter_* functions
+ # these callables work to generate a cache key for the lambda
+ # based on what's inside its closure variables.
+ self.closure_trackers = []
+
+ self.build_py_wrappers = []
+
+ if enable_tracking:
+ if track_on:
+ self._init_track_on(track_on)
+
+ self._init_globals(fn)
+
+ if closure:
+ self._init_closure(fn)
+
+ self._setup_additional_closure_trackers(fn, lambda_element, opts)
+
+ def _init_track_on(self, track_on):
+ self.closure_trackers.extend(
+ self._cache_key_getter_track_on(idx, elem)
+ for idx, elem in enumerate(track_on)
+ )
+
+ def _init_globals(self, fn):
+ build_py_wrappers = self.build_py_wrappers
+ bindparam_trackers = self.bindparam_trackers
+ track_bound_values = self.track_bound_values
+
+ for name in fn.__code__.co_names:
+ if name not in fn.__globals__:
+ continue
+
+ _bound_value = self._roll_down_to_literal(fn.__globals__[name])
+
+ if coercions._deep_is_literal(_bound_value):
+ build_py_wrappers.append((name, None))
+ if track_bound_values:
+ bindparam_trackers.append(
+ self._bound_parameter_getter_func_globals(name)
+ )
+
+ def _init_closure(self, fn):
+ build_py_wrappers = self.build_py_wrappers
+ closure = fn.__closure__
+
+ track_bound_values = self.track_bound_values
+ track_closure_variables = self.track_closure_variables
+ bindparam_trackers = self.bindparam_trackers
+ closure_trackers = self.closure_trackers
+
+ for closure_index, (fv, cell) in enumerate(
+ zip(fn.__code__.co_freevars, closure)
+ ):
+ _bound_value = self._roll_down_to_literal(cell.cell_contents)
+
+ if coercions._deep_is_literal(_bound_value):
+ build_py_wrappers.append((fv, closure_index))
+ if track_bound_values:
+ bindparam_trackers.append(
+ self._bound_parameter_getter_func_closure(
+ fv, closure_index
+ )
+ )
+ else:
+ # for normal cell contents, add them to a list that
+ # we can compare later when we get new lambdas. if
+ # any identities have changed, then we will
+ # recalculate the whole lambda and run it again.
+
+ if track_closure_variables:
+ closure_trackers.append(
+ self._cache_key_getter_closure_variable(
+ fn, fv, closure_index, cell.cell_contents
+ )
+ )
+
+ def _setup_additional_closure_trackers(self, fn, lambda_element, opts):
+ # an additional step is to actually run the function, then
+ # go through the PyWrapper objects that were set up to catch a bound
+ # parameter. then if they *didn't* make a param, oh they're another
+ # object in the closure we have to track for our cache key. so
+ # create trackers to catch those.
+
+ analyzed_function = AnalyzedFunction(
+ self,
+ lambda_element,
+ None,
+ fn,
+ )
+
+ closure_trackers = self.closure_trackers
+
+ for pywrapper in analyzed_function.closure_pywrappers:
+ if not pywrapper._sa__has_param:
+ closure_trackers.append(
+ self._cache_key_getter_tracked_literal(fn, pywrapper)
+ )
+
+ @classmethod
+ def _roll_down_to_literal(cls, element):
+ is_clause_element = hasattr(element, "__clause_element__")
+
+ if is_clause_element:
+ while not isinstance(
+ element, (elements.ClauseElement, schema.SchemaItem, type)
+ ):
+ try:
+ element = element.__clause_element__()
+ except AttributeError:
+ break
+
+ if not is_clause_element:
+ insp = inspection.inspect(element, raiseerr=False)
+ if insp is not None:
+ try:
+ return insp.__clause_element__()
+ except AttributeError:
+ return insp
+
+ # TODO: should we coerce consts None/True/False here?
+ return element
+ else:
+ return element
+
+ def _bound_parameter_getter_func_globals(self, name):
+ """Return a getter that will extend a list of bound parameters
+ with new entries from the ``__globals__`` collection of a particular
+ lambda.
+
+ """
+
+ def extract_parameter_value(
+ current_fn, tracker_instrumented_fn, result
+ ):
+ wrapper = tracker_instrumented_fn.__globals__[name]
+ object.__getattribute__(wrapper, "_extract_bound_parameters")(
+ current_fn.__globals__[name], result
+ )
+
+ return extract_parameter_value
+
+ def _bound_parameter_getter_func_closure(self, name, closure_index):
+ """Return a getter that will extend a list of bound parameters
+ with new entries from the ``__closure__`` collection of a particular
+ lambda.
+
+ """
+
+ def extract_parameter_value(
+ current_fn, tracker_instrumented_fn, result
+ ):
+ wrapper = tracker_instrumented_fn.__closure__[
+ closure_index
+ ].cell_contents
+ object.__getattribute__(wrapper, "_extract_bound_parameters")(
+ current_fn.__closure__[closure_index].cell_contents, result
+ )
+
+ return extract_parameter_value
+
+ def _cache_key_getter_track_on(self, idx, elem):
+ """Return a getter that will extend a cache key with new entries
+ from the "track_on" parameter passed to a :class:`.LambdaElement`.
+
+ """
+
+ if isinstance(elem, tuple):
+ # tuple must contain hascachekey elements
+ def get(closure, opts, anon_map, bindparams):
+ return tuple(
+ tup_elem._gen_cache_key(anon_map, bindparams)
+ for tup_elem in opts.track_on[idx]
+ )
+
+ elif isinstance(elem, _cache_key.HasCacheKey):
+
+ def get(closure, opts, anon_map, bindparams):
+ return opts.track_on[idx]._gen_cache_key(anon_map, bindparams)
+
+ else:
+
+ def get(closure, opts, anon_map, bindparams):
+ return opts.track_on[idx]
+
+ return get
+
+ def _cache_key_getter_closure_variable(
+ self,
+ fn,
+ variable_name,
+ idx,
+ cell_contents,
+ use_clause_element=False,
+ use_inspect=False,
+ ):
+ """Return a getter that will extend a cache key with new entries
+ from the ``__closure__`` collection of a particular lambda.
+
+ """
+
+ if isinstance(cell_contents, _cache_key.HasCacheKey):
+
+ def get(closure, opts, anon_map, bindparams):
+ obj = closure[idx].cell_contents
+ if use_inspect:
+ obj = inspection.inspect(obj)
+ elif use_clause_element:
+ while hasattr(obj, "__clause_element__"):
+ if not getattr(obj, "is_clause_element", False):
+ obj = obj.__clause_element__()
+
+ return obj._gen_cache_key(anon_map, bindparams)
+
+ elif isinstance(cell_contents, types.FunctionType):
+
+ def get(closure, opts, anon_map, bindparams):
+ return closure[idx].cell_contents.__code__
+
+ elif isinstance(cell_contents, collections_abc.Sequence):
+
+ def get(closure, opts, anon_map, bindparams):
+ contents = closure[idx].cell_contents
+
+ try:
+ return tuple(
+ elem._gen_cache_key(anon_map, bindparams)
+ for elem in contents
+ )
+ except AttributeError as ae:
+ self._raise_for_uncacheable_closure_variable(
+ variable_name, fn, from_=ae
+ )
+
+ else:
+ # if the object is a mapped class or aliased class, or some
+ # other object in the ORM realm of things like that, imitate
+ # the logic used in coercions.expect() to roll it down to the
+ # SQL element
+ element = cell_contents
+ is_clause_element = False
+ while hasattr(element, "__clause_element__"):
+ is_clause_element = True
+ if not getattr(element, "is_clause_element", False):
+ element = element.__clause_element__()
+ else:
+ break
+
+ if not is_clause_element:
+ insp = inspection.inspect(element, raiseerr=False)
+ if insp is not None:
+ return self._cache_key_getter_closure_variable(
+ fn, variable_name, idx, insp, use_inspect=True
+ )
+ else:
+ return self._cache_key_getter_closure_variable(
+ fn, variable_name, idx, element, use_clause_element=True
+ )
+
+ self._raise_for_uncacheable_closure_variable(variable_name, fn)
+
+ return get
+
+ def _raise_for_uncacheable_closure_variable(
+ self, variable_name, fn, from_=None
+ ):
+ raise exc.InvalidRequestError(
+ "Closure variable named '%s' inside of lambda callable %s "
+ "does not refer to a cacheable SQL element, and also does not "
+ "appear to be serving as a SQL literal bound value based on "
+ "the default "
+ "SQL expression returned by the function. This variable "
+ "needs to remain outside the scope of a SQL-generating lambda "
+ "so that a proper cache key may be generated from the "
+ "lambda's state. Evaluate this variable outside of the "
+ "lambda, set track_on=[<elements>] to explicitly select "
+ "closure elements to track, or set "
+ "track_closure_variables=False to exclude "
+ "closure variables from being part of the cache key."
+ % (variable_name, fn.__code__),
+ ) from from_
+
+ def _cache_key_getter_tracked_literal(self, fn, pytracker):
+ """Return a getter that will extend a cache key with new entries
+ from the ``__closure__`` collection of a particular lambda.
+
+ this getter differs from _cache_key_getter_closure_variable
+ in that these are detected after the function is run, and PyWrapper
+ objects have recorded that a particular literal value is in fact
+ not being interpreted as a bound parameter.
+
+ """
+
+ elem = pytracker._sa__to_evaluate
+ closure_index = pytracker._sa__closure_index
+ variable_name = pytracker._sa__name
+
+ return self._cache_key_getter_closure_variable(
+ fn, variable_name, closure_index, elem
+ )
+
+
+class NonAnalyzedFunction:
+ __slots__ = ("expr",)
+
+ closure_bindparams: Optional[List[BindParameter[Any]]] = None
+ bindparam_trackers: Optional[List[_BoundParameterGetter]] = None
+
+ is_sequence = False
+
+ expr: ClauseElement
+
+ def __init__(self, expr: ClauseElement):
+ self.expr = expr
+
+ @property
+ def expected_expr(self) -> ClauseElement:
+ return self.expr
+
+
+class AnalyzedFunction:
+ __slots__ = (
+ "analyzed_code",
+ "fn",
+ "closure_pywrappers",
+ "tracker_instrumented_fn",
+ "expr",
+ "bindparam_trackers",
+ "expected_expr",
+ "is_sequence",
+ "propagate_attrs",
+ "closure_bindparams",
+ )
+
+ closure_bindparams: Optional[List[BindParameter[Any]]]
+ expected_expr: Union[ClauseElement, List[ClauseElement]]
+ bindparam_trackers: Optional[List[_BoundParameterGetter]]
+
+ def __init__(
+ self,
+ analyzed_code,
+ lambda_element,
+ apply_propagate_attrs,
+ fn,
+ ):
+ self.analyzed_code = analyzed_code
+ self.fn = fn
+
+ self.bindparam_trackers = analyzed_code.bindparam_trackers
+
+ self._instrument_and_run_function(lambda_element)
+
+ self._coerce_expression(lambda_element, apply_propagate_attrs)
+
+ def _instrument_and_run_function(self, lambda_element):
+ analyzed_code = self.analyzed_code
+
+ fn = self.fn
+ self.closure_pywrappers = closure_pywrappers = []
+
+ build_py_wrappers = analyzed_code.build_py_wrappers
+
+ if not build_py_wrappers:
+ self.tracker_instrumented_fn = tracker_instrumented_fn = fn
+ self.expr = lambda_element._invoke_user_fn(tracker_instrumented_fn)
+ else:
+ track_closure_variables = analyzed_code.track_closure_variables
+ closure = fn.__closure__
+
+ # will form the __closure__ of the function when we rebuild it
+ if closure:
+ new_closure = {
+ fv: cell.cell_contents
+ for fv, cell in zip(fn.__code__.co_freevars, closure)
+ }
+ else:
+ new_closure = {}
+
+ # will form the __globals__ of the function when we rebuild it
+ new_globals = fn.__globals__.copy()
+
+ for name, closure_index in build_py_wrappers:
+ if closure_index is not None:
+ value = closure[closure_index].cell_contents
+ new_closure[name] = bind = PyWrapper(
+ fn,
+ name,
+ value,
+ closure_index=closure_index,
+ track_bound_values=(
+ self.analyzed_code.track_bound_values
+ ),
+ )
+ if track_closure_variables:
+ closure_pywrappers.append(bind)
+ else:
+ value = fn.__globals__[name]
+ new_globals[name] = bind = PyWrapper(fn, name, value)
+
+ # rewrite the original fn. things that look like they will
+ # become bound parameters are wrapped in a PyWrapper.
+ self.tracker_instrumented_fn = tracker_instrumented_fn = (
+ self._rewrite_code_obj(
+ fn,
+ [new_closure[name] for name in fn.__code__.co_freevars],
+ new_globals,
+ )
+ )
+
+ # now invoke the function. This will give us a new SQL
+ # expression, but all the places that there would be a bound
+ # parameter, the PyWrapper in its place will give us a bind
+ # with a predictable name we can match up later.
+
+ # additionally, each PyWrapper will log that it did in fact
+ # create a parameter, otherwise, it's some kind of Python
+ # object in the closure and we want to track that, to make
+ # sure it doesn't change to something else, or if it does,
+ # that we create a different tracked function with that
+ # variable.
+ self.expr = lambda_element._invoke_user_fn(tracker_instrumented_fn)
+
+ def _coerce_expression(self, lambda_element, apply_propagate_attrs):
+ """Run the tracker-generated expression through coercion rules.
+
+ After the user-defined lambda has been invoked to produce a statement
+ for re-use, run it through coercion rules to both check that it's the
+ correct type of object and also to coerce it to its useful form.
+
+ """
+
+ parent_lambda = lambda_element.parent_lambda
+ expr = self.expr
+
+ if parent_lambda is None:
+ if isinstance(expr, collections_abc.Sequence):
+ self.expected_expr = [
+ cast(
+ "ClauseElement",
+ coercions.expect(
+ lambda_element.role,
+ sub_expr,
+ apply_propagate_attrs=apply_propagate_attrs,
+ ),
+ )
+ for sub_expr in expr
+ ]
+ self.is_sequence = True
+ else:
+ self.expected_expr = cast(
+ "ClauseElement",
+ coercions.expect(
+ lambda_element.role,
+ expr,
+ apply_propagate_attrs=apply_propagate_attrs,
+ ),
+ )
+ self.is_sequence = False
+ else:
+ self.expected_expr = expr
+ self.is_sequence = False
+
+ if apply_propagate_attrs is not None:
+ self.propagate_attrs = apply_propagate_attrs._propagate_attrs
+ else:
+ self.propagate_attrs = util.EMPTY_DICT
+
+ def _rewrite_code_obj(self, f, cell_values, globals_):
+ """Return a copy of f, with a new closure and new globals
+
+ yes it works in pypy :P
+
+ """
+
+ argrange = range(len(cell_values))
+
+ code = "def make_cells():\n"
+ if cell_values:
+ code += " (%s) = (%s)\n" % (
+ ", ".join("i%d" % i for i in argrange),
+ ", ".join("o%d" % i for i in argrange),
+ )
+ code += " def closure():\n"
+ code += " return %s\n" % ", ".join("i%d" % i for i in argrange)
+ code += " return closure.__closure__"
+ vars_ = {"o%d" % i: cell_values[i] for i in argrange}
+ exec(code, vars_, vars_)
+ closure = vars_["make_cells"]()
+
+ func = type(f)(
+ f.__code__, globals_, f.__name__, f.__defaults__, closure
+ )
+ func.__annotations__ = f.__annotations__
+ func.__kwdefaults__ = f.__kwdefaults__
+ func.__doc__ = f.__doc__
+ func.__module__ = f.__module__
+
+ return func
+
+
+class PyWrapper(ColumnOperators):
+ """A wrapper object that is injected into the ``__globals__`` and
+ ``__closure__`` of a Python function.
+
+ When the function is instrumented with :class:`.PyWrapper` objects, it is
+ then invoked just once in order to set up the wrappers. We look through
+ all the :class:`.PyWrapper` objects we made to find the ones that generated
+ a :class:`.BindParameter` object, e.g. the expression system interpreted
+ something as a literal. Those positions in the globals/closure are then
+ ones that we will look at, each time a new lambda comes in that refers to
+ the same ``__code__`` object. In this way, we keep a single version of
+ the SQL expression that this lambda produced, without calling upon the
+ Python function that created it more than once, unless its other closure
+ variables have changed. The expression is then transformed to have the
+ new bound values embedded into it.
+
+ """
+
+ def __init__(
+ self,
+ fn,
+ name,
+ to_evaluate,
+ closure_index=None,
+ getter=None,
+ track_bound_values=True,
+ ):
+ self.fn = fn
+ self._name = name
+ self._to_evaluate = to_evaluate
+ self._param = None
+ self._has_param = False
+ self._bind_paths = {}
+ self._getter = getter
+ self._closure_index = closure_index
+ self.track_bound_values = track_bound_values
+
+ def __call__(self, *arg, **kw):
+ elem = object.__getattribute__(self, "_to_evaluate")
+ value = elem(*arg, **kw)
+ if (
+ self._sa_track_bound_values
+ and coercions._deep_is_literal(value)
+ and not isinstance(
+ # TODO: coverage where an ORM option or similar is here
+ value,
+ _cache_key.HasCacheKey,
+ )
+ ):
+ name = object.__getattribute__(self, "_name")
+ raise exc.InvalidRequestError(
+ "Can't invoke Python callable %s() inside of lambda "
+ "expression argument at %s; lambda SQL constructs should "
+ "not invoke functions from closure variables to produce "
+ "literal values since the "
+ "lambda SQL system normally extracts bound values without "
+ "actually "
+ "invoking the lambda or any functions within it. Call the "
+ "function outside of the "
+ "lambda and assign to a local variable that is used in the "
+ "lambda as a closure variable, or set "
+ "track_bound_values=False if the return value of this "
+ "function is used in some other way other than a SQL bound "
+ "value." % (name, self._sa_fn.__code__)
+ )
+ else:
+ return value
+
+ def operate(self, op, *other, **kwargs):
+ elem = object.__getattribute__(self, "_py_wrapper_literal")()
+ return op(elem, *other, **kwargs)
+
+ def reverse_operate(self, op, other, **kwargs):
+ elem = object.__getattribute__(self, "_py_wrapper_literal")()
+ return op(other, elem, **kwargs)
+
+ def _extract_bound_parameters(self, starting_point, result_list):
+ param = object.__getattribute__(self, "_param")
+ if param is not None:
+ param = param._with_value(starting_point, maintain_key=True)
+ result_list.append(param)
+ for pywrapper in object.__getattribute__(self, "_bind_paths").values():
+ getter = object.__getattribute__(pywrapper, "_getter")
+ element = getter(starting_point)
+ pywrapper._sa__extract_bound_parameters(element, result_list)
+
+ def _py_wrapper_literal(self, expr=None, operator=None, **kw):
+ param = object.__getattribute__(self, "_param")
+ to_evaluate = object.__getattribute__(self, "_to_evaluate")
+ if param is None:
+ name = object.__getattribute__(self, "_name")
+ self._param = param = elements.BindParameter(
+ name,
+ required=False,
+ unique=True,
+ _compared_to_operator=operator,
+ _compared_to_type=expr.type if expr is not None else None,
+ )
+ self._has_param = True
+ return param._with_value(to_evaluate, maintain_key=True)
+
+ def __bool__(self):
+ to_evaluate = object.__getattribute__(self, "_to_evaluate")
+ return bool(to_evaluate)
+
+ def __getattribute__(self, key):
+ if key.startswith("_sa_"):
+ return object.__getattribute__(self, key[4:])
+ elif key in (
+ "__clause_element__",
+ "operate",
+ "reverse_operate",
+ "_py_wrapper_literal",
+ "__class__",
+ "__dict__",
+ ):
+ return object.__getattribute__(self, key)
+
+ if key.startswith("__"):
+ elem = object.__getattribute__(self, "_to_evaluate")
+ return getattr(elem, key)
+ else:
+ return self._sa__add_getter(key, operator.attrgetter)
+
+ def __iter__(self):
+ elem = object.__getattribute__(self, "_to_evaluate")
+ return iter(elem)
+
+ def __getitem__(self, key):
+ elem = object.__getattribute__(self, "_to_evaluate")
+ if not hasattr(elem, "__getitem__"):
+ raise AttributeError("__getitem__")
+
+ if isinstance(key, PyWrapper):
+ # TODO: coverage
+ raise exc.InvalidRequestError(
+ "Dictionary keys / list indexes inside of a cached "
+ "lambda must be Python literals only"
+ )
+ return self._sa__add_getter(key, operator.itemgetter)
+
+ def _add_getter(self, key, getter_fn):
+ bind_paths = object.__getattribute__(self, "_bind_paths")
+
+ bind_path_key = (key, getter_fn)
+ if bind_path_key in bind_paths:
+ return bind_paths[bind_path_key]
+
+ getter = getter_fn(key)
+ elem = object.__getattribute__(self, "_to_evaluate")
+ value = getter(elem)
+
+ rolled_down_value = AnalyzedCode._roll_down_to_literal(value)
+
+ if coercions._deep_is_literal(rolled_down_value):
+ wrapper = PyWrapper(self._sa_fn, key, value, getter=getter)
+ bind_paths[bind_path_key] = wrapper
+ return wrapper
+ else:
+ return value
+
+
+@inspection._inspects(LambdaElement)
+def insp(lmb):
+ return inspection.inspect(lmb._resolved)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/naming.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/naming.py
new file mode 100644
index 0000000..7213ddb
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/naming.py
@@ -0,0 +1,212 @@
+# sql/naming.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+# mypy: allow-untyped-defs, allow-untyped-calls
+
+"""Establish constraint and index naming conventions.
+
+
+"""
+
+from __future__ import annotations
+
+import re
+
+from . import events # noqa
+from .base import _NONE_NAME
+from .elements import conv as conv
+from .schema import CheckConstraint
+from .schema import Column
+from .schema import Constraint
+from .schema import ForeignKeyConstraint
+from .schema import Index
+from .schema import PrimaryKeyConstraint
+from .schema import Table
+from .schema import UniqueConstraint
+from .. import event
+from .. import exc
+
+
+class ConventionDict:
+ def __init__(self, const, table, convention):
+ self.const = const
+ self._is_fk = isinstance(const, ForeignKeyConstraint)
+ self.table = table
+ self.convention = convention
+ self._const_name = const.name
+
+ def _key_table_name(self):
+ return self.table.name
+
+ def _column_X(self, idx, attrname):
+ if self._is_fk:
+ try:
+ fk = self.const.elements[idx]
+ except IndexError:
+ return ""
+ else:
+ return getattr(fk.parent, attrname)
+ else:
+ cols = list(self.const.columns)
+ try:
+ col = cols[idx]
+ except IndexError:
+ return ""
+ else:
+ return getattr(col, attrname)
+
+ def _key_constraint_name(self):
+ if self._const_name in (None, _NONE_NAME):
+ raise exc.InvalidRequestError(
+ "Naming convention including "
+ "%(constraint_name)s token requires that "
+ "constraint is explicitly named."
+ )
+ if not isinstance(self._const_name, conv):
+ self.const.name = None
+ return self._const_name
+
+ def _key_column_X_key(self, idx):
+ # note this method was missing before
+ # [ticket:3989], meaning tokens like ``%(column_0_key)s`` weren't
+ # working even though documented.
+ return self._column_X(idx, "key")
+
+ def _key_column_X_name(self, idx):
+ return self._column_X(idx, "name")
+
+ def _key_column_X_label(self, idx):
+ return self._column_X(idx, "_ddl_label")
+
+ def _key_referred_table_name(self):
+ fk = self.const.elements[0]
+ refs = fk.target_fullname.split(".")
+ if len(refs) == 3:
+ refschema, reftable, refcol = refs
+ else:
+ reftable, refcol = refs
+ return reftable
+
+ def _key_referred_column_X_name(self, idx):
+ fk = self.const.elements[idx]
+ # note that before [ticket:3989], this method was returning
+ # the specification for the :class:`.ForeignKey` itself, which normally
+ # would be using the ``.key`` of the column, not the name.
+ return fk.column.name
+
+ def __getitem__(self, key):
+ if key in self.convention:
+ return self.convention[key](self.const, self.table)
+ elif hasattr(self, "_key_%s" % key):
+ return getattr(self, "_key_%s" % key)()
+ else:
+ col_template = re.match(r".*_?column_(\d+)(_?N)?_.+", key)
+ if col_template:
+ idx = col_template.group(1)
+ multiples = col_template.group(2)
+
+ if multiples:
+ if self._is_fk:
+ elems = self.const.elements
+ else:
+ elems = list(self.const.columns)
+ tokens = []
+ for idx, elem in enumerate(elems):
+ attr = "_key_" + key.replace("0" + multiples, "X")
+ try:
+ tokens.append(getattr(self, attr)(idx))
+ except AttributeError:
+ raise KeyError(key)
+ sep = "_" if multiples.startswith("_") else ""
+ return sep.join(tokens)
+ else:
+ attr = "_key_" + key.replace(idx, "X")
+ idx = int(idx)
+ if hasattr(self, attr):
+ return getattr(self, attr)(idx)
+ raise KeyError(key)
+
+
+_prefix_dict = {
+ Index: "ix",
+ PrimaryKeyConstraint: "pk",
+ CheckConstraint: "ck",
+ UniqueConstraint: "uq",
+ ForeignKeyConstraint: "fk",
+}
+
+
+def _get_convention(dict_, key):
+ for super_ in key.__mro__:
+ if super_ in _prefix_dict and _prefix_dict[super_] in dict_:
+ return dict_[_prefix_dict[super_]]
+ elif super_ in dict_:
+ return dict_[super_]
+ else:
+ return None
+
+
+def _constraint_name_for_table(const, table):
+ metadata = table.metadata
+ convention = _get_convention(metadata.naming_convention, type(const))
+
+ if isinstance(const.name, conv):
+ return const.name
+ elif (
+ convention is not None
+ and not isinstance(const.name, conv)
+ and (
+ const.name is None
+ or "constraint_name" in convention
+ or const.name is _NONE_NAME
+ )
+ ):
+ return conv(
+ convention
+ % ConventionDict(const, table, metadata.naming_convention)
+ )
+ elif convention is _NONE_NAME:
+ return None
+
+
+@event.listens_for(
+ PrimaryKeyConstraint, "_sa_event_column_added_to_pk_constraint"
+)
+def _column_added_to_pk_constraint(pk_constraint, col):
+ if pk_constraint._implicit_generated:
+ # only operate upon the "implicit" pk constraint for now,
+ # as we have to force the name to None to reset it. the
+ # "implicit" constraint will only have a naming convention name
+ # if at all.
+ table = pk_constraint.table
+ pk_constraint.name = None
+ newname = _constraint_name_for_table(pk_constraint, table)
+ if newname:
+ pk_constraint.name = newname
+
+
+@event.listens_for(Constraint, "after_parent_attach")
+@event.listens_for(Index, "after_parent_attach")
+def _constraint_name(const, table):
+ if isinstance(table, Column):
+ # this path occurs for a CheckConstraint linked to a Column
+
+ # for column-attached constraint, set another event
+ # to link the column attached to the table as this constraint
+ # associated with the table.
+ event.listen(
+ table,
+ "after_parent_attach",
+ lambda col, table: _constraint_name(const, table),
+ )
+
+ elif isinstance(table, Table):
+ if isinstance(const.name, conv) or const.name is _NONE_NAME:
+ return
+
+ newname = _constraint_name_for_table(const, table)
+ if newname:
+ const.name = newname
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/operators.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/operators.py
new file mode 100644
index 0000000..9fb096e
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/operators.py
@@ -0,0 +1,2573 @@
+# sql/operators.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+"""Defines operators used in SQL expressions."""
+
+from __future__ import annotations
+
+from enum import IntEnum
+from operator import add as _uncast_add
+from operator import and_ as _uncast_and_
+from operator import contains as _uncast_contains
+from operator import eq as _uncast_eq
+from operator import floordiv as _uncast_floordiv
+from operator import ge as _uncast_ge
+from operator import getitem as _uncast_getitem
+from operator import gt as _uncast_gt
+from operator import inv as _uncast_inv
+from operator import le as _uncast_le
+from operator import lshift as _uncast_lshift
+from operator import lt as _uncast_lt
+from operator import mod as _uncast_mod
+from operator import mul as _uncast_mul
+from operator import ne as _uncast_ne
+from operator import neg as _uncast_neg
+from operator import or_ as _uncast_or_
+from operator import rshift as _uncast_rshift
+from operator import sub as _uncast_sub
+from operator import truediv as _uncast_truediv
+import typing
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import Dict
+from typing import Generic
+from typing import Optional
+from typing import overload
+from typing import Set
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from .. import exc
+from .. import util
+from ..util.typing import Literal
+from ..util.typing import Protocol
+
+if typing.TYPE_CHECKING:
+ from ._typing import ColumnExpressionArgument
+ from .cache_key import CacheConst
+ from .elements import ColumnElement
+ from .type_api import TypeEngine
+
+_T = TypeVar("_T", bound=Any)
+_FN = TypeVar("_FN", bound=Callable[..., Any])
+
+
+class OperatorType(Protocol):
+ """describe an op() function."""
+
+ __slots__ = ()
+
+ __name__: str
+
+ @overload
+ def __call__(
+ self,
+ left: ColumnExpressionArgument[Any],
+ right: Optional[Any] = None,
+ *other: Any,
+ **kwargs: Any,
+ ) -> ColumnElement[Any]: ...
+
+ @overload
+ def __call__(
+ self,
+ left: Operators,
+ right: Optional[Any] = None,
+ *other: Any,
+ **kwargs: Any,
+ ) -> Operators: ...
+
+ def __call__(
+ self,
+ left: Any,
+ right: Optional[Any] = None,
+ *other: Any,
+ **kwargs: Any,
+ ) -> Operators: ...
+
+
+add = cast(OperatorType, _uncast_add)
+and_ = cast(OperatorType, _uncast_and_)
+contains = cast(OperatorType, _uncast_contains)
+eq = cast(OperatorType, _uncast_eq)
+floordiv = cast(OperatorType, _uncast_floordiv)
+ge = cast(OperatorType, _uncast_ge)
+getitem = cast(OperatorType, _uncast_getitem)
+gt = cast(OperatorType, _uncast_gt)
+inv = cast(OperatorType, _uncast_inv)
+le = cast(OperatorType, _uncast_le)
+lshift = cast(OperatorType, _uncast_lshift)
+lt = cast(OperatorType, _uncast_lt)
+mod = cast(OperatorType, _uncast_mod)
+mul = cast(OperatorType, _uncast_mul)
+ne = cast(OperatorType, _uncast_ne)
+neg = cast(OperatorType, _uncast_neg)
+or_ = cast(OperatorType, _uncast_or_)
+rshift = cast(OperatorType, _uncast_rshift)
+sub = cast(OperatorType, _uncast_sub)
+truediv = cast(OperatorType, _uncast_truediv)
+
+
+class Operators:
+ """Base of comparison and logical operators.
+
+ Implements base methods
+ :meth:`~sqlalchemy.sql.operators.Operators.operate` and
+ :meth:`~sqlalchemy.sql.operators.Operators.reverse_operate`, as well as
+ :meth:`~sqlalchemy.sql.operators.Operators.__and__`,
+ :meth:`~sqlalchemy.sql.operators.Operators.__or__`,
+ :meth:`~sqlalchemy.sql.operators.Operators.__invert__`.
+
+ Usually is used via its most common subclass
+ :class:`.ColumnOperators`.
+
+ """
+
+ __slots__ = ()
+
+ def __and__(self, other: Any) -> Operators:
+ """Implement the ``&`` operator.
+
+ When used with SQL expressions, results in an
+ AND operation, equivalent to
+ :func:`_expression.and_`, that is::
+
+ a & b
+
+ is equivalent to::
+
+ from sqlalchemy import and_
+ and_(a, b)
+
+ Care should be taken when using ``&`` regarding
+ operator precedence; the ``&`` operator has the highest precedence.
+ The operands should be enclosed in parenthesis if they contain
+ further sub expressions::
+
+ (a == 2) & (b == 4)
+
+ """
+ return self.operate(and_, other)
+
+ def __or__(self, other: Any) -> Operators:
+ """Implement the ``|`` operator.
+
+ When used with SQL expressions, results in an
+ OR operation, equivalent to
+ :func:`_expression.or_`, that is::
+
+ a | b
+
+ is equivalent to::
+
+ from sqlalchemy import or_
+ or_(a, b)
+
+ Care should be taken when using ``|`` regarding
+ operator precedence; the ``|`` operator has the highest precedence.
+ The operands should be enclosed in parenthesis if they contain
+ further sub expressions::
+
+ (a == 2) | (b == 4)
+
+ """
+ return self.operate(or_, other)
+
+ def __invert__(self) -> Operators:
+ """Implement the ``~`` operator.
+
+ When used with SQL expressions, results in a
+ NOT operation, equivalent to
+ :func:`_expression.not_`, that is::
+
+ ~a
+
+ is equivalent to::
+
+ from sqlalchemy import not_
+ not_(a)
+
+ """
+ return self.operate(inv)
+
+ def op(
+ self,
+ opstring: str,
+ precedence: int = 0,
+ is_comparison: bool = False,
+ return_type: Optional[
+ Union[Type[TypeEngine[Any]], TypeEngine[Any]]
+ ] = None,
+ python_impl: Optional[Callable[..., Any]] = None,
+ ) -> Callable[[Any], Operators]:
+ """Produce a generic operator function.
+
+ e.g.::
+
+ somecolumn.op("*")(5)
+
+ produces::
+
+ somecolumn * 5
+
+ This function can also be used to make bitwise operators explicit. For
+ example::
+
+ somecolumn.op('&')(0xff)
+
+ is a bitwise AND of the value in ``somecolumn``.
+
+ :param opstring: a string which will be output as the infix operator
+ between this element and the expression passed to the
+ generated function.
+
+ :param precedence: precedence which the database is expected to apply
+ to the operator in SQL expressions. This integer value acts as a hint
+ for the SQL compiler to know when explicit parenthesis should be
+ rendered around a particular operation. A lower number will cause the
+ expression to be parenthesized when applied against another operator
+ with higher precedence. The default value of ``0`` is lower than all
+ operators except for the comma (``,``) and ``AS`` operators. A value
+ of 100 will be higher or equal to all operators, and -100 will be
+ lower than or equal to all operators.
+
+ .. seealso::
+
+ :ref:`faq_sql_expression_op_parenthesis` - detailed description
+ of how the SQLAlchemy SQL compiler renders parenthesis
+
+ :param is_comparison: legacy; if True, the operator will be considered
+ as a "comparison" operator, that is which evaluates to a boolean
+ true/false value, like ``==``, ``>``, etc. This flag is provided
+ so that ORM relationships can establish that the operator is a
+ comparison operator when used in a custom join condition.
+
+ Using the ``is_comparison`` parameter is superseded by using the
+ :meth:`.Operators.bool_op` method instead; this more succinct
+ operator sets this parameter automatically, but also provides
+ correct :pep:`484` typing support as the returned object will
+ express a "boolean" datatype, i.e. ``BinaryExpression[bool]``.
+
+ :param return_type: a :class:`.TypeEngine` class or object that will
+ force the return type of an expression produced by this operator
+ to be of that type. By default, operators that specify
+ :paramref:`.Operators.op.is_comparison` will resolve to
+ :class:`.Boolean`, and those that do not will be of the same
+ type as the left-hand operand.
+
+ :param python_impl: an optional Python function that can evaluate
+ two Python values in the same way as this operator works when
+ run on the database server. Useful for in-Python SQL expression
+ evaluation functions, such as for ORM hybrid attributes, and the
+ ORM "evaluator" used to match objects in a session after a multi-row
+ update or delete.
+
+ e.g.::
+
+ >>> expr = column('x').op('+', python_impl=lambda a, b: a + b)('y')
+
+ The operator for the above expression will also work for non-SQL
+ left and right objects::
+
+ >>> expr.operator(5, 10)
+ 15
+
+ .. versionadded:: 2.0
+
+
+ .. seealso::
+
+ :meth:`.Operators.bool_op`
+
+ :ref:`types_operators`
+
+ :ref:`relationship_custom_operator`
+
+ """
+ operator = custom_op(
+ opstring,
+ precedence,
+ is_comparison,
+ return_type,
+ python_impl=python_impl,
+ )
+
+ def against(other: Any) -> Operators:
+ return operator(self, other)
+
+ return against
+
+ def bool_op(
+ self,
+ opstring: str,
+ precedence: int = 0,
+ python_impl: Optional[Callable[..., Any]] = None,
+ ) -> Callable[[Any], Operators]:
+ """Return a custom boolean operator.
+
+ This method is shorthand for calling
+ :meth:`.Operators.op` and passing the
+ :paramref:`.Operators.op.is_comparison`
+ flag with True. A key advantage to using :meth:`.Operators.bool_op`
+ is that when using column constructs, the "boolean" nature of the
+ returned expression will be present for :pep:`484` purposes.
+
+ .. seealso::
+
+ :meth:`.Operators.op`
+
+ """
+ return self.op(
+ opstring,
+ precedence=precedence,
+ is_comparison=True,
+ python_impl=python_impl,
+ )
+
+ def operate(
+ self, op: OperatorType, *other: Any, **kwargs: Any
+ ) -> Operators:
+ r"""Operate on an argument.
+
+ This is the lowest level of operation, raises
+ :class:`NotImplementedError` by default.
+
+ Overriding this on a subclass can allow common
+ behavior to be applied to all operations.
+ For example, overriding :class:`.ColumnOperators`
+ to apply ``func.lower()`` to the left and right
+ side::
+
+ class MyComparator(ColumnOperators):
+ def operate(self, op, other, **kwargs):
+ return op(func.lower(self), func.lower(other), **kwargs)
+
+ :param op: Operator callable.
+ :param \*other: the 'other' side of the operation. Will
+ be a single scalar for most operations.
+ :param \**kwargs: modifiers. These may be passed by special
+ operators such as :meth:`ColumnOperators.contains`.
+
+
+ """
+ raise NotImplementedError(str(op))
+
+ __sa_operate__ = operate
+
+ def reverse_operate(
+ self, op: OperatorType, other: Any, **kwargs: Any
+ ) -> Operators:
+ """Reverse operate on an argument.
+
+ Usage is the same as :meth:`operate`.
+
+ """
+ raise NotImplementedError(str(op))
+
+
+class custom_op(OperatorType, Generic[_T]):
+ """Represent a 'custom' operator.
+
+ :class:`.custom_op` is normally instantiated when the
+ :meth:`.Operators.op` or :meth:`.Operators.bool_op` methods
+ are used to create a custom operator callable. The class can also be
+ used directly when programmatically constructing expressions. E.g.
+ to represent the "factorial" operation::
+
+ from sqlalchemy.sql import UnaryExpression
+ from sqlalchemy.sql import operators
+ from sqlalchemy import Numeric
+
+ unary = UnaryExpression(table.c.somecolumn,
+ modifier=operators.custom_op("!"),
+ type_=Numeric)
+
+
+ .. seealso::
+
+ :meth:`.Operators.op`
+
+ :meth:`.Operators.bool_op`
+
+ """
+
+ __name__ = "custom_op"
+
+ __slots__ = (
+ "opstring",
+ "precedence",
+ "is_comparison",
+ "natural_self_precedent",
+ "eager_grouping",
+ "return_type",
+ "python_impl",
+ )
+
+ def __init__(
+ self,
+ opstring: str,
+ precedence: int = 0,
+ is_comparison: bool = False,
+ return_type: Optional[
+ Union[Type[TypeEngine[_T]], TypeEngine[_T]]
+ ] = None,
+ natural_self_precedent: bool = False,
+ eager_grouping: bool = False,
+ python_impl: Optional[Callable[..., Any]] = None,
+ ):
+ self.opstring = opstring
+ self.precedence = precedence
+ self.is_comparison = is_comparison
+ self.natural_self_precedent = natural_self_precedent
+ self.eager_grouping = eager_grouping
+ self.return_type = (
+ return_type._to_instance(return_type) if return_type else None
+ )
+ self.python_impl = python_impl
+
+ def __eq__(self, other: Any) -> bool:
+ return (
+ isinstance(other, custom_op)
+ and other._hash_key() == self._hash_key()
+ )
+
+ def __hash__(self) -> int:
+ return hash(self._hash_key())
+
+ def _hash_key(self) -> Union[CacheConst, Tuple[Any, ...]]:
+ return (
+ self.__class__,
+ self.opstring,
+ self.precedence,
+ self.is_comparison,
+ self.natural_self_precedent,
+ self.eager_grouping,
+ self.return_type._static_cache_key if self.return_type else None,
+ )
+
+ @overload
+ def __call__(
+ self,
+ left: ColumnExpressionArgument[Any],
+ right: Optional[Any] = None,
+ *other: Any,
+ **kwargs: Any,
+ ) -> ColumnElement[Any]: ...
+
+ @overload
+ def __call__(
+ self,
+ left: Operators,
+ right: Optional[Any] = None,
+ *other: Any,
+ **kwargs: Any,
+ ) -> Operators: ...
+
+ def __call__(
+ self,
+ left: Any,
+ right: Optional[Any] = None,
+ *other: Any,
+ **kwargs: Any,
+ ) -> Operators:
+ if hasattr(left, "__sa_operate__"):
+ return left.operate(self, right, *other, **kwargs) # type: ignore
+ elif self.python_impl:
+ return self.python_impl(left, right, *other, **kwargs) # type: ignore # noqa: E501
+ else:
+ raise exc.InvalidRequestError(
+ f"Custom operator {self.opstring!r} can't be used with "
+ "plain Python objects unless it includes the "
+ "'python_impl' parameter."
+ )
+
+
+class ColumnOperators(Operators):
+ """Defines boolean, comparison, and other operators for
+ :class:`_expression.ColumnElement` expressions.
+
+ By default, all methods call down to
+ :meth:`.operate` or :meth:`.reverse_operate`,
+ passing in the appropriate operator function from the
+ Python builtin ``operator`` module or
+ a SQLAlchemy-specific operator function from
+ :mod:`sqlalchemy.expression.operators`. For example
+ the ``__eq__`` function::
+
+ def __eq__(self, other):
+ return self.operate(operators.eq, other)
+
+ Where ``operators.eq`` is essentially::
+
+ def eq(a, b):
+ return a == b
+
+ The core column expression unit :class:`_expression.ColumnElement`
+ overrides :meth:`.Operators.operate` and others
+ to return further :class:`_expression.ColumnElement` constructs,
+ so that the ``==`` operation above is replaced by a clause
+ construct.
+
+ .. seealso::
+
+ :ref:`types_operators`
+
+ :attr:`.TypeEngine.comparator_factory`
+
+ :class:`.ColumnOperators`
+
+ :class:`.PropComparator`
+
+ """
+
+ __slots__ = ()
+
+ timetuple: Literal[None] = None
+ """Hack, allows datetime objects to be compared on the LHS."""
+
+ if typing.TYPE_CHECKING:
+
+ def operate(
+ self, op: OperatorType, *other: Any, **kwargs: Any
+ ) -> ColumnOperators: ...
+
+ def reverse_operate(
+ self, op: OperatorType, other: Any, **kwargs: Any
+ ) -> ColumnOperators: ...
+
+ def __lt__(self, other: Any) -> ColumnOperators:
+ """Implement the ``<`` operator.
+
+ In a column context, produces the clause ``a < b``.
+
+ """
+ return self.operate(lt, other)
+
+ def __le__(self, other: Any) -> ColumnOperators:
+ """Implement the ``<=`` operator.
+
+ In a column context, produces the clause ``a <= b``.
+
+ """
+ return self.operate(le, other)
+
+ # ColumnOperators defines an __eq__ so it must explicitly declare also
+ # an hash or it's set to None by python:
+ # https://docs.python.org/3/reference/datamodel.html#object.__hash__
+ if TYPE_CHECKING:
+
+ def __hash__(self) -> int: ...
+
+ else:
+ __hash__ = Operators.__hash__
+
+ def __eq__(self, other: Any) -> ColumnOperators: # type: ignore[override]
+ """Implement the ``==`` operator.
+
+ In a column context, produces the clause ``a = b``.
+ If the target is ``None``, produces ``a IS NULL``.
+
+ """
+ return self.operate(eq, other)
+
+ def __ne__(self, other: Any) -> ColumnOperators: # type: ignore[override]
+ """Implement the ``!=`` operator.
+
+ In a column context, produces the clause ``a != b``.
+ If the target is ``None``, produces ``a IS NOT NULL``.
+
+ """
+ return self.operate(ne, other)
+
+ def is_distinct_from(self, other: Any) -> ColumnOperators:
+ """Implement the ``IS DISTINCT FROM`` operator.
+
+ Renders "a IS DISTINCT FROM b" on most platforms;
+ on some such as SQLite may render "a IS NOT b".
+
+ """
+ return self.operate(is_distinct_from, other)
+
+ def is_not_distinct_from(self, other: Any) -> ColumnOperators:
+ """Implement the ``IS NOT DISTINCT FROM`` operator.
+
+ Renders "a IS NOT DISTINCT FROM b" on most platforms;
+ on some such as SQLite may render "a IS b".
+
+ .. versionchanged:: 1.4 The ``is_not_distinct_from()`` operator is
+ renamed from ``isnot_distinct_from()`` in previous releases.
+ The previous name remains available for backwards compatibility.
+
+ """
+ return self.operate(is_not_distinct_from, other)
+
+ # deprecated 1.4; see #5435
+ if TYPE_CHECKING:
+
+ def isnot_distinct_from(self, other: Any) -> ColumnOperators: ...
+
+ else:
+ isnot_distinct_from = is_not_distinct_from
+
+ def __gt__(self, other: Any) -> ColumnOperators:
+ """Implement the ``>`` operator.
+
+ In a column context, produces the clause ``a > b``.
+
+ """
+ return self.operate(gt, other)
+
+ def __ge__(self, other: Any) -> ColumnOperators:
+ """Implement the ``>=`` operator.
+
+ In a column context, produces the clause ``a >= b``.
+
+ """
+ return self.operate(ge, other)
+
+ def __neg__(self) -> ColumnOperators:
+ """Implement the ``-`` operator.
+
+ In a column context, produces the clause ``-a``.
+
+ """
+ return self.operate(neg)
+
+ def __contains__(self, other: Any) -> ColumnOperators:
+ return self.operate(contains, other)
+
+ def __getitem__(self, index: Any) -> ColumnOperators:
+ """Implement the [] operator.
+
+ This can be used by some database-specific types
+ such as PostgreSQL ARRAY and HSTORE.
+
+ """
+ return self.operate(getitem, index)
+
+ def __lshift__(self, other: Any) -> ColumnOperators:
+ """implement the << operator.
+
+ Not used by SQLAlchemy core, this is provided
+ for custom operator systems which want to use
+ << as an extension point.
+ """
+ return self.operate(lshift, other)
+
+ def __rshift__(self, other: Any) -> ColumnOperators:
+ """implement the >> operator.
+
+ Not used by SQLAlchemy core, this is provided
+ for custom operator systems which want to use
+ >> as an extension point.
+ """
+ return self.operate(rshift, other)
+
+ def concat(self, other: Any) -> ColumnOperators:
+ """Implement the 'concat' operator.
+
+ In a column context, produces the clause ``a || b``,
+ or uses the ``concat()`` operator on MySQL.
+
+ """
+ return self.operate(concat_op, other)
+
+ def _rconcat(self, other: Any) -> ColumnOperators:
+ """Implement an 'rconcat' operator.
+
+ this is for internal use at the moment
+
+ .. versionadded:: 1.4.40
+
+ """
+ return self.reverse_operate(concat_op, other)
+
+ def like(
+ self, other: Any, escape: Optional[str] = None
+ ) -> ColumnOperators:
+ r"""Implement the ``like`` operator.
+
+ In a column context, produces the expression::
+
+ a LIKE other
+
+ E.g.::
+
+ stmt = select(sometable).\
+ where(sometable.c.column.like("%foobar%"))
+
+ :param other: expression to be compared
+ :param escape: optional escape character, renders the ``ESCAPE``
+ keyword, e.g.::
+
+ somecolumn.like("foo/%bar", escape="/")
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.ilike`
+
+ """
+ return self.operate(like_op, other, escape=escape)
+
+ def ilike(
+ self, other: Any, escape: Optional[str] = None
+ ) -> ColumnOperators:
+ r"""Implement the ``ilike`` operator, e.g. case insensitive LIKE.
+
+ In a column context, produces an expression either of the form::
+
+ lower(a) LIKE lower(other)
+
+ Or on backends that support the ILIKE operator::
+
+ a ILIKE other
+
+ E.g.::
+
+ stmt = select(sometable).\
+ where(sometable.c.column.ilike("%foobar%"))
+
+ :param other: expression to be compared
+ :param escape: optional escape character, renders the ``ESCAPE``
+ keyword, e.g.::
+
+ somecolumn.ilike("foo/%bar", escape="/")
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.like`
+
+ """
+ return self.operate(ilike_op, other, escape=escape)
+
+ def bitwise_xor(self, other: Any) -> ColumnOperators:
+ """Produce a bitwise XOR operation, typically via the ``^``
+ operator, or ``#`` for PostgreSQL.
+
+ .. versionadded:: 2.0.2
+
+ .. seealso::
+
+ :ref:`operators_bitwise`
+
+ """
+
+ return self.operate(bitwise_xor_op, other)
+
+ def bitwise_or(self, other: Any) -> ColumnOperators:
+ """Produce a bitwise OR operation, typically via the ``|``
+ operator.
+
+ .. versionadded:: 2.0.2
+
+ .. seealso::
+
+ :ref:`operators_bitwise`
+
+ """
+
+ return self.operate(bitwise_or_op, other)
+
+ def bitwise_and(self, other: Any) -> ColumnOperators:
+ """Produce a bitwise AND operation, typically via the ``&``
+ operator.
+
+ .. versionadded:: 2.0.2
+
+ .. seealso::
+
+ :ref:`operators_bitwise`
+
+ """
+
+ return self.operate(bitwise_and_op, other)
+
+ def bitwise_not(self) -> ColumnOperators:
+ """Produce a bitwise NOT operation, typically via the ``~``
+ operator.
+
+ .. versionadded:: 2.0.2
+
+ .. seealso::
+
+ :ref:`operators_bitwise`
+
+ """
+
+ return self.operate(bitwise_not_op)
+
+ def bitwise_lshift(self, other: Any) -> ColumnOperators:
+ """Produce a bitwise LSHIFT operation, typically via the ``<<``
+ operator.
+
+ .. versionadded:: 2.0.2
+
+ .. seealso::
+
+ :ref:`operators_bitwise`
+
+ """
+
+ return self.operate(bitwise_lshift_op, other)
+
+ def bitwise_rshift(self, other: Any) -> ColumnOperators:
+ """Produce a bitwise RSHIFT operation, typically via the ``>>``
+ operator.
+
+ .. versionadded:: 2.0.2
+
+ .. seealso::
+
+ :ref:`operators_bitwise`
+
+ """
+
+ return self.operate(bitwise_rshift_op, other)
+
+ def in_(self, other: Any) -> ColumnOperators:
+ """Implement the ``in`` operator.
+
+ In a column context, produces the clause ``column IN <other>``.
+
+ The given parameter ``other`` may be:
+
+ * A list of literal values, e.g.::
+
+ stmt.where(column.in_([1, 2, 3]))
+
+ In this calling form, the list of items is converted to a set of
+ bound parameters the same length as the list given::
+
+ WHERE COL IN (?, ?, ?)
+
+ * A list of tuples may be provided if the comparison is against a
+ :func:`.tuple_` containing multiple expressions::
+
+ from sqlalchemy import tuple_
+ stmt.where(tuple_(col1, col2).in_([(1, 10), (2, 20), (3, 30)]))
+
+ * An empty list, e.g.::
+
+ stmt.where(column.in_([]))
+
+ In this calling form, the expression renders an "empty set"
+ expression. These expressions are tailored to individual backends
+ and are generally trying to get an empty SELECT statement as a
+ subquery. Such as on SQLite, the expression is::
+
+ WHERE col IN (SELECT 1 FROM (SELECT 1) WHERE 1!=1)
+
+ .. versionchanged:: 1.4 empty IN expressions now use an
+ execution-time generated SELECT subquery in all cases.
+
+ * A bound parameter, e.g. :func:`.bindparam`, may be used if it
+ includes the :paramref:`.bindparam.expanding` flag::
+
+ stmt.where(column.in_(bindparam('value', expanding=True)))
+
+ In this calling form, the expression renders a special non-SQL
+ placeholder expression that looks like::
+
+ WHERE COL IN ([EXPANDING_value])
+
+ This placeholder expression is intercepted at statement execution
+ time to be converted into the variable number of bound parameter
+ form illustrated earlier. If the statement were executed as::
+
+ connection.execute(stmt, {"value": [1, 2, 3]})
+
+ The database would be passed a bound parameter for each value::
+
+ WHERE COL IN (?, ?, ?)
+
+ .. versionadded:: 1.2 added "expanding" bound parameters
+
+ If an empty list is passed, a special "empty list" expression,
+ which is specific to the database in use, is rendered. On
+ SQLite this would be::
+
+ WHERE COL IN (SELECT 1 FROM (SELECT 1) WHERE 1!=1)
+
+ .. versionadded:: 1.3 "expanding" bound parameters now support
+ empty lists
+
+ * a :func:`_expression.select` construct, which is usually a
+ correlated scalar select::
+
+ stmt.where(
+ column.in_(
+ select(othertable.c.y).
+ where(table.c.x == othertable.c.x)
+ )
+ )
+
+ In this calling form, :meth:`.ColumnOperators.in_` renders as given::
+
+ WHERE COL IN (SELECT othertable.y
+ FROM othertable WHERE othertable.x = table.x)
+
+ :param other: a list of literals, a :func:`_expression.select`
+ construct, or a :func:`.bindparam` construct that includes the
+ :paramref:`.bindparam.expanding` flag set to True.
+
+ """
+ return self.operate(in_op, other)
+
+ def not_in(self, other: Any) -> ColumnOperators:
+ """implement the ``NOT IN`` operator.
+
+ This is equivalent to using negation with
+ :meth:`.ColumnOperators.in_`, i.e. ``~x.in_(y)``.
+
+ In the case that ``other`` is an empty sequence, the compiler
+ produces an "empty not in" expression. This defaults to the
+ expression "1 = 1" to produce true in all cases. The
+ :paramref:`_sa.create_engine.empty_in_strategy` may be used to
+ alter this behavior.
+
+ .. versionchanged:: 1.4 The ``not_in()`` operator is renamed from
+ ``notin_()`` in previous releases. The previous name remains
+ available for backwards compatibility.
+
+ .. versionchanged:: 1.2 The :meth:`.ColumnOperators.in_` and
+ :meth:`.ColumnOperators.not_in` operators
+ now produce a "static" expression for an empty IN sequence
+ by default.
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.in_`
+
+ """
+ return self.operate(not_in_op, other)
+
+ # deprecated 1.4; see #5429
+ if TYPE_CHECKING:
+
+ def notin_(self, other: Any) -> ColumnOperators: ...
+
+ else:
+ notin_ = not_in
+
+ def not_like(
+ self, other: Any, escape: Optional[str] = None
+ ) -> ColumnOperators:
+ """implement the ``NOT LIKE`` operator.
+
+ This is equivalent to using negation with
+ :meth:`.ColumnOperators.like`, i.e. ``~x.like(y)``.
+
+ .. versionchanged:: 1.4 The ``not_like()`` operator is renamed from
+ ``notlike()`` in previous releases. The previous name remains
+ available for backwards compatibility.
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.like`
+
+ """
+ return self.operate(not_like_op, other, escape=escape)
+
+ # deprecated 1.4; see #5435
+ if TYPE_CHECKING:
+
+ def notlike(
+ self, other: Any, escape: Optional[str] = None
+ ) -> ColumnOperators: ...
+
+ else:
+ notlike = not_like
+
+ def not_ilike(
+ self, other: Any, escape: Optional[str] = None
+ ) -> ColumnOperators:
+ """implement the ``NOT ILIKE`` operator.
+
+ This is equivalent to using negation with
+ :meth:`.ColumnOperators.ilike`, i.e. ``~x.ilike(y)``.
+
+ .. versionchanged:: 1.4 The ``not_ilike()`` operator is renamed from
+ ``notilike()`` in previous releases. The previous name remains
+ available for backwards compatibility.
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.ilike`
+
+ """
+ return self.operate(not_ilike_op, other, escape=escape)
+
+ # deprecated 1.4; see #5435
+ if TYPE_CHECKING:
+
+ def notilike(
+ self, other: Any, escape: Optional[str] = None
+ ) -> ColumnOperators: ...
+
+ else:
+ notilike = not_ilike
+
+ def is_(self, other: Any) -> ColumnOperators:
+ """Implement the ``IS`` operator.
+
+ Normally, ``IS`` is generated automatically when comparing to a
+ value of ``None``, which resolves to ``NULL``. However, explicit
+ usage of ``IS`` may be desirable if comparing to boolean values
+ on certain platforms.
+
+ .. seealso:: :meth:`.ColumnOperators.is_not`
+
+ """
+ return self.operate(is_, other)
+
+ def is_not(self, other: Any) -> ColumnOperators:
+ """Implement the ``IS NOT`` operator.
+
+ Normally, ``IS NOT`` is generated automatically when comparing to a
+ value of ``None``, which resolves to ``NULL``. However, explicit
+ usage of ``IS NOT`` may be desirable if comparing to boolean values
+ on certain platforms.
+
+ .. versionchanged:: 1.4 The ``is_not()`` operator is renamed from
+ ``isnot()`` in previous releases. The previous name remains
+ available for backwards compatibility.
+
+ .. seealso:: :meth:`.ColumnOperators.is_`
+
+ """
+ return self.operate(is_not, other)
+
+ # deprecated 1.4; see #5429
+ if TYPE_CHECKING:
+
+ def isnot(self, other: Any) -> ColumnOperators: ...
+
+ else:
+ isnot = is_not
+
+ def startswith(
+ self,
+ other: Any,
+ escape: Optional[str] = None,
+ autoescape: bool = False,
+ ) -> ColumnOperators:
+ r"""Implement the ``startswith`` operator.
+
+ Produces a LIKE expression that tests against a match for the start
+ of a string value::
+
+ column LIKE <other> || '%'
+
+ E.g.::
+
+ stmt = select(sometable).\
+ where(sometable.c.column.startswith("foobar"))
+
+ Since the operator uses ``LIKE``, wildcard characters
+ ``"%"`` and ``"_"`` that are present inside the <other> expression
+ will behave like wildcards as well. For literal string
+ values, the :paramref:`.ColumnOperators.startswith.autoescape` flag
+ may be set to ``True`` to apply escaping to occurrences of these
+ characters within the string value so that they match as themselves
+ and not as wildcard characters. Alternatively, the
+ :paramref:`.ColumnOperators.startswith.escape` parameter will establish
+ a given character as an escape character which can be of use when
+ the target expression is not a literal string.
+
+ :param other: expression to be compared. This is usually a plain
+ string value, but can also be an arbitrary SQL expression. LIKE
+ wildcard characters ``%`` and ``_`` are not escaped by default unless
+ the :paramref:`.ColumnOperators.startswith.autoescape` flag is
+ set to True.
+
+ :param autoescape: boolean; when True, establishes an escape character
+ within the LIKE expression, then applies it to all occurrences of
+ ``"%"``, ``"_"`` and the escape character itself within the
+ comparison value, which is assumed to be a literal string and not a
+ SQL expression.
+
+ An expression such as::
+
+ somecolumn.startswith("foo%bar", autoescape=True)
+
+ Will render as::
+
+ somecolumn LIKE :param || '%' ESCAPE '/'
+
+ With the value of ``:param`` as ``"foo/%bar"``.
+
+ :param escape: a character which when given will render with the
+ ``ESCAPE`` keyword to establish that character as the escape
+ character. This character can then be placed preceding occurrences
+ of ``%`` and ``_`` to allow them to act as themselves and not
+ wildcard characters.
+
+ An expression such as::
+
+ somecolumn.startswith("foo/%bar", escape="^")
+
+ Will render as::
+
+ somecolumn LIKE :param || '%' ESCAPE '^'
+
+ The parameter may also be combined with
+ :paramref:`.ColumnOperators.startswith.autoescape`::
+
+ somecolumn.startswith("foo%bar^bat", escape="^", autoescape=True)
+
+ Where above, the given literal parameter will be converted to
+ ``"foo^%bar^^bat"`` before being passed to the database.
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.endswith`
+
+ :meth:`.ColumnOperators.contains`
+
+ :meth:`.ColumnOperators.like`
+
+ """
+ return self.operate(
+ startswith_op, other, escape=escape, autoescape=autoescape
+ )
+
+ def istartswith(
+ self,
+ other: Any,
+ escape: Optional[str] = None,
+ autoescape: bool = False,
+ ) -> ColumnOperators:
+ r"""Implement the ``istartswith`` operator, e.g. case insensitive
+ version of :meth:`.ColumnOperators.startswith`.
+
+ Produces a LIKE expression that tests against an insensitive
+ match for the start of a string value::
+
+ lower(column) LIKE lower(<other>) || '%'
+
+ E.g.::
+
+ stmt = select(sometable).\
+ where(sometable.c.column.istartswith("foobar"))
+
+ Since the operator uses ``LIKE``, wildcard characters
+ ``"%"`` and ``"_"`` that are present inside the <other> expression
+ will behave like wildcards as well. For literal string
+ values, the :paramref:`.ColumnOperators.istartswith.autoescape` flag
+ may be set to ``True`` to apply escaping to occurrences of these
+ characters within the string value so that they match as themselves
+ and not as wildcard characters. Alternatively, the
+ :paramref:`.ColumnOperators.istartswith.escape` parameter will
+ establish a given character as an escape character which can be of
+ use when the target expression is not a literal string.
+
+ :param other: expression to be compared. This is usually a plain
+ string value, but can also be an arbitrary SQL expression. LIKE
+ wildcard characters ``%`` and ``_`` are not escaped by default unless
+ the :paramref:`.ColumnOperators.istartswith.autoescape` flag is
+ set to True.
+
+ :param autoescape: boolean; when True, establishes an escape character
+ within the LIKE expression, then applies it to all occurrences of
+ ``"%"``, ``"_"`` and the escape character itself within the
+ comparison value, which is assumed to be a literal string and not a
+ SQL expression.
+
+ An expression such as::
+
+ somecolumn.istartswith("foo%bar", autoescape=True)
+
+ Will render as::
+
+ lower(somecolumn) LIKE lower(:param) || '%' ESCAPE '/'
+
+ With the value of ``:param`` as ``"foo/%bar"``.
+
+ :param escape: a character which when given will render with the
+ ``ESCAPE`` keyword to establish that character as the escape
+ character. This character can then be placed preceding occurrences
+ of ``%`` and ``_`` to allow them to act as themselves and not
+ wildcard characters.
+
+ An expression such as::
+
+ somecolumn.istartswith("foo/%bar", escape="^")
+
+ Will render as::
+
+ lower(somecolumn) LIKE lower(:param) || '%' ESCAPE '^'
+
+ The parameter may also be combined with
+ :paramref:`.ColumnOperators.istartswith.autoescape`::
+
+ somecolumn.istartswith("foo%bar^bat", escape="^", autoescape=True)
+
+ Where above, the given literal parameter will be converted to
+ ``"foo^%bar^^bat"`` before being passed to the database.
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.startswith`
+ """
+ return self.operate(
+ istartswith_op, other, escape=escape, autoescape=autoescape
+ )
+
+ def endswith(
+ self,
+ other: Any,
+ escape: Optional[str] = None,
+ autoescape: bool = False,
+ ) -> ColumnOperators:
+ r"""Implement the 'endswith' operator.
+
+ Produces a LIKE expression that tests against a match for the end
+ of a string value::
+
+ column LIKE '%' || <other>
+
+ E.g.::
+
+ stmt = select(sometable).\
+ where(sometable.c.column.endswith("foobar"))
+
+ Since the operator uses ``LIKE``, wildcard characters
+ ``"%"`` and ``"_"`` that are present inside the <other> expression
+ will behave like wildcards as well. For literal string
+ values, the :paramref:`.ColumnOperators.endswith.autoescape` flag
+ may be set to ``True`` to apply escaping to occurrences of these
+ characters within the string value so that they match as themselves
+ and not as wildcard characters. Alternatively, the
+ :paramref:`.ColumnOperators.endswith.escape` parameter will establish
+ a given character as an escape character which can be of use when
+ the target expression is not a literal string.
+
+ :param other: expression to be compared. This is usually a plain
+ string value, but can also be an arbitrary SQL expression. LIKE
+ wildcard characters ``%`` and ``_`` are not escaped by default unless
+ the :paramref:`.ColumnOperators.endswith.autoescape` flag is
+ set to True.
+
+ :param autoescape: boolean; when True, establishes an escape character
+ within the LIKE expression, then applies it to all occurrences of
+ ``"%"``, ``"_"`` and the escape character itself within the
+ comparison value, which is assumed to be a literal string and not a
+ SQL expression.
+
+ An expression such as::
+
+ somecolumn.endswith("foo%bar", autoescape=True)
+
+ Will render as::
+
+ somecolumn LIKE '%' || :param ESCAPE '/'
+
+ With the value of ``:param`` as ``"foo/%bar"``.
+
+ :param escape: a character which when given will render with the
+ ``ESCAPE`` keyword to establish that character as the escape
+ character. This character can then be placed preceding occurrences
+ of ``%`` and ``_`` to allow them to act as themselves and not
+ wildcard characters.
+
+ An expression such as::
+
+ somecolumn.endswith("foo/%bar", escape="^")
+
+ Will render as::
+
+ somecolumn LIKE '%' || :param ESCAPE '^'
+
+ The parameter may also be combined with
+ :paramref:`.ColumnOperators.endswith.autoescape`::
+
+ somecolumn.endswith("foo%bar^bat", escape="^", autoescape=True)
+
+ Where above, the given literal parameter will be converted to
+ ``"foo^%bar^^bat"`` before being passed to the database.
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.startswith`
+
+ :meth:`.ColumnOperators.contains`
+
+ :meth:`.ColumnOperators.like`
+
+ """
+ return self.operate(
+ endswith_op, other, escape=escape, autoescape=autoescape
+ )
+
+ def iendswith(
+ self,
+ other: Any,
+ escape: Optional[str] = None,
+ autoescape: bool = False,
+ ) -> ColumnOperators:
+ r"""Implement the ``iendswith`` operator, e.g. case insensitive
+ version of :meth:`.ColumnOperators.endswith`.
+
+ Produces a LIKE expression that tests against an insensitive match
+ for the end of a string value::
+
+ lower(column) LIKE '%' || lower(<other>)
+
+ E.g.::
+
+ stmt = select(sometable).\
+ where(sometable.c.column.iendswith("foobar"))
+
+ Since the operator uses ``LIKE``, wildcard characters
+ ``"%"`` and ``"_"`` that are present inside the <other> expression
+ will behave like wildcards as well. For literal string
+ values, the :paramref:`.ColumnOperators.iendswith.autoescape` flag
+ may be set to ``True`` to apply escaping to occurrences of these
+ characters within the string value so that they match as themselves
+ and not as wildcard characters. Alternatively, the
+ :paramref:`.ColumnOperators.iendswith.escape` parameter will establish
+ a given character as an escape character which can be of use when
+ the target expression is not a literal string.
+
+ :param other: expression to be compared. This is usually a plain
+ string value, but can also be an arbitrary SQL expression. LIKE
+ wildcard characters ``%`` and ``_`` are not escaped by default unless
+ the :paramref:`.ColumnOperators.iendswith.autoescape` flag is
+ set to True.
+
+ :param autoescape: boolean; when True, establishes an escape character
+ within the LIKE expression, then applies it to all occurrences of
+ ``"%"``, ``"_"`` and the escape character itself within the
+ comparison value, which is assumed to be a literal string and not a
+ SQL expression.
+
+ An expression such as::
+
+ somecolumn.iendswith("foo%bar", autoescape=True)
+
+ Will render as::
+
+ lower(somecolumn) LIKE '%' || lower(:param) ESCAPE '/'
+
+ With the value of ``:param`` as ``"foo/%bar"``.
+
+ :param escape: a character which when given will render with the
+ ``ESCAPE`` keyword to establish that character as the escape
+ character. This character can then be placed preceding occurrences
+ of ``%`` and ``_`` to allow them to act as themselves and not
+ wildcard characters.
+
+ An expression such as::
+
+ somecolumn.iendswith("foo/%bar", escape="^")
+
+ Will render as::
+
+ lower(somecolumn) LIKE '%' || lower(:param) ESCAPE '^'
+
+ The parameter may also be combined with
+ :paramref:`.ColumnOperators.iendswith.autoescape`::
+
+ somecolumn.endswith("foo%bar^bat", escape="^", autoescape=True)
+
+ Where above, the given literal parameter will be converted to
+ ``"foo^%bar^^bat"`` before being passed to the database.
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.endswith`
+ """
+ return self.operate(
+ iendswith_op, other, escape=escape, autoescape=autoescape
+ )
+
+ def contains(self, other: Any, **kw: Any) -> ColumnOperators:
+ r"""Implement the 'contains' operator.
+
+ Produces a LIKE expression that tests against a match for the middle
+ of a string value::
+
+ column LIKE '%' || <other> || '%'
+
+ E.g.::
+
+ stmt = select(sometable).\
+ where(sometable.c.column.contains("foobar"))
+
+ Since the operator uses ``LIKE``, wildcard characters
+ ``"%"`` and ``"_"`` that are present inside the <other> expression
+ will behave like wildcards as well. For literal string
+ values, the :paramref:`.ColumnOperators.contains.autoescape` flag
+ may be set to ``True`` to apply escaping to occurrences of these
+ characters within the string value so that they match as themselves
+ and not as wildcard characters. Alternatively, the
+ :paramref:`.ColumnOperators.contains.escape` parameter will establish
+ a given character as an escape character which can be of use when
+ the target expression is not a literal string.
+
+ :param other: expression to be compared. This is usually a plain
+ string value, but can also be an arbitrary SQL expression. LIKE
+ wildcard characters ``%`` and ``_`` are not escaped by default unless
+ the :paramref:`.ColumnOperators.contains.autoescape` flag is
+ set to True.
+
+ :param autoescape: boolean; when True, establishes an escape character
+ within the LIKE expression, then applies it to all occurrences of
+ ``"%"``, ``"_"`` and the escape character itself within the
+ comparison value, which is assumed to be a literal string and not a
+ SQL expression.
+
+ An expression such as::
+
+ somecolumn.contains("foo%bar", autoescape=True)
+
+ Will render as::
+
+ somecolumn LIKE '%' || :param || '%' ESCAPE '/'
+
+ With the value of ``:param`` as ``"foo/%bar"``.
+
+ :param escape: a character which when given will render with the
+ ``ESCAPE`` keyword to establish that character as the escape
+ character. This character can then be placed preceding occurrences
+ of ``%`` and ``_`` to allow them to act as themselves and not
+ wildcard characters.
+
+ An expression such as::
+
+ somecolumn.contains("foo/%bar", escape="^")
+
+ Will render as::
+
+ somecolumn LIKE '%' || :param || '%' ESCAPE '^'
+
+ The parameter may also be combined with
+ :paramref:`.ColumnOperators.contains.autoescape`::
+
+ somecolumn.contains("foo%bar^bat", escape="^", autoescape=True)
+
+ Where above, the given literal parameter will be converted to
+ ``"foo^%bar^^bat"`` before being passed to the database.
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.startswith`
+
+ :meth:`.ColumnOperators.endswith`
+
+ :meth:`.ColumnOperators.like`
+
+
+ """
+ return self.operate(contains_op, other, **kw)
+
+ def icontains(self, other: Any, **kw: Any) -> ColumnOperators:
+ r"""Implement the ``icontains`` operator, e.g. case insensitive
+ version of :meth:`.ColumnOperators.contains`.
+
+ Produces a LIKE expression that tests against an insensitive match
+ for the middle of a string value::
+
+ lower(column) LIKE '%' || lower(<other>) || '%'
+
+ E.g.::
+
+ stmt = select(sometable).\
+ where(sometable.c.column.icontains("foobar"))
+
+ Since the operator uses ``LIKE``, wildcard characters
+ ``"%"`` and ``"_"`` that are present inside the <other> expression
+ will behave like wildcards as well. For literal string
+ values, the :paramref:`.ColumnOperators.icontains.autoescape` flag
+ may be set to ``True`` to apply escaping to occurrences of these
+ characters within the string value so that they match as themselves
+ and not as wildcard characters. Alternatively, the
+ :paramref:`.ColumnOperators.icontains.escape` parameter will establish
+ a given character as an escape character which can be of use when
+ the target expression is not a literal string.
+
+ :param other: expression to be compared. This is usually a plain
+ string value, but can also be an arbitrary SQL expression. LIKE
+ wildcard characters ``%`` and ``_`` are not escaped by default unless
+ the :paramref:`.ColumnOperators.icontains.autoescape` flag is
+ set to True.
+
+ :param autoescape: boolean; when True, establishes an escape character
+ within the LIKE expression, then applies it to all occurrences of
+ ``"%"``, ``"_"`` and the escape character itself within the
+ comparison value, which is assumed to be a literal string and not a
+ SQL expression.
+
+ An expression such as::
+
+ somecolumn.icontains("foo%bar", autoescape=True)
+
+ Will render as::
+
+ lower(somecolumn) LIKE '%' || lower(:param) || '%' ESCAPE '/'
+
+ With the value of ``:param`` as ``"foo/%bar"``.
+
+ :param escape: a character which when given will render with the
+ ``ESCAPE`` keyword to establish that character as the escape
+ character. This character can then be placed preceding occurrences
+ of ``%`` and ``_`` to allow them to act as themselves and not
+ wildcard characters.
+
+ An expression such as::
+
+ somecolumn.icontains("foo/%bar", escape="^")
+
+ Will render as::
+
+ lower(somecolumn) LIKE '%' || lower(:param) || '%' ESCAPE '^'
+
+ The parameter may also be combined with
+ :paramref:`.ColumnOperators.contains.autoescape`::
+
+ somecolumn.icontains("foo%bar^bat", escape="^", autoescape=True)
+
+ Where above, the given literal parameter will be converted to
+ ``"foo^%bar^^bat"`` before being passed to the database.
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.contains`
+
+ """
+ return self.operate(icontains_op, other, **kw)
+
+ def match(self, other: Any, **kwargs: Any) -> ColumnOperators:
+ """Implements a database-specific 'match' operator.
+
+ :meth:`_sql.ColumnOperators.match` attempts to resolve to
+ a MATCH-like function or operator provided by the backend.
+ Examples include:
+
+ * PostgreSQL - renders ``x @@ plainto_tsquery(y)``
+
+ .. versionchanged:: 2.0 ``plainto_tsquery()`` is used instead
+ of ``to_tsquery()`` for PostgreSQL now; for compatibility with
+ other forms, see :ref:`postgresql_match`.
+
+
+ * MySQL - renders ``MATCH (x) AGAINST (y IN BOOLEAN MODE)``
+
+ .. seealso::
+
+ :class:`_mysql.match` - MySQL specific construct with
+ additional features.
+
+ * Oracle - renders ``CONTAINS(x, y)``
+ * other backends may provide special implementations.
+ * Backends without any special implementation will emit
+ the operator as "MATCH". This is compatible with SQLite, for
+ example.
+
+ """
+ return self.operate(match_op, other, **kwargs)
+
+ def regexp_match(
+ self, pattern: Any, flags: Optional[str] = None
+ ) -> ColumnOperators:
+ """Implements a database-specific 'regexp match' operator.
+
+ E.g.::
+
+ stmt = select(table.c.some_column).where(
+ table.c.some_column.regexp_match('^(b|c)')
+ )
+
+ :meth:`_sql.ColumnOperators.regexp_match` attempts to resolve to
+ a REGEXP-like function or operator provided by the backend, however
+ the specific regular expression syntax and flags available are
+ **not backend agnostic**.
+
+ Examples include:
+
+ * PostgreSQL - renders ``x ~ y`` or ``x !~ y`` when negated.
+ * Oracle - renders ``REGEXP_LIKE(x, y)``
+ * SQLite - uses SQLite's ``REGEXP`` placeholder operator and calls into
+ the Python ``re.match()`` builtin.
+ * other backends may provide special implementations.
+ * Backends without any special implementation will emit
+ the operator as "REGEXP" or "NOT REGEXP". This is compatible with
+ SQLite and MySQL, for example.
+
+ Regular expression support is currently implemented for Oracle,
+ PostgreSQL, MySQL and MariaDB. Partial support is available for
+ SQLite. Support among third-party dialects may vary.
+
+ :param pattern: The regular expression pattern string or column
+ clause.
+ :param flags: Any regular expression string flags to apply, passed as
+ plain Python string only. These flags are backend specific.
+ Some backends, like PostgreSQL and MariaDB, may alternatively
+ specify the flags as part of the pattern.
+ When using the ignore case flag 'i' in PostgreSQL, the ignore case
+ regexp match operator ``~*`` or ``!~*`` will be used.
+
+ .. versionadded:: 1.4
+
+ .. versionchanged:: 1.4.48, 2.0.18 Note that due to an implementation
+ error, the "flags" parameter previously accepted SQL expression
+ objects such as column expressions in addition to plain Python
+ strings. This implementation did not work correctly with caching
+ and was removed; strings only should be passed for the "flags"
+ parameter, as these flags are rendered as literal inline values
+ within SQL expressions.
+
+ .. seealso::
+
+ :meth:`_sql.ColumnOperators.regexp_replace`
+
+
+ """
+ return self.operate(regexp_match_op, pattern, flags=flags)
+
+ def regexp_replace(
+ self, pattern: Any, replacement: Any, flags: Optional[str] = None
+ ) -> ColumnOperators:
+ """Implements a database-specific 'regexp replace' operator.
+
+ E.g.::
+
+ stmt = select(
+ table.c.some_column.regexp_replace(
+ 'b(..)',
+ 'X\1Y',
+ flags='g'
+ )
+ )
+
+ :meth:`_sql.ColumnOperators.regexp_replace` attempts to resolve to
+ a REGEXP_REPLACE-like function provided by the backend, that
+ usually emit the function ``REGEXP_REPLACE()``. However,
+ the specific regular expression syntax and flags available are
+ **not backend agnostic**.
+
+ Regular expression replacement support is currently implemented for
+ Oracle, PostgreSQL, MySQL 8 or greater and MariaDB. Support among
+ third-party dialects may vary.
+
+ :param pattern: The regular expression pattern string or column
+ clause.
+ :param pattern: The replacement string or column clause.
+ :param flags: Any regular expression string flags to apply, passed as
+ plain Python string only. These flags are backend specific.
+ Some backends, like PostgreSQL and MariaDB, may alternatively
+ specify the flags as part of the pattern.
+
+ .. versionadded:: 1.4
+
+ .. versionchanged:: 1.4.48, 2.0.18 Note that due to an implementation
+ error, the "flags" parameter previously accepted SQL expression
+ objects such as column expressions in addition to plain Python
+ strings. This implementation did not work correctly with caching
+ and was removed; strings only should be passed for the "flags"
+ parameter, as these flags are rendered as literal inline values
+ within SQL expressions.
+
+
+ .. seealso::
+
+ :meth:`_sql.ColumnOperators.regexp_match`
+
+ """
+ return self.operate(
+ regexp_replace_op,
+ pattern,
+ replacement=replacement,
+ flags=flags,
+ )
+
+ def desc(self) -> ColumnOperators:
+ """Produce a :func:`_expression.desc` clause against the
+ parent object."""
+ return self.operate(desc_op)
+
+ def asc(self) -> ColumnOperators:
+ """Produce a :func:`_expression.asc` clause against the
+ parent object."""
+ return self.operate(asc_op)
+
+ def nulls_first(self) -> ColumnOperators:
+ """Produce a :func:`_expression.nulls_first` clause against the
+ parent object.
+
+ .. versionchanged:: 1.4 The ``nulls_first()`` operator is
+ renamed from ``nullsfirst()`` in previous releases.
+ The previous name remains available for backwards compatibility.
+ """
+ return self.operate(nulls_first_op)
+
+ # deprecated 1.4; see #5435
+ if TYPE_CHECKING:
+
+ def nullsfirst(self) -> ColumnOperators: ...
+
+ else:
+ nullsfirst = nulls_first
+
+ def nulls_last(self) -> ColumnOperators:
+ """Produce a :func:`_expression.nulls_last` clause against the
+ parent object.
+
+ .. versionchanged:: 1.4 The ``nulls_last()`` operator is
+ renamed from ``nullslast()`` in previous releases.
+ The previous name remains available for backwards compatibility.
+ """
+ return self.operate(nulls_last_op)
+
+ # deprecated 1.4; see #5429
+ if TYPE_CHECKING:
+
+ def nullslast(self) -> ColumnOperators: ...
+
+ else:
+ nullslast = nulls_last
+
+ def collate(self, collation: str) -> ColumnOperators:
+ """Produce a :func:`_expression.collate` clause against
+ the parent object, given the collation string.
+
+ .. seealso::
+
+ :func:`_expression.collate`
+
+ """
+ return self.operate(collate, collation)
+
+ def __radd__(self, other: Any) -> ColumnOperators:
+ """Implement the ``+`` operator in reverse.
+
+ See :meth:`.ColumnOperators.__add__`.
+
+ """
+ return self.reverse_operate(add, other)
+
+ def __rsub__(self, other: Any) -> ColumnOperators:
+ """Implement the ``-`` operator in reverse.
+
+ See :meth:`.ColumnOperators.__sub__`.
+
+ """
+ return self.reverse_operate(sub, other)
+
+ def __rmul__(self, other: Any) -> ColumnOperators:
+ """Implement the ``*`` operator in reverse.
+
+ See :meth:`.ColumnOperators.__mul__`.
+
+ """
+ return self.reverse_operate(mul, other)
+
+ def __rmod__(self, other: Any) -> ColumnOperators:
+ """Implement the ``%`` operator in reverse.
+
+ See :meth:`.ColumnOperators.__mod__`.
+
+ """
+ return self.reverse_operate(mod, other)
+
+ def between(
+ self, cleft: Any, cright: Any, symmetric: bool = False
+ ) -> ColumnOperators:
+ """Produce a :func:`_expression.between` clause against
+ the parent object, given the lower and upper range.
+
+ """
+ return self.operate(between_op, cleft, cright, symmetric=symmetric)
+
+ def distinct(self) -> ColumnOperators:
+ """Produce a :func:`_expression.distinct` clause against the
+ parent object.
+
+ """
+ return self.operate(distinct_op)
+
+ def any_(self) -> ColumnOperators:
+ """Produce an :func:`_expression.any_` clause against the
+ parent object.
+
+ See the documentation for :func:`_sql.any_` for examples.
+
+ .. note:: be sure to not confuse the newer
+ :meth:`_sql.ColumnOperators.any_` method with the **legacy**
+ version of this method, the :meth:`_types.ARRAY.Comparator.any`
+ method that's specific to :class:`_types.ARRAY`, which uses a
+ different calling style.
+
+ """
+ return self.operate(any_op)
+
+ def all_(self) -> ColumnOperators:
+ """Produce an :func:`_expression.all_` clause against the
+ parent object.
+
+ See the documentation for :func:`_sql.all_` for examples.
+
+ .. note:: be sure to not confuse the newer
+ :meth:`_sql.ColumnOperators.all_` method with the **legacy**
+ version of this method, the :meth:`_types.ARRAY.Comparator.all`
+ method that's specific to :class:`_types.ARRAY`, which uses a
+ different calling style.
+
+ """
+ return self.operate(all_op)
+
+ def __add__(self, other: Any) -> ColumnOperators:
+ """Implement the ``+`` operator.
+
+ In a column context, produces the clause ``a + b``
+ if the parent object has non-string affinity.
+ If the parent object has a string affinity,
+ produces the concatenation operator, ``a || b`` -
+ see :meth:`.ColumnOperators.concat`.
+
+ """
+ return self.operate(add, other)
+
+ def __sub__(self, other: Any) -> ColumnOperators:
+ """Implement the ``-`` operator.
+
+ In a column context, produces the clause ``a - b``.
+
+ """
+ return self.operate(sub, other)
+
+ def __mul__(self, other: Any) -> ColumnOperators:
+ """Implement the ``*`` operator.
+
+ In a column context, produces the clause ``a * b``.
+
+ """
+ return self.operate(mul, other)
+
+ def __mod__(self, other: Any) -> ColumnOperators:
+ """Implement the ``%`` operator.
+
+ In a column context, produces the clause ``a % b``.
+
+ """
+ return self.operate(mod, other)
+
+ def __truediv__(self, other: Any) -> ColumnOperators:
+ """Implement the ``/`` operator.
+
+ In a column context, produces the clause ``a / b``, and
+ considers the result type to be numeric.
+
+ .. versionchanged:: 2.0 The truediv operator against two integers
+ is now considered to return a numeric value. Behavior on specific
+ backends may vary.
+
+ """
+ return self.operate(truediv, other)
+
+ def __rtruediv__(self, other: Any) -> ColumnOperators:
+ """Implement the ``/`` operator in reverse.
+
+ See :meth:`.ColumnOperators.__truediv__`.
+
+ """
+ return self.reverse_operate(truediv, other)
+
+ def __floordiv__(self, other: Any) -> ColumnOperators:
+ """Implement the ``//`` operator.
+
+ In a column context, produces the clause ``a / b``,
+ which is the same as "truediv", but considers the result
+ type to be integer.
+
+ .. versionadded:: 2.0
+
+ """
+ return self.operate(floordiv, other)
+
+ def __rfloordiv__(self, other: Any) -> ColumnOperators:
+ """Implement the ``//`` operator in reverse.
+
+ See :meth:`.ColumnOperators.__floordiv__`.
+
+ """
+ return self.reverse_operate(floordiv, other)
+
+
+_commutative: Set[Any] = {eq, ne, add, mul}
+_comparison: Set[Any] = {eq, ne, lt, gt, ge, le}
+
+
+def _operator_fn(fn: Callable[..., Any]) -> OperatorType:
+ return cast(OperatorType, fn)
+
+
+def commutative_op(fn: _FN) -> _FN:
+ _commutative.add(fn)
+ return fn
+
+
+def comparison_op(fn: _FN) -> _FN:
+ _comparison.add(fn)
+ return fn
+
+
+@_operator_fn
+def from_() -> Any:
+ raise NotImplementedError()
+
+
+@_operator_fn
+@comparison_op
+def function_as_comparison_op() -> Any:
+ raise NotImplementedError()
+
+
+@_operator_fn
+def as_() -> Any:
+ raise NotImplementedError()
+
+
+@_operator_fn
+def exists() -> Any:
+ raise NotImplementedError()
+
+
+@_operator_fn
+def is_true(a: Any) -> Any:
+ raise NotImplementedError()
+
+
+# 1.4 deprecated; see #5435
+if TYPE_CHECKING:
+
+ @_operator_fn
+ def istrue(a: Any) -> Any: ...
+
+else:
+ istrue = is_true
+
+
+@_operator_fn
+def is_false(a: Any) -> Any:
+ raise NotImplementedError()
+
+
+# 1.4 deprecated; see #5435
+if TYPE_CHECKING:
+
+ @_operator_fn
+ def isfalse(a: Any) -> Any: ...
+
+else:
+ isfalse = is_false
+
+
+@comparison_op
+@_operator_fn
+def is_distinct_from(a: Any, b: Any) -> Any:
+ return a.is_distinct_from(b)
+
+
+@comparison_op
+@_operator_fn
+def is_not_distinct_from(a: Any, b: Any) -> Any:
+ return a.is_not_distinct_from(b)
+
+
+# deprecated 1.4; see #5435
+if TYPE_CHECKING:
+
+ @_operator_fn
+ def isnot_distinct_from(a: Any, b: Any) -> Any: ...
+
+else:
+ isnot_distinct_from = is_not_distinct_from
+
+
+@comparison_op
+@_operator_fn
+def is_(a: Any, b: Any) -> Any:
+ return a.is_(b)
+
+
+@comparison_op
+@_operator_fn
+def is_not(a: Any, b: Any) -> Any:
+ return a.is_not(b)
+
+
+# 1.4 deprecated; see #5429
+if TYPE_CHECKING:
+
+ @_operator_fn
+ def isnot(a: Any, b: Any) -> Any: ...
+
+else:
+ isnot = is_not
+
+
+@_operator_fn
+def collate(a: Any, b: Any) -> Any:
+ return a.collate(b)
+
+
+@_operator_fn
+def op(a: Any, opstring: str, b: Any) -> Any:
+ return a.op(opstring)(b)
+
+
+@comparison_op
+@_operator_fn
+def like_op(a: Any, b: Any, escape: Optional[str] = None) -> Any:
+ return a.like(b, escape=escape)
+
+
+@comparison_op
+@_operator_fn
+def not_like_op(a: Any, b: Any, escape: Optional[str] = None) -> Any:
+ return a.notlike(b, escape=escape)
+
+
+# 1.4 deprecated; see #5435
+if TYPE_CHECKING:
+
+ @_operator_fn
+ def notlike_op(a: Any, b: Any, escape: Optional[str] = None) -> Any: ...
+
+else:
+ notlike_op = not_like_op
+
+
+@comparison_op
+@_operator_fn
+def ilike_op(a: Any, b: Any, escape: Optional[str] = None) -> Any:
+ return a.ilike(b, escape=escape)
+
+
+@comparison_op
+@_operator_fn
+def not_ilike_op(a: Any, b: Any, escape: Optional[str] = None) -> Any:
+ return a.not_ilike(b, escape=escape)
+
+
+# 1.4 deprecated; see #5435
+if TYPE_CHECKING:
+
+ @_operator_fn
+ def notilike_op(a: Any, b: Any, escape: Optional[str] = None) -> Any: ...
+
+else:
+ notilike_op = not_ilike_op
+
+
+@comparison_op
+@_operator_fn
+def between_op(a: Any, b: Any, c: Any, symmetric: bool = False) -> Any:
+ return a.between(b, c, symmetric=symmetric)
+
+
+@comparison_op
+@_operator_fn
+def not_between_op(a: Any, b: Any, c: Any, symmetric: bool = False) -> Any:
+ return ~a.between(b, c, symmetric=symmetric)
+
+
+# 1.4 deprecated; see #5435
+if TYPE_CHECKING:
+
+ @_operator_fn
+ def notbetween_op(
+ a: Any, b: Any, c: Any, symmetric: bool = False
+ ) -> Any: ...
+
+else:
+ notbetween_op = not_between_op
+
+
+@comparison_op
+@_operator_fn
+def in_op(a: Any, b: Any) -> Any:
+ return a.in_(b)
+
+
+@comparison_op
+@_operator_fn
+def not_in_op(a: Any, b: Any) -> Any:
+ return a.not_in(b)
+
+
+# 1.4 deprecated; see #5429
+if TYPE_CHECKING:
+
+ @_operator_fn
+ def notin_op(a: Any, b: Any) -> Any: ...
+
+else:
+ notin_op = not_in_op
+
+
+@_operator_fn
+def distinct_op(a: Any) -> Any:
+ return a.distinct()
+
+
+@_operator_fn
+def any_op(a: Any) -> Any:
+ return a.any_()
+
+
+@_operator_fn
+def all_op(a: Any) -> Any:
+ return a.all_()
+
+
+def _escaped_like_impl(
+ fn: Callable[..., Any], other: Any, escape: Optional[str], autoescape: bool
+) -> Any:
+ if autoescape:
+ if autoescape is not True:
+ util.warn(
+ "The autoescape parameter is now a simple boolean True/False"
+ )
+ if escape is None:
+ escape = "/"
+
+ if not isinstance(other, str):
+ raise TypeError("String value expected when autoescape=True")
+
+ if escape not in ("%", "_"):
+ other = other.replace(escape, escape + escape)
+
+ other = other.replace("%", escape + "%").replace("_", escape + "_")
+
+ return fn(other, escape=escape)
+
+
+@comparison_op
+@_operator_fn
+def startswith_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return _escaped_like_impl(a.startswith, b, escape, autoescape)
+
+
+@comparison_op
+@_operator_fn
+def not_startswith_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return ~_escaped_like_impl(a.startswith, b, escape, autoescape)
+
+
+# 1.4 deprecated; see #5435
+if TYPE_CHECKING:
+
+ @_operator_fn
+ def notstartswith_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+ ) -> Any: ...
+
+else:
+ notstartswith_op = not_startswith_op
+
+
+@comparison_op
+@_operator_fn
+def istartswith_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return _escaped_like_impl(a.istartswith, b, escape, autoescape)
+
+
+@comparison_op
+@_operator_fn
+def not_istartswith_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return ~_escaped_like_impl(a.istartswith, b, escape, autoescape)
+
+
+@comparison_op
+@_operator_fn
+def endswith_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return _escaped_like_impl(a.endswith, b, escape, autoescape)
+
+
+@comparison_op
+@_operator_fn
+def not_endswith_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return ~_escaped_like_impl(a.endswith, b, escape, autoescape)
+
+
+# 1.4 deprecated; see #5435
+if TYPE_CHECKING:
+
+ @_operator_fn
+ def notendswith_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+ ) -> Any: ...
+
+else:
+ notendswith_op = not_endswith_op
+
+
+@comparison_op
+@_operator_fn
+def iendswith_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return _escaped_like_impl(a.iendswith, b, escape, autoescape)
+
+
+@comparison_op
+@_operator_fn
+def not_iendswith_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return ~_escaped_like_impl(a.iendswith, b, escape, autoescape)
+
+
+@comparison_op
+@_operator_fn
+def contains_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return _escaped_like_impl(a.contains, b, escape, autoescape)
+
+
+@comparison_op
+@_operator_fn
+def not_contains_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return ~_escaped_like_impl(a.contains, b, escape, autoescape)
+
+
+# 1.4 deprecated; see #5435
+if TYPE_CHECKING:
+
+ @_operator_fn
+ def notcontains_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+ ) -> Any: ...
+
+else:
+ notcontains_op = not_contains_op
+
+
+@comparison_op
+@_operator_fn
+def icontains_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return _escaped_like_impl(a.icontains, b, escape, autoescape)
+
+
+@comparison_op
+@_operator_fn
+def not_icontains_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return ~_escaped_like_impl(a.icontains, b, escape, autoescape)
+
+
+@comparison_op
+@_operator_fn
+def match_op(a: Any, b: Any, **kw: Any) -> Any:
+ return a.match(b, **kw)
+
+
+@comparison_op
+@_operator_fn
+def regexp_match_op(a: Any, b: Any, flags: Optional[str] = None) -> Any:
+ return a.regexp_match(b, flags=flags)
+
+
+@comparison_op
+@_operator_fn
+def not_regexp_match_op(a: Any, b: Any, flags: Optional[str] = None) -> Any:
+ return ~a.regexp_match(b, flags=flags)
+
+
+@_operator_fn
+def regexp_replace_op(
+ a: Any, b: Any, replacement: Any, flags: Optional[str] = None
+) -> Any:
+ return a.regexp_replace(b, replacement=replacement, flags=flags)
+
+
+@comparison_op
+@_operator_fn
+def not_match_op(a: Any, b: Any, **kw: Any) -> Any:
+ return ~a.match(b, **kw)
+
+
+# 1.4 deprecated; see #5429
+if TYPE_CHECKING:
+
+ @_operator_fn
+ def notmatch_op(a: Any, b: Any, **kw: Any) -> Any: ...
+
+else:
+ notmatch_op = not_match_op
+
+
+@_operator_fn
+def comma_op(a: Any, b: Any) -> Any:
+ raise NotImplementedError()
+
+
+@_operator_fn
+def filter_op(a: Any, b: Any) -> Any:
+ raise NotImplementedError()
+
+
+@_operator_fn
+def concat_op(a: Any, b: Any) -> Any:
+ try:
+ concat = a.concat
+ except AttributeError:
+ return b._rconcat(a)
+ else:
+ return concat(b)
+
+
+@_operator_fn
+def desc_op(a: Any) -> Any:
+ return a.desc()
+
+
+@_operator_fn
+def asc_op(a: Any) -> Any:
+ return a.asc()
+
+
+@_operator_fn
+def nulls_first_op(a: Any) -> Any:
+ return a.nulls_first()
+
+
+# 1.4 deprecated; see #5435
+if TYPE_CHECKING:
+
+ @_operator_fn
+ def nullsfirst_op(a: Any) -> Any: ...
+
+else:
+ nullsfirst_op = nulls_first_op
+
+
+@_operator_fn
+def nulls_last_op(a: Any) -> Any:
+ return a.nulls_last()
+
+
+# 1.4 deprecated; see #5435
+if TYPE_CHECKING:
+
+ @_operator_fn
+ def nullslast_op(a: Any) -> Any: ...
+
+else:
+ nullslast_op = nulls_last_op
+
+
+@_operator_fn
+def json_getitem_op(a: Any, b: Any) -> Any:
+ raise NotImplementedError()
+
+
+@_operator_fn
+def json_path_getitem_op(a: Any, b: Any) -> Any:
+ raise NotImplementedError()
+
+
+@_operator_fn
+def bitwise_xor_op(a: Any, b: Any) -> Any:
+ return a.bitwise_xor(b)
+
+
+@_operator_fn
+def bitwise_or_op(a: Any, b: Any) -> Any:
+ return a.bitwise_or(b)
+
+
+@_operator_fn
+def bitwise_and_op(a: Any, b: Any) -> Any:
+ return a.bitwise_and(b)
+
+
+@_operator_fn
+def bitwise_not_op(a: Any) -> Any:
+ return a.bitwise_not()
+
+
+@_operator_fn
+def bitwise_lshift_op(a: Any, b: Any) -> Any:
+ return a.bitwise_lshift(b)
+
+
+@_operator_fn
+def bitwise_rshift_op(a: Any, b: Any) -> Any:
+ return a.bitwise_rshift(b)
+
+
+def is_comparison(op: OperatorType) -> bool:
+ return op in _comparison or isinstance(op, custom_op) and op.is_comparison
+
+
+def is_commutative(op: OperatorType) -> bool:
+ return op in _commutative
+
+
+def is_ordering_modifier(op: OperatorType) -> bool:
+ return op in (asc_op, desc_op, nulls_first_op, nulls_last_op)
+
+
+def is_natural_self_precedent(op: OperatorType) -> bool:
+ return (
+ op in _natural_self_precedent
+ or isinstance(op, custom_op)
+ and op.natural_self_precedent
+ )
+
+
+_booleans = (inv, is_true, is_false, and_, or_)
+
+
+def is_boolean(op: OperatorType) -> bool:
+ return is_comparison(op) or op in _booleans
+
+
+_mirror = {gt: lt, ge: le, lt: gt, le: ge}
+
+
+def mirror(op: OperatorType) -> OperatorType:
+ """rotate a comparison operator 180 degrees.
+
+ Note this is not the same as negation.
+
+ """
+ return _mirror.get(op, op)
+
+
+_associative = _commutative.union([concat_op, and_, or_]).difference([eq, ne])
+
+
+def is_associative(op: OperatorType) -> bool:
+ return op in _associative
+
+
+_natural_self_precedent = _associative.union(
+ [getitem, json_getitem_op, json_path_getitem_op]
+)
+"""Operators where if we have (a op b) op c, we don't want to
+parenthesize (a op b).
+
+"""
+
+
+@_operator_fn
+def _asbool(a: Any) -> Any:
+ raise NotImplementedError()
+
+
+class _OpLimit(IntEnum):
+ _smallest = -100
+ _largest = 100
+
+
+_PRECEDENCE: Dict[OperatorType, int] = {
+ from_: 15,
+ function_as_comparison_op: 15,
+ any_op: 15,
+ all_op: 15,
+ getitem: 15,
+ json_getitem_op: 15,
+ json_path_getitem_op: 15,
+ mul: 8,
+ truediv: 8,
+ floordiv: 8,
+ mod: 8,
+ neg: 8,
+ bitwise_not_op: 8,
+ add: 7,
+ sub: 7,
+ bitwise_xor_op: 7,
+ bitwise_or_op: 7,
+ bitwise_and_op: 7,
+ bitwise_lshift_op: 7,
+ bitwise_rshift_op: 7,
+ filter_op: 6,
+ concat_op: 5,
+ match_op: 5,
+ not_match_op: 5,
+ regexp_match_op: 5,
+ not_regexp_match_op: 5,
+ regexp_replace_op: 5,
+ ilike_op: 5,
+ not_ilike_op: 5,
+ like_op: 5,
+ not_like_op: 5,
+ in_op: 5,
+ not_in_op: 5,
+ is_: 5,
+ is_not: 5,
+ eq: 5,
+ ne: 5,
+ is_distinct_from: 5,
+ is_not_distinct_from: 5,
+ gt: 5,
+ lt: 5,
+ ge: 5,
+ le: 5,
+ between_op: 5,
+ not_between_op: 5,
+ distinct_op: 5,
+ inv: 5,
+ is_true: 5,
+ is_false: 5,
+ and_: 3,
+ or_: 2,
+ comma_op: -1,
+ desc_op: 3,
+ asc_op: 3,
+ collate: 4,
+ as_: -1,
+ exists: 0,
+ _asbool: -10,
+}
+
+
+def is_precedent(
+ operator: OperatorType, against: Optional[OperatorType]
+) -> bool:
+ if operator is against and is_natural_self_precedent(operator):
+ return False
+ elif against is None:
+ return True
+ else:
+ return bool(
+ _PRECEDENCE.get(
+ operator, getattr(operator, "precedence", _OpLimit._smallest)
+ )
+ <= _PRECEDENCE.get(
+ against, getattr(against, "precedence", _OpLimit._largest)
+ )
+ )
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/roles.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/roles.py
new file mode 100644
index 0000000..ae70ac3
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/roles.py
@@ -0,0 +1,323 @@
+# sql/roles.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+from __future__ import annotations
+
+from typing import Any
+from typing import Generic
+from typing import Optional
+from typing import TYPE_CHECKING
+from typing import TypeVar
+
+from .. import util
+from ..util.typing import Literal
+
+if TYPE_CHECKING:
+ from ._typing import _PropagateAttrsType
+ from .elements import Label
+ from .selectable import _SelectIterable
+ from .selectable import FromClause
+ from .selectable import Subquery
+
+_T = TypeVar("_T", bound=Any)
+_T_co = TypeVar("_T_co", bound=Any, covariant=True)
+
+
+class SQLRole:
+ """Define a "role" within a SQL statement structure.
+
+ Classes within SQL Core participate within SQLRole hierarchies in order
+ to more accurately indicate where they may be used within SQL statements
+ of all types.
+
+ .. versionadded:: 1.4
+
+ """
+
+ __slots__ = ()
+ allows_lambda = False
+ uses_inspection = False
+
+
+class UsesInspection:
+ __slots__ = ()
+ _post_inspect: Literal[None] = None
+ uses_inspection = True
+
+
+class AllowsLambdaRole:
+ __slots__ = ()
+ allows_lambda = True
+
+
+class HasCacheKeyRole(SQLRole):
+ __slots__ = ()
+ _role_name = "Cacheable Core or ORM object"
+
+
+class ExecutableOptionRole(SQLRole):
+ __slots__ = ()
+ _role_name = "ExecutionOption 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 StringRole(SQLRole):
+ """mixin indicating a role that results in strings"""
+
+ __slots__ = ()
+
+
+class TruncatedLabelRole(StringRole, SQLRole):
+ __slots__ = ()
+ _role_name = "String SQL identifier"
+
+
+class ColumnsClauseRole(AllowsLambdaRole, UsesInspection, ColumnListRole):
+ __slots__ = ()
+ _role_name = (
+ "Column expression, FROM clause, or other columns clause element"
+ )
+
+ @property
+ def _select_iterable(self) -> _SelectIterable:
+ raise NotImplementedError()
+
+
+class TypedColumnsClauseRole(Generic[_T_co], SQLRole):
+ """element-typed form of ColumnsClauseRole"""
+
+ __slots__ = ()
+
+
+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
+
+ _role_name = "GROUP BY expression"
+
+
+class OrderByRole(AllowsLambdaRole, ByOfRole):
+ __slots__ = ()
+ _role_name = "ORDER BY expression"
+
+
+class StructuralRole(SQLRole):
+ __slots__ = ()
+
+
+class StatementOptionRole(StructuralRole):
+ __slots__ = ()
+ _role_name = "statement sub-expression element"
+
+
+class OnClauseRole(AllowsLambdaRole, StructuralRole):
+ __slots__ = ()
+ _role_name = (
+ "ON clause, typically a SQL expression or "
+ "ORM relationship attribute"
+ )
+
+
+class WhereHavingRole(OnClauseRole):
+ __slots__ = ()
+ _role_name = "SQL expression for WHERE/HAVING role"
+
+
+class ExpressionElementRole(TypedColumnsClauseRole[_T_co]):
+ # note when using generics for ExpressionElementRole,
+ # the generic type needs to be in
+ # sqlalchemy.sql.coercions._impl_lookup mapping also.
+ # these are set up for basic types like int, bool, str, float
+ # right now
+
+ __slots__ = ()
+ _role_name = "SQL expression element"
+
+ def label(self, name: Optional[str]) -> Label[_T]:
+ raise NotImplementedError()
+
+
+class ConstExprRole(ExpressionElementRole[_T]):
+ __slots__ = ()
+ _role_name = "Constant True/False/None expression"
+
+
+class LabeledColumnExprRole(ExpressionElementRole[_T]):
+ __slots__ = ()
+
+
+class BinaryElementRole(ExpressionElementRole[_T]):
+ __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"
+ )
+
+
+class FromClauseRole(ColumnsClauseRole, JoinTargetRole):
+ __slots__ = ()
+ _role_name = "FROM expression, such as a Table or alias() object"
+
+ _is_subquery = False
+
+ named_with_column: bool
+
+
+class StrictFromClauseRole(FromClauseRole):
+ __slots__ = ()
+ # does not allow text() or select() objects
+
+
+class AnonymizedFromClauseRole(StrictFromClauseRole):
+ __slots__ = ()
+
+ if TYPE_CHECKING:
+
+ def _anonymous_fromclause(
+ self, *, name: Optional[str] = None, flat: bool = False
+ ) -> FromClause: ...
+
+
+class ReturnsRowsRole(SQLRole):
+ __slots__ = ()
+ _role_name = (
+ "Row returning expression such as a SELECT, a FROM clause, or an "
+ "INSERT/UPDATE/DELETE with RETURNING"
+ )
+
+
+class StatementRole(SQLRole):
+ __slots__ = ()
+ _role_name = "Executable SQL or text() construct"
+
+ if TYPE_CHECKING:
+
+ @util.memoized_property
+ def _propagate_attrs(self) -> _PropagateAttrsType: ...
+
+ else:
+ _propagate_attrs = util.EMPTY_DICT
+
+
+class SelectStatementRole(StatementRole, ReturnsRowsRole):
+ __slots__ = ()
+ _role_name = "SELECT construct or equivalent text() construct"
+
+ def subquery(self) -> Subquery:
+ raise NotImplementedError(
+ "All SelectStatementRole objects should implement a "
+ ".subquery() method."
+ )
+
+
+class HasCTERole(ReturnsRowsRole):
+ __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"
+ )
+
+
+# TODO: are we using this?
+class DMLRole(StatementRole):
+ __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):
+ __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/venv/lib/python3.11/site-packages/sqlalchemy/sql/schema.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/schema.py
new file mode 100644
index 0000000..6d5f941
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/schema.py
@@ -0,0 +1,6115 @@
+# sql/schema.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+"""The schema module provides the building blocks for database metadata.
+
+Each element within this module describes a database entity which can be
+created and dropped, or is otherwise part of such an entity. Examples include
+tables, columns, sequences, and indexes.
+
+All entities are subclasses of :class:`~sqlalchemy.schema.SchemaItem`, and as
+defined in this module they are intended to be agnostic of any vendor-specific
+constructs.
+
+A collection of entities are grouped into a unit called
+:class:`~sqlalchemy.schema.MetaData`. MetaData serves as a logical grouping of
+schema elements, and can also be associated with an actual database connection
+such that operations involving the contained elements can contact the database
+as needed.
+
+Two of the elements here also build upon their "syntactic" counterparts, which
+are defined in :class:`~sqlalchemy.sql.expression.`, specifically
+:class:`~sqlalchemy.schema.Table` and :class:`~sqlalchemy.schema.Column`.
+Since these objects are part of the SQL expression language, they are usable
+as components in SQL expressions.
+
+"""
+from __future__ import annotations
+
+from abc import ABC
+import collections
+from enum import Enum
+import operator
+import typing
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import Collection
+from typing import Dict
+from typing import Iterable
+from typing import Iterator
+from typing import List
+from typing import Mapping
+from typing import NoReturn
+from typing import Optional
+from typing import overload
+from typing import Sequence as _typing_Sequence
+from typing import Set
+from typing import Tuple
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from . import coercions
+from . import ddl
+from . import roles
+from . import type_api
+from . import visitors
+from .base import _DefaultDescriptionTuple
+from .base import _NoneName
+from .base import _SentinelColumnCharacterization
+from .base import _SentinelDefaultCharacterization
+from .base import DedupeColumnCollection
+from .base import DialectKWArgs
+from .base import Executable
+from .base import SchemaEventTarget as SchemaEventTarget
+from .coercions import _document_text_coercion
+from .elements import ClauseElement
+from .elements import ColumnClause
+from .elements import ColumnElement
+from .elements import quoted_name
+from .elements import TextClause
+from .selectable import TableClause
+from .type_api import to_instance
+from .visitors import ExternallyTraversible
+from .visitors import InternalTraversal
+from .. import event
+from .. import exc
+from .. import inspection
+from .. import util
+from ..util import HasMemoized
+from ..util.typing import Final
+from ..util.typing import Literal
+from ..util.typing import Protocol
+from ..util.typing import Self
+from ..util.typing import TypedDict
+from ..util.typing import TypeGuard
+
+if typing.TYPE_CHECKING:
+ from ._typing import _AutoIncrementType
+ from ._typing import _DDLColumnArgument
+ from ._typing import _InfoType
+ from ._typing import _TextCoercedExpressionArgument
+ from ._typing import _TypeEngineArgument
+ from .base import ReadOnlyColumnCollection
+ from .compiler import DDLCompiler
+ from .elements import BindParameter
+ from .functions import Function
+ from .type_api import TypeEngine
+ from .visitors import _TraverseInternalsType
+ from .visitors import anon_map
+ from ..engine import Connection
+ from ..engine import Engine
+ from ..engine.interfaces import _CoreMultiExecuteParams
+ from ..engine.interfaces import CoreExecuteOptionsParameter
+ from ..engine.interfaces import ExecutionContext
+ from ..engine.mock import MockConnection
+ from ..engine.reflection import _ReflectionInfo
+ from ..sql.selectable import FromClause
+
+_T = TypeVar("_T", bound="Any")
+_SI = TypeVar("_SI", bound="SchemaItem")
+_TAB = TypeVar("_TAB", bound="Table")
+
+
+_CreateDropBind = Union["Engine", "Connection", "MockConnection"]
+
+_ConstraintNameArgument = Optional[Union[str, _NoneName]]
+
+_ServerDefaultArgument = Union[
+ "FetchedValue", str, TextClause, ColumnElement[Any]
+]
+
+
+class SchemaConst(Enum):
+ RETAIN_SCHEMA = 1
+ """Symbol indicating that a :class:`_schema.Table`, :class:`.Sequence`
+ or in some cases a :class:`_schema.ForeignKey` object, in situations
+ where the object is being copied for a :meth:`.Table.to_metadata`
+ operation, should retain the schema name that it already has.
+
+ """
+
+ BLANK_SCHEMA = 2
+ """Symbol indicating that a :class:`_schema.Table` or :class:`.Sequence`
+ should have 'None' for its schema, even if the parent
+ :class:`_schema.MetaData` has specified a schema.
+
+ .. seealso::
+
+ :paramref:`_schema.MetaData.schema`
+
+ :paramref:`_schema.Table.schema`
+
+ :paramref:`.Sequence.schema`
+
+ """
+
+ NULL_UNSPECIFIED = 3
+ """Symbol indicating the "nullable" keyword was not passed to a Column.
+
+ This is used to distinguish between the use case of passing
+ ``nullable=None`` to a :class:`.Column`, which has special meaning
+ on some backends such as SQL Server.
+
+ """
+
+
+RETAIN_SCHEMA: Final[Literal[SchemaConst.RETAIN_SCHEMA]] = (
+ SchemaConst.RETAIN_SCHEMA
+)
+BLANK_SCHEMA: Final[Literal[SchemaConst.BLANK_SCHEMA]] = (
+ SchemaConst.BLANK_SCHEMA
+)
+NULL_UNSPECIFIED: Final[Literal[SchemaConst.NULL_UNSPECIFIED]] = (
+ SchemaConst.NULL_UNSPECIFIED
+)
+
+
+def _get_table_key(name: str, schema: Optional[str]) -> str:
+ if schema is None:
+ return name
+ else:
+ return schema + "." + name
+
+
+# this should really be in sql/util.py but we'd have to
+# break an import cycle
+def _copy_expression(
+ expression: ColumnElement[Any],
+ source_table: Optional[Table],
+ target_table: Optional[Table],
+) -> ColumnElement[Any]:
+ if source_table is None or target_table is None:
+ return expression
+
+ fixed_source_table = source_table
+ fixed_target_table = target_table
+
+ def replace(
+ element: ExternallyTraversible, **kw: Any
+ ) -> Optional[ExternallyTraversible]:
+ if (
+ isinstance(element, Column)
+ and element.table is fixed_source_table
+ and element.key in fixed_source_table.c
+ ):
+ return fixed_target_table.c[element.key]
+ else:
+ return None
+
+ return cast(
+ ColumnElement[Any],
+ visitors.replacement_traverse(expression, {}, replace),
+ )
+
+
+@inspection._self_inspects
+class SchemaItem(SchemaEventTarget, visitors.Visitable):
+ """Base class for items that define a database schema."""
+
+ __visit_name__ = "schema_item"
+
+ create_drop_stringify_dialect = "default"
+
+ def _init_items(self, *args: SchemaItem, **kw: Any) -> None:
+ """Initialize the list of child items for this SchemaItem."""
+ for item in args:
+ if item is not None:
+ try:
+ spwd = item._set_parent_with_dispatch
+ except AttributeError as err:
+ raise exc.ArgumentError(
+ "'SchemaItem' object, such as a 'Column' or a "
+ f"'Constraint' expected, got {item!r}"
+ ) from err
+ else:
+ spwd(self, **kw)
+
+ def __repr__(self) -> str:
+ return util.generic_repr(self, omit_kwarg=["info"])
+
+ @util.memoized_property
+ def info(self) -> _InfoType:
+ """Info dictionary associated with the object, allowing user-defined
+ data to be associated with this :class:`.SchemaItem`.
+
+ The dictionary is automatically generated when first accessed.
+ It can also be specified in the constructor of some objects,
+ such as :class:`_schema.Table` and :class:`_schema.Column`.
+
+ """
+ return {}
+
+ def _schema_item_copy(self, schema_item: _SI) -> _SI:
+ if "info" in self.__dict__:
+ schema_item.info = self.info.copy()
+ schema_item.dispatch._update(self.dispatch)
+ return schema_item
+
+ _use_schema_map = True
+
+
+class HasConditionalDDL:
+ """define a class that includes the :meth:`.HasConditionalDDL.ddl_if`
+ method, allowing for conditional rendering of DDL.
+
+ Currently applies to constraints and indexes.
+
+ .. versionadded:: 2.0
+
+
+ """
+
+ _ddl_if: Optional[ddl.DDLIf] = None
+
+ def ddl_if(
+ self,
+ dialect: Optional[str] = None,
+ callable_: Optional[ddl.DDLIfCallable] = None,
+ state: Optional[Any] = None,
+ ) -> Self:
+ r"""apply a conditional DDL rule to this schema item.
+
+ These rules work in a similar manner to the
+ :meth:`.ExecutableDDLElement.execute_if` callable, with the added
+ feature that the criteria may be checked within the DDL compilation
+ phase for a construct such as :class:`.CreateTable`.
+ :meth:`.HasConditionalDDL.ddl_if` currently applies towards the
+ :class:`.Index` construct as well as all :class:`.Constraint`
+ constructs.
+
+ :param dialect: string name of a dialect, or a tuple of string names
+ to indicate multiple dialect types.
+
+ :param callable\_: a callable that is constructed using the same form
+ as that described in
+ :paramref:`.ExecutableDDLElement.execute_if.callable_`.
+
+ :param state: any arbitrary object that will be passed to the
+ callable, if present.
+
+ .. versionadded:: 2.0
+
+ .. seealso::
+
+ :ref:`schema_ddl_ddl_if` - background and usage examples
+
+
+ """
+ self._ddl_if = ddl.DDLIf(dialect, callable_, state)
+ return self
+
+
+class HasSchemaAttr(SchemaItem):
+ """schema item that includes a top-level schema name"""
+
+ schema: Optional[str]
+
+
+class Table(
+ DialectKWArgs, HasSchemaAttr, TableClause, inspection.Inspectable["Table"]
+):
+ r"""Represent a table in a database.
+
+ e.g.::
+
+ mytable = Table(
+ "mytable", metadata,
+ Column('mytable_id', Integer, primary_key=True),
+ Column('value', String(50))
+ )
+
+ The :class:`_schema.Table`
+ object constructs a unique instance of itself based
+ on its name and optional schema name within the given
+ :class:`_schema.MetaData` object. Calling the :class:`_schema.Table`
+ constructor with the same name and same :class:`_schema.MetaData` argument
+ a second time will return the *same* :class:`_schema.Table`
+ object - in this way
+ the :class:`_schema.Table` constructor acts as a registry function.
+
+ .. seealso::
+
+ :ref:`metadata_describing` - Introduction to database metadata
+
+ """
+
+ __visit_name__ = "table"
+
+ if TYPE_CHECKING:
+
+ @util.ro_non_memoized_property
+ def primary_key(self) -> PrimaryKeyConstraint: ...
+
+ @util.ro_non_memoized_property
+ def foreign_keys(self) -> Set[ForeignKey]: ...
+
+ _columns: DedupeColumnCollection[Column[Any]]
+
+ _sentinel_column: Optional[Column[Any]]
+
+ constraints: Set[Constraint]
+ """A collection of all :class:`_schema.Constraint` objects associated with
+ this :class:`_schema.Table`.
+
+ Includes :class:`_schema.PrimaryKeyConstraint`,
+ :class:`_schema.ForeignKeyConstraint`, :class:`_schema.UniqueConstraint`,
+ :class:`_schema.CheckConstraint`. A separate collection
+ :attr:`_schema.Table.foreign_key_constraints` refers to the collection
+ of all :class:`_schema.ForeignKeyConstraint` objects, and the
+ :attr:`_schema.Table.primary_key` attribute refers to the single
+ :class:`_schema.PrimaryKeyConstraint` associated with the
+ :class:`_schema.Table`.
+
+ .. seealso::
+
+ :attr:`_schema.Table.constraints`
+
+ :attr:`_schema.Table.primary_key`
+
+ :attr:`_schema.Table.foreign_key_constraints`
+
+ :attr:`_schema.Table.indexes`
+
+ :class:`_reflection.Inspector`
+
+
+ """
+
+ indexes: Set[Index]
+ """A collection of all :class:`_schema.Index` objects associated with this
+ :class:`_schema.Table`.
+
+ .. seealso::
+
+ :meth:`_reflection.Inspector.get_indexes`
+
+ """
+
+ _traverse_internals: _TraverseInternalsType = (
+ TableClause._traverse_internals
+ + [("schema", InternalTraversal.dp_string)]
+ )
+
+ if TYPE_CHECKING:
+
+ @util.ro_non_memoized_property
+ def columns(self) -> ReadOnlyColumnCollection[str, Column[Any]]: ...
+
+ @util.ro_non_memoized_property
+ def exported_columns(
+ self,
+ ) -> ReadOnlyColumnCollection[str, Column[Any]]: ...
+
+ @util.ro_non_memoized_property
+ def c(self) -> ReadOnlyColumnCollection[str, Column[Any]]: ...
+
+ def _gen_cache_key(
+ self, anon_map: anon_map, bindparams: List[BindParameter[Any]]
+ ) -> Tuple[Any, ...]:
+ if self._annotations:
+ return (self,) + self._annotations_cache_key
+ else:
+ return (self,)
+
+ if not typing.TYPE_CHECKING:
+ # typing tools seem to be inconsistent in how they handle
+ # __new__, so suggest this pattern for classes that use
+ # __new__. apply typing to the __init__ method normally
+ @util.deprecated_params(
+ mustexist=(
+ "1.4",
+ "Deprecated alias of :paramref:`_schema.Table.must_exist`",
+ ),
+ )
+ def __new__(cls, *args: Any, **kw: Any) -> Any:
+ return cls._new(*args, **kw)
+
+ @classmethod
+ def _new(cls, *args: Any, **kw: Any) -> Any:
+ if not args and not kw:
+ # python3k pickle seems to call this
+ return object.__new__(cls)
+
+ try:
+ name, metadata, args = args[0], args[1], args[2:]
+ except IndexError:
+ raise TypeError(
+ "Table() takes at least two positional-only "
+ "arguments 'name' and 'metadata'"
+ )
+
+ schema = kw.get("schema", None)
+ if schema is None:
+ schema = metadata.schema
+ elif schema is BLANK_SCHEMA:
+ schema = None
+ keep_existing = kw.get("keep_existing", False)
+ extend_existing = kw.get("extend_existing", False)
+
+ if keep_existing and extend_existing:
+ msg = "keep_existing and extend_existing are mutually exclusive."
+ raise exc.ArgumentError(msg)
+
+ must_exist = kw.pop("must_exist", kw.pop("mustexist", False))
+ key = _get_table_key(name, schema)
+ if key in metadata.tables:
+ if not keep_existing and not extend_existing and bool(args):
+ raise exc.InvalidRequestError(
+ f"Table '{key}' is already defined for this MetaData "
+ "instance. Specify 'extend_existing=True' "
+ "to redefine "
+ "options and columns on an "
+ "existing Table object."
+ )
+ table = metadata.tables[key]
+ if extend_existing:
+ table._init_existing(*args, **kw)
+ return table
+ else:
+ if must_exist:
+ raise exc.InvalidRequestError(f"Table '{key}' not defined")
+ table = object.__new__(cls)
+ table.dispatch.before_parent_attach(table, metadata)
+ metadata._add_table(name, schema, table)
+ try:
+ table.__init__(name, metadata, *args, _no_init=False, **kw)
+ table.dispatch.after_parent_attach(table, metadata)
+ return table
+ except Exception:
+ with util.safe_reraise():
+ metadata._remove_table(name, schema)
+
+ def __init__(
+ self,
+ name: str,
+ metadata: MetaData,
+ *args: SchemaItem,
+ schema: Optional[Union[str, Literal[SchemaConst.BLANK_SCHEMA]]] = None,
+ quote: Optional[bool] = None,
+ quote_schema: Optional[bool] = None,
+ autoload_with: Optional[Union[Engine, Connection]] = None,
+ autoload_replace: bool = True,
+ keep_existing: bool = False,
+ extend_existing: bool = False,
+ resolve_fks: bool = True,
+ include_columns: Optional[Collection[str]] = None,
+ implicit_returning: bool = True,
+ comment: Optional[str] = None,
+ info: Optional[Dict[Any, Any]] = None,
+ listeners: Optional[
+ _typing_Sequence[Tuple[str, Callable[..., Any]]]
+ ] = None,
+ prefixes: Optional[_typing_Sequence[str]] = None,
+ # used internally in the metadata.reflect() process
+ _extend_on: Optional[Set[Table]] = None,
+ # used by __new__ to bypass __init__
+ _no_init: bool = True,
+ # dialect-specific keyword args
+ **kw: Any,
+ ) -> None:
+ r"""Constructor for :class:`_schema.Table`.
+
+
+ :param name: The name of this table as represented in the database.
+
+ The table name, along with the value of the ``schema`` parameter,
+ forms a key which uniquely identifies this :class:`_schema.Table`
+ within
+ the owning :class:`_schema.MetaData` collection.
+ Additional calls to :class:`_schema.Table` with the same name,
+ metadata,
+ and schema name will return the same :class:`_schema.Table` object.
+
+ Names which contain no upper case characters
+ will be treated as case insensitive names, and will not be quoted
+ unless they are a reserved word or contain special characters.
+ A name with any number of upper case characters is considered
+ to be case sensitive, and will be sent as quoted.
+
+ To enable unconditional quoting for the table name, specify the flag
+ ``quote=True`` to the constructor, or use the :class:`.quoted_name`
+ construct to specify the name.
+
+ :param metadata: a :class:`_schema.MetaData`
+ object which will contain this
+ table. The metadata is used as a point of association of this table
+ with other tables which are referenced via foreign key. It also
+ may be used to associate this table with a particular
+ :class:`.Connection` or :class:`.Engine`.
+
+ :param \*args: Additional positional arguments are used primarily
+ to add the list of :class:`_schema.Column`
+ objects contained within this
+ table. Similar to the style of a CREATE TABLE statement, other
+ :class:`.SchemaItem` constructs may be added here, including
+ :class:`.PrimaryKeyConstraint`, and
+ :class:`_schema.ForeignKeyConstraint`.
+
+ :param autoload_replace: Defaults to ``True``; when using
+ :paramref:`_schema.Table.autoload_with`
+ in conjunction with :paramref:`_schema.Table.extend_existing`,
+ indicates
+ that :class:`_schema.Column` objects present in the already-existing
+ :class:`_schema.Table`
+ object should be replaced with columns of the same
+ name retrieved from the autoload process. When ``False``, columns
+ already present under existing names will be omitted from the
+ reflection process.
+
+ Note that this setting does not impact :class:`_schema.Column` objects
+ specified programmatically within the call to :class:`_schema.Table`
+ that
+ also is autoloading; those :class:`_schema.Column` objects will always
+ replace existing columns of the same name when
+ :paramref:`_schema.Table.extend_existing` is ``True``.
+
+ .. seealso::
+
+ :paramref:`_schema.Table.autoload_with`
+
+ :paramref:`_schema.Table.extend_existing`
+
+ :param autoload_with: An :class:`_engine.Engine` or
+ :class:`_engine.Connection` object,
+ or a :class:`_reflection.Inspector` object as returned by
+ :func:`_sa.inspect`
+ against one, with which this :class:`_schema.Table`
+ object will be reflected.
+ When set to a non-None value, the autoload process will take place
+ for this table against the given engine or connection.
+
+ .. seealso::
+
+ :ref:`metadata_reflection_toplevel`
+
+ :meth:`_events.DDLEvents.column_reflect`
+
+ :ref:`metadata_reflection_dbagnostic_types`
+
+ :param extend_existing: When ``True``, indicates that if this
+ :class:`_schema.Table` is already present in the given
+ :class:`_schema.MetaData`,
+ apply further arguments within the constructor to the existing
+ :class:`_schema.Table`.
+
+ If :paramref:`_schema.Table.extend_existing` or
+ :paramref:`_schema.Table.keep_existing` are not set,
+ and the given name
+ of the new :class:`_schema.Table` refers to a :class:`_schema.Table`
+ that is
+ already present in the target :class:`_schema.MetaData` collection,
+ and
+ this :class:`_schema.Table`
+ specifies additional columns or other constructs
+ or flags that modify the table's state, an
+ error is raised. The purpose of these two mutually-exclusive flags
+ is to specify what action should be taken when a
+ :class:`_schema.Table`
+ is specified that matches an existing :class:`_schema.Table`,
+ yet specifies
+ additional constructs.
+
+ :paramref:`_schema.Table.extend_existing`
+ will also work in conjunction
+ with :paramref:`_schema.Table.autoload_with` to run a new reflection
+ operation against the database, even if a :class:`_schema.Table`
+ of the same name is already present in the target
+ :class:`_schema.MetaData`; newly reflected :class:`_schema.Column`
+ objects
+ and other options will be added into the state of the
+ :class:`_schema.Table`, potentially overwriting existing columns
+ and options of the same name.
+
+ As is always the case with :paramref:`_schema.Table.autoload_with`,
+ :class:`_schema.Column` objects can be specified in the same
+ :class:`_schema.Table`
+ constructor, which will take precedence. Below, the existing
+ table ``mytable`` will be augmented with :class:`_schema.Column`
+ objects
+ both reflected from the database, as well as the given
+ :class:`_schema.Column`
+ named "y"::
+
+ Table("mytable", metadata,
+ Column('y', Integer),
+ extend_existing=True,
+ autoload_with=engine
+ )
+
+ .. seealso::
+
+ :paramref:`_schema.Table.autoload_with`
+
+ :paramref:`_schema.Table.autoload_replace`
+
+ :paramref:`_schema.Table.keep_existing`
+
+
+ :param implicit_returning: True by default - indicates that
+ RETURNING can be used, typically by the ORM, in order to fetch
+ server-generated values such as primary key values and
+ server side defaults, on those backends which support RETURNING.
+
+ In modern SQLAlchemy there is generally no reason to alter this
+ setting, except for some backend specific cases
+ (see :ref:`mssql_triggers` in the SQL Server dialect documentation
+ for one such example).
+
+ :param include_columns: A list of strings indicating a subset of
+ columns to be loaded via the ``autoload`` operation; table columns who
+ aren't present in this list will not be represented on the resulting
+ ``Table`` object. Defaults to ``None`` which indicates all columns
+ should be reflected.
+
+ :param resolve_fks: Whether or not to reflect :class:`_schema.Table`
+ objects
+ related to this one via :class:`_schema.ForeignKey` objects, when
+ :paramref:`_schema.Table.autoload_with` is
+ specified. Defaults to True. Set to False to disable reflection of
+ related tables as :class:`_schema.ForeignKey`
+ objects are encountered; may be
+ used either to save on SQL calls or to avoid issues with related tables
+ that can't be accessed. Note that if a related table is already present
+ in the :class:`_schema.MetaData` collection, or becomes present later,
+ a
+ :class:`_schema.ForeignKey` object associated with this
+ :class:`_schema.Table` will
+ resolve to that table normally.
+
+ .. versionadded:: 1.3
+
+ .. seealso::
+
+ :paramref:`.MetaData.reflect.resolve_fks`
+
+
+ :param info: Optional data dictionary which will be populated into the
+ :attr:`.SchemaItem.info` attribute of this object.
+
+ :param keep_existing: When ``True``, indicates that if this Table
+ is already present in the given :class:`_schema.MetaData`, ignore
+ further arguments within the constructor to the existing
+ :class:`_schema.Table`, and return the :class:`_schema.Table`
+ object as
+ originally created. This is to allow a function that wishes
+ to define a new :class:`_schema.Table` on first call, but on
+ subsequent calls will return the same :class:`_schema.Table`,
+ without any of the declarations (particularly constraints)
+ being applied a second time.
+
+ If :paramref:`_schema.Table.extend_existing` or
+ :paramref:`_schema.Table.keep_existing` are not set,
+ and the given name
+ of the new :class:`_schema.Table` refers to a :class:`_schema.Table`
+ that is
+ already present in the target :class:`_schema.MetaData` collection,
+ and
+ this :class:`_schema.Table`
+ specifies additional columns or other constructs
+ or flags that modify the table's state, an
+ error is raised. The purpose of these two mutually-exclusive flags
+ is to specify what action should be taken when a
+ :class:`_schema.Table`
+ is specified that matches an existing :class:`_schema.Table`,
+ yet specifies
+ additional constructs.
+
+ .. seealso::
+
+ :paramref:`_schema.Table.extend_existing`
+
+ :param listeners: A list of tuples of the form ``(<eventname>, <fn>)``
+ which will be passed to :func:`.event.listen` upon construction.
+ This alternate hook to :func:`.event.listen` allows the establishment
+ of a listener function specific to this :class:`_schema.Table` before
+ the "autoload" process begins. Historically this has been intended
+ for use with the :meth:`.DDLEvents.column_reflect` event, however
+ note that this event hook may now be associated with the
+ :class:`_schema.MetaData` object directly::
+
+ def listen_for_reflect(table, column_info):
+ "handle the column reflection event"
+ # ...
+
+ t = Table(
+ 'sometable',
+ autoload_with=engine,
+ listeners=[
+ ('column_reflect', listen_for_reflect)
+ ])
+
+ .. seealso::
+
+ :meth:`_events.DDLEvents.column_reflect`
+
+ :param must_exist: When ``True``, indicates that this Table must already
+ be present in the given :class:`_schema.MetaData` collection, else
+ an exception is raised.
+
+ :param prefixes:
+ A list of strings to insert after CREATE in the CREATE TABLE
+ statement. They will be separated by spaces.
+
+ :param quote: Force quoting of this table's name on or off, corresponding
+ to ``True`` or ``False``. When left at its default of ``None``,
+ the column identifier will be quoted according to whether the name is
+ case sensitive (identifiers with at least one upper case character are
+ treated as case sensitive), or if it's a reserved word. This flag
+ is only needed to force quoting of a reserved word which is not known
+ by the SQLAlchemy dialect.
+
+ .. note:: setting this flag to ``False`` will not provide
+ case-insensitive behavior for table reflection; table reflection
+ will always search for a mixed-case name in a case sensitive
+ fashion. Case insensitive names are specified in SQLAlchemy only
+ by stating the name with all lower case characters.
+
+ :param quote_schema: same as 'quote' but applies to the schema identifier.
+
+ :param schema: The schema name for this table, which is required if
+ the table resides in a schema other than the default selected schema
+ for the engine's database connection. Defaults to ``None``.
+
+ If the owning :class:`_schema.MetaData` of this :class:`_schema.Table`
+ specifies its
+ own :paramref:`_schema.MetaData.schema` parameter,
+ then that schema name will
+ be applied to this :class:`_schema.Table`
+ if the schema parameter here is set
+ to ``None``. To set a blank schema name on a :class:`_schema.Table`
+ that
+ would otherwise use the schema set on the owning
+ :class:`_schema.MetaData`,
+ specify the special symbol :attr:`.BLANK_SCHEMA`.
+
+ The quoting rules for the schema name are the same as those for the
+ ``name`` parameter, in that quoting is applied for reserved words or
+ case-sensitive names; to enable unconditional quoting for the schema
+ name, specify the flag ``quote_schema=True`` to the constructor, or use
+ the :class:`.quoted_name` construct to specify the name.
+
+ :param comment: Optional string that will render an SQL comment on table
+ creation.
+
+ .. versionadded:: 1.2 Added the :paramref:`_schema.Table.comment`
+ parameter
+ to :class:`_schema.Table`.
+
+ :param \**kw: Additional keyword arguments not mentioned above are
+ dialect specific, and passed in the form ``<dialectname>_<argname>``.
+ See the documentation regarding an individual dialect at
+ :ref:`dialect_toplevel` for detail on documented arguments.
+
+ """ # noqa: E501
+ if _no_init:
+ # don't run __init__ from __new__ by default;
+ # __new__ has a specific place that __init__ is called
+ return
+
+ super().__init__(quoted_name(name, quote))
+ self.metadata = metadata
+
+ if schema is None:
+ self.schema = metadata.schema
+ elif schema is BLANK_SCHEMA:
+ self.schema = None
+ else:
+ quote_schema = quote_schema
+ assert isinstance(schema, str)
+ self.schema = quoted_name(schema, quote_schema)
+
+ self._sentinel_column = None
+
+ self.indexes = set()
+ self.constraints = set()
+ PrimaryKeyConstraint(
+ _implicit_generated=True
+ )._set_parent_with_dispatch(self)
+ self.foreign_keys = set() # type: ignore
+ self._extra_dependencies: Set[Table] = set()
+ if self.schema is not None:
+ self.fullname = "%s.%s" % (self.schema, self.name)
+ else:
+ self.fullname = self.name
+
+ self.implicit_returning = implicit_returning
+ _reflect_info = kw.pop("_reflect_info", None)
+
+ self.comment = comment
+
+ if info is not None:
+ self.info = info
+
+ if listeners is not None:
+ for evt, fn in listeners:
+ event.listen(self, evt, fn)
+
+ self._prefixes = prefixes if prefixes else []
+
+ self._extra_kwargs(**kw)
+
+ # load column definitions from the database if 'autoload' is defined
+ # we do it after the table is in the singleton dictionary to support
+ # circular foreign keys
+ if autoload_with is not None:
+ self._autoload(
+ metadata,
+ autoload_with,
+ include_columns,
+ _extend_on=_extend_on,
+ _reflect_info=_reflect_info,
+ resolve_fks=resolve_fks,
+ )
+
+ # initialize all the column, etc. objects. done after reflection to
+ # allow user-overrides
+
+ self._init_items(
+ *args,
+ allow_replacements=extend_existing
+ or keep_existing
+ or autoload_with,
+ all_names={},
+ )
+
+ def _autoload(
+ self,
+ metadata: MetaData,
+ autoload_with: Union[Engine, Connection],
+ include_columns: Optional[Collection[str]],
+ exclude_columns: Collection[str] = (),
+ resolve_fks: bool = True,
+ _extend_on: Optional[Set[Table]] = None,
+ _reflect_info: _ReflectionInfo | None = None,
+ ) -> None:
+ insp = inspection.inspect(autoload_with)
+ with insp._inspection_context() as conn_insp:
+ conn_insp.reflect_table(
+ self,
+ include_columns,
+ exclude_columns,
+ resolve_fks,
+ _extend_on=_extend_on,
+ _reflect_info=_reflect_info,
+ )
+
+ @property
+ def _sorted_constraints(self) -> List[Constraint]:
+ """Return the set of constraints as a list, sorted by creation
+ order.
+
+ """
+
+ return sorted(self.constraints, key=lambda c: c._creation_order)
+
+ @property
+ def foreign_key_constraints(self) -> Set[ForeignKeyConstraint]:
+ """:class:`_schema.ForeignKeyConstraint` objects referred to by this
+ :class:`_schema.Table`.
+
+ This list is produced from the collection of
+ :class:`_schema.ForeignKey`
+ objects currently associated.
+
+
+ .. seealso::
+
+ :attr:`_schema.Table.constraints`
+
+ :attr:`_schema.Table.foreign_keys`
+
+ :attr:`_schema.Table.indexes`
+
+ """
+ return {
+ fkc.constraint
+ for fkc in self.foreign_keys
+ if fkc.constraint is not None
+ }
+
+ def _init_existing(self, *args: Any, **kwargs: Any) -> None:
+ autoload_with = kwargs.pop("autoload_with", None)
+ autoload = kwargs.pop("autoload", autoload_with is not None)
+ autoload_replace = kwargs.pop("autoload_replace", True)
+ schema = kwargs.pop("schema", None)
+ _extend_on = kwargs.pop("_extend_on", None)
+ _reflect_info = kwargs.pop("_reflect_info", None)
+
+ # these arguments are only used with _init()
+ extend_existing = kwargs.pop("extend_existing", False)
+ keep_existing = kwargs.pop("keep_existing", False)
+
+ assert extend_existing
+ assert not keep_existing
+
+ if schema and schema != self.schema:
+ raise exc.ArgumentError(
+ f"Can't change schema of existing table "
+ f"from '{self.schema}' to '{schema}'",
+ )
+
+ include_columns = kwargs.pop("include_columns", None)
+ if include_columns is not None:
+ for c in self.c:
+ if c.name not in include_columns:
+ self._columns.remove(c)
+
+ resolve_fks = kwargs.pop("resolve_fks", True)
+
+ for key in ("quote", "quote_schema"):
+ if key in kwargs:
+ raise exc.ArgumentError(
+ "Can't redefine 'quote' or 'quote_schema' arguments"
+ )
+
+ # update `self` with these kwargs, if provided
+ self.comment = kwargs.pop("comment", self.comment)
+ self.implicit_returning = kwargs.pop(
+ "implicit_returning", self.implicit_returning
+ )
+ self.info = kwargs.pop("info", self.info)
+
+ exclude_columns: _typing_Sequence[str]
+
+ if autoload:
+ if not autoload_replace:
+ # don't replace columns already present.
+ # we'd like to do this for constraints also however we don't
+ # have simple de-duping for unnamed constraints.
+ exclude_columns = [c.name for c in self.c]
+ else:
+ exclude_columns = ()
+ self._autoload(
+ self.metadata,
+ autoload_with,
+ include_columns,
+ exclude_columns,
+ resolve_fks,
+ _extend_on=_extend_on,
+ _reflect_info=_reflect_info,
+ )
+
+ all_names = {c.name: c for c in self.c}
+ self._extra_kwargs(**kwargs)
+ self._init_items(*args, allow_replacements=True, all_names=all_names)
+
+ def _extra_kwargs(self, **kwargs: Any) -> None:
+ self._validate_dialect_kwargs(kwargs)
+
+ def _init_collections(self) -> None:
+ pass
+
+ def _reset_exported(self) -> None:
+ pass
+
+ @util.ro_non_memoized_property
+ def _autoincrement_column(self) -> Optional[Column[int]]:
+ return self.primary_key._autoincrement_column
+
+ @util.ro_memoized_property
+ def _sentinel_column_characteristics(
+ self,
+ ) -> _SentinelColumnCharacterization:
+ """determine a candidate column (or columns, in case of a client
+ generated composite primary key) which can be used as an
+ "insert sentinel" for an INSERT statement.
+
+ The returned structure, :class:`_SentinelColumnCharacterization`,
+ includes all the details needed by :class:`.Dialect` and
+ :class:`.SQLCompiler` to determine if these column(s) can be used
+ as an INSERT..RETURNING sentinel for a particular database
+ dialect.
+
+ .. versionadded:: 2.0.10
+
+ """
+
+ sentinel_is_explicit = False
+ sentinel_is_autoinc = False
+ the_sentinel: Optional[_typing_Sequence[Column[Any]]] = None
+
+ # see if a column was explicitly marked "insert_sentinel=True".
+ explicit_sentinel_col = self._sentinel_column
+
+ if explicit_sentinel_col is not None:
+ the_sentinel = (explicit_sentinel_col,)
+ sentinel_is_explicit = True
+
+ autoinc_col = self._autoincrement_column
+ if sentinel_is_explicit and explicit_sentinel_col is autoinc_col:
+ assert autoinc_col is not None
+ sentinel_is_autoinc = True
+ elif explicit_sentinel_col is None and autoinc_col is not None:
+ the_sentinel = (autoinc_col,)
+ sentinel_is_autoinc = True
+
+ default_characterization = _SentinelDefaultCharacterization.UNKNOWN
+
+ if the_sentinel:
+ the_sentinel_zero = the_sentinel[0]
+ if the_sentinel_zero.identity:
+ if the_sentinel_zero.identity._increment_is_negative:
+ if sentinel_is_explicit:
+ raise exc.InvalidRequestError(
+ "Can't use IDENTITY default with negative "
+ "increment as an explicit sentinel column"
+ )
+ else:
+ if sentinel_is_autoinc:
+ autoinc_col = None
+ sentinel_is_autoinc = False
+ the_sentinel = None
+ else:
+ default_characterization = (
+ _SentinelDefaultCharacterization.IDENTITY
+ )
+ elif (
+ the_sentinel_zero.default is None
+ and the_sentinel_zero.server_default is None
+ ):
+ if the_sentinel_zero.nullable:
+ raise exc.InvalidRequestError(
+ f"Column {the_sentinel_zero} has been marked as a "
+ "sentinel "
+ "column with no default generation function; it "
+ "at least needs to be marked nullable=False assuming "
+ "user-populated sentinel values will be used."
+ )
+ default_characterization = (
+ _SentinelDefaultCharacterization.NONE
+ )
+ elif the_sentinel_zero.default is not None:
+ if the_sentinel_zero.default.is_sentinel:
+ default_characterization = (
+ _SentinelDefaultCharacterization.SENTINEL_DEFAULT
+ )
+ elif default_is_sequence(the_sentinel_zero.default):
+ if the_sentinel_zero.default._increment_is_negative:
+ if sentinel_is_explicit:
+ raise exc.InvalidRequestError(
+ "Can't use SEQUENCE default with negative "
+ "increment as an explicit sentinel column"
+ )
+ else:
+ if sentinel_is_autoinc:
+ autoinc_col = None
+ sentinel_is_autoinc = False
+ the_sentinel = None
+
+ default_characterization = (
+ _SentinelDefaultCharacterization.SEQUENCE
+ )
+ elif the_sentinel_zero.default.is_callable:
+ default_characterization = (
+ _SentinelDefaultCharacterization.CLIENTSIDE
+ )
+ elif the_sentinel_zero.server_default is not None:
+ if sentinel_is_explicit:
+ raise exc.InvalidRequestError(
+ f"Column {the_sentinel[0]} can't be a sentinel column "
+ "because it uses an explicit server side default "
+ "that's not the Identity() default."
+ )
+
+ default_characterization = (
+ _SentinelDefaultCharacterization.SERVERSIDE
+ )
+
+ if the_sentinel is None and self.primary_key:
+ assert autoinc_col is None
+
+ # determine for non-autoincrement pk if all elements are
+ # client side
+ for _pkc in self.primary_key:
+ if _pkc.server_default is not None or (
+ _pkc.default and not _pkc.default.is_callable
+ ):
+ break
+ else:
+ the_sentinel = tuple(self.primary_key)
+ default_characterization = (
+ _SentinelDefaultCharacterization.CLIENTSIDE
+ )
+
+ return _SentinelColumnCharacterization(
+ the_sentinel,
+ sentinel_is_explicit,
+ sentinel_is_autoinc,
+ default_characterization,
+ )
+
+ @property
+ def autoincrement_column(self) -> Optional[Column[int]]:
+ """Returns the :class:`.Column` object which currently represents
+ the "auto increment" column, if any, else returns None.
+
+ This is based on the rules for :class:`.Column` as defined by the
+ :paramref:`.Column.autoincrement` parameter, which generally means the
+ column within a single integer column primary key constraint that is
+ not constrained by a foreign key. If the table does not have such
+ a primary key constraint, then there's no "autoincrement" column.
+ A :class:`.Table` may have only one column defined as the
+ "autoincrement" column.
+
+ .. versionadded:: 2.0.4
+
+ .. seealso::
+
+ :paramref:`.Column.autoincrement`
+
+ """
+ return self._autoincrement_column
+
+ @property
+ def key(self) -> str:
+ """Return the 'key' for this :class:`_schema.Table`.
+
+ This value is used as the dictionary key within the
+ :attr:`_schema.MetaData.tables` collection. It is typically the same
+ as that of :attr:`_schema.Table.name` for a table with no
+ :attr:`_schema.Table.schema`
+ set; otherwise it is typically of the form
+ ``schemaname.tablename``.
+
+ """
+ return _get_table_key(self.name, self.schema)
+
+ def __repr__(self) -> str:
+ return "Table(%s)" % ", ".join(
+ [repr(self.name)]
+ + [repr(self.metadata)]
+ + [repr(x) for x in self.columns]
+ + ["%s=%s" % (k, repr(getattr(self, k))) for k in ["schema"]]
+ )
+
+ def __str__(self) -> str:
+ return _get_table_key(self.description, self.schema)
+
+ def add_is_dependent_on(self, table: Table) -> None:
+ """Add a 'dependency' for this Table.
+
+ This is another Table object which must be created
+ first before this one can, or dropped after this one.
+
+ Usually, dependencies between tables are determined via
+ ForeignKey objects. However, for other situations that
+ create dependencies outside of foreign keys (rules, inheriting),
+ this method can manually establish such a link.
+
+ """
+ self._extra_dependencies.add(table)
+
+ def append_column(
+ self, column: ColumnClause[Any], replace_existing: bool = False
+ ) -> None:
+ """Append a :class:`_schema.Column` to this :class:`_schema.Table`.
+
+ The "key" of the newly added :class:`_schema.Column`, i.e. the
+ value of its ``.key`` attribute, will then be available
+ in the ``.c`` collection of this :class:`_schema.Table`, and the
+ column definition will be included in any CREATE TABLE, SELECT,
+ UPDATE, etc. statements generated from this :class:`_schema.Table`
+ construct.
+
+ Note that this does **not** change the definition of the table
+ as it exists within any underlying database, assuming that
+ table has already been created in the database. Relational
+ databases support the addition of columns to existing tables
+ using the SQL ALTER command, which would need to be
+ emitted for an already-existing table that doesn't contain
+ the newly added column.
+
+ :param replace_existing: When ``True``, allows replacing existing
+ columns. When ``False``, the default, an warning will be raised
+ if a column with the same ``.key`` already exists. A future
+ version of sqlalchemy will instead rise a warning.
+
+ .. versionadded:: 1.4.0
+ """
+
+ try:
+ column._set_parent_with_dispatch(
+ self,
+ allow_replacements=replace_existing,
+ all_names={c.name: c for c in self.c},
+ )
+ except exc.DuplicateColumnError as de:
+ raise exc.DuplicateColumnError(
+ f"{de.args[0]} Specify replace_existing=True to "
+ "Table.append_column() to replace an "
+ "existing column."
+ ) from de
+
+ def append_constraint(self, constraint: Union[Index, Constraint]) -> None:
+ """Append a :class:`_schema.Constraint` to this
+ :class:`_schema.Table`.
+
+ This has the effect of the constraint being included in any
+ future CREATE TABLE statement, assuming specific DDL creation
+ events have not been associated with the given
+ :class:`_schema.Constraint` object.
+
+ Note that this does **not** produce the constraint within the
+ relational database automatically, for a table that already exists
+ in the database. To add a constraint to an
+ existing relational database table, the SQL ALTER command must
+ be used. SQLAlchemy also provides the
+ :class:`.AddConstraint` construct which can produce this SQL when
+ invoked as an executable clause.
+
+ """
+
+ constraint._set_parent_with_dispatch(self)
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ metadata = parent
+ assert isinstance(metadata, MetaData)
+ metadata._add_table(self.name, self.schema, self)
+ self.metadata = metadata
+
+ def create(self, bind: _CreateDropBind, checkfirst: bool = False) -> None:
+ """Issue a ``CREATE`` statement for this
+ :class:`_schema.Table`, using the given
+ :class:`.Connection` or :class:`.Engine`
+ for connectivity.
+
+ .. seealso::
+
+ :meth:`_schema.MetaData.create_all`.
+
+ """
+
+ bind._run_ddl_visitor(ddl.SchemaGenerator, self, checkfirst=checkfirst)
+
+ def drop(self, bind: _CreateDropBind, checkfirst: bool = False) -> None:
+ """Issue a ``DROP`` statement for this
+ :class:`_schema.Table`, using the given
+ :class:`.Connection` or :class:`.Engine` for connectivity.
+
+ .. seealso::
+
+ :meth:`_schema.MetaData.drop_all`.
+
+ """
+ bind._run_ddl_visitor(ddl.SchemaDropper, self, checkfirst=checkfirst)
+
+ @util.deprecated(
+ "1.4",
+ ":meth:`_schema.Table.tometadata` is renamed to "
+ ":meth:`_schema.Table.to_metadata`",
+ )
+ def tometadata(
+ self,
+ metadata: MetaData,
+ schema: Union[str, Literal[SchemaConst.RETAIN_SCHEMA]] = RETAIN_SCHEMA,
+ referred_schema_fn: Optional[
+ Callable[
+ [Table, Optional[str], ForeignKeyConstraint, Optional[str]],
+ Optional[str],
+ ]
+ ] = None,
+ name: Optional[str] = None,
+ ) -> Table:
+ """Return a copy of this :class:`_schema.Table`
+ associated with a different
+ :class:`_schema.MetaData`.
+
+ See :meth:`_schema.Table.to_metadata` for a full description.
+
+ """
+ return self.to_metadata(
+ metadata,
+ schema=schema,
+ referred_schema_fn=referred_schema_fn,
+ name=name,
+ )
+
+ def to_metadata(
+ self,
+ metadata: MetaData,
+ schema: Union[str, Literal[SchemaConst.RETAIN_SCHEMA]] = RETAIN_SCHEMA,
+ referred_schema_fn: Optional[
+ Callable[
+ [Table, Optional[str], ForeignKeyConstraint, Optional[str]],
+ Optional[str],
+ ]
+ ] = None,
+ name: Optional[str] = None,
+ ) -> Table:
+ """Return a copy of this :class:`_schema.Table` associated with a
+ different :class:`_schema.MetaData`.
+
+ E.g.::
+
+ m1 = MetaData()
+
+ user = Table('user', m1, Column('id', Integer, primary_key=True))
+
+ m2 = MetaData()
+ user_copy = user.to_metadata(m2)
+
+ .. versionchanged:: 1.4 The :meth:`_schema.Table.to_metadata` function
+ was renamed from :meth:`_schema.Table.tometadata`.
+
+
+ :param metadata: Target :class:`_schema.MetaData` object,
+ into which the
+ new :class:`_schema.Table` object will be created.
+
+ :param schema: optional string name indicating the target schema.
+ Defaults to the special symbol :attr:`.RETAIN_SCHEMA` which indicates
+ that no change to the schema name should be made in the new
+ :class:`_schema.Table`. If set to a string name, the new
+ :class:`_schema.Table`
+ will have this new name as the ``.schema``. If set to ``None``, the
+ schema will be set to that of the schema set on the target
+ :class:`_schema.MetaData`, which is typically ``None`` as well,
+ unless
+ set explicitly::
+
+ m2 = MetaData(schema='newschema')
+
+ # user_copy_one will have "newschema" as the schema name
+ user_copy_one = user.to_metadata(m2, schema=None)
+
+ m3 = MetaData() # schema defaults to None
+
+ # user_copy_two will have None as the schema name
+ user_copy_two = user.to_metadata(m3, schema=None)
+
+ :param referred_schema_fn: optional callable which can be supplied
+ in order to provide for the schema name that should be assigned
+ to the referenced table of a :class:`_schema.ForeignKeyConstraint`.
+ The callable accepts this parent :class:`_schema.Table`, the
+ target schema that we are changing to, the
+ :class:`_schema.ForeignKeyConstraint` object, and the existing
+ "target schema" of that constraint. The function should return the
+ string schema name that should be applied. To reset the schema
+ to "none", return the symbol :data:`.BLANK_SCHEMA`. To effect no
+ change, return ``None`` or :data:`.RETAIN_SCHEMA`.
+
+ .. versionchanged:: 1.4.33 The ``referred_schema_fn`` function
+ may return the :data:`.BLANK_SCHEMA` or :data:`.RETAIN_SCHEMA`
+ symbols.
+
+ E.g.::
+
+ def referred_schema_fn(table, to_schema,
+ constraint, referred_schema):
+ if referred_schema == 'base_tables':
+ return referred_schema
+ else:
+ return to_schema
+
+ new_table = table.to_metadata(m2, schema="alt_schema",
+ referred_schema_fn=referred_schema_fn)
+
+ :param name: optional string name indicating the target table name.
+ If not specified or None, the table name is retained. This allows
+ a :class:`_schema.Table` to be copied to the same
+ :class:`_schema.MetaData` target
+ with a new name.
+
+ """
+ if name is None:
+ name = self.name
+
+ actual_schema: Optional[str]
+
+ if schema is RETAIN_SCHEMA:
+ actual_schema = self.schema
+ elif schema is None:
+ actual_schema = metadata.schema
+ else:
+ actual_schema = schema
+ key = _get_table_key(name, actual_schema)
+ if key in metadata.tables:
+ util.warn(
+ f"Table '{self.description}' already exists within the given "
+ "MetaData - not copying."
+ )
+ return metadata.tables[key]
+
+ args = []
+ for col in self.columns:
+ args.append(col._copy(schema=actual_schema))
+ table = Table(
+ name,
+ metadata,
+ schema=actual_schema,
+ comment=self.comment,
+ *args,
+ **self.kwargs,
+ )
+ for const in self.constraints:
+ if isinstance(const, ForeignKeyConstraint):
+ referred_schema = const._referred_schema
+ if referred_schema_fn:
+ fk_constraint_schema = referred_schema_fn(
+ self, actual_schema, const, referred_schema
+ )
+ else:
+ fk_constraint_schema = (
+ actual_schema
+ if referred_schema == self.schema
+ else None
+ )
+ table.append_constraint(
+ const._copy(
+ schema=fk_constraint_schema, target_table=table
+ )
+ )
+ elif not const._type_bound:
+ # skip unique constraints that would be generated
+ # by the 'unique' flag on Column
+ if const._column_flag:
+ continue
+
+ table.append_constraint(
+ const._copy(schema=actual_schema, target_table=table)
+ )
+ for index in self.indexes:
+ # skip indexes that would be generated
+ # by the 'index' flag on Column
+ if index._column_flag:
+ continue
+ Index(
+ index.name,
+ unique=index.unique,
+ *[
+ _copy_expression(expr, self, table)
+ for expr in index._table_bound_expressions
+ ],
+ _table=table,
+ **index.kwargs,
+ )
+ return self._schema_item_copy(table)
+
+
+class Column(DialectKWArgs, SchemaItem, ColumnClause[_T]):
+ """Represents a column in a database table."""
+
+ __visit_name__ = "column"
+
+ inherit_cache = True
+ key: str
+
+ server_default: Optional[FetchedValue]
+
+ def __init__(
+ self,
+ __name_pos: Optional[
+ Union[str, _TypeEngineArgument[_T], SchemaEventTarget]
+ ] = None,
+ __type_pos: Optional[
+ Union[_TypeEngineArgument[_T], SchemaEventTarget]
+ ] = None,
+ *args: SchemaEventTarget,
+ name: Optional[str] = None,
+ type_: Optional[_TypeEngineArgument[_T]] = None,
+ autoincrement: _AutoIncrementType = "auto",
+ default: Optional[Any] = None,
+ doc: Optional[str] = None,
+ key: Optional[str] = None,
+ index: Optional[bool] = None,
+ unique: Optional[bool] = None,
+ info: Optional[_InfoType] = None,
+ nullable: Optional[
+ Union[bool, Literal[SchemaConst.NULL_UNSPECIFIED]]
+ ] = SchemaConst.NULL_UNSPECIFIED,
+ onupdate: Optional[Any] = None,
+ primary_key: bool = False,
+ server_default: Optional[_ServerDefaultArgument] = None,
+ server_onupdate: Optional[FetchedValue] = None,
+ quote: Optional[bool] = None,
+ system: bool = False,
+ comment: Optional[str] = None,
+ insert_sentinel: bool = False,
+ _omit_from_statements: bool = False,
+ _proxies: Optional[Any] = None,
+ **dialect_kwargs: Any,
+ ):
+ r"""
+ Construct a new ``Column`` object.
+
+ :param name: The name of this column as represented in the database.
+ This argument may be the first positional argument, or specified
+ via keyword.
+
+ Names which contain no upper case characters
+ will be treated as case insensitive names, and will not be quoted
+ unless they are a reserved word. Names with any number of upper
+ case characters will be quoted and sent exactly. Note that this
+ behavior applies even for databases which standardize upper
+ case names as case insensitive such as Oracle.
+
+ The name field may be omitted at construction time and applied
+ later, at any time before the Column is associated with a
+ :class:`_schema.Table`. This is to support convenient
+ usage within the :mod:`~sqlalchemy.ext.declarative` extension.
+
+ :param type\_: The column's type, indicated using an instance which
+ subclasses :class:`~sqlalchemy.types.TypeEngine`. If no arguments
+ are required for the type, the class of the type can be sent
+ as well, e.g.::
+
+ # use a type with arguments
+ Column('data', String(50))
+
+ # use no arguments
+ Column('level', Integer)
+
+ The ``type`` argument may be the second positional argument
+ or specified by keyword.
+
+ If the ``type`` is ``None`` or is omitted, it will first default to
+ the special type :class:`.NullType`. If and when this
+ :class:`_schema.Column` is made to refer to another column using
+ :class:`_schema.ForeignKey` and/or
+ :class:`_schema.ForeignKeyConstraint`, the type
+ of the remote-referenced column will be copied to this column as
+ well, at the moment that the foreign key is resolved against that
+ remote :class:`_schema.Column` object.
+
+ :param \*args: Additional positional arguments include various
+ :class:`.SchemaItem` derived constructs which will be applied
+ as options to the column. These include instances of
+ :class:`.Constraint`, :class:`_schema.ForeignKey`,
+ :class:`.ColumnDefault`, :class:`.Sequence`, :class:`.Computed`
+ :class:`.Identity`. In some cases an
+ equivalent keyword argument is available such as ``server_default``,
+ ``default`` and ``unique``.
+
+ :param autoincrement: Set up "auto increment" semantics for an
+ **integer primary key column with no foreign key dependencies**
+ (see later in this docstring for a more specific definition).
+ This may influence the :term:`DDL` that will be emitted for
+ this column during a table create, as well as how the column
+ will be considered when INSERT statements are compiled and
+ executed.
+
+ The default value is the string ``"auto"``,
+ which indicates that a single-column (i.e. non-composite) primary key
+ that is of an INTEGER type with no other client-side or server-side
+ default constructs indicated should receive auto increment semantics
+ automatically. Other values include ``True`` (force this column to
+ have auto-increment semantics for a :term:`composite primary key` as
+ well), ``False`` (this column should never have auto-increment
+ semantics), and the string ``"ignore_fk"`` (special-case for foreign
+ key columns, see below).
+
+ The term "auto increment semantics" refers both to the kind of DDL
+ that will be emitted for the column within a CREATE TABLE statement,
+ when methods such as :meth:`.MetaData.create_all` and
+ :meth:`.Table.create` are invoked, as well as how the column will be
+ considered when an INSERT statement is compiled and emitted to the
+ database:
+
+ * **DDL rendering** (i.e. :meth:`.MetaData.create_all`,
+ :meth:`.Table.create`): When used on a :class:`.Column` that has
+ no other
+ default-generating construct associated with it (such as a
+ :class:`.Sequence` or :class:`.Identity` construct), the parameter
+ will imply that database-specific keywords such as PostgreSQL
+ ``SERIAL``, MySQL ``AUTO_INCREMENT``, or ``IDENTITY`` on SQL Server
+ should also be rendered. Not every database backend has an
+ "implied" default generator available; for example the Oracle
+ backend always needs an explicit construct such as
+ :class:`.Identity` to be included with a :class:`.Column` in order
+ for the DDL rendered to include auto-generating constructs to also
+ be produced in the database.
+
+ * **INSERT semantics** (i.e. when a :func:`_sql.insert` construct is
+ compiled into a SQL string and is then executed on a database using
+ :meth:`_engine.Connection.execute` or equivalent): A single-row
+ INSERT statement will be known to produce a new integer primary key
+ value automatically for this column, which will be accessible
+ after the statement is invoked via the
+ :attr:`.CursorResult.inserted_primary_key` attribute upon the
+ :class:`_result.Result` object. This also applies towards use of the
+ ORM when ORM-mapped objects are persisted to the database,
+ indicating that a new integer primary key will be available to
+ become part of the :term:`identity key` for that object. This
+ behavior takes place regardless of what DDL constructs are
+ associated with the :class:`_schema.Column` and is independent
+ of the "DDL Rendering" behavior discussed in the previous note
+ above.
+
+ The parameter may be set to ``True`` to indicate that a column which
+ is part of a composite (i.e. multi-column) primary key should
+ have autoincrement semantics, though note that only one column
+ within a primary key may have this setting. It can also
+ be set to ``True`` to indicate autoincrement semantics on a
+ column that has a client-side or server-side default configured,
+ however note that not all dialects can accommodate all styles
+ of default as an "autoincrement". It can also be
+ set to ``False`` on a single-column primary key that has a
+ datatype of INTEGER in order to disable auto increment semantics
+ for that column.
+
+ The setting *only* has an effect for columns which are:
+
+ * Integer derived (i.e. INT, SMALLINT, BIGINT).
+
+ * Part of the primary key
+
+ * Not referring to another column via :class:`_schema.ForeignKey`,
+ unless
+ the value is specified as ``'ignore_fk'``::
+
+ # turn on autoincrement for this column despite
+ # the ForeignKey()
+ Column('id', ForeignKey('other.id'),
+ primary_key=True, autoincrement='ignore_fk')
+
+ It is typically not desirable to have "autoincrement" enabled on a
+ column that refers to another via foreign key, as such a column is
+ required to refer to a value that originates from elsewhere.
+
+ The setting has these effects on columns that meet the
+ above criteria:
+
+ * DDL issued for the column, if the column does not already include
+ a default generating construct supported by the backend such as
+ :class:`.Identity`, will include database-specific
+ keywords intended to signify this column as an
+ "autoincrement" column for specific backends. Behavior for
+ primary SQLAlchemy dialects includes:
+
+ * AUTO INCREMENT on MySQL and MariaDB
+ * SERIAL on PostgreSQL
+ * IDENTITY on MS-SQL - this occurs even without the
+ :class:`.Identity` construct as the
+ :paramref:`.Column.autoincrement` parameter pre-dates this
+ construct.
+ * SQLite - SQLite integer primary key columns are implicitly
+ "auto incrementing" and no additional keywords are rendered;
+ to render the special SQLite keyword ``AUTOINCREMENT``
+ is not included as this is unnecessary and not recommended
+ by the database vendor. See the section
+ :ref:`sqlite_autoincrement` for more background.
+ * Oracle - The Oracle dialect has no default "autoincrement"
+ feature available at this time, instead the :class:`.Identity`
+ construct is recommended to achieve this (the :class:`.Sequence`
+ construct may also be used).
+ * Third-party dialects - consult those dialects' documentation
+ for details on their specific behaviors.
+
+ * When a single-row :func:`_sql.insert` construct is compiled and
+ executed, which does not set the :meth:`_sql.Insert.inline`
+ modifier, newly generated primary key values for this column
+ will be automatically retrieved upon statement execution
+ using a method specific to the database driver in use:
+
+ * MySQL, SQLite - calling upon ``cursor.lastrowid()``
+ (see
+ `https://www.python.org/dev/peps/pep-0249/#lastrowid
+ <https://www.python.org/dev/peps/pep-0249/#lastrowid>`_)
+ * PostgreSQL, SQL Server, Oracle - use RETURNING or an equivalent
+ construct when rendering an INSERT statement, and then retrieving
+ the newly generated primary key values after execution
+ * PostgreSQL, Oracle for :class:`_schema.Table` objects that
+ set :paramref:`_schema.Table.implicit_returning` to False -
+ for a :class:`.Sequence` only, the :class:`.Sequence` is invoked
+ explicitly before the INSERT statement takes place so that the
+ newly generated primary key value is available to the client
+ * SQL Server for :class:`_schema.Table` objects that
+ set :paramref:`_schema.Table.implicit_returning` to False -
+ the ``SELECT scope_identity()`` construct is used after the
+ INSERT statement is invoked to retrieve the newly generated
+ primary key value.
+ * Third-party dialects - consult those dialects' documentation
+ for details on their specific behaviors.
+
+ * For multiple-row :func:`_sql.insert` constructs invoked with
+ a list of parameters (i.e. "executemany" semantics), primary-key
+ retrieving behaviors are generally disabled, however there may
+ be special APIs that may be used to retrieve lists of new
+ primary key values for an "executemany", such as the psycopg2
+ "fast insertmany" feature. Such features are very new and
+ may not yet be well covered in documentation.
+
+ :param default: A scalar, Python callable, or
+ :class:`_expression.ColumnElement` expression representing the
+ *default value* for this column, which will be invoked upon insert
+ if this column is otherwise not specified in the VALUES clause of
+ the insert. This is a shortcut to using :class:`.ColumnDefault` as
+ a positional argument; see that class for full detail on the
+ structure of the argument.
+
+ Contrast this argument to
+ :paramref:`_schema.Column.server_default`
+ which creates a default generator on the database side.
+
+ .. seealso::
+
+ :ref:`metadata_defaults_toplevel`
+
+ :param doc: optional String that can be used by the ORM or similar
+ to document attributes on the Python side. This attribute does
+ **not** render SQL comments; use the
+ :paramref:`_schema.Column.comment`
+ parameter for this purpose.
+
+ :param key: An optional string identifier which will identify this
+ ``Column`` object on the :class:`_schema.Table`.
+ When a key is provided,
+ this is the only identifier referencing the ``Column`` within the
+ application, including ORM attribute mapping; the ``name`` field
+ is used only when rendering SQL.
+
+ :param index: When ``True``, indicates that a :class:`_schema.Index`
+ construct will be automatically generated for this
+ :class:`_schema.Column`, which will result in a "CREATE INDEX"
+ statement being emitted for the :class:`_schema.Table` when the DDL
+ create operation is invoked.
+
+ Using this flag is equivalent to making use of the
+ :class:`_schema.Index` construct explicitly at the level of the
+ :class:`_schema.Table` construct itself::
+
+ Table(
+ "some_table",
+ metadata,
+ Column("x", Integer),
+ Index("ix_some_table_x", "x")
+ )
+
+ To add the :paramref:`_schema.Index.unique` flag to the
+ :class:`_schema.Index`, set both the
+ :paramref:`_schema.Column.unique` and
+ :paramref:`_schema.Column.index` flags to True simultaneously,
+ which will have the effect of rendering the "CREATE UNIQUE INDEX"
+ DDL instruction instead of "CREATE INDEX".
+
+ The name of the index is generated using the
+ :ref:`default naming convention <constraint_default_naming_convention>`
+ which for the :class:`_schema.Index` construct is of the form
+ ``ix_<tablename>_<columnname>``.
+
+ As this flag is intended only as a convenience for the common case
+ of adding a single-column, default configured index to a table
+ definition, explicit use of the :class:`_schema.Index` construct
+ should be preferred for most use cases, including composite indexes
+ that encompass more than one column, indexes with SQL expressions
+ or ordering, backend-specific index configuration options, and
+ indexes that use a specific name.
+
+ .. note:: the :attr:`_schema.Column.index` attribute on
+ :class:`_schema.Column`
+ **does not indicate** if this column is indexed or not, only
+ if this flag was explicitly set here. To view indexes on
+ a column, view the :attr:`_schema.Table.indexes` collection
+ or use :meth:`_reflection.Inspector.get_indexes`.
+
+ .. seealso::
+
+ :ref:`schema_indexes`
+
+ :ref:`constraint_naming_conventions`
+
+ :paramref:`_schema.Column.unique`
+
+ :param info: Optional data dictionary which will be populated into the
+ :attr:`.SchemaItem.info` attribute of this object.
+
+ :param nullable: When set to ``False``, will cause the "NOT NULL"
+ phrase to be added when generating DDL for the column. When
+ ``True``, will normally generate nothing (in SQL this defaults to
+ "NULL"), except in some very specific backend-specific edge cases
+ where "NULL" may render explicitly.
+ Defaults to ``True`` unless :paramref:`_schema.Column.primary_key`
+ is also ``True`` or the column specifies a :class:`_sql.Identity`,
+ in which case it defaults to ``False``.
+ This parameter is only used when issuing CREATE TABLE statements.
+
+ .. note::
+
+ When the column specifies a :class:`_sql.Identity` this
+ parameter is in general ignored by the DDL compiler. The
+ PostgreSQL database allows nullable identity column by
+ setting this parameter to ``True`` explicitly.
+
+ :param onupdate: A scalar, Python callable, or
+ :class:`~sqlalchemy.sql.expression.ClauseElement` representing a
+ default value to be applied to the column within UPDATE
+ statements, which will be invoked upon update if this column is not
+ present in the SET clause of the update. This is a shortcut to
+ using :class:`.ColumnDefault` as a positional argument with
+ ``for_update=True``.
+
+ .. seealso::
+
+ :ref:`metadata_defaults` - complete discussion of onupdate
+
+ :param primary_key: If ``True``, marks this column as a primary key
+ column. Multiple columns can have this flag set to specify
+ composite primary keys. As an alternative, the primary key of a
+ :class:`_schema.Table` can be specified via an explicit
+ :class:`.PrimaryKeyConstraint` object.
+
+ :param server_default: A :class:`.FetchedValue` instance, str, Unicode
+ or :func:`~sqlalchemy.sql.expression.text` construct representing
+ the DDL DEFAULT value for the column.
+
+ String types will be emitted as-is, surrounded by single quotes::
+
+ Column('x', Text, server_default="val")
+
+ x TEXT DEFAULT 'val'
+
+ A :func:`~sqlalchemy.sql.expression.text` expression will be
+ rendered as-is, without quotes::
+
+ Column('y', DateTime, server_default=text('NOW()'))
+
+ y DATETIME DEFAULT NOW()
+
+ Strings and text() will be converted into a
+ :class:`.DefaultClause` object upon initialization.
+
+ This parameter can also accept complex combinations of contextually
+ valid SQLAlchemy expressions or constructs::
+
+ from sqlalchemy import create_engine
+ from sqlalchemy import Table, Column, MetaData, ARRAY, Text
+ from sqlalchemy.dialects.postgresql import array
+
+ engine = create_engine(
+ 'postgresql+psycopg2://scott:tiger@localhost/mydatabase'
+ )
+ metadata_obj = MetaData()
+ tbl = Table(
+ "foo",
+ metadata_obj,
+ Column("bar",
+ ARRAY(Text),
+ server_default=array(["biz", "bang", "bash"])
+ )
+ )
+ metadata_obj.create_all(engine)
+
+ The above results in a table created with the following SQL::
+
+ CREATE TABLE foo (
+ bar TEXT[] DEFAULT ARRAY['biz', 'bang', 'bash']
+ )
+
+ Use :class:`.FetchedValue` to indicate that an already-existing
+ column will generate a default value on the database side which
+ will be available to SQLAlchemy for post-fetch after inserts. This
+ construct does not specify any DDL and the implementation is left
+ to the database, such as via a trigger.
+
+ .. seealso::
+
+ :ref:`server_defaults` - complete discussion of server side
+ defaults
+
+ :param server_onupdate: A :class:`.FetchedValue` instance
+ representing a database-side default generation function,
+ such as a trigger. This
+ indicates to SQLAlchemy that a newly generated value will be
+ available after updates. This construct does not actually
+ implement any kind of generation function within the database,
+ which instead must be specified separately.
+
+
+ .. warning:: This directive **does not** currently produce MySQL's
+ "ON UPDATE CURRENT_TIMESTAMP()" clause. See
+ :ref:`mysql_timestamp_onupdate` for background on how to
+ produce this clause.
+
+ .. seealso::
+
+ :ref:`triggered_columns`
+
+ :param quote: Force quoting of this column's name on or off,
+ corresponding to ``True`` or ``False``. When left at its default
+ of ``None``, the column identifier will be quoted according to
+ whether the name is case sensitive (identifiers with at least one
+ upper case character are treated as case sensitive), or if it's a
+ reserved word. This flag is only needed to force quoting of a
+ reserved word which is not known by the SQLAlchemy dialect.
+
+ :param unique: When ``True``, and the :paramref:`_schema.Column.index`
+ parameter is left at its default value of ``False``,
+ indicates that a :class:`_schema.UniqueConstraint`
+ construct will be automatically generated for this
+ :class:`_schema.Column`,
+ which will result in a "UNIQUE CONSTRAINT" clause referring
+ to this column being included
+ in the ``CREATE TABLE`` statement emitted, when the DDL create
+ operation for the :class:`_schema.Table` object is invoked.
+
+ When this flag is ``True`` while the
+ :paramref:`_schema.Column.index` parameter is simultaneously
+ set to ``True``, the effect instead is that a
+ :class:`_schema.Index` construct which includes the
+ :paramref:`_schema.Index.unique` parameter set to ``True``
+ is generated. See the documentation for
+ :paramref:`_schema.Column.index` for additional detail.
+
+ Using this flag is equivalent to making use of the
+ :class:`_schema.UniqueConstraint` construct explicitly at the
+ level of the :class:`_schema.Table` construct itself::
+
+ Table(
+ "some_table",
+ metadata,
+ Column("x", Integer),
+ UniqueConstraint("x")
+ )
+
+ The :paramref:`_schema.UniqueConstraint.name` parameter
+ of the unique constraint object is left at its default value
+ of ``None``; in the absence of a :ref:`naming convention <constraint_naming_conventions>`
+ for the enclosing :class:`_schema.MetaData`, the UNIQUE CONSTRAINT
+ construct will be emitted as unnamed, which typically invokes
+ a database-specific naming convention to take place.
+
+ As this flag is intended only as a convenience for the common case
+ of adding a single-column, default configured unique constraint to a table
+ definition, explicit use of the :class:`_schema.UniqueConstraint` construct
+ should be preferred for most use cases, including composite constraints
+ that encompass more than one column, backend-specific index configuration options, and
+ constraints that use a specific name.
+
+ .. note:: the :attr:`_schema.Column.unique` attribute on
+ :class:`_schema.Column`
+ **does not indicate** if this column has a unique constraint or
+ not, only if this flag was explicitly set here. To view
+ indexes and unique constraints that may involve this column,
+ view the
+ :attr:`_schema.Table.indexes` and/or
+ :attr:`_schema.Table.constraints` collections or use
+ :meth:`_reflection.Inspector.get_indexes` and/or
+ :meth:`_reflection.Inspector.get_unique_constraints`
+
+ .. seealso::
+
+ :ref:`schema_unique_constraint`
+
+ :ref:`constraint_naming_conventions`
+
+ :paramref:`_schema.Column.index`
+
+ :param system: When ``True``, indicates this is a "system" column,
+ that is a column which is automatically made available by the
+ database, and should not be included in the columns list for a
+ ``CREATE TABLE`` statement.
+
+ For more elaborate scenarios where columns should be
+ conditionally rendered differently on different backends,
+ consider custom compilation rules for :class:`.CreateColumn`.
+
+ :param comment: Optional string that will render an SQL comment on
+ table creation.
+
+ .. versionadded:: 1.2 Added the
+ :paramref:`_schema.Column.comment`
+ parameter to :class:`_schema.Column`.
+
+ :param insert_sentinel: Marks this :class:`_schema.Column` as an
+ :term:`insert sentinel` used for optimizing the performance of the
+ :term:`insertmanyvalues` feature for tables that don't
+ otherwise have qualifying primary key configurations.
+
+ .. versionadded:: 2.0.10
+
+ .. seealso::
+
+ :func:`_schema.insert_sentinel` - all in one helper for declaring
+ sentinel columns
+
+ :ref:`engine_insertmanyvalues`
+
+ :ref:`engine_insertmanyvalues_sentinel_columns`
+
+
+ """ # noqa: E501, RST201, RST202
+
+ l_args = [__name_pos, __type_pos] + list(args)
+ del args
+
+ if l_args:
+ if isinstance(l_args[0], str):
+ if name is not None:
+ raise exc.ArgumentError(
+ "May not pass name positionally and as a keyword."
+ )
+ name = l_args.pop(0) # type: ignore
+ elif l_args[0] is None:
+ l_args.pop(0)
+ if l_args:
+ coltype = l_args[0]
+
+ if hasattr(coltype, "_sqla_type"):
+ if type_ is not None:
+ raise exc.ArgumentError(
+ "May not pass type_ positionally and as a keyword."
+ )
+ type_ = l_args.pop(0) # type: ignore
+ elif l_args[0] is None:
+ l_args.pop(0)
+
+ if name is not None:
+ name = quoted_name(name, quote)
+ elif quote is not None:
+ raise exc.ArgumentError(
+ "Explicit 'name' is required when sending 'quote' argument"
+ )
+
+ # name = None is expected to be an interim state
+ # note this use case is legacy now that ORM declarative has a
+ # dedicated "column" construct local to the ORM
+ super().__init__(name, type_) # type: ignore
+
+ self.key = key if key is not None else name # type: ignore
+ self.primary_key = primary_key
+ self._insert_sentinel = insert_sentinel
+ self._omit_from_statements = _omit_from_statements
+ self._user_defined_nullable = udn = nullable
+ if udn is not NULL_UNSPECIFIED:
+ self.nullable = udn
+ else:
+ self.nullable = not primary_key
+
+ # these default to None because .index and .unique is *not*
+ # an informational flag about Column - there can still be an
+ # Index or UniqueConstraint referring to this Column.
+ self.index = index
+ self.unique = unique
+
+ self.system = system
+ self.doc = doc
+ self.autoincrement: _AutoIncrementType = autoincrement
+ self.constraints = set()
+ self.foreign_keys = set()
+ self.comment = comment
+ self.computed = None
+ self.identity = None
+
+ # check if this Column is proxying another column
+
+ if _proxies is not None:
+ self._proxies = _proxies
+ else:
+ # otherwise, add DDL-related events
+ self._set_type(self.type)
+
+ if default is not None:
+ if not isinstance(default, (ColumnDefault, Sequence)):
+ default = ColumnDefault(default)
+
+ self.default = default
+ l_args.append(default)
+ else:
+ self.default = None
+
+ if onupdate is not None:
+ if not isinstance(onupdate, (ColumnDefault, Sequence)):
+ onupdate = ColumnDefault(onupdate, for_update=True)
+
+ self.onupdate = onupdate
+ l_args.append(onupdate)
+ else:
+ self.onupdate = None
+
+ if server_default is not None:
+ if isinstance(server_default, FetchedValue):
+ server_default = server_default._as_for_update(False)
+ l_args.append(server_default)
+ else:
+ server_default = DefaultClause(server_default)
+ l_args.append(server_default)
+ self.server_default = server_default
+
+ if server_onupdate is not None:
+ if isinstance(server_onupdate, FetchedValue):
+ server_onupdate = server_onupdate._as_for_update(True)
+ l_args.append(server_onupdate)
+ else:
+ server_onupdate = DefaultClause(
+ server_onupdate, for_update=True
+ )
+ l_args.append(server_onupdate)
+ self.server_onupdate = server_onupdate
+
+ self._init_items(*cast(_typing_Sequence[SchemaItem], l_args))
+
+ util.set_creation_order(self)
+
+ if info is not None:
+ self.info = info
+
+ self._extra_kwargs(**dialect_kwargs)
+
+ table: Table
+
+ constraints: Set[Constraint]
+
+ foreign_keys: Set[ForeignKey]
+ """A collection of all :class:`_schema.ForeignKey` marker objects
+ associated with this :class:`_schema.Column`.
+
+ Each object is a member of a :class:`_schema.Table`-wide
+ :class:`_schema.ForeignKeyConstraint`.
+
+ .. seealso::
+
+ :attr:`_schema.Table.foreign_keys`
+
+ """
+
+ index: Optional[bool]
+ """The value of the :paramref:`_schema.Column.index` parameter.
+
+ Does not indicate if this :class:`_schema.Column` is actually indexed
+ or not; use :attr:`_schema.Table.indexes`.
+
+ .. seealso::
+
+ :attr:`_schema.Table.indexes`
+ """
+
+ unique: Optional[bool]
+ """The value of the :paramref:`_schema.Column.unique` parameter.
+
+ Does not indicate if this :class:`_schema.Column` is actually subject to
+ a unique constraint or not; use :attr:`_schema.Table.indexes` and
+ :attr:`_schema.Table.constraints`.
+
+ .. seealso::
+
+ :attr:`_schema.Table.indexes`
+
+ :attr:`_schema.Table.constraints`.
+
+ """
+
+ computed: Optional[Computed]
+
+ identity: Optional[Identity]
+
+ def _set_type(self, type_: TypeEngine[Any]) -> None:
+ assert self.type._isnull or type_ is self.type
+
+ self.type = type_
+ if isinstance(self.type, SchemaEventTarget):
+ self.type._set_parent_with_dispatch(self)
+ for impl in self.type._variant_mapping.values():
+ if isinstance(impl, SchemaEventTarget):
+ impl._set_parent_with_dispatch(self)
+
+ @HasMemoized.memoized_attribute
+ def _default_description_tuple(self) -> _DefaultDescriptionTuple:
+ """used by default.py -> _process_execute_defaults()"""
+
+ return _DefaultDescriptionTuple._from_column_default(self.default)
+
+ @HasMemoized.memoized_attribute
+ def _onupdate_description_tuple(self) -> _DefaultDescriptionTuple:
+ """used by default.py -> _process_execute_defaults()"""
+ return _DefaultDescriptionTuple._from_column_default(self.onupdate)
+
+ @util.memoized_property
+ def _gen_static_annotations_cache_key(self) -> bool: # type: ignore
+ """special attribute used by cache key gen, if true, we will
+ use a static cache key for the annotations dictionary, else we
+ will generate a new cache key for annotations each time.
+
+ Added for #8790
+
+ """
+ return self.table is not None and self.table._is_table
+
+ def _extra_kwargs(self, **kwargs: Any) -> None:
+ self._validate_dialect_kwargs(kwargs)
+
+ def __str__(self) -> str:
+ if self.name is None:
+ return "(no name)"
+ elif self.table is not None:
+ if self.table.named_with_column:
+ return self.table.description + "." + self.description
+ else:
+ return self.description
+ else:
+ return self.description
+
+ def references(self, column: Column[Any]) -> bool:
+ """Return True if this Column references the given column via foreign
+ key."""
+
+ for fk in self.foreign_keys:
+ if fk.column.proxy_set.intersection(column.proxy_set):
+ return True
+ else:
+ return False
+
+ def append_foreign_key(self, fk: ForeignKey) -> None:
+ fk._set_parent_with_dispatch(self)
+
+ def __repr__(self) -> str:
+ kwarg = []
+ if self.key != self.name:
+ kwarg.append("key")
+ if self.primary_key:
+ kwarg.append("primary_key")
+ if not self.nullable:
+ kwarg.append("nullable")
+ if self.onupdate:
+ kwarg.append("onupdate")
+ if self.default:
+ kwarg.append("default")
+ if self.server_default:
+ kwarg.append("server_default")
+ if self.comment:
+ kwarg.append("comment")
+ return "Column(%s)" % ", ".join(
+ [repr(self.name)]
+ + [repr(self.type)]
+ + [repr(x) for x in self.foreign_keys if x is not None]
+ + [repr(x) for x in self.constraints]
+ + [
+ (
+ self.table is not None
+ and "table=<%s>" % self.table.description
+ or "table=None"
+ )
+ ]
+ + ["%s=%s" % (k, repr(getattr(self, k))) for k in kwarg]
+ )
+
+ def _set_parent( # type: ignore[override]
+ self,
+ parent: SchemaEventTarget,
+ *,
+ all_names: Dict[str, Column[Any]],
+ allow_replacements: bool,
+ **kw: Any,
+ ) -> None:
+ table = parent
+ assert isinstance(table, Table)
+ if not self.name:
+ raise exc.ArgumentError(
+ "Column must be constructed with a non-blank name or "
+ "assign a non-blank .name before adding to a Table."
+ )
+
+ self._reset_memoizations()
+
+ if self.key is None:
+ self.key = self.name
+
+ existing = getattr(self, "table", None)
+ if existing is not None and existing is not table:
+ raise exc.ArgumentError(
+ f"Column object '{self.key}' already "
+ f"assigned to Table '{existing.description}'"
+ )
+
+ extra_remove = None
+ existing_col = None
+ conflicts_on = ""
+
+ if self.key in table._columns:
+ existing_col = table._columns[self.key]
+ if self.key == self.name:
+ conflicts_on = "name"
+ else:
+ conflicts_on = "key"
+ elif self.name in all_names:
+ existing_col = all_names[self.name]
+ extra_remove = {existing_col}
+ conflicts_on = "name"
+
+ if existing_col is not None:
+ if existing_col is not self:
+ if not allow_replacements:
+ raise exc.DuplicateColumnError(
+ f"A column with {conflicts_on} "
+ f"""'{
+ self.key if conflicts_on == 'key' else self.name
+ }' """
+ f"is already present in table '{table.name}'."
+ )
+ for fk in existing_col.foreign_keys:
+ table.foreign_keys.remove(fk)
+ if fk.constraint in table.constraints:
+ # this might have been removed
+ # already, if it's a composite constraint
+ # and more than one col being replaced
+ table.constraints.remove(fk.constraint)
+
+ if extra_remove and existing_col is not None and self.key == self.name:
+ util.warn(
+ f'Column with user-specified key "{existing_col.key}" is '
+ "being replaced with "
+ f'plain named column "{self.name}", '
+ f'key "{existing_col.key}" is being removed. If this is a '
+ "reflection operation, specify autoload_replace=False to "
+ "prevent this replacement."
+ )
+ table._columns.replace(self, extra_remove=extra_remove)
+ all_names[self.name] = self
+ self.table = table
+
+ if self._insert_sentinel:
+ if self.table._sentinel_column is not None:
+ raise exc.ArgumentError(
+ "a Table may have only one explicit sentinel column"
+ )
+ self.table._sentinel_column = self
+
+ if self.primary_key:
+ table.primary_key._replace(self)
+ elif self.key in table.primary_key:
+ raise exc.ArgumentError(
+ f"Trying to redefine primary-key column '{self.key}' as a "
+ f"non-primary-key column on table '{table.fullname}'"
+ )
+
+ if self.index:
+ if isinstance(self.index, str):
+ raise exc.ArgumentError(
+ "The 'index' keyword argument on Column is boolean only. "
+ "To create indexes with a specific name, create an "
+ "explicit Index object external to the Table."
+ )
+ table.append_constraint(
+ Index(
+ None, self.key, unique=bool(self.unique), _column_flag=True
+ )
+ )
+
+ elif self.unique:
+ if isinstance(self.unique, str):
+ raise exc.ArgumentError(
+ "The 'unique' keyword argument on Column is boolean "
+ "only. To create unique constraints or indexes with a "
+ "specific name, append an explicit UniqueConstraint to "
+ "the Table's list of elements, or create an explicit "
+ "Index object external to the Table."
+ )
+ table.append_constraint(
+ UniqueConstraint(self.key, _column_flag=True)
+ )
+
+ self._setup_on_memoized_fks(lambda fk: fk._set_remote_table(table))
+
+ if self.identity and (
+ isinstance(self.default, Sequence)
+ or isinstance(self.onupdate, Sequence)
+ ):
+ raise exc.ArgumentError(
+ "An column cannot specify both Identity and Sequence."
+ )
+
+ def _setup_on_memoized_fks(self, fn: Callable[..., Any]) -> None:
+ fk_keys = [
+ ((self.table.key, self.key), False),
+ ((self.table.key, self.name), True),
+ ]
+ for fk_key, link_to_name in fk_keys:
+ if fk_key in self.table.metadata._fk_memos:
+ for fk in self.table.metadata._fk_memos[fk_key]:
+ if fk.link_to_name is link_to_name:
+ fn(fk)
+
+ def _on_table_attach(self, fn: Callable[..., Any]) -> None:
+ if self.table is not None:
+ fn(self, self.table)
+ else:
+ event.listen(self, "after_parent_attach", fn)
+
+ @util.deprecated(
+ "1.4",
+ "The :meth:`_schema.Column.copy` method is deprecated "
+ "and will be removed in a future release.",
+ )
+ def copy(self, **kw: Any) -> Column[Any]:
+ return self._copy(**kw)
+
+ def _copy(self, **kw: Any) -> Column[Any]:
+ """Create a copy of this ``Column``, uninitialized.
+
+ This is used in :meth:`_schema.Table.to_metadata` and by the ORM.
+
+ """
+
+ # Constraint objects plus non-constraint-bound ForeignKey objects
+ args: List[SchemaItem] = [
+ c._copy(**kw) for c in self.constraints if not c._type_bound
+ ] + [c._copy(**kw) for c in self.foreign_keys if not c.constraint]
+
+ # ticket #5276
+ column_kwargs = {}
+ for dialect_name in self.dialect_options:
+ dialect_options = self.dialect_options[dialect_name]._non_defaults
+ for (
+ dialect_option_key,
+ dialect_option_value,
+ ) in dialect_options.items():
+ column_kwargs[dialect_name + "_" + dialect_option_key] = (
+ dialect_option_value
+ )
+
+ server_default = self.server_default
+ server_onupdate = self.server_onupdate
+ if isinstance(server_default, (Computed, Identity)):
+ # TODO: likely should be copied in all cases
+ args.append(server_default._copy(**kw))
+ server_default = server_onupdate = None
+
+ type_ = self.type
+ if isinstance(type_, SchemaEventTarget):
+ type_ = type_.copy(**kw)
+
+ # TODO: DefaultGenerator is not copied here! it's just used again
+ # with _set_parent() pointing to the old column. see the new
+ # use of _copy() in the new _merge() method
+
+ c = self._constructor(
+ name=self.name,
+ type_=type_,
+ key=self.key,
+ primary_key=self.primary_key,
+ unique=self.unique,
+ system=self.system,
+ # quote=self.quote, # disabled 2013-08-27 (commit 031ef080)
+ index=self.index,
+ autoincrement=self.autoincrement,
+ default=self.default,
+ server_default=server_default,
+ onupdate=self.onupdate,
+ server_onupdate=server_onupdate,
+ doc=self.doc,
+ comment=self.comment,
+ _omit_from_statements=self._omit_from_statements,
+ insert_sentinel=self._insert_sentinel,
+ *args,
+ **column_kwargs,
+ )
+
+ # copy the state of "nullable" exactly, to accommodate for
+ # ORM flipping the .nullable flag directly
+ c.nullable = self.nullable
+ c._user_defined_nullable = self._user_defined_nullable
+
+ return self._schema_item_copy(c)
+
+ def _merge(self, other: Column[Any]) -> None:
+ """merge the elements of another column into this one.
+
+ this is used by ORM pep-593 merge and will likely need a lot
+ of fixes.
+
+
+ """
+
+ if self.primary_key:
+ other.primary_key = True
+
+ if self.autoincrement != "auto" and other.autoincrement == "auto":
+ other.autoincrement = self.autoincrement
+
+ if self.system:
+ other.system = self.system
+
+ if self.info:
+ other.info.update(self.info)
+
+ type_ = self.type
+ if not type_._isnull and other.type._isnull:
+ if isinstance(type_, SchemaEventTarget):
+ type_ = type_.copy()
+
+ other.type = type_
+
+ if isinstance(type_, SchemaEventTarget):
+ type_._set_parent_with_dispatch(other)
+
+ for impl in type_._variant_mapping.values():
+ if isinstance(impl, SchemaEventTarget):
+ impl._set_parent_with_dispatch(other)
+
+ if (
+ self._user_defined_nullable is not NULL_UNSPECIFIED
+ and other._user_defined_nullable is NULL_UNSPECIFIED
+ ):
+ other.nullable = self.nullable
+ other._user_defined_nullable = self._user_defined_nullable
+
+ if self.default is not None and other.default is None:
+ new_default = self.default._copy()
+ new_default._set_parent(other)
+
+ if self.server_default and other.server_default is None:
+ new_server_default = self.server_default
+ if isinstance(new_server_default, FetchedValue):
+ new_server_default = new_server_default._copy()
+ new_server_default._set_parent(other)
+ else:
+ other.server_default = new_server_default
+
+ if self.server_onupdate and other.server_onupdate is None:
+ new_server_onupdate = self.server_onupdate
+ new_server_onupdate = new_server_onupdate._copy()
+ new_server_onupdate._set_parent(other)
+
+ if self.onupdate and other.onupdate is None:
+ new_onupdate = self.onupdate._copy()
+ new_onupdate._set_parent(other)
+
+ if self.index in (True, False) and other.index is None:
+ other.index = self.index
+
+ if self.unique in (True, False) and other.unique is None:
+ other.unique = self.unique
+
+ if self.doc and other.doc is None:
+ other.doc = self.doc
+
+ if self.comment and other.comment is None:
+ other.comment = self.comment
+
+ for const in self.constraints:
+ if not const._type_bound:
+ new_const = const._copy()
+ new_const._set_parent(other)
+
+ for fk in self.foreign_keys:
+ if not fk.constraint:
+ new_fk = fk._copy()
+ new_fk._set_parent(other)
+
+ def _make_proxy(
+ self,
+ selectable: FromClause,
+ name: Optional[str] = None,
+ key: Optional[str] = None,
+ name_is_truncatable: bool = False,
+ compound_select_cols: Optional[
+ _typing_Sequence[ColumnElement[Any]]
+ ] = None,
+ **kw: Any,
+ ) -> Tuple[str, ColumnClause[_T]]:
+ """Create a *proxy* for this column.
+
+ This is a copy of this ``Column`` referenced by a different parent
+ (such as an alias or select statement). The column should
+ be used only in select scenarios, as its full DDL/default
+ information is not transferred.
+
+ """
+
+ fk = [
+ ForeignKey(
+ col if col is not None else f._colspec,
+ _unresolvable=col is None,
+ _constraint=f.constraint,
+ )
+ for f, col in [
+ (fk, fk._resolve_column(raiseerr=False))
+ for fk in self.foreign_keys
+ ]
+ ]
+
+ if name is None and self.name is None:
+ raise exc.InvalidRequestError(
+ "Cannot initialize a sub-selectable"
+ " with this Column object until its 'name' has "
+ "been assigned."
+ )
+ try:
+ c = self._constructor(
+ (
+ coercions.expect(
+ roles.TruncatedLabelRole, name if name else self.name
+ )
+ if name_is_truncatable
+ else (name or self.name)
+ ),
+ self.type,
+ # this may actually be ._proxy_key when the key is incoming
+ key=key if key else name if name else self.key,
+ primary_key=self.primary_key,
+ nullable=self.nullable,
+ _proxies=(
+ list(compound_select_cols)
+ if compound_select_cols
+ else [self]
+ ),
+ *fk,
+ )
+ except TypeError as err:
+ raise TypeError(
+ "Could not create a copy of this %r object. "
+ "Ensure the class includes a _constructor() "
+ "attribute or method which accepts the "
+ "standard Column constructor arguments, or "
+ "references the Column class itself." % self.__class__
+ ) from err
+
+ c.table = selectable
+ c._propagate_attrs = selectable._propagate_attrs
+ if selectable._is_clone_of is not None:
+ c._is_clone_of = selectable._is_clone_of.columns.get(c.key)
+ if self.primary_key:
+ selectable.primary_key.add(c) # type: ignore
+ if fk:
+ selectable.foreign_keys.update(fk) # type: ignore
+ return c.key, c
+
+
+def insert_sentinel(
+ name: Optional[str] = None,
+ type_: Optional[_TypeEngineArgument[_T]] = None,
+ *,
+ default: Optional[Any] = None,
+ omit_from_statements: bool = True,
+) -> Column[Any]:
+ """Provides a surrogate :class:`_schema.Column` that will act as a
+ dedicated insert :term:`sentinel` column, allowing efficient bulk
+ inserts with deterministic RETURNING sorting for tables that
+ don't otherwise have qualifying primary key configurations.
+
+ Adding this column to a :class:`.Table` object requires that a
+ corresponding database table actually has this column present, so if adding
+ it to an existing model, existing database tables would need to be migrated
+ (e.g. using ALTER TABLE or similar) to include this column.
+
+ For background on how this object is used, see the section
+ :ref:`engine_insertmanyvalues_sentinel_columns` as part of the
+ section :ref:`engine_insertmanyvalues`.
+
+ The :class:`_schema.Column` returned will be a nullable integer column by
+ default and make use of a sentinel-specific default generator used only in
+ "insertmanyvalues" operations.
+
+ .. seealso::
+
+ :func:`_orm.orm_insert_sentinel`
+
+ :paramref:`_schema.Column.insert_sentinel`
+
+ :ref:`engine_insertmanyvalues`
+
+ :ref:`engine_insertmanyvalues_sentinel_columns`
+
+
+ .. versionadded:: 2.0.10
+
+ """
+ return Column(
+ name=name,
+ type_=type_api.INTEGERTYPE if type_ is None else type_,
+ default=(
+ default if default is not None else _InsertSentinelColumnDefault()
+ ),
+ _omit_from_statements=omit_from_statements,
+ insert_sentinel=True,
+ )
+
+
+class ForeignKey(DialectKWArgs, SchemaItem):
+ """Defines a dependency between two columns.
+
+ ``ForeignKey`` is specified as an argument to a :class:`_schema.Column`
+ object,
+ e.g.::
+
+ t = Table("remote_table", metadata,
+ Column("remote_id", ForeignKey("main_table.id"))
+ )
+
+ Note that ``ForeignKey`` is only a marker object that defines
+ a dependency between two columns. The actual constraint
+ is in all cases represented by the :class:`_schema.ForeignKeyConstraint`
+ object. This object will be generated automatically when
+ a ``ForeignKey`` is associated with a :class:`_schema.Column` which
+ in turn is associated with a :class:`_schema.Table`. Conversely,
+ when :class:`_schema.ForeignKeyConstraint` is applied to a
+ :class:`_schema.Table`,
+ ``ForeignKey`` markers are automatically generated to be
+ present on each associated :class:`_schema.Column`, which are also
+ associated with the constraint object.
+
+ Note that you cannot define a "composite" foreign key constraint,
+ that is a constraint between a grouping of multiple parent/child
+ columns, using ``ForeignKey`` objects. To define this grouping,
+ the :class:`_schema.ForeignKeyConstraint` object must be used, and applied
+ to the :class:`_schema.Table`. The associated ``ForeignKey`` objects
+ are created automatically.
+
+ The ``ForeignKey`` objects associated with an individual
+ :class:`_schema.Column`
+ object are available in the `foreign_keys` collection
+ of that column.
+
+ Further examples of foreign key configuration are in
+ :ref:`metadata_foreignkeys`.
+
+ """
+
+ __visit_name__ = "foreign_key"
+
+ parent: Column[Any]
+
+ _table_column: Optional[Column[Any]]
+
+ def __init__(
+ self,
+ column: _DDLColumnArgument,
+ _constraint: Optional[ForeignKeyConstraint] = None,
+ use_alter: bool = False,
+ name: _ConstraintNameArgument = None,
+ onupdate: Optional[str] = None,
+ ondelete: Optional[str] = None,
+ deferrable: Optional[bool] = None,
+ initially: Optional[str] = None,
+ link_to_name: bool = False,
+ match: Optional[str] = None,
+ info: Optional[_InfoType] = None,
+ comment: Optional[str] = None,
+ _unresolvable: bool = False,
+ **dialect_kw: Any,
+ ):
+ r"""
+ Construct a column-level FOREIGN KEY.
+
+ The :class:`_schema.ForeignKey` object when constructed generates a
+ :class:`_schema.ForeignKeyConstraint`
+ which is associated with the parent
+ :class:`_schema.Table` object's collection of constraints.
+
+ :param column: A single target column for the key relationship. A
+ :class:`_schema.Column` object or a column name as a string:
+ ``tablename.columnkey`` or ``schema.tablename.columnkey``.
+ ``columnkey`` is the ``key`` which has been assigned to the column
+ (defaults to the column name itself), unless ``link_to_name`` is
+ ``True`` in which case the rendered name of the column is used.
+
+ :param name: Optional string. An in-database name for the key if
+ `constraint` is not provided.
+
+ :param onupdate: Optional string. If set, emit ON UPDATE <value> when
+ issuing DDL for this constraint. Typical values include CASCADE,
+ DELETE and RESTRICT.
+
+ :param ondelete: Optional string. If set, emit ON DELETE <value> when
+ issuing DDL for this constraint. Typical values include CASCADE,
+ DELETE and RESTRICT.
+
+ :param deferrable: Optional bool. If set, emit DEFERRABLE or NOT
+ DEFERRABLE when issuing DDL for this constraint.
+
+ :param initially: Optional string. If set, emit INITIALLY <value> when
+ issuing DDL for this constraint.
+
+ :param link_to_name: if True, the string name given in ``column`` is
+ the rendered name of the referenced column, not its locally
+ assigned ``key``.
+
+ :param use_alter: passed to the underlying
+ :class:`_schema.ForeignKeyConstraint`
+ to indicate the constraint should
+ be generated/dropped externally from the CREATE TABLE/ DROP TABLE
+ statement. See :paramref:`_schema.ForeignKeyConstraint.use_alter`
+ for further description.
+
+ .. seealso::
+
+ :paramref:`_schema.ForeignKeyConstraint.use_alter`
+
+ :ref:`use_alter`
+
+ :param match: Optional string. If set, emit MATCH <value> when issuing
+ DDL for this constraint. Typical values include SIMPLE, PARTIAL
+ and FULL.
+
+ :param info: Optional data dictionary which will be populated into the
+ :attr:`.SchemaItem.info` attribute of this object.
+
+ :param comment: Optional string that will render an SQL comment on
+ foreign key constraint creation.
+
+ .. versionadded:: 2.0
+
+ :param \**dialect_kw: Additional keyword arguments are dialect
+ specific, and passed in the form ``<dialectname>_<argname>``. The
+ arguments are ultimately handled by a corresponding
+ :class:`_schema.ForeignKeyConstraint`.
+ See the documentation regarding
+ an individual dialect at :ref:`dialect_toplevel` for detail on
+ documented arguments.
+
+ """
+
+ self._colspec = coercions.expect(roles.DDLReferredColumnRole, column)
+ self._unresolvable = _unresolvable
+
+ if isinstance(self._colspec, str):
+ self._table_column = None
+ else:
+ self._table_column = self._colspec
+
+ if not isinstance(
+ self._table_column.table, (type(None), TableClause)
+ ):
+ raise exc.ArgumentError(
+ "ForeignKey received Column not bound "
+ "to a Table, got: %r" % self._table_column.table
+ )
+
+ # the linked ForeignKeyConstraint.
+ # ForeignKey will create this when parent Column
+ # is attached to a Table, *or* ForeignKeyConstraint
+ # object passes itself in when creating ForeignKey
+ # markers.
+ self.constraint = _constraint
+
+ # .parent is not Optional under normal use
+ self.parent = None # type: ignore
+
+ self.use_alter = use_alter
+ self.name = name
+ self.onupdate = onupdate
+ self.ondelete = ondelete
+ self.deferrable = deferrable
+ self.initially = initially
+ self.link_to_name = link_to_name
+ self.match = match
+ self.comment = comment
+ if info:
+ self.info = info
+ self._unvalidated_dialect_kw = dialect_kw
+
+ def __repr__(self) -> str:
+ return "ForeignKey(%r)" % self._get_colspec()
+
+ @util.deprecated(
+ "1.4",
+ "The :meth:`_schema.ForeignKey.copy` method is deprecated "
+ "and will be removed in a future release.",
+ )
+ def copy(self, *, schema: Optional[str] = None, **kw: Any) -> ForeignKey:
+ return self._copy(schema=schema, **kw)
+
+ def _copy(self, *, schema: Optional[str] = None, **kw: Any) -> ForeignKey:
+ """Produce a copy of this :class:`_schema.ForeignKey` object.
+
+ The new :class:`_schema.ForeignKey` will not be bound
+ to any :class:`_schema.Column`.
+
+ This method is usually used by the internal
+ copy procedures of :class:`_schema.Column`, :class:`_schema.Table`,
+ and :class:`_schema.MetaData`.
+
+ :param schema: The returned :class:`_schema.ForeignKey` will
+ reference the original table and column name, qualified
+ by the given string schema name.
+
+ """
+ fk = ForeignKey(
+ self._get_colspec(schema=schema),
+ use_alter=self.use_alter,
+ name=self.name,
+ onupdate=self.onupdate,
+ ondelete=self.ondelete,
+ deferrable=self.deferrable,
+ initially=self.initially,
+ link_to_name=self.link_to_name,
+ match=self.match,
+ comment=self.comment,
+ **self._unvalidated_dialect_kw,
+ )
+ return self._schema_item_copy(fk)
+
+ def _get_colspec(
+ self,
+ schema: Optional[
+ Union[
+ str,
+ Literal[SchemaConst.RETAIN_SCHEMA, SchemaConst.BLANK_SCHEMA],
+ ]
+ ] = None,
+ table_name: Optional[str] = None,
+ _is_copy: bool = False,
+ ) -> str:
+ """Return a string based 'column specification' for this
+ :class:`_schema.ForeignKey`.
+
+ This is usually the equivalent of the string-based "tablename.colname"
+ argument first passed to the object's constructor.
+
+ """
+ if schema not in (None, RETAIN_SCHEMA):
+ _schema, tname, colname = self._column_tokens
+ if table_name is not None:
+ tname = table_name
+ if schema is BLANK_SCHEMA:
+ return "%s.%s" % (tname, colname)
+ else:
+ return "%s.%s.%s" % (schema, tname, colname)
+ elif table_name:
+ schema, tname, colname = self._column_tokens
+ if schema:
+ return "%s.%s.%s" % (schema, table_name, colname)
+ else:
+ return "%s.%s" % (table_name, colname)
+ elif self._table_column is not None:
+ if self._table_column.table is None:
+ if _is_copy:
+ raise exc.InvalidRequestError(
+ f"Can't copy ForeignKey object which refers to "
+ f"non-table bound Column {self._table_column!r}"
+ )
+ else:
+ return self._table_column.key
+ return "%s.%s" % (
+ self._table_column.table.fullname,
+ self._table_column.key,
+ )
+ else:
+ assert isinstance(self._colspec, str)
+ return self._colspec
+
+ @property
+ def _referred_schema(self) -> Optional[str]:
+ return self._column_tokens[0]
+
+ def _table_key(self) -> Any:
+ if self._table_column is not None:
+ if self._table_column.table is None:
+ return None
+ else:
+ return self._table_column.table.key
+ else:
+ schema, tname, colname = self._column_tokens
+ return _get_table_key(tname, schema)
+
+ target_fullname = property(_get_colspec)
+
+ def references(self, table: Table) -> bool:
+ """Return True if the given :class:`_schema.Table`
+ is referenced by this
+ :class:`_schema.ForeignKey`."""
+
+ return table.corresponding_column(self.column) is not None
+
+ def get_referent(self, table: FromClause) -> Optional[Column[Any]]:
+ """Return the :class:`_schema.Column` in the given
+ :class:`_schema.Table` (or any :class:`.FromClause`)
+ referenced by this :class:`_schema.ForeignKey`.
+
+ Returns None if this :class:`_schema.ForeignKey`
+ does not reference the given
+ :class:`_schema.Table`.
+
+ """
+ # our column is a Column, and any subquery etc. proxying us
+ # would be doing so via another Column, so that's what would
+ # be returned here
+ return table.columns.corresponding_column(self.column) # type: ignore
+
+ @util.memoized_property
+ def _column_tokens(self) -> Tuple[Optional[str], str, Optional[str]]:
+ """parse a string-based _colspec into its component parts."""
+
+ m = self._get_colspec().split(".")
+ if m is None:
+ raise exc.ArgumentError(
+ f"Invalid foreign key column specification: {self._colspec}"
+ )
+ if len(m) == 1:
+ tname = m.pop()
+ colname = None
+ else:
+ colname = m.pop()
+ tname = m.pop()
+
+ # A FK between column 'bar' and table 'foo' can be
+ # specified as 'foo', 'foo.bar', 'dbo.foo.bar',
+ # 'otherdb.dbo.foo.bar'. Once we have the column name and
+ # the table name, treat everything else as the schema
+ # name. Some databases (e.g. Sybase) support
+ # inter-database foreign keys. See tickets#1341 and --
+ # indirectly related -- Ticket #594. This assumes that '.'
+ # will never appear *within* any component of the FK.
+
+ if len(m) > 0:
+ schema = ".".join(m)
+ else:
+ schema = None
+ return schema, tname, colname
+
+ def _resolve_col_tokens(self) -> Tuple[Table, str, Optional[str]]:
+ if self.parent is None:
+ raise exc.InvalidRequestError(
+ "this ForeignKey object does not yet have a "
+ "parent Column associated with it."
+ )
+
+ elif self.parent.table is None:
+ raise exc.InvalidRequestError(
+ "this ForeignKey's parent column is not yet associated "
+ "with a Table."
+ )
+
+ parenttable = self.parent.table
+
+ if self._unresolvable:
+ schema, tname, colname = self._column_tokens
+ tablekey = _get_table_key(tname, schema)
+ return parenttable, tablekey, colname
+
+ # assertion
+ # basically Column._make_proxy() sends the actual
+ # target Column to the ForeignKey object, so the
+ # string resolution here is never called.
+ for c in self.parent.base_columns:
+ if isinstance(c, Column):
+ assert c.table is parenttable
+ break
+ else:
+ assert False
+ ######################
+
+ schema, tname, colname = self._column_tokens
+
+ if schema is None and parenttable.metadata.schema is not None:
+ schema = parenttable.metadata.schema
+
+ tablekey = _get_table_key(tname, schema)
+ return parenttable, tablekey, colname
+
+ def _link_to_col_by_colstring(
+ self, parenttable: Table, table: Table, colname: Optional[str]
+ ) -> Column[Any]:
+ _column = None
+ if colname is None:
+ # colname is None in the case that ForeignKey argument
+ # was specified as table name only, in which case we
+ # match the column name to the same column on the
+ # parent.
+ # this use case wasn't working in later 1.x series
+ # as it had no test coverage; fixed in 2.0
+ parent = self.parent
+ assert parent is not None
+ key = parent.key
+ _column = table.c.get(key, None)
+ elif self.link_to_name:
+ key = colname
+ for c in table.c:
+ if c.name == colname:
+ _column = c
+ else:
+ key = colname
+ _column = table.c.get(colname, None)
+
+ if _column is None:
+ raise exc.NoReferencedColumnError(
+ "Could not initialize target column "
+ f"for ForeignKey '{self._colspec}' "
+ f"on table '{parenttable.name}': "
+ f"table '{table.name}' has no column named '{key}'",
+ table.name,
+ key,
+ )
+
+ return _column
+
+ def _set_target_column(self, column: Column[Any]) -> None:
+ assert self.parent is not None
+
+ # propagate TypeEngine to parent if it didn't have one
+ if self.parent.type._isnull:
+ self.parent.type = column.type
+
+ # super-edgy case, if other FKs point to our column,
+ # they'd get the type propagated out also.
+
+ def set_type(fk: ForeignKey) -> None:
+ if fk.parent.type._isnull:
+ fk.parent.type = column.type
+
+ self.parent._setup_on_memoized_fks(set_type)
+
+ self.column = column # type: ignore
+
+ @util.ro_memoized_property
+ def column(self) -> Column[Any]:
+ """Return the target :class:`_schema.Column` referenced by this
+ :class:`_schema.ForeignKey`.
+
+ If no target column has been established, an exception
+ is raised.
+
+ """
+
+ return self._resolve_column()
+
+ @overload
+ def _resolve_column(
+ self, *, raiseerr: Literal[True] = ...
+ ) -> Column[Any]: ...
+
+ @overload
+ def _resolve_column(
+ self, *, raiseerr: bool = ...
+ ) -> Optional[Column[Any]]: ...
+
+ def _resolve_column(
+ self, *, raiseerr: bool = True
+ ) -> Optional[Column[Any]]:
+ _column: Column[Any]
+
+ if isinstance(self._colspec, str):
+ parenttable, tablekey, colname = self._resolve_col_tokens()
+
+ if self._unresolvable or tablekey not in parenttable.metadata:
+ if not raiseerr:
+ return None
+ raise exc.NoReferencedTableError(
+ f"Foreign key associated with column "
+ f"'{self.parent}' could not find "
+ f"table '{tablekey}' with which to generate a "
+ f"foreign key to target column '{colname}'",
+ tablekey,
+ )
+ elif parenttable.key not in parenttable.metadata:
+ if not raiseerr:
+ return None
+ raise exc.InvalidRequestError(
+ f"Table {parenttable} is no longer associated with its "
+ "parent MetaData"
+ )
+ else:
+ table = parenttable.metadata.tables[tablekey]
+ return self._link_to_col_by_colstring(
+ parenttable, table, colname
+ )
+
+ elif hasattr(self._colspec, "__clause_element__"):
+ _column = self._colspec.__clause_element__()
+ return _column
+ else:
+ _column = self._colspec
+ return _column
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ assert isinstance(parent, Column)
+
+ if self.parent is not None and self.parent is not parent:
+ raise exc.InvalidRequestError(
+ "This ForeignKey already has a parent !"
+ )
+ self.parent = parent
+ self.parent.foreign_keys.add(self)
+ self.parent._on_table_attach(self._set_table)
+
+ def _set_remote_table(self, table: Table) -> None:
+ parenttable, _, colname = self._resolve_col_tokens()
+ _column = self._link_to_col_by_colstring(parenttable, table, colname)
+ self._set_target_column(_column)
+ assert self.constraint is not None
+ self.constraint._validate_dest_table(table)
+
+ def _remove_from_metadata(self, metadata: MetaData) -> None:
+ parenttable, table_key, colname = self._resolve_col_tokens()
+ fk_key = (table_key, colname)
+
+ if self in metadata._fk_memos[fk_key]:
+ # TODO: no test coverage for self not in memos
+ metadata._fk_memos[fk_key].remove(self)
+
+ def _set_table(self, column: Column[Any], table: Table) -> None:
+ # standalone ForeignKey - create ForeignKeyConstraint
+ # on the hosting Table when attached to the Table.
+ assert isinstance(table, Table)
+ if self.constraint is None:
+ self.constraint = ForeignKeyConstraint(
+ [],
+ [],
+ use_alter=self.use_alter,
+ name=self.name,
+ onupdate=self.onupdate,
+ ondelete=self.ondelete,
+ deferrable=self.deferrable,
+ initially=self.initially,
+ match=self.match,
+ comment=self.comment,
+ **self._unvalidated_dialect_kw,
+ )
+ self.constraint._append_element(column, self)
+ self.constraint._set_parent_with_dispatch(table)
+ table.foreign_keys.add(self)
+ # set up remote ".column" attribute, or a note to pick it
+ # up when the other Table/Column shows up
+ if isinstance(self._colspec, str):
+ parenttable, table_key, colname = self._resolve_col_tokens()
+ fk_key = (table_key, colname)
+ if table_key in parenttable.metadata.tables:
+ table = parenttable.metadata.tables[table_key]
+ try:
+ _column = self._link_to_col_by_colstring(
+ parenttable, table, colname
+ )
+ except exc.NoReferencedColumnError:
+ # this is OK, we'll try later
+ pass
+ else:
+ self._set_target_column(_column)
+
+ parenttable.metadata._fk_memos[fk_key].append(self)
+ elif hasattr(self._colspec, "__clause_element__"):
+ _column = self._colspec.__clause_element__()
+ self._set_target_column(_column)
+ else:
+ _column = self._colspec
+ self._set_target_column(_column)
+
+
+if TYPE_CHECKING:
+
+ def default_is_sequence(
+ obj: Optional[DefaultGenerator],
+ ) -> TypeGuard[Sequence]: ...
+
+ def default_is_clause_element(
+ obj: Optional[DefaultGenerator],
+ ) -> TypeGuard[ColumnElementColumnDefault]: ...
+
+ def default_is_scalar(
+ obj: Optional[DefaultGenerator],
+ ) -> TypeGuard[ScalarElementColumnDefault]: ...
+
+else:
+ default_is_sequence = operator.attrgetter("is_sequence")
+
+ default_is_clause_element = operator.attrgetter("is_clause_element")
+
+ default_is_scalar = operator.attrgetter("is_scalar")
+
+
+class DefaultGenerator(Executable, SchemaItem):
+ """Base class for column *default* values.
+
+ This object is only present on column.default or column.onupdate.
+ It's not valid as a server default.
+
+ """
+
+ __visit_name__ = "default_generator"
+
+ _is_default_generator = True
+ is_sequence = False
+ is_identity = False
+ is_server_default = False
+ is_clause_element = False
+ is_callable = False
+ is_scalar = False
+ has_arg = False
+ is_sentinel = False
+ column: Optional[Column[Any]]
+
+ def __init__(self, for_update: bool = False) -> None:
+ self.for_update = for_update
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ if TYPE_CHECKING:
+ assert isinstance(parent, Column)
+ self.column = parent
+ if self.for_update:
+ self.column.onupdate = self
+ else:
+ self.column.default = self
+
+ def _copy(self) -> DefaultGenerator:
+ raise NotImplementedError()
+
+ def _execute_on_connection(
+ self,
+ connection: Connection,
+ distilled_params: _CoreMultiExecuteParams,
+ execution_options: CoreExecuteOptionsParameter,
+ ) -> Any:
+ util.warn_deprecated(
+ "Using the .execute() method to invoke a "
+ "DefaultGenerator object is deprecated; please use "
+ "the .scalar() method.",
+ "2.0",
+ )
+ return self._execute_on_scalar(
+ connection, distilled_params, execution_options
+ )
+
+ def _execute_on_scalar(
+ self,
+ connection: Connection,
+ distilled_params: _CoreMultiExecuteParams,
+ execution_options: CoreExecuteOptionsParameter,
+ ) -> Any:
+ return connection._execute_default(
+ self, distilled_params, execution_options
+ )
+
+
+class ColumnDefault(DefaultGenerator, ABC):
+ """A plain default value on a column.
+
+ This could correspond to a constant, a callable function,
+ or a SQL clause.
+
+ :class:`.ColumnDefault` is generated automatically
+ whenever the ``default``, ``onupdate`` arguments of
+ :class:`_schema.Column` are used. A :class:`.ColumnDefault`
+ can be passed positionally as well.
+
+ For example, the following::
+
+ Column('foo', Integer, default=50)
+
+ Is equivalent to::
+
+ Column('foo', Integer, ColumnDefault(50))
+
+
+ """
+
+ arg: Any
+
+ @overload
+ def __new__(
+ cls, arg: Callable[..., Any], for_update: bool = ...
+ ) -> CallableColumnDefault: ...
+
+ @overload
+ def __new__(
+ cls, arg: ColumnElement[Any], for_update: bool = ...
+ ) -> ColumnElementColumnDefault: ...
+
+ # if I return ScalarElementColumnDefault here, which is what's actually
+ # returned, mypy complains that
+ # overloads overlap w/ incompatible return types.
+ @overload
+ def __new__(cls, arg: object, for_update: bool = ...) -> ColumnDefault: ...
+
+ def __new__(
+ cls, arg: Any = None, for_update: bool = False
+ ) -> ColumnDefault:
+ """Construct a new :class:`.ColumnDefault`.
+
+
+ :param arg: argument representing the default value.
+ May be one of the following:
+
+ * a plain non-callable Python value, such as a
+ string, integer, boolean, or other simple type.
+ The default value will be used as is each time.
+ * a SQL expression, that is one which derives from
+ :class:`_expression.ColumnElement`. The SQL expression will
+ be rendered into the INSERT or UPDATE statement,
+ or in the case of a primary key column when
+ RETURNING is not used may be
+ pre-executed before an INSERT within a SELECT.
+ * A Python callable. The function will be invoked for each
+ new row subject to an INSERT or UPDATE.
+ The callable must accept exactly
+ zero or one positional arguments. The one-argument form
+ will receive an instance of the :class:`.ExecutionContext`,
+ which provides contextual information as to the current
+ :class:`_engine.Connection` in use as well as the current
+ statement and parameters.
+
+ """
+
+ if isinstance(arg, FetchedValue):
+ raise exc.ArgumentError(
+ "ColumnDefault may not be a server-side default type."
+ )
+ elif callable(arg):
+ cls = CallableColumnDefault
+ elif isinstance(arg, ClauseElement):
+ cls = ColumnElementColumnDefault
+ elif arg is not None:
+ cls = ScalarElementColumnDefault
+
+ return object.__new__(cls)
+
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}({self.arg!r})"
+
+
+class ScalarElementColumnDefault(ColumnDefault):
+ """default generator for a fixed scalar Python value
+
+ .. versionadded: 2.0
+
+ """
+
+ is_scalar = True
+ has_arg = True
+
+ def __init__(self, arg: Any, for_update: bool = False) -> None:
+ self.for_update = for_update
+ self.arg = arg
+
+ def _copy(self) -> ScalarElementColumnDefault:
+ return ScalarElementColumnDefault(
+ arg=self.arg, for_update=self.for_update
+ )
+
+
+class _InsertSentinelColumnDefault(ColumnDefault):
+ """Default generator that's specific to the use of a "sentinel" column
+ when using the insertmanyvalues feature.
+
+ This default is used as part of the :func:`_schema.insert_sentinel`
+ construct.
+
+ """
+
+ is_sentinel = True
+ for_update = False
+ arg = None
+
+ def __new__(cls) -> _InsertSentinelColumnDefault:
+ return object.__new__(cls)
+
+ def __init__(self) -> None:
+ pass
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ col = cast("Column[Any]", parent)
+ if not col._insert_sentinel:
+ raise exc.ArgumentError(
+ "The _InsertSentinelColumnDefault may only be applied to a "
+ "Column marked as insert_sentinel=True"
+ )
+ elif not col.nullable:
+ raise exc.ArgumentError(
+ "The _InsertSentinelColumnDefault may only be applied to a "
+ "Column that is nullable"
+ )
+
+ super()._set_parent(parent, **kw)
+
+ def _copy(self) -> _InsertSentinelColumnDefault:
+ return _InsertSentinelColumnDefault()
+
+
+_SQLExprDefault = Union["ColumnElement[Any]", "TextClause"]
+
+
+class ColumnElementColumnDefault(ColumnDefault):
+ """default generator for a SQL expression
+
+ .. versionadded:: 2.0
+
+ """
+
+ is_clause_element = True
+ has_arg = True
+ arg: _SQLExprDefault
+
+ def __init__(
+ self,
+ arg: _SQLExprDefault,
+ for_update: bool = False,
+ ) -> None:
+ self.for_update = for_update
+ self.arg = arg
+
+ def _copy(self) -> ColumnElementColumnDefault:
+ return ColumnElementColumnDefault(
+ arg=self.arg, for_update=self.for_update
+ )
+
+ @util.memoized_property
+ @util.preload_module("sqlalchemy.sql.sqltypes")
+ def _arg_is_typed(self) -> bool:
+ sqltypes = util.preloaded.sql_sqltypes
+
+ return not isinstance(self.arg.type, sqltypes.NullType)
+
+
+class _CallableColumnDefaultProtocol(Protocol):
+ def __call__(self, context: ExecutionContext) -> Any: ...
+
+
+class CallableColumnDefault(ColumnDefault):
+ """default generator for a callable Python function
+
+ .. versionadded:: 2.0
+
+ """
+
+ is_callable = True
+ arg: _CallableColumnDefaultProtocol
+ has_arg = True
+
+ def __init__(
+ self,
+ arg: Union[_CallableColumnDefaultProtocol, Callable[[], Any]],
+ for_update: bool = False,
+ ) -> None:
+ self.for_update = for_update
+ self.arg = self._maybe_wrap_callable(arg)
+
+ def _copy(self) -> CallableColumnDefault:
+ return CallableColumnDefault(arg=self.arg, for_update=self.for_update)
+
+ def _maybe_wrap_callable(
+ self, fn: Union[_CallableColumnDefaultProtocol, Callable[[], Any]]
+ ) -> _CallableColumnDefaultProtocol:
+ """Wrap callables that don't accept a context.
+
+ This is to allow easy compatibility with default callables
+ that aren't specific to accepting of a context.
+
+ """
+
+ try:
+ argspec = util.get_callable_argspec(fn, no_self=True)
+ except TypeError:
+ return util.wrap_callable(lambda ctx: fn(), fn) # type: ignore
+
+ defaulted = argspec[3] is not None and len(argspec[3]) or 0
+ positionals = len(argspec[0]) - defaulted
+
+ if positionals == 0:
+ return util.wrap_callable(lambda ctx: fn(), fn) # type: ignore
+
+ elif positionals == 1:
+ return fn # type: ignore
+ else:
+ raise exc.ArgumentError(
+ "ColumnDefault Python function takes zero or one "
+ "positional arguments"
+ )
+
+
+class IdentityOptions:
+ """Defines options for a named database sequence or an identity column.
+
+ .. versionadded:: 1.3.18
+
+ .. seealso::
+
+ :class:`.Sequence`
+
+ """
+
+ def __init__(
+ self,
+ start: Optional[int] = None,
+ increment: Optional[int] = None,
+ minvalue: Optional[int] = None,
+ maxvalue: Optional[int] = None,
+ nominvalue: Optional[bool] = None,
+ nomaxvalue: Optional[bool] = None,
+ cycle: Optional[bool] = None,
+ cache: Optional[int] = None,
+ order: Optional[bool] = None,
+ ) -> None:
+ """Construct a :class:`.IdentityOptions` object.
+
+ See the :class:`.Sequence` documentation for a complete description
+ of the parameters.
+
+ :param start: the starting index of the sequence.
+ :param increment: the increment value of the sequence.
+ :param minvalue: the minimum value of the sequence.
+ :param maxvalue: the maximum value of the sequence.
+ :param nominvalue: no minimum value of the sequence.
+ :param nomaxvalue: no maximum value of the sequence.
+ :param cycle: allows the sequence to wrap around when the maxvalue
+ or minvalue has been reached.
+ :param cache: optional integer value; number of future values in the
+ sequence which are calculated in advance.
+ :param order: optional boolean value; if ``True``, renders the
+ ORDER keyword.
+
+ """
+ self.start = start
+ self.increment = increment
+ self.minvalue = minvalue
+ self.maxvalue = maxvalue
+ self.nominvalue = nominvalue
+ self.nomaxvalue = nomaxvalue
+ self.cycle = cycle
+ self.cache = cache
+ self.order = order
+
+ @property
+ def _increment_is_negative(self) -> bool:
+ return self.increment is not None and self.increment < 0
+
+
+class Sequence(HasSchemaAttr, IdentityOptions, DefaultGenerator):
+ """Represents a named database sequence.
+
+ The :class:`.Sequence` object represents the name and configurational
+ parameters of a database sequence. It also represents
+ a construct that can be "executed" by a SQLAlchemy :class:`_engine.Engine`
+ or :class:`_engine.Connection`,
+ rendering the appropriate "next value" function
+ for the target database and returning a result.
+
+ The :class:`.Sequence` is typically associated with a primary key column::
+
+ some_table = Table(
+ 'some_table', metadata,
+ Column('id', Integer, Sequence('some_table_seq', start=1),
+ primary_key=True)
+ )
+
+ When CREATE TABLE is emitted for the above :class:`_schema.Table`, if the
+ target platform supports sequences, a CREATE SEQUENCE statement will
+ be emitted as well. For platforms that don't support sequences,
+ the :class:`.Sequence` construct is ignored.
+
+ .. seealso::
+
+ :ref:`defaults_sequences`
+
+ :class:`.CreateSequence`
+
+ :class:`.DropSequence`
+
+ """
+
+ __visit_name__ = "sequence"
+
+ is_sequence = True
+
+ column: Optional[Column[Any]]
+ data_type: Optional[TypeEngine[int]]
+
+ def __init__(
+ self,
+ name: str,
+ start: Optional[int] = None,
+ increment: Optional[int] = None,
+ minvalue: Optional[int] = None,
+ maxvalue: Optional[int] = None,
+ nominvalue: Optional[bool] = None,
+ nomaxvalue: Optional[bool] = None,
+ cycle: Optional[bool] = None,
+ schema: Optional[Union[str, Literal[SchemaConst.BLANK_SCHEMA]]] = None,
+ cache: Optional[int] = None,
+ order: Optional[bool] = None,
+ data_type: Optional[_TypeEngineArgument[int]] = None,
+ optional: bool = False,
+ quote: Optional[bool] = None,
+ metadata: Optional[MetaData] = None,
+ quote_schema: Optional[bool] = None,
+ for_update: bool = False,
+ ) -> None:
+ """Construct a :class:`.Sequence` object.
+
+ :param name: the name of the sequence.
+
+ :param start: the starting index of the sequence. This value is
+ used when the CREATE SEQUENCE command is emitted to the database
+ as the value of the "START WITH" clause. If ``None``, the
+ clause is omitted, which on most platforms indicates a starting
+ value of 1.
+
+ .. versionchanged:: 2.0 The :paramref:`.Sequence.start` parameter
+ is required in order to have DDL emit "START WITH". This is a
+ reversal of a change made in version 1.4 which would implicitly
+ render "START WITH 1" if the :paramref:`.Sequence.start` were
+ not included. See :ref:`change_7211` for more detail.
+
+ :param increment: the increment value of the sequence. This
+ value is used when the CREATE SEQUENCE command is emitted to
+ the database as the value of the "INCREMENT BY" clause. If ``None``,
+ the clause is omitted, which on most platforms indicates an
+ increment of 1.
+ :param minvalue: the minimum value of the sequence. This
+ value is used when the CREATE SEQUENCE command is emitted to
+ the database as the value of the "MINVALUE" clause. If ``None``,
+ the clause is omitted, which on most platforms indicates a
+ minvalue of 1 and -2^63-1 for ascending and descending sequences,
+ respectively.
+
+ :param maxvalue: the maximum value of the sequence. This
+ value is used when the CREATE SEQUENCE command is emitted to
+ the database as the value of the "MAXVALUE" clause. If ``None``,
+ the clause is omitted, which on most platforms indicates a
+ maxvalue of 2^63-1 and -1 for ascending and descending sequences,
+ respectively.
+
+ :param nominvalue: no minimum value of the sequence. This
+ value is used when the CREATE SEQUENCE command is emitted to
+ the database as the value of the "NO MINVALUE" clause. If ``None``,
+ the clause is omitted, which on most platforms indicates a
+ minvalue of 1 and -2^63-1 for ascending and descending sequences,
+ respectively.
+
+ :param nomaxvalue: no maximum value of the sequence. This
+ value is used when the CREATE SEQUENCE command is emitted to
+ the database as the value of the "NO MAXVALUE" clause. If ``None``,
+ the clause is omitted, which on most platforms indicates a
+ maxvalue of 2^63-1 and -1 for ascending and descending sequences,
+ respectively.
+
+ :param cycle: allows the sequence to wrap around when the maxvalue
+ or minvalue has been reached by an ascending or descending sequence
+ respectively. This value is used when the CREATE SEQUENCE command
+ is emitted to the database as the "CYCLE" clause. If the limit is
+ reached, the next number generated will be the minvalue or maxvalue,
+ respectively. If cycle=False (the default) any calls to nextval
+ after the sequence has reached its maximum value will return an
+ error.
+
+ :param schema: optional schema name for the sequence, if located
+ in a schema other than the default. The rules for selecting the
+ schema name when a :class:`_schema.MetaData`
+ is also present are the same
+ as that of :paramref:`_schema.Table.schema`.
+
+ :param cache: optional integer value; number of future values in the
+ sequence which are calculated in advance. Renders the CACHE keyword
+ understood by Oracle and PostgreSQL.
+
+ :param order: optional boolean value; if ``True``, renders the
+ ORDER keyword, understood by Oracle, indicating the sequence is
+ definitively ordered. May be necessary to provide deterministic
+ ordering using Oracle RAC.
+
+ :param data_type: The type to be returned by the sequence, for
+ dialects that allow us to choose between INTEGER, BIGINT, etc.
+ (e.g., mssql).
+
+ .. versionadded:: 1.4.0
+
+ :param optional: boolean value, when ``True``, indicates that this
+ :class:`.Sequence` object only needs to be explicitly generated
+ on backends that don't provide another way to generate primary
+ key identifiers. Currently, it essentially means, "don't create
+ this sequence on the PostgreSQL backend, where the SERIAL keyword
+ creates a sequence for us automatically".
+ :param quote: boolean value, when ``True`` or ``False``, explicitly
+ forces quoting of the :paramref:`_schema.Sequence.name` on or off.
+ When left at its default of ``None``, normal quoting rules based
+ on casing and reserved words take place.
+ :param quote_schema: Set the quoting preferences for the ``schema``
+ name.
+
+ :param metadata: optional :class:`_schema.MetaData` object which this
+ :class:`.Sequence` will be associated with. A :class:`.Sequence`
+ that is associated with a :class:`_schema.MetaData`
+ gains the following
+ capabilities:
+
+ * The :class:`.Sequence` will inherit the
+ :paramref:`_schema.MetaData.schema`
+ parameter specified to the target :class:`_schema.MetaData`, which
+ affects the production of CREATE / DROP DDL, if any.
+
+ * The :meth:`.Sequence.create` and :meth:`.Sequence.drop` methods
+ automatically use the engine bound to the :class:`_schema.MetaData`
+ object, if any.
+
+ * The :meth:`_schema.MetaData.create_all` and
+ :meth:`_schema.MetaData.drop_all`
+ methods will emit CREATE / DROP for this :class:`.Sequence`,
+ even if the :class:`.Sequence` is not associated with any
+ :class:`_schema.Table` / :class:`_schema.Column`
+ that's a member of this
+ :class:`_schema.MetaData`.
+
+ The above behaviors can only occur if the :class:`.Sequence` is
+ explicitly associated with the :class:`_schema.MetaData`
+ via this parameter.
+
+ .. seealso::
+
+ :ref:`sequence_metadata` - full discussion of the
+ :paramref:`.Sequence.metadata` parameter.
+
+ :param for_update: Indicates this :class:`.Sequence`, when associated
+ with a :class:`_schema.Column`,
+ should be invoked for UPDATE statements
+ on that column's table, rather than for INSERT statements, when
+ no value is otherwise present for that column in the statement.
+
+ """
+ DefaultGenerator.__init__(self, for_update=for_update)
+ IdentityOptions.__init__(
+ self,
+ start=start,
+ increment=increment,
+ minvalue=minvalue,
+ maxvalue=maxvalue,
+ nominvalue=nominvalue,
+ nomaxvalue=nomaxvalue,
+ cycle=cycle,
+ cache=cache,
+ order=order,
+ )
+ self.column = None
+ self.name = quoted_name(name, quote)
+ self.optional = optional
+ if schema is BLANK_SCHEMA:
+ self.schema = schema = None
+ elif metadata is not None and schema is None and metadata.schema:
+ self.schema = schema = metadata.schema
+ else:
+ self.schema = quoted_name.construct(schema, quote_schema)
+ self.metadata = metadata
+ self._key = _get_table_key(name, schema)
+ if metadata:
+ self._set_metadata(metadata)
+ if data_type is not None:
+ self.data_type = to_instance(data_type)
+ else:
+ self.data_type = None
+
+ @util.preload_module("sqlalchemy.sql.functions")
+ def next_value(self) -> Function[int]:
+ """Return a :class:`.next_value` function element
+ which will render the appropriate increment function
+ for this :class:`.Sequence` within any SQL expression.
+
+ """
+ return util.preloaded.sql_functions.func.next_value(self)
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ column = parent
+ assert isinstance(column, Column)
+ super()._set_parent(column)
+ column._on_table_attach(self._set_table)
+
+ def _copy(self) -> Sequence:
+ return Sequence(
+ name=self.name,
+ start=self.start,
+ increment=self.increment,
+ minvalue=self.minvalue,
+ maxvalue=self.maxvalue,
+ nominvalue=self.nominvalue,
+ nomaxvalue=self.nomaxvalue,
+ cycle=self.cycle,
+ schema=self.schema,
+ cache=self.cache,
+ order=self.order,
+ data_type=self.data_type,
+ optional=self.optional,
+ metadata=self.metadata,
+ for_update=self.for_update,
+ )
+
+ def _set_table(self, column: Column[Any], table: Table) -> None:
+ self._set_metadata(table.metadata)
+
+ def _set_metadata(self, metadata: MetaData) -> None:
+ self.metadata = metadata
+ self.metadata._sequences[self._key] = self
+
+ def create(self, bind: _CreateDropBind, checkfirst: bool = True) -> None:
+ """Creates this sequence in the database."""
+
+ bind._run_ddl_visitor(ddl.SchemaGenerator, self, checkfirst=checkfirst)
+
+ def drop(self, bind: _CreateDropBind, checkfirst: bool = True) -> None:
+ """Drops this sequence from the database."""
+
+ bind._run_ddl_visitor(ddl.SchemaDropper, self, checkfirst=checkfirst)
+
+ def _not_a_column_expr(self) -> NoReturn:
+ raise exc.InvalidRequestError(
+ f"This {self.__class__.__name__} cannot be used directly "
+ "as a column expression. Use func.next_value(sequence) "
+ "to produce a 'next value' function that's usable "
+ "as a column element."
+ )
+
+
+@inspection._self_inspects
+class FetchedValue(SchemaEventTarget):
+ """A marker for a transparent database-side default.
+
+ Use :class:`.FetchedValue` when the database is configured
+ to provide some automatic default for a column.
+
+ E.g.::
+
+ Column('foo', Integer, FetchedValue())
+
+ Would indicate that some trigger or default generator
+ will create a new value for the ``foo`` column during an
+ INSERT.
+
+ .. seealso::
+
+ :ref:`triggered_columns`
+
+ """
+
+ is_server_default = True
+ reflected = False
+ has_argument = False
+ is_clause_element = False
+ is_identity = False
+
+ column: Optional[Column[Any]]
+
+ def __init__(self, for_update: bool = False) -> None:
+ self.for_update = for_update
+
+ def _as_for_update(self, for_update: bool) -> FetchedValue:
+ if for_update == self.for_update:
+ return self
+ else:
+ return self._clone(for_update)
+
+ def _copy(self) -> FetchedValue:
+ return FetchedValue(self.for_update)
+
+ def _clone(self, for_update: bool) -> Self:
+ n = self.__class__.__new__(self.__class__)
+ n.__dict__.update(self.__dict__)
+ n.__dict__.pop("column", None)
+ n.for_update = for_update
+ return n
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ column = parent
+ assert isinstance(column, Column)
+ self.column = column
+ if self.for_update:
+ self.column.server_onupdate = self
+ else:
+ self.column.server_default = self
+
+ def __repr__(self) -> str:
+ return util.generic_repr(self)
+
+
+class DefaultClause(FetchedValue):
+ """A DDL-specified DEFAULT column value.
+
+ :class:`.DefaultClause` is a :class:`.FetchedValue`
+ that also generates a "DEFAULT" clause when
+ "CREATE TABLE" is emitted.
+
+ :class:`.DefaultClause` is generated automatically
+ whenever the ``server_default``, ``server_onupdate`` arguments of
+ :class:`_schema.Column` are used. A :class:`.DefaultClause`
+ can be passed positionally as well.
+
+ For example, the following::
+
+ Column('foo', Integer, server_default="50")
+
+ Is equivalent to::
+
+ Column('foo', Integer, DefaultClause("50"))
+
+ """
+
+ has_argument = True
+
+ def __init__(
+ self,
+ arg: Union[str, ClauseElement, TextClause],
+ for_update: bool = False,
+ _reflected: bool = False,
+ ) -> None:
+ util.assert_arg_type(arg, (str, ClauseElement, TextClause), "arg")
+ super().__init__(for_update)
+ self.arg = arg
+ self.reflected = _reflected
+
+ def _copy(self) -> DefaultClause:
+ return DefaultClause(
+ arg=self.arg, for_update=self.for_update, _reflected=self.reflected
+ )
+
+ def __repr__(self) -> str:
+ return "DefaultClause(%r, for_update=%r)" % (self.arg, self.for_update)
+
+
+class Constraint(DialectKWArgs, HasConditionalDDL, SchemaItem):
+ """A table-level SQL constraint.
+
+ :class:`_schema.Constraint` serves as the base class for the series of
+ constraint objects that can be associated with :class:`_schema.Table`
+ objects, including :class:`_schema.PrimaryKeyConstraint`,
+ :class:`_schema.ForeignKeyConstraint`
+ :class:`_schema.UniqueConstraint`, and
+ :class:`_schema.CheckConstraint`.
+
+ """
+
+ __visit_name__ = "constraint"
+
+ _creation_order: int
+ _column_flag: bool
+
+ def __init__(
+ self,
+ name: _ConstraintNameArgument = None,
+ deferrable: Optional[bool] = None,
+ initially: Optional[str] = None,
+ info: Optional[_InfoType] = None,
+ comment: Optional[str] = None,
+ _create_rule: Optional[Any] = None,
+ _type_bound: bool = False,
+ **dialect_kw: Any,
+ ) -> None:
+ r"""Create a SQL constraint.
+
+ :param name:
+ Optional, the in-database name of this ``Constraint``.
+
+ :param deferrable:
+ Optional bool. If set, emit DEFERRABLE or NOT DEFERRABLE when
+ issuing DDL for this constraint.
+
+ :param initially:
+ Optional string. If set, emit INITIALLY <value> when issuing DDL
+ for this constraint.
+
+ :param info: Optional data dictionary which will be populated into the
+ :attr:`.SchemaItem.info` attribute of this object.
+
+ :param comment: Optional string that will render an SQL comment on
+ foreign key constraint creation.
+
+ .. versionadded:: 2.0
+
+ :param \**dialect_kw: Additional keyword arguments are dialect
+ specific, and passed in the form ``<dialectname>_<argname>``. See
+ the documentation regarding an individual dialect at
+ :ref:`dialect_toplevel` for detail on documented arguments.
+
+ :param _create_rule:
+ used internally by some datatypes that also create constraints.
+
+ :param _type_bound:
+ used internally to indicate that this constraint is associated with
+ a specific datatype.
+
+ """
+
+ self.name = name
+ self.deferrable = deferrable
+ self.initially = initially
+ if info:
+ self.info = info
+ self._create_rule = _create_rule
+ self._type_bound = _type_bound
+ util.set_creation_order(self)
+ self._validate_dialect_kwargs(dialect_kw)
+ self.comment = comment
+
+ def _should_create_for_compiler(
+ self, compiler: DDLCompiler, **kw: Any
+ ) -> bool:
+ if self._create_rule is not None and not self._create_rule(compiler):
+ return False
+ elif self._ddl_if is not None:
+ return self._ddl_if._should_execute(
+ ddl.CreateConstraint(self), self, None, compiler=compiler, **kw
+ )
+ else:
+ return True
+
+ @property
+ def table(self) -> Table:
+ try:
+ if isinstance(self.parent, Table):
+ return self.parent
+ except AttributeError:
+ pass
+ raise exc.InvalidRequestError(
+ "This constraint is not bound to a table. Did you "
+ "mean to call table.append_constraint(constraint) ?"
+ )
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ assert isinstance(parent, (Table, Column))
+ self.parent = parent
+ parent.constraints.add(self)
+
+ @util.deprecated(
+ "1.4",
+ "The :meth:`_schema.Constraint.copy` method is deprecated "
+ "and will be removed in a future release.",
+ )
+ def copy(self, **kw: Any) -> Self:
+ return self._copy(**kw)
+
+ def _copy(self, **kw: Any) -> Self:
+ raise NotImplementedError()
+
+
+class ColumnCollectionMixin:
+ """A :class:`_expression.ColumnCollection` of :class:`_schema.Column`
+ objects.
+
+ This collection represents the columns which are referred to by
+ this object.
+
+ """
+
+ _columns: DedupeColumnCollection[Column[Any]]
+
+ _allow_multiple_tables = False
+
+ _pending_colargs: List[Optional[Union[str, Column[Any]]]]
+
+ if TYPE_CHECKING:
+
+ def _set_parent_with_dispatch(
+ self, parent: SchemaEventTarget, **kw: Any
+ ) -> None: ...
+
+ def __init__(
+ self,
+ *columns: _DDLColumnArgument,
+ _autoattach: bool = True,
+ _column_flag: bool = False,
+ _gather_expressions: Optional[
+ List[Union[str, ColumnElement[Any]]]
+ ] = None,
+ ) -> None:
+ self._column_flag = _column_flag
+ self._columns = DedupeColumnCollection()
+
+ processed_expressions: Optional[
+ List[Union[ColumnElement[Any], str]]
+ ] = _gather_expressions
+
+ if processed_expressions is not None:
+ self._pending_colargs = []
+ for (
+ expr,
+ _,
+ _,
+ add_element,
+ ) in coercions.expect_col_expression_collection(
+ roles.DDLConstraintColumnRole, columns
+ ):
+ self._pending_colargs.append(add_element)
+ processed_expressions.append(expr)
+ else:
+ self._pending_colargs = [
+ coercions.expect(roles.DDLConstraintColumnRole, column)
+ for column in columns
+ ]
+
+ if _autoattach and self._pending_colargs:
+ self._check_attach()
+
+ def _check_attach(self, evt: bool = False) -> None:
+ col_objs = [c for c in self._pending_colargs if isinstance(c, Column)]
+
+ cols_w_table = [c for c in col_objs if isinstance(c.table, Table)]
+
+ cols_wo_table = set(col_objs).difference(cols_w_table)
+ if cols_wo_table:
+ # feature #3341 - place event listeners for Column objects
+ # such that when all those cols are attached, we autoattach.
+ assert not evt, "Should not reach here on event call"
+
+ # issue #3411 - don't do the per-column auto-attach if some of the
+ # columns are specified as strings.
+ has_string_cols = {
+ c for c in self._pending_colargs if c is not None
+ }.difference(col_objs)
+ if not has_string_cols:
+
+ def _col_attached(column: Column[Any], table: Table) -> None:
+ # this isinstance() corresponds with the
+ # isinstance() above; only want to count Table-bound
+ # columns
+ if isinstance(table, Table):
+ cols_wo_table.discard(column)
+ if not cols_wo_table:
+ self._check_attach(evt=True)
+
+ self._cols_wo_table = cols_wo_table
+ for col in cols_wo_table:
+ col._on_table_attach(_col_attached)
+ return
+
+ columns = cols_w_table
+
+ tables = {c.table for c in columns}
+ if len(tables) == 1:
+ self._set_parent_with_dispatch(tables.pop())
+ elif len(tables) > 1 and not self._allow_multiple_tables:
+ table = columns[0].table
+ others = [c for c in columns[1:] if c.table is not table]
+ if others:
+ # black could not format this inline
+ other_str = ", ".join("'%s'" % c for c in others)
+ raise exc.ArgumentError(
+ f"Column(s) {other_str} "
+ f"are not part of table '{table.description}'."
+ )
+
+ @util.ro_memoized_property
+ def columns(self) -> ReadOnlyColumnCollection[str, Column[Any]]:
+ return self._columns.as_readonly()
+
+ @util.ro_memoized_property
+ def c(self) -> ReadOnlyColumnCollection[str, Column[Any]]:
+ return self._columns.as_readonly()
+
+ def _col_expressions(
+ self, parent: Union[Table, Column[Any]]
+ ) -> List[Optional[Column[Any]]]:
+ if isinstance(parent, Column):
+ result: List[Optional[Column[Any]]] = [
+ c for c in self._pending_colargs if isinstance(c, Column)
+ ]
+ assert len(result) == len(self._pending_colargs)
+ return result
+ else:
+ try:
+ return [
+ parent.c[col] if isinstance(col, str) else col
+ for col in self._pending_colargs
+ ]
+ except KeyError as ke:
+ raise exc.ConstraintColumnNotFoundError(
+ f"Can't create {self.__class__.__name__} "
+ f"on table '{parent.description}': no column "
+ f"named '{ke.args[0]}' is present."
+ ) from ke
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ assert isinstance(parent, (Table, Column))
+
+ for col in self._col_expressions(parent):
+ if col is not None:
+ self._columns.add(col)
+
+
+class ColumnCollectionConstraint(ColumnCollectionMixin, Constraint):
+ """A constraint that proxies a ColumnCollection."""
+
+ def __init__(
+ self,
+ *columns: _DDLColumnArgument,
+ name: _ConstraintNameArgument = None,
+ deferrable: Optional[bool] = None,
+ initially: Optional[str] = None,
+ info: Optional[_InfoType] = None,
+ _autoattach: bool = True,
+ _column_flag: bool = False,
+ _gather_expressions: Optional[List[_DDLColumnArgument]] = None,
+ **dialect_kw: Any,
+ ) -> None:
+ r"""
+ :param \*columns:
+ A sequence of column names or Column objects.
+
+ :param name:
+ Optional, the in-database name of this constraint.
+
+ :param deferrable:
+ Optional bool. If set, emit DEFERRABLE or NOT DEFERRABLE when
+ issuing DDL for this constraint.
+
+ :param initially:
+ Optional string. If set, emit INITIALLY <value> when issuing DDL
+ for this constraint.
+
+ :param \**dialect_kw: other keyword arguments including
+ dialect-specific arguments are propagated to the :class:`.Constraint`
+ superclass.
+
+ """
+ Constraint.__init__(
+ self,
+ name=name,
+ deferrable=deferrable,
+ initially=initially,
+ info=info,
+ **dialect_kw,
+ )
+ ColumnCollectionMixin.__init__(
+ self, *columns, _autoattach=_autoattach, _column_flag=_column_flag
+ )
+
+ columns: ReadOnlyColumnCollection[str, Column[Any]]
+ """A :class:`_expression.ColumnCollection` representing the set of columns
+ for this constraint.
+
+ """
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ assert isinstance(parent, (Column, Table))
+ Constraint._set_parent(self, parent)
+ ColumnCollectionMixin._set_parent(self, parent)
+
+ def __contains__(self, x: Any) -> bool:
+ return x in self._columns
+
+ @util.deprecated(
+ "1.4",
+ "The :meth:`_schema.ColumnCollectionConstraint.copy` method "
+ "is deprecated and will be removed in a future release.",
+ )
+ def copy(
+ self,
+ *,
+ target_table: Optional[Table] = None,
+ **kw: Any,
+ ) -> ColumnCollectionConstraint:
+ return self._copy(target_table=target_table, **kw)
+
+ def _copy(
+ self,
+ *,
+ target_table: Optional[Table] = None,
+ **kw: Any,
+ ) -> ColumnCollectionConstraint:
+ # ticket #5276
+ constraint_kwargs = {}
+ for dialect_name in self.dialect_options:
+ dialect_options = self.dialect_options[dialect_name]._non_defaults
+ for (
+ dialect_option_key,
+ dialect_option_value,
+ ) in dialect_options.items():
+ constraint_kwargs[dialect_name + "_" + dialect_option_key] = (
+ dialect_option_value
+ )
+
+ assert isinstance(self.parent, Table)
+ c = self.__class__(
+ name=self.name,
+ deferrable=self.deferrable,
+ initially=self.initially,
+ *[
+ _copy_expression(expr, self.parent, target_table)
+ for expr in self._columns
+ ],
+ comment=self.comment,
+ **constraint_kwargs,
+ )
+ return self._schema_item_copy(c)
+
+ def contains_column(self, col: Column[Any]) -> bool:
+ """Return True if this constraint contains the given column.
+
+ Note that this object also contains an attribute ``.columns``
+ which is a :class:`_expression.ColumnCollection` of
+ :class:`_schema.Column` objects.
+
+ """
+
+ return self._columns.contains_column(col)
+
+ def __iter__(self) -> Iterator[Column[Any]]:
+ return iter(self._columns)
+
+ def __len__(self) -> int:
+ return len(self._columns)
+
+
+class CheckConstraint(ColumnCollectionConstraint):
+ """A table- or column-level CHECK constraint.
+
+ Can be included in the definition of a Table or Column.
+ """
+
+ _allow_multiple_tables = True
+
+ __visit_name__ = "table_or_column_check_constraint"
+
+ @_document_text_coercion(
+ "sqltext",
+ ":class:`.CheckConstraint`",
+ ":paramref:`.CheckConstraint.sqltext`",
+ )
+ def __init__(
+ self,
+ sqltext: _TextCoercedExpressionArgument[Any],
+ name: _ConstraintNameArgument = None,
+ deferrable: Optional[bool] = None,
+ initially: Optional[str] = None,
+ table: Optional[Table] = None,
+ info: Optional[_InfoType] = None,
+ _create_rule: Optional[Any] = None,
+ _autoattach: bool = True,
+ _type_bound: bool = False,
+ **dialect_kw: Any,
+ ) -> None:
+ r"""Construct a CHECK constraint.
+
+ :param sqltext:
+ A string containing the constraint definition, which will be used
+ verbatim, or a SQL expression construct. If given as a string,
+ the object is converted to a :func:`_expression.text` object.
+ If the textual
+ string includes a colon character, escape this using a backslash::
+
+ CheckConstraint(r"foo ~ E'a(?\:b|c)d")
+
+ :param name:
+ Optional, the in-database name of the constraint.
+
+ :param deferrable:
+ Optional bool. If set, emit DEFERRABLE or NOT DEFERRABLE when
+ issuing DDL for this constraint.
+
+ :param initially:
+ Optional string. If set, emit INITIALLY <value> when issuing DDL
+ for this constraint.
+
+ :param info: Optional data dictionary which will be populated into the
+ :attr:`.SchemaItem.info` attribute of this object.
+
+ """
+
+ self.sqltext = coercions.expect(roles.DDLExpressionRole, sqltext)
+ columns: List[Column[Any]] = []
+ visitors.traverse(self.sqltext, {}, {"column": columns.append})
+
+ super().__init__(
+ name=name,
+ deferrable=deferrable,
+ initially=initially,
+ _create_rule=_create_rule,
+ info=info,
+ _type_bound=_type_bound,
+ _autoattach=_autoattach,
+ *columns,
+ **dialect_kw,
+ )
+ if table is not None:
+ self._set_parent_with_dispatch(table)
+
+ @property
+ def is_column_level(self) -> bool:
+ return not isinstance(self.parent, Table)
+
+ @util.deprecated(
+ "1.4",
+ "The :meth:`_schema.CheckConstraint.copy` method is deprecated "
+ "and will be removed in a future release.",
+ )
+ def copy(
+ self, *, target_table: Optional[Table] = None, **kw: Any
+ ) -> CheckConstraint:
+ return self._copy(target_table=target_table, **kw)
+
+ def _copy(
+ self, *, target_table: Optional[Table] = None, **kw: Any
+ ) -> CheckConstraint:
+ if target_table is not None:
+ # note that target_table is None for the copy process of
+ # a column-bound CheckConstraint, so this path is not reached
+ # in that case.
+ sqltext = _copy_expression(self.sqltext, self.table, target_table)
+ else:
+ sqltext = self.sqltext
+ c = CheckConstraint(
+ sqltext,
+ name=self.name,
+ initially=self.initially,
+ deferrable=self.deferrable,
+ _create_rule=self._create_rule,
+ table=target_table,
+ comment=self.comment,
+ _autoattach=False,
+ _type_bound=self._type_bound,
+ )
+ return self._schema_item_copy(c)
+
+
+class ForeignKeyConstraint(ColumnCollectionConstraint):
+ """A table-level FOREIGN KEY constraint.
+
+ Defines a single column or composite FOREIGN KEY ... REFERENCES
+ constraint. For a no-frills, single column foreign key, adding a
+ :class:`_schema.ForeignKey` to the definition of a :class:`_schema.Column`
+ is a
+ shorthand equivalent for an unnamed, single column
+ :class:`_schema.ForeignKeyConstraint`.
+
+ Examples of foreign key configuration are in :ref:`metadata_foreignkeys`.
+
+ """
+
+ __visit_name__ = "foreign_key_constraint"
+
+ def __init__(
+ self,
+ columns: _typing_Sequence[_DDLColumnArgument],
+ refcolumns: _typing_Sequence[_DDLColumnArgument],
+ name: _ConstraintNameArgument = None,
+ onupdate: Optional[str] = None,
+ ondelete: Optional[str] = None,
+ deferrable: Optional[bool] = None,
+ initially: Optional[str] = None,
+ use_alter: bool = False,
+ link_to_name: bool = False,
+ match: Optional[str] = None,
+ table: Optional[Table] = None,
+ info: Optional[_InfoType] = None,
+ comment: Optional[str] = None,
+ **dialect_kw: Any,
+ ) -> None:
+ r"""Construct a composite-capable FOREIGN KEY.
+
+ :param columns: A sequence of local column names. The named columns
+ must be defined and present in the parent Table. The names should
+ match the ``key`` given to each column (defaults to the name) unless
+ ``link_to_name`` is True.
+
+ :param refcolumns: A sequence of foreign column names or Column
+ objects. The columns must all be located within the same Table.
+
+ :param name: Optional, the in-database name of the key.
+
+ :param onupdate: Optional string. If set, emit ON UPDATE <value> when
+ issuing DDL for this constraint. Typical values include CASCADE,
+ DELETE and RESTRICT.
+
+ :param ondelete: Optional string. If set, emit ON DELETE <value> when
+ issuing DDL for this constraint. Typical values include CASCADE,
+ DELETE and RESTRICT.
+
+ :param deferrable: Optional bool. If set, emit DEFERRABLE or NOT
+ DEFERRABLE when issuing DDL for this constraint.
+
+ :param initially: Optional string. If set, emit INITIALLY <value> when
+ issuing DDL for this constraint.
+
+ :param link_to_name: if True, the string name given in ``column`` is
+ the rendered name of the referenced column, not its locally assigned
+ ``key``.
+
+ :param use_alter: If True, do not emit the DDL for this constraint as
+ part of the CREATE TABLE definition. Instead, generate it via an
+ ALTER TABLE statement issued after the full collection of tables
+ have been created, and drop it via an ALTER TABLE statement before
+ the full collection of tables are dropped.
+
+ The use of :paramref:`_schema.ForeignKeyConstraint.use_alter` is
+ particularly geared towards the case where two or more tables
+ are established within a mutually-dependent foreign key constraint
+ relationship; however, the :meth:`_schema.MetaData.create_all` and
+ :meth:`_schema.MetaData.drop_all`
+ methods will perform this resolution
+ automatically, so the flag is normally not needed.
+
+ .. seealso::
+
+ :ref:`use_alter`
+
+ :param match: Optional string. If set, emit MATCH <value> when issuing
+ DDL for this constraint. Typical values include SIMPLE, PARTIAL
+ and FULL.
+
+ :param info: Optional data dictionary which will be populated into the
+ :attr:`.SchemaItem.info` attribute of this object.
+
+ :param comment: Optional string that will render an SQL comment on
+ foreign key constraint creation.
+
+ .. versionadded:: 2.0
+
+ :param \**dialect_kw: Additional keyword arguments are dialect
+ specific, and passed in the form ``<dialectname>_<argname>``. See
+ the documentation regarding an individual dialect at
+ :ref:`dialect_toplevel` for detail on documented arguments.
+
+ """
+
+ Constraint.__init__(
+ self,
+ name=name,
+ deferrable=deferrable,
+ initially=initially,
+ info=info,
+ comment=comment,
+ **dialect_kw,
+ )
+ self.onupdate = onupdate
+ self.ondelete = ondelete
+ self.link_to_name = link_to_name
+ self.use_alter = use_alter
+ self.match = match
+
+ if len(set(columns)) != len(refcolumns):
+ if len(set(columns)) != len(columns):
+ # e.g. FOREIGN KEY (a, a) REFERENCES r (b, c)
+ raise exc.ArgumentError(
+ "ForeignKeyConstraint with duplicate source column "
+ "references are not supported."
+ )
+ else:
+ # e.g. FOREIGN KEY (a) REFERENCES r (b, c)
+ # paraphrasing
+ # https://www.postgresql.org/docs/current/static/ddl-constraints.html
+ raise exc.ArgumentError(
+ "ForeignKeyConstraint number "
+ "of constrained columns must match the number of "
+ "referenced columns."
+ )
+
+ # standalone ForeignKeyConstraint - create
+ # associated ForeignKey objects which will be applied to hosted
+ # Column objects (in col.foreign_keys), either now or when attached
+ # to the Table for string-specified names
+ self.elements = [
+ ForeignKey(
+ refcol,
+ _constraint=self,
+ name=self.name,
+ onupdate=self.onupdate,
+ ondelete=self.ondelete,
+ use_alter=self.use_alter,
+ link_to_name=self.link_to_name,
+ match=self.match,
+ deferrable=self.deferrable,
+ initially=self.initially,
+ **self.dialect_kwargs,
+ )
+ for refcol in refcolumns
+ ]
+
+ ColumnCollectionMixin.__init__(self, *columns)
+ if table is not None:
+ if hasattr(self, "parent"):
+ assert table is self.parent
+ self._set_parent_with_dispatch(table)
+
+ def _append_element(self, column: Column[Any], fk: ForeignKey) -> None:
+ self._columns.add(column)
+ self.elements.append(fk)
+
+ columns: ReadOnlyColumnCollection[str, Column[Any]]
+ """A :class:`_expression.ColumnCollection` representing the set of columns
+ for this constraint.
+
+ """
+
+ elements: List[ForeignKey]
+ """A sequence of :class:`_schema.ForeignKey` objects.
+
+ Each :class:`_schema.ForeignKey`
+ represents a single referring column/referred
+ column pair.
+
+ This collection is intended to be read-only.
+
+ """
+
+ @property
+ def _elements(self) -> util.OrderedDict[str, ForeignKey]:
+ # legacy - provide a dictionary view of (column_key, fk)
+ return util.OrderedDict(zip(self.column_keys, self.elements))
+
+ @property
+ def _referred_schema(self) -> Optional[str]:
+ for elem in self.elements:
+ return elem._referred_schema
+ else:
+ return None
+
+ @property
+ def referred_table(self) -> Table:
+ """The :class:`_schema.Table` object to which this
+ :class:`_schema.ForeignKeyConstraint` references.
+
+ This is a dynamically calculated attribute which may not be available
+ if the constraint and/or parent table is not yet associated with
+ a metadata collection that contains the referred table.
+
+ """
+ return self.elements[0].column.table
+
+ def _validate_dest_table(self, table: Table) -> None:
+ table_keys = {elem._table_key() for elem in self.elements}
+ if None not in table_keys and len(table_keys) > 1:
+ elem0, elem1 = sorted(table_keys)[0:2]
+ raise exc.ArgumentError(
+ f"ForeignKeyConstraint on "
+ f"{table.fullname}({self._col_description}) refers to "
+ f"multiple remote tables: {elem0} and {elem1}"
+ )
+
+ @property
+ def column_keys(self) -> _typing_Sequence[str]:
+ """Return a list of string keys representing the local
+ columns in this :class:`_schema.ForeignKeyConstraint`.
+
+ This list is either the original string arguments sent
+ to the constructor of the :class:`_schema.ForeignKeyConstraint`,
+ or if the constraint has been initialized with :class:`_schema.Column`
+ objects, is the string ``.key`` of each element.
+
+ """
+ if hasattr(self, "parent"):
+ return self._columns.keys()
+ else:
+ return [
+ col.key if isinstance(col, ColumnElement) else str(col)
+ for col in self._pending_colargs
+ ]
+
+ @property
+ def _col_description(self) -> str:
+ return ", ".join(self.column_keys)
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ table = parent
+ assert isinstance(table, Table)
+ Constraint._set_parent(self, table)
+
+ ColumnCollectionConstraint._set_parent(self, table)
+
+ for col, fk in zip(self._columns, self.elements):
+ if not hasattr(fk, "parent") or fk.parent is not col:
+ fk._set_parent_with_dispatch(col)
+
+ self._validate_dest_table(table)
+
+ @util.deprecated(
+ "1.4",
+ "The :meth:`_schema.ForeignKeyConstraint.copy` method is deprecated "
+ "and will be removed in a future release.",
+ )
+ def copy(
+ self,
+ *,
+ schema: Optional[str] = None,
+ target_table: Optional[Table] = None,
+ **kw: Any,
+ ) -> ForeignKeyConstraint:
+ return self._copy(schema=schema, target_table=target_table, **kw)
+
+ def _copy(
+ self,
+ *,
+ schema: Optional[str] = None,
+ target_table: Optional[Table] = None,
+ **kw: Any,
+ ) -> ForeignKeyConstraint:
+ fkc = ForeignKeyConstraint(
+ [x.parent.key for x in self.elements],
+ [
+ x._get_colspec(
+ schema=schema,
+ table_name=(
+ target_table.name
+ if target_table is not None
+ and x._table_key() == x.parent.table.key
+ else None
+ ),
+ _is_copy=True,
+ )
+ for x in self.elements
+ ],
+ name=self.name,
+ onupdate=self.onupdate,
+ ondelete=self.ondelete,
+ use_alter=self.use_alter,
+ deferrable=self.deferrable,
+ initially=self.initially,
+ link_to_name=self.link_to_name,
+ match=self.match,
+ comment=self.comment,
+ )
+ for self_fk, other_fk in zip(self.elements, fkc.elements):
+ self_fk._schema_item_copy(other_fk)
+ return self._schema_item_copy(fkc)
+
+
+class PrimaryKeyConstraint(ColumnCollectionConstraint):
+ """A table-level PRIMARY KEY constraint.
+
+ The :class:`.PrimaryKeyConstraint` object is present automatically
+ on any :class:`_schema.Table` object; it is assigned a set of
+ :class:`_schema.Column` objects corresponding to those marked with
+ the :paramref:`_schema.Column.primary_key` flag::
+
+ >>> my_table = Table('mytable', metadata,
+ ... Column('id', Integer, primary_key=True),
+ ... Column('version_id', Integer, primary_key=True),
+ ... Column('data', String(50))
+ ... )
+ >>> my_table.primary_key
+ PrimaryKeyConstraint(
+ Column('id', Integer(), table=<mytable>,
+ primary_key=True, nullable=False),
+ Column('version_id', Integer(), table=<mytable>,
+ primary_key=True, nullable=False)
+ )
+
+ The primary key of a :class:`_schema.Table` can also be specified by using
+ a :class:`.PrimaryKeyConstraint` object explicitly; in this mode of usage,
+ the "name" of the constraint can also be specified, as well as other
+ options which may be recognized by dialects::
+
+ my_table = Table('mytable', metadata,
+ Column('id', Integer),
+ Column('version_id', Integer),
+ Column('data', String(50)),
+ PrimaryKeyConstraint('id', 'version_id',
+ name='mytable_pk')
+ )
+
+ The two styles of column-specification should generally not be mixed.
+ An warning is emitted if the columns present in the
+ :class:`.PrimaryKeyConstraint`
+ don't match the columns that were marked as ``primary_key=True``, if both
+ are present; in this case, the columns are taken strictly from the
+ :class:`.PrimaryKeyConstraint` declaration, and those columns otherwise
+ marked as ``primary_key=True`` are ignored. This behavior is intended to
+ be backwards compatible with previous behavior.
+
+ For the use case where specific options are to be specified on the
+ :class:`.PrimaryKeyConstraint`, but the usual style of using
+ ``primary_key=True`` flags is still desirable, an empty
+ :class:`.PrimaryKeyConstraint` may be specified, which will take on the
+ primary key column collection from the :class:`_schema.Table` based on the
+ flags::
+
+ my_table = Table('mytable', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('version_id', Integer, primary_key=True),
+ Column('data', String(50)),
+ PrimaryKeyConstraint(name='mytable_pk',
+ mssql_clustered=True)
+ )
+
+ """
+
+ __visit_name__ = "primary_key_constraint"
+
+ def __init__(
+ self,
+ *columns: _DDLColumnArgument,
+ name: Optional[str] = None,
+ deferrable: Optional[bool] = None,
+ initially: Optional[str] = None,
+ info: Optional[_InfoType] = None,
+ _implicit_generated: bool = False,
+ **dialect_kw: Any,
+ ) -> None:
+ self._implicit_generated = _implicit_generated
+ super().__init__(
+ *columns,
+ name=name,
+ deferrable=deferrable,
+ initially=initially,
+ info=info,
+ **dialect_kw,
+ )
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ table = parent
+ assert isinstance(table, Table)
+ super()._set_parent(table)
+
+ if table.primary_key is not self:
+ table.constraints.discard(table.primary_key)
+ table.primary_key = self # type: ignore
+ table.constraints.add(self)
+
+ table_pks = [c for c in table.c if c.primary_key]
+ if (
+ self._columns
+ and table_pks
+ and set(table_pks) != set(self._columns)
+ ):
+ # black could not format these inline
+ table_pk_str = ", ".join("'%s'" % c.name for c in table_pks)
+ col_str = ", ".join("'%s'" % c.name for c in self._columns)
+
+ util.warn(
+ f"Table '{table.name}' specifies columns "
+ f"{table_pk_str} as "
+ f"primary_key=True, "
+ f"not matching locally specified columns {col_str}; "
+ f"setting the "
+ f"current primary key columns to "
+ f"{col_str}. "
+ f"This warning "
+ f"may become an exception in a future release"
+ )
+ table_pks[:] = []
+
+ for c in self._columns:
+ c.primary_key = True
+ if c._user_defined_nullable is NULL_UNSPECIFIED:
+ c.nullable = False
+ if table_pks:
+ self._columns.extend(table_pks)
+
+ def _reload(self, columns: Iterable[Column[Any]]) -> None:
+ """repopulate this :class:`.PrimaryKeyConstraint` given
+ a set of columns.
+
+ Existing columns in the table that are marked as primary_key=True
+ are maintained.
+
+ Also fires a new event.
+
+ This is basically like putting a whole new
+ :class:`.PrimaryKeyConstraint` object on the parent
+ :class:`_schema.Table` object without actually replacing the object.
+
+ The ordering of the given list of columns is also maintained; these
+ columns will be appended to the list of columns after any which
+ are already present.
+
+ """
+ # set the primary key flag on new columns.
+ # note any existing PK cols on the table also have their
+ # flag still set.
+ for col in columns:
+ col.primary_key = True
+
+ self._columns.extend(columns)
+
+ PrimaryKeyConstraint._autoincrement_column._reset(self) # type: ignore
+ self._set_parent_with_dispatch(self.table)
+
+ def _replace(self, col: Column[Any]) -> None:
+ PrimaryKeyConstraint._autoincrement_column._reset(self) # type: ignore
+ self._columns.replace(col)
+
+ self.dispatch._sa_event_column_added_to_pk_constraint(self, col)
+
+ @property
+ def columns_autoinc_first(self) -> List[Column[Any]]:
+ autoinc = self._autoincrement_column
+
+ if autoinc is not None:
+ return [autoinc] + [c for c in self._columns if c is not autoinc]
+ else:
+ return list(self._columns)
+
+ @util.ro_memoized_property
+ def _autoincrement_column(self) -> Optional[Column[int]]:
+ def _validate_autoinc(col: Column[Any], autoinc_true: bool) -> bool:
+ if col.type._type_affinity is None or not issubclass(
+ col.type._type_affinity,
+ (
+ type_api.INTEGERTYPE._type_affinity,
+ type_api.NUMERICTYPE._type_affinity,
+ ),
+ ):
+ if autoinc_true:
+ raise exc.ArgumentError(
+ f"Column type {col.type} on column '{col}' is not "
+ f"compatible with autoincrement=True"
+ )
+ else:
+ return False
+ elif (
+ not isinstance(col.default, (type(None), Sequence))
+ and not autoinc_true
+ ):
+ return False
+ elif (
+ col.server_default is not None
+ and not isinstance(col.server_default, Identity)
+ and not autoinc_true
+ ):
+ return False
+ elif col.foreign_keys and col.autoincrement not in (
+ True,
+ "ignore_fk",
+ ):
+ return False
+ return True
+
+ if len(self._columns) == 1:
+ col = list(self._columns)[0]
+
+ if col.autoincrement is True:
+ _validate_autoinc(col, True)
+ return col
+ elif col.autoincrement in (
+ "auto",
+ "ignore_fk",
+ ) and _validate_autoinc(col, False):
+ return col
+ else:
+ return None
+
+ else:
+ autoinc = None
+ for col in self._columns:
+ if col.autoincrement is True:
+ _validate_autoinc(col, True)
+ if autoinc is not None:
+ raise exc.ArgumentError(
+ f"Only one Column may be marked "
+ f"autoincrement=True, found both "
+ f"{col.name} and {autoinc.name}."
+ )
+ else:
+ autoinc = col
+
+ return autoinc
+
+
+class UniqueConstraint(ColumnCollectionConstraint):
+ """A table-level UNIQUE constraint.
+
+ Defines a single column or composite UNIQUE constraint. For a no-frills,
+ single column constraint, adding ``unique=True`` to the ``Column``
+ definition is a shorthand equivalent for an unnamed, single column
+ UniqueConstraint.
+ """
+
+ __visit_name__ = "unique_constraint"
+
+
+class Index(
+ DialectKWArgs, ColumnCollectionMixin, HasConditionalDDL, SchemaItem
+):
+ """A table-level INDEX.
+
+ Defines a composite (one or more column) INDEX.
+
+ E.g.::
+
+ sometable = Table("sometable", metadata,
+ Column("name", String(50)),
+ Column("address", String(100))
+ )
+
+ Index("some_index", sometable.c.name)
+
+ For a no-frills, single column index, adding
+ :class:`_schema.Column` also supports ``index=True``::
+
+ sometable = Table("sometable", metadata,
+ Column("name", String(50), index=True)
+ )
+
+ For a composite index, multiple columns can be specified::
+
+ Index("some_index", sometable.c.name, sometable.c.address)
+
+ Functional indexes are supported as well, typically by using the
+ :data:`.func` construct in conjunction with table-bound
+ :class:`_schema.Column` objects::
+
+ Index("some_index", func.lower(sometable.c.name))
+
+ An :class:`.Index` can also be manually associated with a
+ :class:`_schema.Table`,
+ either through inline declaration or using
+ :meth:`_schema.Table.append_constraint`. When this approach is used,
+ the names
+ of the indexed columns can be specified as strings::
+
+ Table("sometable", metadata,
+ Column("name", String(50)),
+ Column("address", String(100)),
+ Index("some_index", "name", "address")
+ )
+
+ To support functional or expression-based indexes in this form, the
+ :func:`_expression.text` construct may be used::
+
+ from sqlalchemy import text
+
+ Table("sometable", metadata,
+ Column("name", String(50)),
+ Column("address", String(100)),
+ Index("some_index", text("lower(name)"))
+ )
+
+ .. seealso::
+
+ :ref:`schema_indexes` - General information on :class:`.Index`.
+
+ :ref:`postgresql_indexes` - PostgreSQL-specific options available for
+ the :class:`.Index` construct.
+
+ :ref:`mysql_indexes` - MySQL-specific options available for the
+ :class:`.Index` construct.
+
+ :ref:`mssql_indexes` - MSSQL-specific options available for the
+ :class:`.Index` construct.
+
+ """
+
+ __visit_name__ = "index"
+
+ table: Optional[Table]
+ expressions: _typing_Sequence[Union[str, ColumnElement[Any]]]
+ _table_bound_expressions: _typing_Sequence[ColumnElement[Any]]
+
+ def __init__(
+ self,
+ name: Optional[str],
+ *expressions: _DDLColumnArgument,
+ unique: bool = False,
+ quote: Optional[bool] = None,
+ info: Optional[_InfoType] = None,
+ _table: Optional[Table] = None,
+ _column_flag: bool = False,
+ **dialect_kw: Any,
+ ) -> None:
+ r"""Construct an index object.
+
+ :param name:
+ The name of the index
+
+ :param \*expressions:
+ Column expressions to include in the index. The expressions
+ are normally instances of :class:`_schema.Column`, but may also
+ be arbitrary SQL expressions which ultimately refer to a
+ :class:`_schema.Column`.
+
+ :param unique=False:
+ Keyword only argument; if True, create a unique index.
+
+ :param quote=None:
+ Keyword only argument; whether to apply quoting to the name of
+ the index. Works in the same manner as that of
+ :paramref:`_schema.Column.quote`.
+
+ :param info=None: Optional data dictionary which will be populated
+ into the :attr:`.SchemaItem.info` attribute of this object.
+
+ :param \**dialect_kw: Additional keyword arguments not mentioned above
+ are dialect specific, and passed in the form
+ ``<dialectname>_<argname>``. See the documentation regarding an
+ individual dialect at :ref:`dialect_toplevel` for detail on
+ documented arguments.
+
+ """
+ self.table = table = None
+
+ self.name = quoted_name.construct(name, quote)
+ self.unique = unique
+ if info is not None:
+ self.info = info
+
+ # TODO: consider "table" argument being public, but for
+ # the purpose of the fix here, it starts as private.
+ if _table is not None:
+ table = _table
+
+ self._validate_dialect_kwargs(dialect_kw)
+
+ self.expressions = []
+ # will call _set_parent() if table-bound column
+ # objects are present
+ ColumnCollectionMixin.__init__(
+ self,
+ *expressions,
+ _column_flag=_column_flag,
+ _gather_expressions=self.expressions,
+ )
+ if table is not None:
+ self._set_parent(table)
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ table = parent
+ assert isinstance(table, Table)
+ ColumnCollectionMixin._set_parent(self, table)
+
+ if self.table is not None and table is not self.table:
+ raise exc.ArgumentError(
+ f"Index '{self.name}' is against table "
+ f"'{self.table.description}', and "
+ f"cannot be associated with table '{table.description}'."
+ )
+ self.table = table
+ table.indexes.add(self)
+
+ expressions = self.expressions
+ col_expressions = self._col_expressions(table)
+ assert len(expressions) == len(col_expressions)
+
+ exprs = []
+ for expr, colexpr in zip(expressions, col_expressions):
+ if isinstance(expr, ClauseElement):
+ exprs.append(expr)
+ elif colexpr is not None:
+ exprs.append(colexpr)
+ else:
+ assert False
+ self.expressions = self._table_bound_expressions = exprs
+
+ def create(self, bind: _CreateDropBind, checkfirst: bool = False) -> None:
+ """Issue a ``CREATE`` statement for this
+ :class:`.Index`, using the given
+ :class:`.Connection` or :class:`.Engine`` for connectivity.
+
+ .. seealso::
+
+ :meth:`_schema.MetaData.create_all`.
+
+ """
+ bind._run_ddl_visitor(ddl.SchemaGenerator, self, checkfirst=checkfirst)
+
+ def drop(self, bind: _CreateDropBind, checkfirst: bool = False) -> None:
+ """Issue a ``DROP`` statement for this
+ :class:`.Index`, using the given
+ :class:`.Connection` or :class:`.Engine` for connectivity.
+
+ .. seealso::
+
+ :meth:`_schema.MetaData.drop_all`.
+
+ """
+ bind._run_ddl_visitor(ddl.SchemaDropper, self, checkfirst=checkfirst)
+
+ def __repr__(self) -> str:
+ exprs: _typing_Sequence[Any] # noqa: F842
+
+ return "Index(%s)" % (
+ ", ".join(
+ [repr(self.name)]
+ + [repr(e) for e in self.expressions]
+ + (self.unique and ["unique=True"] or [])
+ )
+ )
+
+
+_NamingSchemaCallable = Callable[[Constraint, Table], str]
+_NamingSchemaDirective = Union[str, _NamingSchemaCallable]
+
+
+class _NamingSchemaTD(TypedDict, total=False):
+ fk: _NamingSchemaDirective
+ pk: _NamingSchemaDirective
+ ix: _NamingSchemaDirective
+ ck: _NamingSchemaDirective
+ uq: _NamingSchemaDirective
+
+
+_NamingSchemaParameter = Union[
+ # it seems like the TypedDict here is useful for pylance typeahead,
+ # and not much else
+ _NamingSchemaTD,
+ # there is no form that allows Union[Type[Any], str] to work in all
+ # cases, including breaking out Mapping[] entries for each combination
+ # even, therefore keys must be `Any` (see #10264)
+ Mapping[Any, _NamingSchemaDirective],
+]
+
+
+DEFAULT_NAMING_CONVENTION: _NamingSchemaParameter = util.immutabledict(
+ {"ix": "ix_%(column_0_label)s"}
+)
+
+
+class MetaData(HasSchemaAttr):
+ """A collection of :class:`_schema.Table`
+ objects and their associated schema
+ constructs.
+
+ Holds a collection of :class:`_schema.Table` objects as well as
+ an optional binding to an :class:`_engine.Engine` or
+ :class:`_engine.Connection`. If bound, the :class:`_schema.Table` objects
+ in the collection and their columns may participate in implicit SQL
+ execution.
+
+ The :class:`_schema.Table` objects themselves are stored in the
+ :attr:`_schema.MetaData.tables` dictionary.
+
+ :class:`_schema.MetaData` is a thread-safe object for read operations.
+ Construction of new tables within a single :class:`_schema.MetaData`
+ object,
+ either explicitly or via reflection, may not be completely thread-safe.
+
+ .. seealso::
+
+ :ref:`metadata_describing` - Introduction to database metadata
+
+ """
+
+ __visit_name__ = "metadata"
+
+ def __init__(
+ self,
+ schema: Optional[str] = None,
+ quote_schema: Optional[bool] = None,
+ naming_convention: Optional[_NamingSchemaParameter] = None,
+ info: Optional[_InfoType] = None,
+ ) -> None:
+ """Create a new MetaData object.
+
+ :param schema:
+ The default schema to use for the :class:`_schema.Table`,
+ :class:`.Sequence`, and potentially other objects associated with
+ this :class:`_schema.MetaData`. Defaults to ``None``.
+
+ .. seealso::
+
+ :ref:`schema_metadata_schema_name` - details on how the
+ :paramref:`_schema.MetaData.schema` parameter is used.
+
+ :paramref:`_schema.Table.schema`
+
+ :paramref:`.Sequence.schema`
+
+ :param quote_schema:
+ Sets the ``quote_schema`` flag for those :class:`_schema.Table`,
+ :class:`.Sequence`, and other objects which make usage of the
+ local ``schema`` name.
+
+ :param info: Optional data dictionary which will be populated into the
+ :attr:`.SchemaItem.info` attribute of this object.
+
+ :param naming_convention: a dictionary referring to values which
+ will establish default naming conventions for :class:`.Constraint`
+ and :class:`.Index` objects, for those objects which are not given
+ a name explicitly.
+
+ The keys of this dictionary may be:
+
+ * a constraint or Index class, e.g. the :class:`.UniqueConstraint`,
+ :class:`_schema.ForeignKeyConstraint` class, the :class:`.Index`
+ class
+
+ * a string mnemonic for one of the known constraint classes;
+ ``"fk"``, ``"pk"``, ``"ix"``, ``"ck"``, ``"uq"`` for foreign key,
+ primary key, index, check, and unique constraint, respectively.
+
+ * the string name of a user-defined "token" that can be used
+ to define new naming tokens.
+
+ The values associated with each "constraint class" or "constraint
+ mnemonic" key are string naming templates, such as
+ ``"uq_%(table_name)s_%(column_0_name)s"``,
+ which describe how the name should be composed. The values
+ associated with user-defined "token" keys should be callables of the
+ form ``fn(constraint, table)``, which accepts the constraint/index
+ object and :class:`_schema.Table` as arguments, returning a string
+ result.
+
+ The built-in names are as follows, some of which may only be
+ available for certain types of constraint:
+
+ * ``%(table_name)s`` - the name of the :class:`_schema.Table`
+ object
+ associated with the constraint.
+
+ * ``%(referred_table_name)s`` - the name of the
+ :class:`_schema.Table`
+ object associated with the referencing target of a
+ :class:`_schema.ForeignKeyConstraint`.
+
+ * ``%(column_0_name)s`` - the name of the :class:`_schema.Column`
+ at
+ index position "0" within the constraint.
+
+ * ``%(column_0N_name)s`` - the name of all :class:`_schema.Column`
+ objects in order within the constraint, joined without a
+ separator.
+
+ * ``%(column_0_N_name)s`` - the name of all
+ :class:`_schema.Column`
+ objects in order within the constraint, joined with an
+ underscore as a separator.
+
+ * ``%(column_0_label)s``, ``%(column_0N_label)s``,
+ ``%(column_0_N_label)s`` - the label of either the zeroth
+ :class:`_schema.Column` or all :class:`.Columns`, separated with
+ or without an underscore
+
+ * ``%(column_0_key)s``, ``%(column_0N_key)s``,
+ ``%(column_0_N_key)s`` - the key of either the zeroth
+ :class:`_schema.Column` or all :class:`.Columns`, separated with
+ or without an underscore
+
+ * ``%(referred_column_0_name)s``, ``%(referred_column_0N_name)s``
+ ``%(referred_column_0_N_name)s``, ``%(referred_column_0_key)s``,
+ ``%(referred_column_0N_key)s``, ... column tokens which
+ render the names/keys/labels of columns that are referenced
+ by a :class:`_schema.ForeignKeyConstraint`.
+
+ * ``%(constraint_name)s`` - a special key that refers to the
+ existing name given to the constraint. When this key is
+ present, the :class:`.Constraint` object's existing name will be
+ replaced with one that is composed from template string that
+ uses this token. When this token is present, it is required that
+ the :class:`.Constraint` is given an explicit name ahead of time.
+
+ * user-defined: any additional token may be implemented by passing
+ it along with a ``fn(constraint, table)`` callable to the
+ naming_convention dictionary.
+
+ .. versionadded:: 1.3.0 - added new ``%(column_0N_name)s``,
+ ``%(column_0_N_name)s``, and related tokens that produce
+ concatenations of names, keys, or labels for all columns referred
+ to by a given constraint.
+
+ .. seealso::
+
+ :ref:`constraint_naming_conventions` - for detailed usage
+ examples.
+
+ """
+ if schema is not None and not isinstance(schema, str):
+ raise exc.ArgumentError(
+ "expected schema argument to be a string, "
+ f"got {type(schema)}."
+ )
+ self.tables = util.FacadeDict()
+ self.schema = quoted_name.construct(schema, quote_schema)
+ self.naming_convention = (
+ naming_convention
+ if naming_convention
+ else DEFAULT_NAMING_CONVENTION
+ )
+ if info:
+ self.info = info
+ self._schemas: Set[str] = set()
+ self._sequences: Dict[str, Sequence] = {}
+ self._fk_memos: Dict[Tuple[str, Optional[str]], List[ForeignKey]] = (
+ collections.defaultdict(list)
+ )
+
+ tables: util.FacadeDict[str, Table]
+ """A dictionary of :class:`_schema.Table`
+ objects keyed to their name or "table key".
+
+ The exact key is that determined by the :attr:`_schema.Table.key`
+ attribute;
+ for a table with no :attr:`_schema.Table.schema` attribute,
+ this is the same
+ as :attr:`_schema.Table.name`. For a table with a schema,
+ it is typically of the
+ form ``schemaname.tablename``.
+
+ .. seealso::
+
+ :attr:`_schema.MetaData.sorted_tables`
+
+ """
+
+ def __repr__(self) -> str:
+ return "MetaData()"
+
+ def __contains__(self, table_or_key: Union[str, Table]) -> bool:
+ if not isinstance(table_or_key, str):
+ table_or_key = table_or_key.key
+ return table_or_key in self.tables
+
+ def _add_table(
+ self, name: str, schema: Optional[str], table: Table
+ ) -> None:
+ key = _get_table_key(name, schema)
+ self.tables._insert_item(key, table)
+ if schema:
+ self._schemas.add(schema)
+
+ def _remove_table(self, name: str, schema: Optional[str]) -> None:
+ key = _get_table_key(name, schema)
+ removed = dict.pop(self.tables, key, None)
+ if removed is not None:
+ for fk in removed.foreign_keys:
+ fk._remove_from_metadata(self)
+ if self._schemas:
+ self._schemas = {
+ t.schema for t in self.tables.values() if t.schema is not None
+ }
+
+ def __getstate__(self) -> Dict[str, Any]:
+ return {
+ "tables": self.tables,
+ "schema": self.schema,
+ "schemas": self._schemas,
+ "sequences": self._sequences,
+ "fk_memos": self._fk_memos,
+ "naming_convention": self.naming_convention,
+ }
+
+ def __setstate__(self, state: Dict[str, Any]) -> None:
+ self.tables = state["tables"]
+ self.schema = state["schema"]
+ self.naming_convention = state["naming_convention"]
+ self._sequences = state["sequences"]
+ self._schemas = state["schemas"]
+ self._fk_memos = state["fk_memos"]
+
+ def clear(self) -> None:
+ """Clear all Table objects from this MetaData."""
+
+ dict.clear(self.tables) # type: ignore
+ self._schemas.clear()
+ self._fk_memos.clear()
+
+ def remove(self, table: Table) -> None:
+ """Remove the given Table object from this MetaData."""
+
+ self._remove_table(table.name, table.schema)
+
+ @property
+ def sorted_tables(self) -> List[Table]:
+ """Returns a list of :class:`_schema.Table` objects sorted in order of
+ foreign key dependency.
+
+ The sorting will place :class:`_schema.Table`
+ objects that have dependencies
+ first, before the dependencies themselves, representing the
+ order in which they can be created. To get the order in which
+ the tables would be dropped, use the ``reversed()`` Python built-in.
+
+ .. warning::
+
+ The :attr:`.MetaData.sorted_tables` attribute cannot by itself
+ accommodate automatic resolution of dependency cycles between
+ tables, which are usually caused by mutually dependent foreign key
+ constraints. When these cycles are detected, the foreign keys
+ of these tables are omitted from consideration in the sort.
+ A warning is emitted when this condition occurs, which will be an
+ exception raise in a future release. Tables which are not part
+ of the cycle will still be returned in dependency order.
+
+ To resolve these cycles, the
+ :paramref:`_schema.ForeignKeyConstraint.use_alter` parameter may be
+ applied to those constraints which create a cycle. Alternatively,
+ the :func:`_schema.sort_tables_and_constraints` function will
+ automatically return foreign key constraints in a separate
+ collection when cycles are detected so that they may be applied
+ to a schema separately.
+
+ .. versionchanged:: 1.3.17 - a warning is emitted when
+ :attr:`.MetaData.sorted_tables` cannot perform a proper sort
+ due to cyclical dependencies. This will be an exception in a
+ future release. Additionally, the sort will continue to return
+ other tables not involved in the cycle in dependency order which
+ was not the case previously.
+
+ .. seealso::
+
+ :func:`_schema.sort_tables`
+
+ :func:`_schema.sort_tables_and_constraints`
+
+ :attr:`_schema.MetaData.tables`
+
+ :meth:`_reflection.Inspector.get_table_names`
+
+ :meth:`_reflection.Inspector.get_sorted_table_and_fkc_names`
+
+
+ """
+ return ddl.sort_tables(
+ sorted(self.tables.values(), key=lambda t: t.key) # type: ignore
+ )
+
+ @util.preload_module("sqlalchemy.engine.reflection")
+ def reflect(
+ self,
+ bind: Union[Engine, Connection],
+ schema: Optional[str] = None,
+ views: bool = False,
+ only: Union[
+ _typing_Sequence[str], Callable[[str, MetaData], bool], None
+ ] = None,
+ extend_existing: bool = False,
+ autoload_replace: bool = True,
+ resolve_fks: bool = True,
+ **dialect_kwargs: Any,
+ ) -> None:
+ r"""Load all available table definitions from the database.
+
+ Automatically creates ``Table`` entries in this ``MetaData`` for any
+ table available in the database but not yet present in the
+ ``MetaData``. May be called multiple times to pick up tables recently
+ added to the database, however no special action is taken if a table
+ in this ``MetaData`` no longer exists in the database.
+
+ :param bind:
+ A :class:`.Connection` or :class:`.Engine` used to access the
+ database.
+
+ :param schema:
+ Optional, query and reflect tables from an alternate schema.
+ If None, the schema associated with this :class:`_schema.MetaData`
+ is used, if any.
+
+ :param views:
+ If True, also reflect views (materialized and plain).
+
+ :param only:
+ Optional. Load only a sub-set of available named tables. May be
+ specified as a sequence of names or a callable.
+
+ If a sequence of names is provided, only those tables will be
+ reflected. An error is raised if a table is requested but not
+ available. Named tables already present in this ``MetaData`` are
+ ignored.
+
+ If a callable is provided, it will be used as a boolean predicate to
+ filter the list of potential table names. The callable is called
+ with a table name and this ``MetaData`` instance as positional
+ arguments and should return a true value for any table to reflect.
+
+ :param extend_existing: Passed along to each :class:`_schema.Table` as
+ :paramref:`_schema.Table.extend_existing`.
+
+ :param autoload_replace: Passed along to each :class:`_schema.Table`
+ as
+ :paramref:`_schema.Table.autoload_replace`.
+
+ :param resolve_fks: if True, reflect :class:`_schema.Table`
+ objects linked
+ to :class:`_schema.ForeignKey` objects located in each
+ :class:`_schema.Table`.
+ For :meth:`_schema.MetaData.reflect`,
+ this has the effect of reflecting
+ related tables that might otherwise not be in the list of tables
+ being reflected, for example if the referenced table is in a
+ different schema or is omitted via the
+ :paramref:`.MetaData.reflect.only` parameter. When False,
+ :class:`_schema.ForeignKey` objects are not followed to the
+ :class:`_schema.Table`
+ in which they link, however if the related table is also part of the
+ list of tables that would be reflected in any case, the
+ :class:`_schema.ForeignKey` object will still resolve to its related
+ :class:`_schema.Table` after the :meth:`_schema.MetaData.reflect`
+ operation is
+ complete. Defaults to True.
+
+ .. versionadded:: 1.3.0
+
+ .. seealso::
+
+ :paramref:`_schema.Table.resolve_fks`
+
+ :param \**dialect_kwargs: Additional keyword arguments not mentioned
+ above are dialect specific, and passed in the form
+ ``<dialectname>_<argname>``. See the documentation regarding an
+ individual dialect at :ref:`dialect_toplevel` for detail on
+ documented arguments.
+
+ .. seealso::
+
+ :ref:`metadata_reflection_toplevel`
+
+ :meth:`_events.DDLEvents.column_reflect` - Event used to customize
+ the reflected columns. Usually used to generalize the types using
+ :meth:`_types.TypeEngine.as_generic`
+
+ :ref:`metadata_reflection_dbagnostic_types` - describes how to
+ reflect tables using general types.
+
+ """
+
+ with inspection.inspect(bind)._inspection_context() as insp:
+ reflect_opts: Any = {
+ "autoload_with": insp,
+ "extend_existing": extend_existing,
+ "autoload_replace": autoload_replace,
+ "resolve_fks": resolve_fks,
+ "_extend_on": set(),
+ }
+
+ reflect_opts.update(dialect_kwargs)
+
+ if schema is None:
+ schema = self.schema
+
+ if schema is not None:
+ reflect_opts["schema"] = schema
+
+ kind = util.preloaded.engine_reflection.ObjectKind.TABLE
+ available: util.OrderedSet[str] = util.OrderedSet(
+ insp.get_table_names(schema)
+ )
+ if views:
+ kind = util.preloaded.engine_reflection.ObjectKind.ANY
+ available.update(insp.get_view_names(schema))
+ try:
+ available.update(insp.get_materialized_view_names(schema))
+ except NotImplementedError:
+ pass
+
+ if schema is not None:
+ available_w_schema: util.OrderedSet[str] = util.OrderedSet(
+ [f"{schema}.{name}" for name in available]
+ )
+ else:
+ available_w_schema = available
+
+ current = set(self.tables)
+
+ if only is None:
+ load = [
+ name
+ for name, schname in zip(available, available_w_schema)
+ if extend_existing or schname not in current
+ ]
+ elif callable(only):
+ load = [
+ name
+ for name, schname in zip(available, available_w_schema)
+ if (extend_existing or schname not in current)
+ and only(name, self)
+ ]
+ else:
+ missing = [name for name in only if name not in available]
+ if missing:
+ s = schema and (" schema '%s'" % schema) or ""
+ missing_str = ", ".join(missing)
+ raise exc.InvalidRequestError(
+ f"Could not reflect: requested table(s) not available "
+ f"in {bind.engine!r}{s}: ({missing_str})"
+ )
+ load = [
+ name
+ for name in only
+ if extend_existing or name not in current
+ ]
+ # pass the available tables so the inspector can
+ # choose to ignore the filter_names
+ _reflect_info = insp._get_reflection_info(
+ schema=schema,
+ filter_names=load,
+ available=available,
+ kind=kind,
+ scope=util.preloaded.engine_reflection.ObjectScope.ANY,
+ **dialect_kwargs,
+ )
+ reflect_opts["_reflect_info"] = _reflect_info
+
+ for name in load:
+ try:
+ Table(name, self, **reflect_opts)
+ except exc.UnreflectableTableError as uerr:
+ util.warn(f"Skipping table {name}: {uerr}")
+
+ def create_all(
+ self,
+ bind: _CreateDropBind,
+ tables: Optional[_typing_Sequence[Table]] = None,
+ checkfirst: bool = True,
+ ) -> None:
+ """Create all tables stored in this metadata.
+
+ Conditional by default, will not attempt to recreate tables already
+ present in the target database.
+
+ :param bind:
+ A :class:`.Connection` or :class:`.Engine` used to access the
+ database.
+
+ :param tables:
+ Optional list of ``Table`` objects, which is a subset of the total
+ tables in the ``MetaData`` (others are ignored).
+
+ :param checkfirst:
+ Defaults to True, don't issue CREATEs for tables already present
+ in the target database.
+
+ """
+ bind._run_ddl_visitor(
+ ddl.SchemaGenerator, self, checkfirst=checkfirst, tables=tables
+ )
+
+ def drop_all(
+ self,
+ bind: _CreateDropBind,
+ tables: Optional[_typing_Sequence[Table]] = None,
+ checkfirst: bool = True,
+ ) -> None:
+ """Drop all tables stored in this metadata.
+
+ Conditional by default, will not attempt to drop tables not present in
+ the target database.
+
+ :param bind:
+ A :class:`.Connection` or :class:`.Engine` used to access the
+ database.
+
+ :param tables:
+ Optional list of ``Table`` objects, which is a subset of the
+ total tables in the ``MetaData`` (others are ignored).
+
+ :param checkfirst:
+ Defaults to True, only issue DROPs for tables confirmed to be
+ present in the target database.
+
+ """
+ bind._run_ddl_visitor(
+ ddl.SchemaDropper, self, checkfirst=checkfirst, tables=tables
+ )
+
+
+class Computed(FetchedValue, SchemaItem):
+ """Defines a generated column, i.e. "GENERATED ALWAYS AS" syntax.
+
+ The :class:`.Computed` construct is an inline construct added to the
+ argument list of a :class:`_schema.Column` object::
+
+ from sqlalchemy import Computed
+
+ Table('square', metadata_obj,
+ Column('side', Float, nullable=False),
+ Column('area', Float, Computed('side * side'))
+ )
+
+ See the linked documentation below for complete details.
+
+ .. versionadded:: 1.3.11
+
+ .. seealso::
+
+ :ref:`computed_ddl`
+
+ """
+
+ __visit_name__ = "computed_column"
+
+ column: Optional[Column[Any]]
+
+ @_document_text_coercion(
+ "sqltext", ":class:`.Computed`", ":paramref:`.Computed.sqltext`"
+ )
+ def __init__(
+ self, sqltext: _DDLColumnArgument, persisted: Optional[bool] = None
+ ) -> None:
+ """Construct a GENERATED ALWAYS AS DDL construct to accompany a
+ :class:`_schema.Column`.
+
+ :param sqltext:
+ A string containing the column generation expression, which will be
+ used verbatim, or a SQL expression construct, such as a
+ :func:`_expression.text`
+ object. If given as a string, the object is converted to a
+ :func:`_expression.text` object.
+
+ :param persisted:
+ Optional, controls how this column should be persisted by the
+ database. Possible values are:
+
+ * ``None``, the default, it will use the default persistence
+ defined by the database.
+ * ``True``, will render ``GENERATED ALWAYS AS ... STORED``, or the
+ equivalent for the target database if supported.
+ * ``False``, will render ``GENERATED ALWAYS AS ... VIRTUAL``, or
+ the equivalent for the target database if supported.
+
+ Specifying ``True`` or ``False`` may raise an error when the DDL
+ is emitted to the target database if the database does not support
+ that persistence option. Leaving this parameter at its default
+ of ``None`` is guaranteed to succeed for all databases that support
+ ``GENERATED ALWAYS AS``.
+
+ """
+ self.sqltext = coercions.expect(roles.DDLExpressionRole, sqltext)
+ self.persisted = persisted
+ self.column = None
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ assert isinstance(parent, Column)
+
+ if not isinstance(
+ parent.server_default, (type(None), Computed)
+ ) or not isinstance(parent.server_onupdate, (type(None), Computed)):
+ raise exc.ArgumentError(
+ "A generated column cannot specify a server_default or a "
+ "server_onupdate argument"
+ )
+ self.column = parent
+ parent.computed = self
+ self.column.server_onupdate = self
+ self.column.server_default = self
+
+ def _as_for_update(self, for_update: bool) -> FetchedValue:
+ return self
+
+ @util.deprecated(
+ "1.4",
+ "The :meth:`_schema.Computed.copy` method is deprecated "
+ "and will be removed in a future release.",
+ )
+ def copy(
+ self, *, target_table: Optional[Table] = None, **kw: Any
+ ) -> Computed:
+ return self._copy(target_table=target_table, **kw)
+
+ def _copy(
+ self, *, target_table: Optional[Table] = None, **kw: Any
+ ) -> Computed:
+ sqltext = _copy_expression(
+ self.sqltext,
+ self.column.table if self.column is not None else None,
+ target_table,
+ )
+ g = Computed(sqltext, persisted=self.persisted)
+
+ return self._schema_item_copy(g)
+
+
+class Identity(IdentityOptions, FetchedValue, SchemaItem):
+ """Defines an identity column, i.e. "GENERATED { ALWAYS | BY DEFAULT }
+ AS IDENTITY" syntax.
+
+ The :class:`.Identity` construct is an inline construct added to the
+ argument list of a :class:`_schema.Column` object::
+
+ from sqlalchemy import Identity
+
+ Table('foo', metadata_obj,
+ Column('id', Integer, Identity())
+ Column('description', Text),
+ )
+
+ See the linked documentation below for complete details.
+
+ .. versionadded:: 1.4
+
+ .. seealso::
+
+ :ref:`identity_ddl`
+
+ """
+
+ __visit_name__ = "identity_column"
+
+ is_identity = True
+
+ def __init__(
+ self,
+ always: bool = False,
+ on_null: Optional[bool] = None,
+ start: Optional[int] = None,
+ increment: Optional[int] = None,
+ minvalue: Optional[int] = None,
+ maxvalue: Optional[int] = None,
+ nominvalue: Optional[bool] = None,
+ nomaxvalue: Optional[bool] = None,
+ cycle: Optional[bool] = None,
+ cache: Optional[int] = None,
+ order: Optional[bool] = None,
+ ) -> None:
+ """Construct a GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY DDL
+ construct to accompany a :class:`_schema.Column`.
+
+ See the :class:`.Sequence` documentation for a complete description
+ of most parameters.
+
+ .. note::
+ MSSQL supports this construct as the preferred alternative to
+ generate an IDENTITY on a column, but it uses non standard
+ syntax that only support :paramref:`_schema.Identity.start`
+ and :paramref:`_schema.Identity.increment`.
+ All other parameters are ignored.
+
+ :param always:
+ A boolean, that indicates the type of identity column.
+ If ``False`` is specified, the default, then the user-specified
+ value takes precedence.
+ If ``True`` is specified, a user-specified value is not accepted (
+ on some backends, like PostgreSQL, OVERRIDING SYSTEM VALUE, or
+ similar, may be specified in an INSERT to override the sequence
+ value).
+ Some backends also have a default value for this parameter,
+ ``None`` can be used to omit rendering this part in the DDL. It
+ will be treated as ``False`` if a backend does not have a default
+ value.
+
+ :param on_null:
+ Set to ``True`` to specify ON NULL in conjunction with a
+ ``always=False`` identity column. This option is only supported on
+ some backends, like Oracle.
+
+ :param start: the starting index of the sequence.
+ :param increment: the increment value of the sequence.
+ :param minvalue: the minimum value of the sequence.
+ :param maxvalue: the maximum value of the sequence.
+ :param nominvalue: no minimum value of the sequence.
+ :param nomaxvalue: no maximum value of the sequence.
+ :param cycle: allows the sequence to wrap around when the maxvalue
+ or minvalue has been reached.
+ :param cache: optional integer value; number of future values in the
+ sequence which are calculated in advance.
+ :param order: optional boolean value; if true, renders the
+ ORDER keyword.
+
+ """
+ IdentityOptions.__init__(
+ self,
+ start=start,
+ increment=increment,
+ minvalue=minvalue,
+ maxvalue=maxvalue,
+ nominvalue=nominvalue,
+ nomaxvalue=nomaxvalue,
+ cycle=cycle,
+ cache=cache,
+ order=order,
+ )
+ self.always = always
+ self.on_null = on_null
+ self.column = None
+
+ def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None:
+ assert isinstance(parent, Column)
+ if not isinstance(
+ parent.server_default, (type(None), Identity)
+ ) or not isinstance(parent.server_onupdate, type(None)):
+ raise exc.ArgumentError(
+ "A column with an Identity object cannot specify a "
+ "server_default or a server_onupdate argument"
+ )
+ if parent.autoincrement is False:
+ raise exc.ArgumentError(
+ "A column with an Identity object cannot specify "
+ "autoincrement=False"
+ )
+ self.column = parent
+
+ parent.identity = self
+ if parent._user_defined_nullable is NULL_UNSPECIFIED:
+ parent.nullable = False
+
+ parent.server_default = self
+
+ def _as_for_update(self, for_update: bool) -> FetchedValue:
+ return self
+
+ @util.deprecated(
+ "1.4",
+ "The :meth:`_schema.Identity.copy` method is deprecated "
+ "and will be removed in a future release.",
+ )
+ def copy(self, **kw: Any) -> Identity:
+ return self._copy(**kw)
+
+ def _copy(self, **kw: Any) -> Identity:
+ i = Identity(
+ always=self.always,
+ on_null=self.on_null,
+ start=self.start,
+ increment=self.increment,
+ minvalue=self.minvalue,
+ maxvalue=self.maxvalue,
+ nominvalue=self.nominvalue,
+ nomaxvalue=self.nomaxvalue,
+ cycle=self.cycle,
+ cache=self.cache,
+ order=self.order,
+ )
+
+ return self._schema_item_copy(i)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/selectable.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/selectable.py
new file mode 100644
index 0000000..65978f6
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/selectable.py
@@ -0,0 +1,6913 @@
+# sql/selectable.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+"""The :class:`_expression.FromClause` class of SQL expression elements,
+representing
+SQL tables and derived rowsets.
+
+"""
+
+from __future__ import annotations
+
+import collections
+from enum import Enum
+import itertools
+from typing import AbstractSet
+from typing import Any as TODO_Any
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import Dict
+from typing import Generic
+from typing import Iterable
+from typing import Iterator
+from typing import List
+from typing import NamedTuple
+from typing import NoReturn
+from typing import Optional
+from typing import overload
+from typing import Sequence
+from typing import Set
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from . import cache_key
+from . import coercions
+from . import operators
+from . import roles
+from . import traversals
+from . import type_api
+from . import visitors
+from ._typing import _ColumnsClauseArgument
+from ._typing import _no_kw
+from ._typing import _TP
+from ._typing import is_column_element
+from ._typing import is_select_statement
+from ._typing import is_subquery
+from ._typing import is_table
+from ._typing import is_text_clause
+from .annotation import Annotated
+from .annotation import SupportsCloneAnnotations
+from .base import _clone
+from .base import _cloned_difference
+from .base import _cloned_intersection
+from .base import _entity_namespace_key
+from .base import _EntityNamespace
+from .base import _expand_cloned
+from .base import _from_objects
+from .base import _generative
+from .base import _never_select_column
+from .base import _NoArg
+from .base import _select_iterables
+from .base import CacheableOptions
+from .base import ColumnCollection
+from .base import ColumnSet
+from .base import CompileState
+from .base import DedupeColumnCollection
+from .base import Executable
+from .base import Generative
+from .base import HasCompileState
+from .base import HasMemoized
+from .base import Immutable
+from .coercions import _document_text_coercion
+from .elements import _anonymous_label
+from .elements import BindParameter
+from .elements import BooleanClauseList
+from .elements import ClauseElement
+from .elements import ClauseList
+from .elements import ColumnClause
+from .elements import ColumnElement
+from .elements import DQLDMLClauseElement
+from .elements import GroupedElement
+from .elements import literal_column
+from .elements import TableValuedColumn
+from .elements import UnaryExpression
+from .operators import OperatorType
+from .sqltypes import NULLTYPE
+from .visitors import _TraverseInternalsType
+from .visitors import InternalTraversal
+from .visitors import prefix_anon_map
+from .. import exc
+from .. import util
+from ..util import HasMemoized_ro_memoized_attribute
+from ..util.typing import Literal
+from ..util.typing import Protocol
+from ..util.typing import Self
+
+and_ = BooleanClauseList.and_
+
+_T = TypeVar("_T", bound=Any)
+
+if TYPE_CHECKING:
+ from ._typing import _ColumnExpressionArgument
+ from ._typing import _ColumnExpressionOrStrLabelArgument
+ from ._typing import _FromClauseArgument
+ from ._typing import _JoinTargetArgument
+ from ._typing import _LimitOffsetType
+ from ._typing import _MAYBE_ENTITY
+ from ._typing import _NOT_ENTITY
+ from ._typing import _OnClauseArgument
+ from ._typing import _SelectStatementForCompoundArgument
+ from ._typing import _T0
+ from ._typing import _T1
+ from ._typing import _T2
+ from ._typing import _T3
+ from ._typing import _T4
+ from ._typing import _T5
+ from ._typing import _T6
+ from ._typing import _T7
+ from ._typing import _TextCoercedExpressionArgument
+ from ._typing import _TypedColumnClauseArgument as _TCCA
+ from ._typing import _TypeEngineArgument
+ from .base import _AmbiguousTableNameMap
+ from .base import ExecutableOption
+ from .base import ReadOnlyColumnCollection
+ from .cache_key import _CacheKeyTraversalType
+ from .compiler import SQLCompiler
+ from .dml import Delete
+ from .dml import Update
+ from .elements import BinaryExpression
+ from .elements import KeyedColumnElement
+ from .elements import Label
+ from .elements import NamedColumn
+ from .elements import TextClause
+ from .functions import Function
+ from .schema import ForeignKey
+ from .schema import ForeignKeyConstraint
+ from .sqltypes import TableValueType
+ from .type_api import TypeEngine
+ from .visitors import _CloneCallableType
+
+
+_ColumnsClauseElement = Union["FromClause", ColumnElement[Any], "TextClause"]
+_LabelConventionCallable = Callable[
+ [Union["ColumnElement[Any]", "TextClause"]], Optional[str]
+]
+
+
+class _JoinTargetProtocol(Protocol):
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]: ...
+
+ @util.ro_non_memoized_property
+ def entity_namespace(self) -> _EntityNamespace: ...
+
+
+_JoinTargetElement = Union["FromClause", _JoinTargetProtocol]
+_OnClauseElement = Union["ColumnElement[bool]", _JoinTargetProtocol]
+
+_ForUpdateOfArgument = Union[
+ # single column, Table, ORM Entity
+ Union[
+ "_ColumnExpressionArgument[Any]",
+ "_FromClauseArgument",
+ ],
+ # or sequence of single column elements
+ Sequence["_ColumnExpressionArgument[Any]"],
+]
+
+
+_SetupJoinsElement = Tuple[
+ _JoinTargetElement,
+ Optional[_OnClauseElement],
+ Optional["FromClause"],
+ Dict[str, Any],
+]
+
+
+_SelectIterable = Iterable[Union["ColumnElement[Any]", "TextClause"]]
+
+
+class _OffsetLimitParam(BindParameter[int]):
+ inherit_cache = True
+
+ @property
+ def _limit_offset_value(self) -> Optional[int]:
+ return self.effective_value
+
+
+class ReturnsRows(roles.ReturnsRowsRole, DQLDMLClauseElement):
+ """The base-most class for Core constructs that have some concept of
+ columns that can represent rows.
+
+ While the SELECT statement and TABLE are the primary things we think
+ of in this category, DML like INSERT, UPDATE and DELETE can also specify
+ RETURNING which means they can be used in CTEs and other forms, and
+ PostgreSQL has functions that return rows also.
+
+ .. versionadded:: 1.4
+
+ """
+
+ _is_returns_rows = True
+
+ # sub-elements of returns_rows
+ _is_from_clause = False
+ _is_select_base = False
+ _is_select_statement = False
+ _is_lateral = False
+
+ @property
+ def selectable(self) -> ReturnsRows:
+ return self
+
+ @util.ro_non_memoized_property
+ def _all_selected_columns(self) -> _SelectIterable:
+ """A sequence of column expression objects that represents the
+ "selected" columns of this :class:`_expression.ReturnsRows`.
+
+ This is typically equivalent to .exported_columns except it is
+ delivered in the form of a straight sequence and not keyed
+ :class:`_expression.ColumnCollection`.
+
+ """
+ raise NotImplementedError()
+
+ def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
+ """Return ``True`` if this :class:`.ReturnsRows` is
+ 'derived' from the given :class:`.FromClause`.
+
+ An example would be an Alias of a Table is derived from that Table.
+
+ """
+ raise NotImplementedError()
+
+ def _generate_fromclause_column_proxies(
+ self, fromclause: FromClause
+ ) -> None:
+ """Populate columns into an :class:`.AliasedReturnsRows` object."""
+
+ raise NotImplementedError()
+
+ def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+ """reset internal collections for an incoming column being added."""
+ raise NotImplementedError()
+
+ @property
+ def exported_columns(self) -> ReadOnlyColumnCollection[Any, Any]:
+ """A :class:`_expression.ColumnCollection`
+ that represents the "exported"
+ columns of this :class:`_expression.ReturnsRows`.
+
+ The "exported" columns represent the collection of
+ :class:`_expression.ColumnElement`
+ expressions that are rendered by this SQL
+ construct. There are primary varieties which are the
+ "FROM clause columns" of a FROM clause, such as a table, join,
+ or subquery, the "SELECTed columns", which are the columns in
+ the "columns clause" of a SELECT statement, and the RETURNING
+ columns in a DML statement..
+
+ .. versionadded:: 1.4
+
+ .. seealso::
+
+ :attr:`_expression.FromClause.exported_columns`
+
+ :attr:`_expression.SelectBase.exported_columns`
+ """
+
+ raise NotImplementedError()
+
+
+class ExecutableReturnsRows(Executable, ReturnsRows):
+ """base for executable statements that return rows."""
+
+
+class TypedReturnsRows(ExecutableReturnsRows, Generic[_TP]):
+ """base for executable statements that return rows."""
+
+
+class Selectable(ReturnsRows):
+ """Mark a class as being selectable."""
+
+ __visit_name__ = "selectable"
+
+ is_selectable = True
+
+ def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+ raise NotImplementedError()
+
+ def lateral(self, name: Optional[str] = None) -> LateralFromClause:
+ """Return a LATERAL alias of this :class:`_expression.Selectable`.
+
+ The return value is the :class:`_expression.Lateral` construct also
+ provided by the top-level :func:`_expression.lateral` function.
+
+ .. seealso::
+
+ :ref:`tutorial_lateral_correlation` - overview of usage.
+
+ """
+ return Lateral._construct(self, name=name)
+
+ @util.deprecated(
+ "1.4",
+ message="The :meth:`.Selectable.replace_selectable` method is "
+ "deprecated, and will be removed in a future release. Similar "
+ "functionality is available via the sqlalchemy.sql.visitors module.",
+ )
+ @util.preload_module("sqlalchemy.sql.util")
+ def replace_selectable(self, old: FromClause, alias: Alias) -> Self:
+ """Replace all occurrences of :class:`_expression.FromClause`
+ 'old' with the given :class:`_expression.Alias`
+ object, returning a copy of this :class:`_expression.FromClause`.
+
+ """
+ return util.preloaded.sql_util.ClauseAdapter(alias).traverse(self)
+
+ def corresponding_column(
+ self, column: KeyedColumnElement[Any], require_embedded: bool = False
+ ) -> Optional[KeyedColumnElement[Any]]:
+ """Given a :class:`_expression.ColumnElement`, return the exported
+ :class:`_expression.ColumnElement` object from the
+ :attr:`_expression.Selectable.exported_columns`
+ collection of this :class:`_expression.Selectable`
+ which corresponds to that
+ original :class:`_expression.ColumnElement` via a common ancestor
+ column.
+
+ :param column: the target :class:`_expression.ColumnElement`
+ to be matched.
+
+ :param require_embedded: only return corresponding columns for
+ the given :class:`_expression.ColumnElement`, if the given
+ :class:`_expression.ColumnElement`
+ is actually present within a sub-element
+ of this :class:`_expression.Selectable`.
+ Normally the column will match if
+ it merely shares a common ancestor with one of the exported
+ columns of this :class:`_expression.Selectable`.
+
+ .. seealso::
+
+ :attr:`_expression.Selectable.exported_columns` - the
+ :class:`_expression.ColumnCollection`
+ that is used for the operation.
+
+ :meth:`_expression.ColumnCollection.corresponding_column`
+ - implementation
+ method.
+
+ """
+
+ return self.exported_columns.corresponding_column(
+ column, require_embedded
+ )
+
+
+class HasPrefixes:
+ _prefixes: Tuple[Tuple[DQLDMLClauseElement, str], ...] = ()
+
+ _has_prefixes_traverse_internals: _TraverseInternalsType = [
+ ("_prefixes", InternalTraversal.dp_prefix_sequence)
+ ]
+
+ @_generative
+ @_document_text_coercion(
+ "prefixes",
+ ":meth:`_expression.HasPrefixes.prefix_with`",
+ ":paramref:`.HasPrefixes.prefix_with.*prefixes`",
+ )
+ def prefix_with(
+ self,
+ *prefixes: _TextCoercedExpressionArgument[Any],
+ dialect: str = "*",
+ ) -> Self:
+ r"""Add one or more expressions following the statement keyword, i.e.
+ SELECT, INSERT, UPDATE, or DELETE. Generative.
+
+ This is used to support backend-specific prefix keywords such as those
+ provided by MySQL.
+
+ E.g.::
+
+ stmt = table.insert().prefix_with("LOW_PRIORITY", dialect="mysql")
+
+ # MySQL 5.7 optimizer hints
+ stmt = select(table).prefix_with(
+ "/*+ BKA(t1) */", dialect="mysql")
+
+ Multiple prefixes can be specified by multiple calls
+ to :meth:`_expression.HasPrefixes.prefix_with`.
+
+ :param \*prefixes: textual or :class:`_expression.ClauseElement`
+ construct which
+ will be rendered following the INSERT, UPDATE, or DELETE
+ keyword.
+ :param dialect: optional string dialect name which will
+ limit rendering of this prefix to only that dialect.
+
+ """
+ self._prefixes = self._prefixes + tuple(
+ [
+ (coercions.expect(roles.StatementOptionRole, p), dialect)
+ for p in prefixes
+ ]
+ )
+ return self
+
+
+class HasSuffixes:
+ _suffixes: Tuple[Tuple[DQLDMLClauseElement, str], ...] = ()
+
+ _has_suffixes_traverse_internals: _TraverseInternalsType = [
+ ("_suffixes", InternalTraversal.dp_prefix_sequence)
+ ]
+
+ @_generative
+ @_document_text_coercion(
+ "suffixes",
+ ":meth:`_expression.HasSuffixes.suffix_with`",
+ ":paramref:`.HasSuffixes.suffix_with.*suffixes`",
+ )
+ def suffix_with(
+ self,
+ *suffixes: _TextCoercedExpressionArgument[Any],
+ dialect: str = "*",
+ ) -> Self:
+ r"""Add one or more expressions following the statement as a whole.
+
+ This is used to support backend-specific suffix keywords on
+ certain constructs.
+
+ E.g.::
+
+ stmt = select(col1, col2).cte().suffix_with(
+ "cycle empno set y_cycle to 1 default 0", dialect="oracle")
+
+ Multiple suffixes can be specified by multiple calls
+ to :meth:`_expression.HasSuffixes.suffix_with`.
+
+ :param \*suffixes: textual or :class:`_expression.ClauseElement`
+ construct which
+ will be rendered following the target clause.
+ :param dialect: Optional string dialect name which will
+ limit rendering of this suffix to only that dialect.
+
+ """
+ self._suffixes = self._suffixes + tuple(
+ [
+ (coercions.expect(roles.StatementOptionRole, p), dialect)
+ for p in suffixes
+ ]
+ )
+ return self
+
+
+class HasHints:
+ _hints: util.immutabledict[Tuple[FromClause, str], str] = (
+ util.immutabledict()
+ )
+ _statement_hints: Tuple[Tuple[str, str], ...] = ()
+
+ _has_hints_traverse_internals: _TraverseInternalsType = [
+ ("_statement_hints", InternalTraversal.dp_statement_hint_list),
+ ("_hints", InternalTraversal.dp_table_hint_list),
+ ]
+
+ def with_statement_hint(self, text: str, dialect_name: str = "*") -> Self:
+ """Add a statement hint to this :class:`_expression.Select` or
+ other selectable object.
+
+ This method is similar to :meth:`_expression.Select.with_hint`
+ except that
+ it does not require an individual table, and instead applies to the
+ statement as a whole.
+
+ Hints here are specific to the backend database and may include
+ directives such as isolation levels, file directives, fetch directives,
+ etc.
+
+ .. seealso::
+
+ :meth:`_expression.Select.with_hint`
+
+ :meth:`_expression.Select.prefix_with` - generic SELECT prefixing
+ which also can suit some database-specific HINT syntaxes such as
+ MySQL optimizer hints
+
+ """
+ return self._with_hint(None, text, dialect_name)
+
+ @_generative
+ def with_hint(
+ self,
+ selectable: _FromClauseArgument,
+ text: str,
+ dialect_name: str = "*",
+ ) -> Self:
+ r"""Add an indexing or other executional context hint for the given
+ selectable to this :class:`_expression.Select` or other selectable
+ object.
+
+ The text of the hint is rendered in the appropriate
+ location for the database backend in use, relative
+ to the given :class:`_schema.Table` or :class:`_expression.Alias`
+ passed as the
+ ``selectable`` argument. The dialect implementation
+ typically uses Python string substitution syntax
+ with the token ``%(name)s`` to render the name of
+ the table or alias. E.g. when using Oracle, the
+ following::
+
+ select(mytable).\
+ with_hint(mytable, "index(%(name)s ix_mytable)")
+
+ Would render SQL as::
+
+ select /*+ index(mytable ix_mytable) */ ... from mytable
+
+ The ``dialect_name`` option will limit the rendering of a particular
+ hint to a particular backend. Such as, to add hints for both Oracle
+ and Sybase simultaneously::
+
+ select(mytable).\
+ with_hint(mytable, "index(%(name)s ix_mytable)", 'oracle').\
+ with_hint(mytable, "WITH INDEX ix_mytable", 'mssql')
+
+ .. seealso::
+
+ :meth:`_expression.Select.with_statement_hint`
+
+ """
+
+ return self._with_hint(selectable, text, dialect_name)
+
+ def _with_hint(
+ self,
+ selectable: Optional[_FromClauseArgument],
+ text: str,
+ dialect_name: str,
+ ) -> Self:
+ if selectable is None:
+ self._statement_hints += ((dialect_name, text),)
+ else:
+ self._hints = self._hints.union(
+ {
+ (
+ coercions.expect(roles.FromClauseRole, selectable),
+ dialect_name,
+ ): text
+ }
+ )
+ return self
+
+
+class FromClause(roles.AnonymizedFromClauseRole, Selectable):
+ """Represent an element that can be used within the ``FROM``
+ clause of a ``SELECT`` statement.
+
+ The most common forms of :class:`_expression.FromClause` are the
+ :class:`_schema.Table` and the :func:`_expression.select` constructs. Key
+ features common to all :class:`_expression.FromClause` objects include:
+
+ * a :attr:`.c` collection, which provides per-name access to a collection
+ of :class:`_expression.ColumnElement` objects.
+ * a :attr:`.primary_key` attribute, which is a collection of all those
+ :class:`_expression.ColumnElement`
+ objects that indicate the ``primary_key`` flag.
+ * Methods to generate various derivations of a "from" clause, including
+ :meth:`_expression.FromClause.alias`,
+ :meth:`_expression.FromClause.join`,
+ :meth:`_expression.FromClause.select`.
+
+
+ """
+
+ __visit_name__ = "fromclause"
+ named_with_column = False
+
+ @util.ro_non_memoized_property
+ def _hide_froms(self) -> Iterable[FromClause]:
+ return ()
+
+ _is_clone_of: Optional[FromClause]
+
+ _columns: ColumnCollection[Any, Any]
+
+ schema: Optional[str] = None
+ """Define the 'schema' attribute for this :class:`_expression.FromClause`.
+
+ This is typically ``None`` for most objects except that of
+ :class:`_schema.Table`, where it is taken as the value of the
+ :paramref:`_schema.Table.schema` argument.
+
+ """
+
+ is_selectable = True
+ _is_from_clause = True
+ _is_join = False
+
+ _use_schema_map = False
+
+ def select(self) -> Select[Any]:
+ r"""Return a SELECT of this :class:`_expression.FromClause`.
+
+
+ e.g.::
+
+ stmt = some_table.select().where(some_table.c.id == 5)
+
+ .. seealso::
+
+ :func:`_expression.select` - general purpose
+ method which allows for arbitrary column lists.
+
+ """
+ return Select(self)
+
+ def join(
+ self,
+ right: _FromClauseArgument,
+ onclause: Optional[_ColumnExpressionArgument[bool]] = None,
+ isouter: bool = False,
+ full: bool = False,
+ ) -> Join:
+ """Return a :class:`_expression.Join` from this
+ :class:`_expression.FromClause`
+ to another :class:`FromClause`.
+
+ E.g.::
+
+ from sqlalchemy import join
+
+ j = user_table.join(address_table,
+ user_table.c.id == address_table.c.user_id)
+ stmt = select(user_table).select_from(j)
+
+ would emit SQL along the lines of::
+
+ SELECT user.id, user.name FROM user
+ JOIN address ON user.id = address.user_id
+
+ :param right: the right side of the join; this is any
+ :class:`_expression.FromClause` object such as a
+ :class:`_schema.Table` object, and
+ may also be a selectable-compatible object such as an ORM-mapped
+ class.
+
+ :param onclause: a SQL expression representing the ON clause of the
+ join. If left at ``None``, :meth:`_expression.FromClause.join`
+ will attempt to
+ join the two tables based on a foreign key relationship.
+
+ :param isouter: if True, render a LEFT OUTER JOIN, instead of JOIN.
+
+ :param full: if True, render a FULL OUTER JOIN, instead of LEFT OUTER
+ JOIN. Implies :paramref:`.FromClause.join.isouter`.
+
+ .. seealso::
+
+ :func:`_expression.join` - standalone function
+
+ :class:`_expression.Join` - the type of object produced
+
+ """
+
+ return Join(self, right, onclause, isouter, full)
+
+ def outerjoin(
+ self,
+ right: _FromClauseArgument,
+ onclause: Optional[_ColumnExpressionArgument[bool]] = None,
+ full: bool = False,
+ ) -> Join:
+ """Return a :class:`_expression.Join` from this
+ :class:`_expression.FromClause`
+ to another :class:`FromClause`, with the "isouter" flag set to
+ True.
+
+ E.g.::
+
+ from sqlalchemy import outerjoin
+
+ j = user_table.outerjoin(address_table,
+ user_table.c.id == address_table.c.user_id)
+
+ The above is equivalent to::
+
+ j = user_table.join(
+ address_table,
+ user_table.c.id == address_table.c.user_id,
+ isouter=True)
+
+ :param right: the right side of the join; this is any
+ :class:`_expression.FromClause` object such as a
+ :class:`_schema.Table` object, and
+ may also be a selectable-compatible object such as an ORM-mapped
+ class.
+
+ :param onclause: a SQL expression representing the ON clause of the
+ join. If left at ``None``, :meth:`_expression.FromClause.join`
+ will attempt to
+ join the two tables based on a foreign key relationship.
+
+ :param full: if True, render a FULL OUTER JOIN, instead of
+ LEFT OUTER JOIN.
+
+ .. seealso::
+
+ :meth:`_expression.FromClause.join`
+
+ :class:`_expression.Join`
+
+ """
+
+ return Join(self, right, onclause, True, full)
+
+ def alias(
+ self, name: Optional[str] = None, flat: bool = False
+ ) -> NamedFromClause:
+ """Return an alias of this :class:`_expression.FromClause`.
+
+ E.g.::
+
+ a2 = some_table.alias('a2')
+
+ The above code creates an :class:`_expression.Alias`
+ object which can be used
+ as a FROM clause in any SELECT statement.
+
+ .. seealso::
+
+ :ref:`tutorial_using_aliases`
+
+ :func:`_expression.alias`
+
+ """
+
+ return Alias._construct(self, name=name)
+
+ def tablesample(
+ self,
+ sampling: Union[float, Function[Any]],
+ name: Optional[str] = None,
+ seed: Optional[roles.ExpressionElementRole[Any]] = None,
+ ) -> TableSample:
+ """Return a TABLESAMPLE alias of this :class:`_expression.FromClause`.
+
+ The return value is the :class:`_expression.TableSample`
+ construct also
+ provided by the top-level :func:`_expression.tablesample` function.
+
+ .. seealso::
+
+ :func:`_expression.tablesample` - usage guidelines and parameters
+
+ """
+ return TableSample._construct(
+ self, sampling=sampling, name=name, seed=seed
+ )
+
+ def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
+ """Return ``True`` if this :class:`_expression.FromClause` is
+ 'derived' from the given ``FromClause``.
+
+ An example would be an Alias of a Table is derived from that Table.
+
+ """
+ # this is essentially an "identity" check in the base class.
+ # Other constructs override this to traverse through
+ # contained elements.
+ return fromclause in self._cloned_set
+
+ def _is_lexical_equivalent(self, other: FromClause) -> bool:
+ """Return ``True`` if this :class:`_expression.FromClause` and
+ the other represent the same lexical identity.
+
+ This tests if either one is a copy of the other, or
+ if they are the same via annotation identity.
+
+ """
+ return bool(self._cloned_set.intersection(other._cloned_set))
+
+ @util.ro_non_memoized_property
+ def description(self) -> str:
+ """A brief description of this :class:`_expression.FromClause`.
+
+ Used primarily for error message formatting.
+
+ """
+ return getattr(self, "name", self.__class__.__name__ + " object")
+
+ def _generate_fromclause_column_proxies(
+ self, fromclause: FromClause
+ ) -> None:
+ fromclause._columns._populate_separate_keys(
+ col._make_proxy(fromclause) for col in self.c
+ )
+
+ @util.ro_non_memoized_property
+ def exported_columns(
+ self,
+ ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+ """A :class:`_expression.ColumnCollection`
+ that represents the "exported"
+ columns of this :class:`_expression.Selectable`.
+
+ The "exported" columns for a :class:`_expression.FromClause`
+ object are synonymous
+ with the :attr:`_expression.FromClause.columns` collection.
+
+ .. versionadded:: 1.4
+
+ .. seealso::
+
+ :attr:`_expression.Selectable.exported_columns`
+
+ :attr:`_expression.SelectBase.exported_columns`
+
+
+ """
+ return self.c
+
+ @util.ro_non_memoized_property
+ def columns(
+ self,
+ ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+ """A named-based collection of :class:`_expression.ColumnElement`
+ objects maintained by this :class:`_expression.FromClause`.
+
+ The :attr:`.columns`, or :attr:`.c` collection, is the gateway
+ to the construction of SQL expressions using table-bound or
+ other selectable-bound columns::
+
+ select(mytable).where(mytable.c.somecolumn == 5)
+
+ :return: a :class:`.ColumnCollection` object.
+
+ """
+ return self.c
+
+ @util.ro_memoized_property
+ def c(self) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+ """
+ A synonym for :attr:`.FromClause.columns`
+
+ :return: a :class:`.ColumnCollection`
+
+ """
+ if "_columns" not in self.__dict__:
+ self._init_collections()
+ self._populate_column_collection()
+ return self._columns.as_readonly()
+
+ @util.ro_non_memoized_property
+ def entity_namespace(self) -> _EntityNamespace:
+ """Return a namespace used for name-based access in SQL expressions.
+
+ This is the namespace that is used to resolve "filter_by()" type
+ expressions, such as::
+
+ stmt.filter_by(address='some address')
+
+ It defaults to the ``.c`` collection, however internally it can
+ be overridden using the "entity_namespace" annotation to deliver
+ alternative results.
+
+ """
+ return self.c
+
+ @util.ro_memoized_property
+ def primary_key(self) -> Iterable[NamedColumn[Any]]:
+ """Return the iterable collection of :class:`_schema.Column` objects
+ which comprise the primary key of this :class:`_selectable.FromClause`.
+
+ For a :class:`_schema.Table` object, this collection is represented
+ by the :class:`_schema.PrimaryKeyConstraint` which itself is an
+ iterable collection of :class:`_schema.Column` objects.
+
+ """
+ self._init_collections()
+ self._populate_column_collection()
+ return self.primary_key
+
+ @util.ro_memoized_property
+ def foreign_keys(self) -> Iterable[ForeignKey]:
+ """Return the collection of :class:`_schema.ForeignKey` marker objects
+ which this FromClause references.
+
+ Each :class:`_schema.ForeignKey` is a member of a
+ :class:`_schema.Table`-wide
+ :class:`_schema.ForeignKeyConstraint`.
+
+ .. seealso::
+
+ :attr:`_schema.Table.foreign_key_constraints`
+
+ """
+ self._init_collections()
+ self._populate_column_collection()
+ return self.foreign_keys
+
+ def _reset_column_collection(self) -> None:
+ """Reset the attributes linked to the ``FromClause.c`` attribute.
+
+ This collection is separate from all the other memoized things
+ as it has shown to be sensitive to being cleared out in situations
+ where enclosing code, typically in a replacement traversal scenario,
+ has already established strong relationships
+ with the exported columns.
+
+ The collection is cleared for the case where a table is having a
+ column added to it as well as within a Join during copy internals.
+
+ """
+
+ for key in ["_columns", "columns", "c", "primary_key", "foreign_keys"]:
+ self.__dict__.pop(key, None)
+
+ @util.ro_non_memoized_property
+ def _select_iterable(self) -> _SelectIterable:
+ return (c for c in self.c if not _never_select_column(c))
+
+ def _init_collections(self) -> None:
+ assert "_columns" not in self.__dict__
+ assert "primary_key" not in self.__dict__
+ assert "foreign_keys" not in self.__dict__
+
+ self._columns = ColumnCollection()
+ self.primary_key = ColumnSet() # type: ignore
+ self.foreign_keys = set() # type: ignore
+
+ @property
+ def _cols_populated(self) -> bool:
+ return "_columns" in self.__dict__
+
+ def _populate_column_collection(self) -> None:
+ """Called on subclasses to establish the .c collection.
+
+ Each implementation has a different way of establishing
+ this collection.
+
+ """
+
+ def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+ """Given a column added to the .c collection of an underlying
+ selectable, produce the local version of that column, assuming this
+ selectable ultimately should proxy this column.
+
+ this is used to "ping" a derived selectable to add a new column
+ to its .c. collection when a Column has been added to one of the
+ Table objects it ultimately derives from.
+
+ If the given selectable hasn't populated its .c. collection yet,
+ it should at least pass on the message to the contained selectables,
+ but it will return None.
+
+ This method is currently used by Declarative to allow Table
+ columns to be added to a partially constructed inheritance
+ mapping that may have already produced joins. The method
+ isn't public right now, as the full span of implications
+ and/or caveats aren't yet clear.
+
+ It's also possible that this functionality could be invoked by
+ default via an event, which would require that
+ selectables maintain a weak referencing collection of all
+ derivations.
+
+ """
+ self._reset_column_collection()
+
+ def _anonymous_fromclause(
+ self, *, name: Optional[str] = None, flat: bool = False
+ ) -> FromClause:
+ return self.alias(name=name)
+
+ if TYPE_CHECKING:
+
+ def self_group(
+ self, against: Optional[OperatorType] = None
+ ) -> Union[FromGrouping, Self]: ...
+
+
+class NamedFromClause(FromClause):
+ """A :class:`.FromClause` that has a name.
+
+ Examples include tables, subqueries, CTEs, aliased tables.
+
+ .. versionadded:: 2.0
+
+ """
+
+ named_with_column = True
+
+ name: str
+
+ @util.preload_module("sqlalchemy.sql.sqltypes")
+ def table_valued(self) -> TableValuedColumn[Any]:
+ """Return a :class:`_sql.TableValuedColumn` object for this
+ :class:`_expression.FromClause`.
+
+ A :class:`_sql.TableValuedColumn` is a :class:`_sql.ColumnElement` that
+ represents a complete row in a table. Support for this construct is
+ backend dependent, and is supported in various forms by backends
+ such as PostgreSQL, Oracle and SQL Server.
+
+ E.g.:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import select, column, func, table
+ >>> a = table("a", column("id"), column("x"), column("y"))
+ >>> stmt = select(func.row_to_json(a.table_valued()))
+ >>> print(stmt)
+ {printsql}SELECT row_to_json(a) AS row_to_json_1
+ FROM a
+
+ .. versionadded:: 1.4.0b2
+
+ .. seealso::
+
+ :ref:`tutorial_functions` - in the :ref:`unified_tutorial`
+
+ """
+ return TableValuedColumn(self, type_api.TABLEVALUE)
+
+
+class SelectLabelStyle(Enum):
+ """Label style constants that may be passed to
+ :meth:`_sql.Select.set_label_style`."""
+
+ LABEL_STYLE_NONE = 0
+ """Label style indicating no automatic labeling should be applied to the
+ columns clause of a SELECT statement.
+
+ Below, the columns named ``columna`` are both rendered as is, meaning that
+ the name ``columna`` can only refer to the first occurrence of this name
+ within a result set, as well as if the statement were used as a subquery:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import table, column, select, true, LABEL_STYLE_NONE
+ >>> table1 = table("table1", column("columna"), column("columnb"))
+ >>> table2 = table("table2", column("columna"), column("columnc"))
+ >>> print(select(table1, table2).join(table2, true()).set_label_style(LABEL_STYLE_NONE))
+ {printsql}SELECT table1.columna, table1.columnb, table2.columna, table2.columnc
+ FROM table1 JOIN table2 ON true
+
+ Used with the :meth:`_sql.Select.set_label_style` method.
+
+ .. versionadded:: 1.4
+
+ """ # noqa: E501
+
+ LABEL_STYLE_TABLENAME_PLUS_COL = 1
+ """Label style indicating all columns should be labeled as
+ ``<tablename>_<columnname>`` when generating the columns clause of a SELECT
+ statement, to disambiguate same-named columns referenced from different
+ tables, aliases, or subqueries.
+
+ Below, all column names are given a label so that the two same-named
+ columns ``columna`` are disambiguated as ``table1_columna`` and
+ ``table2_columna``:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import table, column, select, true, LABEL_STYLE_TABLENAME_PLUS_COL
+ >>> table1 = table("table1", column("columna"), column("columnb"))
+ >>> table2 = table("table2", column("columna"), column("columnc"))
+ >>> print(select(table1, table2).join(table2, true()).set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL))
+ {printsql}SELECT table1.columna AS table1_columna, table1.columnb AS table1_columnb, table2.columna AS table2_columna, table2.columnc AS table2_columnc
+ FROM table1 JOIN table2 ON true
+
+ Used with the :meth:`_sql.GenerativeSelect.set_label_style` method.
+ Equivalent to the legacy method ``Select.apply_labels()``;
+ :data:`_sql.LABEL_STYLE_TABLENAME_PLUS_COL` is SQLAlchemy's legacy
+ auto-labeling style. :data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY` provides a
+ less intrusive approach to disambiguation of same-named column expressions.
+
+
+ .. versionadded:: 1.4
+
+ """ # noqa: E501
+
+ LABEL_STYLE_DISAMBIGUATE_ONLY = 2
+ """Label style indicating that columns with a name that conflicts with
+ an existing name should be labeled with a semi-anonymizing label
+ when generating the columns clause of a SELECT statement.
+
+ Below, most column names are left unaffected, except for the second
+ occurrence of the name ``columna``, which is labeled using the
+ label ``columna_1`` to disambiguate it from that of ``tablea.columna``:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import table, column, select, true, LABEL_STYLE_DISAMBIGUATE_ONLY
+ >>> table1 = table("table1", column("columna"), column("columnb"))
+ >>> table2 = table("table2", column("columna"), column("columnc"))
+ >>> print(select(table1, table2).join(table2, true()).set_label_style(LABEL_STYLE_DISAMBIGUATE_ONLY))
+ {printsql}SELECT table1.columna, table1.columnb, table2.columna AS columna_1, table2.columnc
+ FROM table1 JOIN table2 ON true
+
+ Used with the :meth:`_sql.GenerativeSelect.set_label_style` method,
+ :data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY` is the default labeling style
+ for all SELECT statements outside of :term:`1.x style` ORM queries.
+
+ .. versionadded:: 1.4
+
+ """ # noqa: E501
+
+ LABEL_STYLE_DEFAULT = LABEL_STYLE_DISAMBIGUATE_ONLY
+ """The default label style, refers to
+ :data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY`.
+
+ .. versionadded:: 1.4
+
+ """
+
+ LABEL_STYLE_LEGACY_ORM = 3
+
+
+(
+ LABEL_STYLE_NONE,
+ LABEL_STYLE_TABLENAME_PLUS_COL,
+ LABEL_STYLE_DISAMBIGUATE_ONLY,
+ _,
+) = list(SelectLabelStyle)
+
+LABEL_STYLE_DEFAULT = LABEL_STYLE_DISAMBIGUATE_ONLY
+
+
+class Join(roles.DMLTableRole, FromClause):
+ """Represent a ``JOIN`` construct between two
+ :class:`_expression.FromClause`
+ elements.
+
+ The public constructor function for :class:`_expression.Join`
+ is the module-level
+ :func:`_expression.join()` function, as well as the
+ :meth:`_expression.FromClause.join` method
+ of any :class:`_expression.FromClause` (e.g. such as
+ :class:`_schema.Table`).
+
+ .. seealso::
+
+ :func:`_expression.join`
+
+ :meth:`_expression.FromClause.join`
+
+ """
+
+ __visit_name__ = "join"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("left", InternalTraversal.dp_clauseelement),
+ ("right", InternalTraversal.dp_clauseelement),
+ ("onclause", InternalTraversal.dp_clauseelement),
+ ("isouter", InternalTraversal.dp_boolean),
+ ("full", InternalTraversal.dp_boolean),
+ ]
+
+ _is_join = True
+
+ left: FromClause
+ right: FromClause
+ onclause: Optional[ColumnElement[bool]]
+ isouter: bool
+ full: bool
+
+ def __init__(
+ self,
+ left: _FromClauseArgument,
+ right: _FromClauseArgument,
+ onclause: Optional[_OnClauseArgument] = None,
+ isouter: bool = False,
+ full: bool = False,
+ ):
+ """Construct a new :class:`_expression.Join`.
+
+ The usual entrypoint here is the :func:`_expression.join`
+ function or the :meth:`_expression.FromClause.join` method of any
+ :class:`_expression.FromClause` object.
+
+ """
+
+ # when deannotate was removed here, callcounts went up for ORM
+ # compilation of eager joins, since there were more comparisons of
+ # annotated objects. test_orm.py -> test_fetch_results
+ # was therefore changed to show a more real-world use case, where the
+ # compilation is cached; there's no change in post-cache callcounts.
+ # callcounts for a single compilation in that particular test
+ # that includes about eight joins about 1100 extra fn calls, from
+ # 29200 -> 30373
+
+ self.left = coercions.expect(
+ roles.FromClauseRole,
+ left,
+ )
+ self.right = coercions.expect(
+ roles.FromClauseRole,
+ right,
+ ).self_group()
+
+ if onclause is None:
+ self.onclause = self._match_primaries(self.left, self.right)
+ else:
+ # note: taken from If91f61527236fd4d7ae3cad1f24c38be921c90ba
+ # not merged yet
+ self.onclause = coercions.expect(
+ roles.OnClauseRole, onclause
+ ).self_group(against=operators._asbool)
+
+ self.isouter = isouter
+ self.full = full
+
+ @util.ro_non_memoized_property
+ def description(self) -> str:
+ return "Join object on %s(%d) and %s(%d)" % (
+ self.left.description,
+ id(self.left),
+ self.right.description,
+ id(self.right),
+ )
+
+ def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
+ return (
+ # use hash() to ensure direct comparison to annotated works
+ # as well
+ hash(fromclause) == hash(self)
+ or self.left.is_derived_from(fromclause)
+ or self.right.is_derived_from(fromclause)
+ )
+
+ def self_group(
+ self, against: Optional[OperatorType] = None
+ ) -> FromGrouping:
+ ...
+ return FromGrouping(self)
+
+ @util.preload_module("sqlalchemy.sql.util")
+ def _populate_column_collection(self) -> None:
+ sqlutil = util.preloaded.sql_util
+ columns: List[KeyedColumnElement[Any]] = [c for c in self.left.c] + [
+ c for c in self.right.c
+ ]
+
+ self.primary_key.extend( # type: ignore
+ sqlutil.reduce_columns(
+ (c for c in columns if c.primary_key), self.onclause
+ )
+ )
+ self._columns._populate_separate_keys(
+ (col._tq_key_label, col) for col in columns
+ )
+ self.foreign_keys.update( # type: ignore
+ itertools.chain(*[col.foreign_keys for col in columns])
+ )
+
+ def _copy_internals(
+ self, clone: _CloneCallableType = _clone, **kw: Any
+ ) -> None:
+ # see Select._copy_internals() for similar concept
+
+ # here we pre-clone "left" and "right" so that we can
+ # determine the new FROM clauses
+ all_the_froms = set(
+ itertools.chain(
+ _from_objects(self.left),
+ _from_objects(self.right),
+ )
+ )
+
+ # run the clone on those. these will be placed in the
+ # cache used by the clone function
+ new_froms = {f: clone(f, **kw) for f in all_the_froms}
+
+ # set up a special replace function that will replace for
+ # ColumnClause with parent table referring to those
+ # replaced FromClause objects
+ def replace(
+ obj: Union[BinaryExpression[Any], ColumnClause[Any]],
+ **kw: Any,
+ ) -> Optional[KeyedColumnElement[ColumnElement[Any]]]:
+ if isinstance(obj, ColumnClause) and obj.table in new_froms:
+ newelem = new_froms[obj.table].corresponding_column(obj)
+ return newelem
+ return None
+
+ kw["replace"] = replace
+
+ # run normal _copy_internals. the clones for
+ # left and right will come from the clone function's
+ # cache
+ super()._copy_internals(clone=clone, **kw)
+
+ self._reset_memoizations()
+
+ def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+ super()._refresh_for_new_column(column)
+ self.left._refresh_for_new_column(column)
+ self.right._refresh_for_new_column(column)
+
+ def _match_primaries(
+ self,
+ left: FromClause,
+ right: FromClause,
+ ) -> ColumnElement[bool]:
+ if isinstance(left, Join):
+ left_right = left.right
+ else:
+ left_right = None
+ return self._join_condition(left, right, a_subset=left_right)
+
+ @classmethod
+ def _join_condition(
+ cls,
+ a: FromClause,
+ b: FromClause,
+ *,
+ a_subset: Optional[FromClause] = None,
+ consider_as_foreign_keys: Optional[
+ AbstractSet[ColumnClause[Any]]
+ ] = None,
+ ) -> ColumnElement[bool]:
+ """Create a join condition between two tables or selectables.
+
+ See sqlalchemy.sql.util.join_condition() for full docs.
+
+ """
+ constraints = cls._joincond_scan_left_right(
+ a, a_subset, b, consider_as_foreign_keys
+ )
+
+ if len(constraints) > 1:
+ cls._joincond_trim_constraints(
+ a, b, constraints, consider_as_foreign_keys
+ )
+
+ if len(constraints) == 0:
+ if isinstance(b, FromGrouping):
+ hint = (
+ " Perhaps you meant to convert the right side to a "
+ "subquery using alias()?"
+ )
+ else:
+ hint = ""
+ raise exc.NoForeignKeysError(
+ "Can't find any foreign key relationships "
+ "between '%s' and '%s'.%s"
+ % (a.description, b.description, hint)
+ )
+
+ crit = [(x == y) for x, y in list(constraints.values())[0]]
+ if len(crit) == 1:
+ return crit[0]
+ else:
+ return and_(*crit)
+
+ @classmethod
+ def _can_join(
+ cls,
+ left: FromClause,
+ right: FromClause,
+ *,
+ consider_as_foreign_keys: Optional[
+ AbstractSet[ColumnClause[Any]]
+ ] = None,
+ ) -> bool:
+ if isinstance(left, Join):
+ left_right = left.right
+ else:
+ left_right = None
+
+ constraints = cls._joincond_scan_left_right(
+ a=left,
+ b=right,
+ a_subset=left_right,
+ consider_as_foreign_keys=consider_as_foreign_keys,
+ )
+
+ return bool(constraints)
+
+ @classmethod
+ @util.preload_module("sqlalchemy.sql.util")
+ def _joincond_scan_left_right(
+ cls,
+ a: FromClause,
+ a_subset: Optional[FromClause],
+ b: FromClause,
+ consider_as_foreign_keys: Optional[AbstractSet[ColumnClause[Any]]],
+ ) -> collections.defaultdict[
+ Optional[ForeignKeyConstraint],
+ List[Tuple[ColumnClause[Any], ColumnClause[Any]]],
+ ]:
+ sql_util = util.preloaded.sql_util
+
+ a = coercions.expect(roles.FromClauseRole, a)
+ b = coercions.expect(roles.FromClauseRole, b)
+
+ constraints: collections.defaultdict[
+ Optional[ForeignKeyConstraint],
+ List[Tuple[ColumnClause[Any], ColumnClause[Any]]],
+ ] = collections.defaultdict(list)
+
+ for left in (a_subset, a):
+ if left is None:
+ continue
+ for fk in sorted(
+ b.foreign_keys,
+ key=lambda fk: fk.parent._creation_order,
+ ):
+ if (
+ consider_as_foreign_keys is not None
+ and fk.parent not in consider_as_foreign_keys
+ ):
+ continue
+ try:
+ col = fk.get_referent(left)
+ except exc.NoReferenceError as nrte:
+ table_names = {t.name for t in sql_util.find_tables(left)}
+ if nrte.table_name in table_names:
+ raise
+ else:
+ continue
+
+ if col is not None:
+ constraints[fk.constraint].append((col, fk.parent))
+ if left is not b:
+ for fk in sorted(
+ left.foreign_keys,
+ key=lambda fk: fk.parent._creation_order,
+ ):
+ if (
+ consider_as_foreign_keys is not None
+ and fk.parent not in consider_as_foreign_keys
+ ):
+ continue
+ try:
+ col = fk.get_referent(b)
+ except exc.NoReferenceError as nrte:
+ table_names = {t.name for t in sql_util.find_tables(b)}
+ if nrte.table_name in table_names:
+ raise
+ else:
+ continue
+
+ if col is not None:
+ constraints[fk.constraint].append((col, fk.parent))
+ if constraints:
+ break
+ return constraints
+
+ @classmethod
+ def _joincond_trim_constraints(
+ cls,
+ a: FromClause,
+ b: FromClause,
+ constraints: Dict[Any, Any],
+ consider_as_foreign_keys: Optional[Any],
+ ) -> None:
+ # more than one constraint matched. narrow down the list
+ # to include just those FKCs that match exactly to
+ # "consider_as_foreign_keys".
+ if consider_as_foreign_keys:
+ for const in list(constraints):
+ if {f.parent for f in const.elements} != set(
+ consider_as_foreign_keys
+ ):
+ del constraints[const]
+
+ # if still multiple constraints, but
+ # they all refer to the exact same end result, use it.
+ if len(constraints) > 1:
+ dedupe = {tuple(crit) for crit in constraints.values()}
+ if len(dedupe) == 1:
+ key = list(constraints)[0]
+ constraints = {key: constraints[key]}
+
+ if len(constraints) != 1:
+ raise exc.AmbiguousForeignKeysError(
+ "Can't determine join between '%s' and '%s'; "
+ "tables have more than one foreign key "
+ "constraint relationship between them. "
+ "Please specify the 'onclause' of this "
+ "join explicitly." % (a.description, b.description)
+ )
+
+ def select(self) -> Select[Any]:
+ r"""Create a :class:`_expression.Select` from this
+ :class:`_expression.Join`.
+
+ E.g.::
+
+ stmt = table_a.join(table_b, table_a.c.id == table_b.c.a_id)
+
+ stmt = stmt.select()
+
+ The above will produce a SQL string resembling::
+
+ SELECT table_a.id, table_a.col, table_b.id, table_b.a_id
+ FROM table_a JOIN table_b ON table_a.id = table_b.a_id
+
+ """
+ return Select(self.left, self.right).select_from(self)
+
+ @util.preload_module("sqlalchemy.sql.util")
+ def _anonymous_fromclause(
+ self, name: Optional[str] = None, flat: bool = False
+ ) -> TODO_Any:
+ sqlutil = util.preloaded.sql_util
+ if flat:
+ if name is not None:
+ raise exc.ArgumentError("Can't send name argument with flat")
+ left_a, right_a = (
+ self.left._anonymous_fromclause(flat=True),
+ self.right._anonymous_fromclause(flat=True),
+ )
+ adapter = sqlutil.ClauseAdapter(left_a).chain(
+ sqlutil.ClauseAdapter(right_a)
+ )
+
+ return left_a.join(
+ right_a,
+ adapter.traverse(self.onclause),
+ isouter=self.isouter,
+ full=self.full,
+ )
+ else:
+ return (
+ self.select()
+ .set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
+ .correlate(None)
+ .alias(name)
+ )
+
+ @util.ro_non_memoized_property
+ def _hide_froms(self) -> Iterable[FromClause]:
+ return itertools.chain(
+ *[_from_objects(x.left, x.right) for x in self._cloned_set]
+ )
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ self_list: List[FromClause] = [self]
+ return self_list + self.left._from_objects + self.right._from_objects
+
+
+class NoInit:
+ def __init__(self, *arg: Any, **kw: Any):
+ raise NotImplementedError(
+ "The %s class is not intended to be constructed "
+ "directly. Please use the %s() standalone "
+ "function or the %s() method available from appropriate "
+ "selectable objects."
+ % (
+ self.__class__.__name__,
+ self.__class__.__name__.lower(),
+ self.__class__.__name__.lower(),
+ )
+ )
+
+
+class LateralFromClause(NamedFromClause):
+ """mark a FROM clause as being able to render directly as LATERAL"""
+
+
+# FromClause ->
+# AliasedReturnsRows
+# -> Alias only for FromClause
+# -> Subquery only for SelectBase
+# -> CTE only for HasCTE -> SelectBase, DML
+# -> Lateral -> FromClause, but we accept SelectBase
+# w/ non-deprecated coercion
+# -> TableSample -> only for FromClause
+
+
+class AliasedReturnsRows(NoInit, NamedFromClause):
+ """Base class of aliases against tables, subqueries, and other
+ selectables."""
+
+ _is_from_container = True
+
+ _supports_derived_columns = False
+
+ element: ReturnsRows
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("element", InternalTraversal.dp_clauseelement),
+ ("name", InternalTraversal.dp_anon_name),
+ ]
+
+ @classmethod
+ def _construct(
+ cls,
+ selectable: Any,
+ *,
+ name: Optional[str] = None,
+ **kw: Any,
+ ) -> Self:
+ obj = cls.__new__(cls)
+ obj._init(selectable, name=name, **kw)
+ return obj
+
+ def _init(self, selectable: Any, *, name: Optional[str] = None) -> None:
+ self.element = coercions.expect(
+ roles.ReturnsRowsRole, selectable, apply_propagate_attrs=self
+ )
+ self.element = selectable
+ self._orig_name = name
+ if name is None:
+ if (
+ isinstance(selectable, FromClause)
+ and selectable.named_with_column
+ ):
+ name = getattr(selectable, "name", None)
+ if isinstance(name, _anonymous_label):
+ name = None
+ name = _anonymous_label.safe_construct(id(self), name or "anon")
+ self.name = name
+
+ def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+ super()._refresh_for_new_column(column)
+ self.element._refresh_for_new_column(column)
+
+ def _populate_column_collection(self) -> None:
+ self.element._generate_fromclause_column_proxies(self)
+
+ @util.ro_non_memoized_property
+ def description(self) -> str:
+ name = self.name
+ if isinstance(name, _anonymous_label):
+ name = "anon_1"
+
+ return name
+
+ @util.ro_non_memoized_property
+ def implicit_returning(self) -> bool:
+ return self.element.implicit_returning # type: ignore
+
+ @property
+ def original(self) -> ReturnsRows:
+ """Legacy for dialects that are referring to Alias.original."""
+ return self.element
+
+ def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
+ if fromclause in self._cloned_set:
+ return True
+ return self.element.is_derived_from(fromclause)
+
+ def _copy_internals(
+ self, clone: _CloneCallableType = _clone, **kw: Any
+ ) -> None:
+ existing_element = self.element
+
+ super()._copy_internals(clone=clone, **kw)
+
+ # the element clone is usually against a Table that returns the
+ # same object. don't reset exported .c. collections and other
+ # memoized details if it was not changed. this saves a lot on
+ # performance.
+ if existing_element is not self.element:
+ self._reset_column_collection()
+
+ @property
+ def _from_objects(self) -> List[FromClause]:
+ return [self]
+
+
+class FromClauseAlias(AliasedReturnsRows):
+ element: FromClause
+
+
+class Alias(roles.DMLTableRole, FromClauseAlias):
+ """Represents an table or selectable alias (AS).
+
+ Represents an alias, as typically applied to any table or
+ sub-select within a SQL statement using the ``AS`` keyword (or
+ without the keyword on certain databases such as Oracle).
+
+ This object is constructed from the :func:`_expression.alias` module
+ level function as well as the :meth:`_expression.FromClause.alias`
+ method available
+ on all :class:`_expression.FromClause` subclasses.
+
+ .. seealso::
+
+ :meth:`_expression.FromClause.alias`
+
+ """
+
+ __visit_name__ = "alias"
+
+ inherit_cache = True
+
+ element: FromClause
+
+ @classmethod
+ def _factory(
+ cls,
+ selectable: FromClause,
+ name: Optional[str] = None,
+ flat: bool = False,
+ ) -> NamedFromClause:
+ return coercions.expect(
+ roles.FromClauseRole, selectable, allow_select=True
+ ).alias(name=name, flat=flat)
+
+
+class TableValuedAlias(LateralFromClause, Alias):
+ """An alias against a "table valued" SQL function.
+
+ This construct provides for a SQL function that returns columns
+ to be used in the FROM clause of a SELECT statement. The
+ object is generated using the :meth:`_functions.FunctionElement.table_valued`
+ method, e.g.:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import select, func
+ >>> fn = func.json_array_elements_text('["one", "two", "three"]').table_valued("value")
+ >>> print(select(fn.c.value))
+ {printsql}SELECT anon_1.value
+ FROM json_array_elements_text(:json_array_elements_text_1) AS anon_1
+
+ .. versionadded:: 1.4.0b2
+
+ .. seealso::
+
+ :ref:`tutorial_functions_table_valued` - in the :ref:`unified_tutorial`
+
+ """ # noqa: E501
+
+ __visit_name__ = "table_valued_alias"
+
+ _supports_derived_columns = True
+ _render_derived = False
+ _render_derived_w_types = False
+ joins_implicitly = False
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("element", InternalTraversal.dp_clauseelement),
+ ("name", InternalTraversal.dp_anon_name),
+ ("_tableval_type", InternalTraversal.dp_type),
+ ("_render_derived", InternalTraversal.dp_boolean),
+ ("_render_derived_w_types", InternalTraversal.dp_boolean),
+ ]
+
+ def _init(
+ self,
+ selectable: Any,
+ *,
+ name: Optional[str] = None,
+ table_value_type: Optional[TableValueType] = None,
+ joins_implicitly: bool = False,
+ ) -> None:
+ super()._init(selectable, name=name)
+
+ self.joins_implicitly = joins_implicitly
+ self._tableval_type = (
+ type_api.TABLEVALUE
+ if table_value_type is None
+ else table_value_type
+ )
+
+ @HasMemoized.memoized_attribute
+ def column(self) -> TableValuedColumn[Any]:
+ """Return a column expression representing this
+ :class:`_sql.TableValuedAlias`.
+
+ This accessor is used to implement the
+ :meth:`_functions.FunctionElement.column_valued` method. See that
+ method for further details.
+
+ E.g.:
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(select(func.some_func().table_valued("value").column))
+ {printsql}SELECT anon_1 FROM some_func() AS anon_1
+
+ .. seealso::
+
+ :meth:`_functions.FunctionElement.column_valued`
+
+ """
+
+ return TableValuedColumn(self, self._tableval_type)
+
+ def alias(
+ self, name: Optional[str] = None, flat: bool = False
+ ) -> TableValuedAlias:
+ """Return a new alias of this :class:`_sql.TableValuedAlias`.
+
+ This creates a distinct FROM object that will be distinguished
+ from the original one when used in a SQL statement.
+
+ """
+
+ tva: TableValuedAlias = TableValuedAlias._construct(
+ self,
+ name=name,
+ table_value_type=self._tableval_type,
+ joins_implicitly=self.joins_implicitly,
+ )
+
+ if self._render_derived:
+ tva._render_derived = True
+ tva._render_derived_w_types = self._render_derived_w_types
+
+ return tva
+
+ def lateral(self, name: Optional[str] = None) -> LateralFromClause:
+ """Return a new :class:`_sql.TableValuedAlias` with the lateral flag
+ set, so that it renders as LATERAL.
+
+ .. seealso::
+
+ :func:`_expression.lateral`
+
+ """
+ tva = self.alias(name=name)
+ tva._is_lateral = True
+ return tva
+
+ def render_derived(
+ self,
+ name: Optional[str] = None,
+ with_types: bool = False,
+ ) -> TableValuedAlias:
+ """Apply "render derived" to this :class:`_sql.TableValuedAlias`.
+
+ This has the effect of the individual column names listed out
+ after the alias name in the "AS" sequence, e.g.:
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(
+ ... select(
+ ... func.unnest(array(["one", "two", "three"])).
+ table_valued("x", with_ordinality="o").render_derived()
+ ... )
+ ... )
+ {printsql}SELECT anon_1.x, anon_1.o
+ FROM unnest(ARRAY[%(param_1)s, %(param_2)s, %(param_3)s]) WITH ORDINALITY AS anon_1(x, o)
+
+ The ``with_types`` keyword will render column types inline within
+ the alias expression (this syntax currently applies to the
+ PostgreSQL database):
+
+ .. sourcecode:: pycon+sql
+
+ >>> print(
+ ... select(
+ ... func.json_to_recordset(
+ ... '[{"a":1,"b":"foo"},{"a":"2","c":"bar"}]'
+ ... )
+ ... .table_valued(column("a", Integer), column("b", String))
+ ... .render_derived(with_types=True)
+ ... )
+ ... )
+ {printsql}SELECT anon_1.a, anon_1.b FROM json_to_recordset(:json_to_recordset_1)
+ AS anon_1(a INTEGER, b VARCHAR)
+
+ :param name: optional string name that will be applied to the alias
+ generated. If left as None, a unique anonymizing name will be used.
+
+ :param with_types: if True, the derived columns will include the
+ datatype specification with each column. This is a special syntax
+ currently known to be required by PostgreSQL for some SQL functions.
+
+ """ # noqa: E501
+
+ # note: don't use the @_generative system here, keep a reference
+ # to the original object. otherwise you can have re-use of the
+ # python id() of the original which can cause name conflicts if
+ # a new anon-name grabs the same identifier as the local anon-name
+ # (just saw it happen on CI)
+
+ # construct against original to prevent memory growth
+ # for repeated generations
+ new_alias: TableValuedAlias = TableValuedAlias._construct(
+ self.element,
+ name=name,
+ table_value_type=self._tableval_type,
+ joins_implicitly=self.joins_implicitly,
+ )
+ new_alias._render_derived = True
+ new_alias._render_derived_w_types = with_types
+ return new_alias
+
+
+class Lateral(FromClauseAlias, LateralFromClause):
+ """Represent a LATERAL subquery.
+
+ This object is constructed from the :func:`_expression.lateral` module
+ level function as well as the :meth:`_expression.FromClause.lateral`
+ method available
+ on all :class:`_expression.FromClause` subclasses.
+
+ While LATERAL is part of the SQL standard, currently only more recent
+ PostgreSQL versions provide support for this keyword.
+
+ .. seealso::
+
+ :ref:`tutorial_lateral_correlation` - overview of usage.
+
+ """
+
+ __visit_name__ = "lateral"
+ _is_lateral = True
+
+ inherit_cache = True
+
+ @classmethod
+ def _factory(
+ cls,
+ selectable: Union[SelectBase, _FromClauseArgument],
+ name: Optional[str] = None,
+ ) -> LateralFromClause:
+ return coercions.expect(
+ roles.FromClauseRole, selectable, explicit_subquery=True
+ ).lateral(name=name)
+
+
+class TableSample(FromClauseAlias):
+ """Represent a TABLESAMPLE clause.
+
+ This object is constructed from the :func:`_expression.tablesample` module
+ level function as well as the :meth:`_expression.FromClause.tablesample`
+ method
+ available on all :class:`_expression.FromClause` subclasses.
+
+ .. seealso::
+
+ :func:`_expression.tablesample`
+
+ """
+
+ __visit_name__ = "tablesample"
+
+ _traverse_internals: _TraverseInternalsType = (
+ AliasedReturnsRows._traverse_internals
+ + [
+ ("sampling", InternalTraversal.dp_clauseelement),
+ ("seed", InternalTraversal.dp_clauseelement),
+ ]
+ )
+
+ @classmethod
+ def _factory(
+ cls,
+ selectable: _FromClauseArgument,
+ sampling: Union[float, Function[Any]],
+ name: Optional[str] = None,
+ seed: Optional[roles.ExpressionElementRole[Any]] = None,
+ ) -> TableSample:
+ return coercions.expect(roles.FromClauseRole, selectable).tablesample(
+ sampling, name=name, seed=seed
+ )
+
+ @util.preload_module("sqlalchemy.sql.functions")
+ def _init( # type: ignore[override]
+ self,
+ selectable: Any,
+ *,
+ name: Optional[str] = None,
+ sampling: Union[float, Function[Any]],
+ seed: Optional[roles.ExpressionElementRole[Any]] = None,
+ ) -> None:
+ assert sampling is not None
+ functions = util.preloaded.sql_functions
+ if not isinstance(sampling, functions.Function):
+ sampling = functions.func.system(sampling)
+
+ self.sampling: Function[Any] = sampling
+ self.seed = seed
+ super()._init(selectable, name=name)
+
+ def _get_method(self) -> Function[Any]:
+ return self.sampling
+
+
+class CTE(
+ roles.DMLTableRole,
+ roles.IsCTERole,
+ Generative,
+ HasPrefixes,
+ HasSuffixes,
+ AliasedReturnsRows,
+):
+ """Represent a Common Table Expression.
+
+ The :class:`_expression.CTE` object is obtained using the
+ :meth:`_sql.SelectBase.cte` method from any SELECT statement. A less often
+ available syntax also allows use of the :meth:`_sql.HasCTE.cte` method
+ present on :term:`DML` constructs such as :class:`_sql.Insert`,
+ :class:`_sql.Update` and
+ :class:`_sql.Delete`. See the :meth:`_sql.HasCTE.cte` method for
+ usage details on CTEs.
+
+ .. seealso::
+
+ :ref:`tutorial_subqueries_ctes` - in the 2.0 tutorial
+
+ :meth:`_sql.HasCTE.cte` - examples of calling styles
+
+ """
+
+ __visit_name__ = "cte"
+
+ _traverse_internals: _TraverseInternalsType = (
+ AliasedReturnsRows._traverse_internals
+ + [
+ ("_cte_alias", InternalTraversal.dp_clauseelement),
+ ("_restates", InternalTraversal.dp_clauseelement),
+ ("recursive", InternalTraversal.dp_boolean),
+ ("nesting", InternalTraversal.dp_boolean),
+ ]
+ + HasPrefixes._has_prefixes_traverse_internals
+ + HasSuffixes._has_suffixes_traverse_internals
+ )
+
+ element: HasCTE
+
+ @classmethod
+ def _factory(
+ cls,
+ selectable: HasCTE,
+ name: Optional[str] = None,
+ recursive: bool = False,
+ ) -> CTE:
+ r"""Return a new :class:`_expression.CTE`,
+ or Common Table Expression instance.
+
+ Please see :meth:`_expression.HasCTE.cte` for detail on CTE usage.
+
+ """
+ return coercions.expect(roles.HasCTERole, selectable).cte(
+ name=name, recursive=recursive
+ )
+
+ def _init(
+ self,
+ selectable: Select[Any],
+ *,
+ name: Optional[str] = None,
+ recursive: bool = False,
+ nesting: bool = False,
+ _cte_alias: Optional[CTE] = None,
+ _restates: Optional[CTE] = None,
+ _prefixes: Optional[Tuple[()]] = None,
+ _suffixes: Optional[Tuple[()]] = None,
+ ) -> None:
+ self.recursive = recursive
+ self.nesting = nesting
+ self._cte_alias = _cte_alias
+ # Keep recursivity reference with union/union_all
+ self._restates = _restates
+ if _prefixes:
+ self._prefixes = _prefixes
+ if _suffixes:
+ self._suffixes = _suffixes
+ super()._init(selectable, name=name)
+
+ def _populate_column_collection(self) -> None:
+ if self._cte_alias is not None:
+ self._cte_alias._generate_fromclause_column_proxies(self)
+ else:
+ self.element._generate_fromclause_column_proxies(self)
+
+ def alias(self, name: Optional[str] = None, flat: bool = False) -> CTE:
+ """Return an :class:`_expression.Alias` of this
+ :class:`_expression.CTE`.
+
+ This method is a CTE-specific specialization of the
+ :meth:`_expression.FromClause.alias` method.
+
+ .. seealso::
+
+ :ref:`tutorial_using_aliases`
+
+ :func:`_expression.alias`
+
+ """
+ return CTE._construct(
+ self.element,
+ name=name,
+ recursive=self.recursive,
+ nesting=self.nesting,
+ _cte_alias=self,
+ _prefixes=self._prefixes,
+ _suffixes=self._suffixes,
+ )
+
+ def union(self, *other: _SelectStatementForCompoundArgument) -> CTE:
+ r"""Return a new :class:`_expression.CTE` with a SQL ``UNION``
+ of the original CTE against the given selectables provided
+ as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
+
+ .. versionchanged:: 1.4.28 multiple elements are now accepted.
+
+ .. seealso::
+
+ :meth:`_sql.HasCTE.cte` - examples of calling styles
+
+ """
+ assert is_select_statement(
+ self.element
+ ), f"CTE element f{self.element} does not support union()"
+
+ return CTE._construct(
+ self.element.union(*other),
+ name=self.name,
+ recursive=self.recursive,
+ nesting=self.nesting,
+ _restates=self,
+ _prefixes=self._prefixes,
+ _suffixes=self._suffixes,
+ )
+
+ def union_all(self, *other: _SelectStatementForCompoundArgument) -> CTE:
+ r"""Return a new :class:`_expression.CTE` with a SQL ``UNION ALL``
+ of the original CTE against the given selectables provided
+ as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
+
+ .. versionchanged:: 1.4.28 multiple elements are now accepted.
+
+ .. seealso::
+
+ :meth:`_sql.HasCTE.cte` - examples of calling styles
+
+ """
+
+ assert is_select_statement(
+ self.element
+ ), f"CTE element f{self.element} does not support union_all()"
+
+ return CTE._construct(
+ self.element.union_all(*other),
+ name=self.name,
+ recursive=self.recursive,
+ nesting=self.nesting,
+ _restates=self,
+ _prefixes=self._prefixes,
+ _suffixes=self._suffixes,
+ )
+
+ def _get_reference_cte(self) -> CTE:
+ """
+ A recursive CTE is updated to attach the recursive part.
+ Updated CTEs should still refer to the original CTE.
+ This function returns this reference identifier.
+ """
+ return self._restates if self._restates is not None else self
+
+
+class _CTEOpts(NamedTuple):
+ nesting: bool
+
+
+class _ColumnsPlusNames(NamedTuple):
+ required_label_name: Optional[str]
+ """
+ string label name, if non-None, must be rendered as a
+ label, i.e. "AS <name>"
+ """
+
+ proxy_key: Optional[str]
+ """
+ proxy_key that is to be part of the result map for this
+ col. this is also the key in a fromclause.c or
+ select.selected_columns collection
+ """
+
+ fallback_label_name: Optional[str]
+ """
+ name that can be used to render an "AS <name>" when
+ we have to render a label even though
+ required_label_name was not given
+ """
+
+ column: Union[ColumnElement[Any], TextClause]
+ """
+ the ColumnElement itself
+ """
+
+ repeated: bool
+ """
+ True if this is a duplicate of a previous column
+ in the list of columns
+ """
+
+
+class SelectsRows(ReturnsRows):
+ """Sub-base of ReturnsRows for elements that deliver rows
+ directly, namely SELECT and INSERT/UPDATE/DELETE..RETURNING"""
+
+ _label_style: SelectLabelStyle = LABEL_STYLE_NONE
+
+ def _generate_columns_plus_names(
+ self,
+ anon_for_dupe_key: bool,
+ cols: Optional[_SelectIterable] = None,
+ ) -> List[_ColumnsPlusNames]:
+ """Generate column names as rendered in a SELECT statement by
+ the compiler.
+
+ This is distinct from the _column_naming_convention generator that's
+ intended for population of .c collections and similar, which has
+ different rules. the collection returned here calls upon the
+ _column_naming_convention as well.
+
+ """
+
+ if cols is None:
+ cols = self._all_selected_columns
+
+ key_naming_convention = SelectState._column_naming_convention(
+ self._label_style
+ )
+
+ names = {}
+
+ result: List[_ColumnsPlusNames] = []
+ result_append = result.append
+
+ table_qualified = self._label_style is LABEL_STYLE_TABLENAME_PLUS_COL
+ label_style_none = self._label_style is LABEL_STYLE_NONE
+
+ # a counter used for "dedupe" labels, which have double underscores
+ # in them and are never referred by name; they only act
+ # as positional placeholders. they need only be unique within
+ # the single columns clause they're rendered within (required by
+ # some dbs such as mysql). So their anon identity is tracked against
+ # a fixed counter rather than hash() identity.
+ dedupe_hash = 1
+
+ for c in cols:
+ repeated = False
+
+ if not c._render_label_in_columns_clause:
+ effective_name = required_label_name = fallback_label_name = (
+ None
+ )
+ elif label_style_none:
+ if TYPE_CHECKING:
+ assert is_column_element(c)
+
+ effective_name = required_label_name = None
+ fallback_label_name = c._non_anon_label or c._anon_name_label
+ else:
+ if TYPE_CHECKING:
+ assert is_column_element(c)
+
+ if table_qualified:
+ required_label_name = effective_name = (
+ fallback_label_name
+ ) = c._tq_label
+ else:
+ effective_name = fallback_label_name = c._non_anon_label
+ required_label_name = None
+
+ if effective_name is None:
+ # it seems like this could be _proxy_key and we would
+ # not need _expression_label but it isn't
+ # giving us a clue when to use anon_label instead
+ expr_label = c._expression_label
+ if expr_label is None:
+ repeated = c._anon_name_label in names
+ names[c._anon_name_label] = c
+ effective_name = required_label_name = None
+
+ if repeated:
+ # here, "required_label_name" is sent as
+ # "None" and "fallback_label_name" is sent.
+ if table_qualified:
+ fallback_label_name = (
+ c._dedupe_anon_tq_label_idx(dedupe_hash)
+ )
+ dedupe_hash += 1
+ else:
+ fallback_label_name = c._dedupe_anon_label_idx(
+ dedupe_hash
+ )
+ dedupe_hash += 1
+ else:
+ fallback_label_name = c._anon_name_label
+ else:
+ required_label_name = effective_name = (
+ fallback_label_name
+ ) = expr_label
+
+ if effective_name is not None:
+ if TYPE_CHECKING:
+ assert is_column_element(c)
+
+ if effective_name in names:
+ # when looking to see if names[name] is the same column as
+ # c, use hash(), so that an annotated version of the column
+ # is seen as the same as the non-annotated
+ if hash(names[effective_name]) != hash(c):
+ # different column under the same name. apply
+ # disambiguating label
+ if table_qualified:
+ required_label_name = fallback_label_name = (
+ c._anon_tq_label
+ )
+ else:
+ required_label_name = fallback_label_name = (
+ c._anon_name_label
+ )
+
+ if anon_for_dupe_key and required_label_name in names:
+ # here, c._anon_tq_label is definitely unique to
+ # that column identity (or annotated version), so
+ # this should always be true.
+ # this is also an infrequent codepath because
+ # you need two levels of duplication to be here
+ assert hash(names[required_label_name]) == hash(c)
+
+ # the column under the disambiguating label is
+ # already present. apply the "dedupe" label to
+ # subsequent occurrences of the column so that the
+ # original stays non-ambiguous
+ if table_qualified:
+ required_label_name = fallback_label_name = (
+ c._dedupe_anon_tq_label_idx(dedupe_hash)
+ )
+ dedupe_hash += 1
+ else:
+ required_label_name = fallback_label_name = (
+ c._dedupe_anon_label_idx(dedupe_hash)
+ )
+ dedupe_hash += 1
+ repeated = True
+ else:
+ names[required_label_name] = c
+ elif anon_for_dupe_key:
+ # same column under the same name. apply the "dedupe"
+ # label so that the original stays non-ambiguous
+ if table_qualified:
+ required_label_name = fallback_label_name = (
+ c._dedupe_anon_tq_label_idx(dedupe_hash)
+ )
+ dedupe_hash += 1
+ else:
+ required_label_name = fallback_label_name = (
+ c._dedupe_anon_label_idx(dedupe_hash)
+ )
+ dedupe_hash += 1
+ repeated = True
+ else:
+ names[effective_name] = c
+
+ result_append(
+ _ColumnsPlusNames(
+ required_label_name,
+ key_naming_convention(c),
+ fallback_label_name,
+ c,
+ repeated,
+ )
+ )
+
+ return result
+
+
+class HasCTE(roles.HasCTERole, SelectsRows):
+ """Mixin that declares a class to include CTE support."""
+
+ _has_ctes_traverse_internals: _TraverseInternalsType = [
+ ("_independent_ctes", InternalTraversal.dp_clauseelement_list),
+ ("_independent_ctes_opts", InternalTraversal.dp_plain_obj),
+ ]
+
+ _independent_ctes: Tuple[CTE, ...] = ()
+ _independent_ctes_opts: Tuple[_CTEOpts, ...] = ()
+
+ @_generative
+ def add_cte(self, *ctes: CTE, nest_here: bool = False) -> Self:
+ r"""Add one or more :class:`_sql.CTE` constructs to this statement.
+
+ This method will associate the given :class:`_sql.CTE` constructs with
+ the parent statement such that they will each be unconditionally
+ rendered in the WITH clause of the final statement, even if not
+ referenced elsewhere within the statement or any sub-selects.
+
+ The optional :paramref:`.HasCTE.add_cte.nest_here` parameter when set
+ to True will have the effect that each given :class:`_sql.CTE` will
+ render in a WITH clause rendered directly along with this statement,
+ rather than being moved to the top of the ultimate rendered statement,
+ even if this statement is rendered as a subquery within a larger
+ statement.
+
+ This method has two general uses. One is to embed CTE statements that
+ serve some purpose without being referenced explicitly, such as the use
+ case of embedding a DML statement such as an INSERT or UPDATE as a CTE
+ inline with a primary statement that may draw from its results
+ indirectly. The other is to provide control over the exact placement
+ of a particular series of CTE constructs that should remain rendered
+ directly in terms of a particular statement that may be nested in a
+ larger statement.
+
+ E.g.::
+
+ from sqlalchemy import table, column, select
+ t = table('t', column('c1'), column('c2'))
+
+ ins = t.insert().values({"c1": "x", "c2": "y"}).cte()
+
+ stmt = select(t).add_cte(ins)
+
+ Would render::
+
+ WITH anon_1 AS
+ (INSERT INTO t (c1, c2) VALUES (:param_1, :param_2))
+ SELECT t.c1, t.c2
+ FROM t
+
+ Above, the "anon_1" CTE is not referenced in the SELECT
+ statement, however still accomplishes the task of running an INSERT
+ statement.
+
+ Similarly in a DML-related context, using the PostgreSQL
+ :class:`_postgresql.Insert` construct to generate an "upsert"::
+
+ from sqlalchemy import table, column
+ from sqlalchemy.dialects.postgresql import insert
+
+ t = table("t", column("c1"), column("c2"))
+
+ delete_statement_cte = (
+ t.delete().where(t.c.c1 < 1).cte("deletions")
+ )
+
+ insert_stmt = insert(t).values({"c1": 1, "c2": 2})
+ update_statement = insert_stmt.on_conflict_do_update(
+ index_elements=[t.c.c1],
+ set_={
+ "c1": insert_stmt.excluded.c1,
+ "c2": insert_stmt.excluded.c2,
+ },
+ ).add_cte(delete_statement_cte)
+
+ print(update_statement)
+
+ The above statement renders as::
+
+ WITH deletions AS
+ (DELETE FROM t WHERE t.c1 < %(c1_1)s)
+ INSERT INTO t (c1, c2) VALUES (%(c1)s, %(c2)s)
+ ON CONFLICT (c1) DO UPDATE SET c1 = excluded.c1, c2 = excluded.c2
+
+ .. versionadded:: 1.4.21
+
+ :param \*ctes: zero or more :class:`.CTE` constructs.
+
+ .. versionchanged:: 2.0 Multiple CTE instances are accepted
+
+ :param nest_here: if True, the given CTE or CTEs will be rendered
+ as though they specified the :paramref:`.HasCTE.cte.nesting` flag
+ to ``True`` when they were added to this :class:`.HasCTE`.
+ Assuming the given CTEs are not referenced in an outer-enclosing
+ statement as well, the CTEs given should render at the level of
+ this statement when this flag is given.
+
+ .. versionadded:: 2.0
+
+ .. seealso::
+
+ :paramref:`.HasCTE.cte.nesting`
+
+
+ """
+ opt = _CTEOpts(
+ nest_here,
+ )
+ for cte in ctes:
+ cte = coercions.expect(roles.IsCTERole, cte)
+ self._independent_ctes += (cte,)
+ self._independent_ctes_opts += (opt,)
+ return self
+
+ def cte(
+ self,
+ name: Optional[str] = None,
+ recursive: bool = False,
+ nesting: bool = False,
+ ) -> CTE:
+ r"""Return a new :class:`_expression.CTE`,
+ or Common Table Expression instance.
+
+ Common table expressions are a SQL standard whereby SELECT
+ statements can draw upon secondary statements specified along
+ with the primary statement, using a clause called "WITH".
+ Special semantics regarding UNION can also be employed to
+ allow "recursive" queries, where a SELECT statement can draw
+ upon the set of rows that have previously been selected.
+
+ CTEs can also be applied to DML constructs UPDATE, INSERT
+ and DELETE on some databases, both as a source of CTE rows
+ when combined with RETURNING, as well as a consumer of
+ CTE rows.
+
+ SQLAlchemy detects :class:`_expression.CTE` objects, which are treated
+ similarly to :class:`_expression.Alias` objects, as special elements
+ to be delivered to the FROM clause of the statement as well
+ as to a WITH clause at the top of the statement.
+
+ For special prefixes such as PostgreSQL "MATERIALIZED" and
+ "NOT MATERIALIZED", the :meth:`_expression.CTE.prefix_with`
+ method may be
+ used to establish these.
+
+ .. versionchanged:: 1.3.13 Added support for prefixes.
+ In particular - MATERIALIZED and NOT MATERIALIZED.
+
+ :param name: name given to the common table expression. Like
+ :meth:`_expression.FromClause.alias`, the name can be left as
+ ``None`` in which case an anonymous symbol will be used at query
+ compile time.
+ :param recursive: if ``True``, will render ``WITH RECURSIVE``.
+ A recursive common table expression is intended to be used in
+ conjunction with UNION ALL in order to derive rows
+ from those already selected.
+ :param nesting: if ``True``, will render the CTE locally to the
+ statement in which it is referenced. For more complex scenarios,
+ the :meth:`.HasCTE.add_cte` method using the
+ :paramref:`.HasCTE.add_cte.nest_here`
+ parameter may also be used to more carefully
+ control the exact placement of a particular CTE.
+
+ .. versionadded:: 1.4.24
+
+ .. seealso::
+
+ :meth:`.HasCTE.add_cte`
+
+ The following examples include two from PostgreSQL's documentation at
+ https://www.postgresql.org/docs/current/static/queries-with.html,
+ as well as additional examples.
+
+ Example 1, non recursive::
+
+ from sqlalchemy import (Table, Column, String, Integer,
+ MetaData, select, func)
+
+ metadata = MetaData()
+
+ orders = Table('orders', metadata,
+ Column('region', String),
+ Column('amount', Integer),
+ Column('product', String),
+ Column('quantity', Integer)
+ )
+
+ regional_sales = select(
+ orders.c.region,
+ func.sum(orders.c.amount).label('total_sales')
+ ).group_by(orders.c.region).cte("regional_sales")
+
+
+ top_regions = select(regional_sales.c.region).\
+ where(
+ regional_sales.c.total_sales >
+ select(
+ func.sum(regional_sales.c.total_sales) / 10
+ )
+ ).cte("top_regions")
+
+ statement = select(
+ orders.c.region,
+ orders.c.product,
+ func.sum(orders.c.quantity).label("product_units"),
+ func.sum(orders.c.amount).label("product_sales")
+ ).where(orders.c.region.in_(
+ select(top_regions.c.region)
+ )).group_by(orders.c.region, orders.c.product)
+
+ result = conn.execute(statement).fetchall()
+
+ Example 2, WITH RECURSIVE::
+
+ from sqlalchemy import (Table, Column, String, Integer,
+ MetaData, select, func)
+
+ metadata = MetaData()
+
+ parts = Table('parts', metadata,
+ Column('part', String),
+ Column('sub_part', String),
+ Column('quantity', Integer),
+ )
+
+ included_parts = select(\
+ parts.c.sub_part, parts.c.part, parts.c.quantity\
+ ).\
+ where(parts.c.part=='our part').\
+ cte(recursive=True)
+
+
+ incl_alias = included_parts.alias()
+ parts_alias = parts.alias()
+ included_parts = included_parts.union_all(
+ select(
+ parts_alias.c.sub_part,
+ parts_alias.c.part,
+ parts_alias.c.quantity
+ ).\
+ where(parts_alias.c.part==incl_alias.c.sub_part)
+ )
+
+ statement = select(
+ included_parts.c.sub_part,
+ func.sum(included_parts.c.quantity).
+ label('total_quantity')
+ ).\
+ group_by(included_parts.c.sub_part)
+
+ result = conn.execute(statement).fetchall()
+
+ Example 3, an upsert using UPDATE and INSERT with CTEs::
+
+ from datetime import date
+ from sqlalchemy import (MetaData, Table, Column, Integer,
+ Date, select, literal, and_, exists)
+
+ metadata = MetaData()
+
+ visitors = Table('visitors', metadata,
+ Column('product_id', Integer, primary_key=True),
+ Column('date', Date, primary_key=True),
+ Column('count', Integer),
+ )
+
+ # add 5 visitors for the product_id == 1
+ product_id = 1
+ day = date.today()
+ count = 5
+
+ update_cte = (
+ visitors.update()
+ .where(and_(visitors.c.product_id == product_id,
+ visitors.c.date == day))
+ .values(count=visitors.c.count + count)
+ .returning(literal(1))
+ .cte('update_cte')
+ )
+
+ upsert = visitors.insert().from_select(
+ [visitors.c.product_id, visitors.c.date, visitors.c.count],
+ select(literal(product_id), literal(day), literal(count))
+ .where(~exists(update_cte.select()))
+ )
+
+ connection.execute(upsert)
+
+ Example 4, Nesting CTE (SQLAlchemy 1.4.24 and above)::
+
+ value_a = select(
+ literal("root").label("n")
+ ).cte("value_a")
+
+ # A nested CTE with the same name as the root one
+ value_a_nested = select(
+ literal("nesting").label("n")
+ ).cte("value_a", nesting=True)
+
+ # Nesting CTEs takes ascendency locally
+ # over the CTEs at a higher level
+ value_b = select(value_a_nested.c.n).cte("value_b")
+
+ value_ab = select(value_a.c.n.label("a"), value_b.c.n.label("b"))
+
+ The above query will render the second CTE nested inside the first,
+ shown with inline parameters below as::
+
+ WITH
+ value_a AS
+ (SELECT 'root' AS n),
+ value_b AS
+ (WITH value_a AS
+ (SELECT 'nesting' AS n)
+ SELECT value_a.n AS n FROM value_a)
+ SELECT value_a.n AS a, value_b.n AS b
+ FROM value_a, value_b
+
+ The same CTE can be set up using the :meth:`.HasCTE.add_cte` method
+ as follows (SQLAlchemy 2.0 and above)::
+
+ value_a = select(
+ literal("root").label("n")
+ ).cte("value_a")
+
+ # A nested CTE with the same name as the root one
+ value_a_nested = select(
+ literal("nesting").label("n")
+ ).cte("value_a")
+
+ # Nesting CTEs takes ascendency locally
+ # over the CTEs at a higher level
+ value_b = (
+ select(value_a_nested.c.n).
+ add_cte(value_a_nested, nest_here=True).
+ cte("value_b")
+ )
+
+ value_ab = select(value_a.c.n.label("a"), value_b.c.n.label("b"))
+
+ Example 5, Non-Linear CTE (SQLAlchemy 1.4.28 and above)::
+
+ edge = Table(
+ "edge",
+ metadata,
+ Column("id", Integer, primary_key=True),
+ Column("left", Integer),
+ Column("right", Integer),
+ )
+
+ root_node = select(literal(1).label("node")).cte(
+ "nodes", recursive=True
+ )
+
+ left_edge = select(edge.c.left).join(
+ root_node, edge.c.right == root_node.c.node
+ )
+ right_edge = select(edge.c.right).join(
+ root_node, edge.c.left == root_node.c.node
+ )
+
+ subgraph_cte = root_node.union(left_edge, right_edge)
+
+ subgraph = select(subgraph_cte)
+
+ The above query will render 2 UNIONs inside the recursive CTE::
+
+ WITH RECURSIVE nodes(node) AS (
+ SELECT 1 AS node
+ UNION
+ SELECT edge."left" AS "left"
+ FROM edge JOIN nodes ON edge."right" = nodes.node
+ UNION
+ SELECT edge."right" AS "right"
+ FROM edge JOIN nodes ON edge."left" = nodes.node
+ )
+ SELECT nodes.node FROM nodes
+
+ .. seealso::
+
+ :meth:`_orm.Query.cte` - ORM version of
+ :meth:`_expression.HasCTE.cte`.
+
+ """
+ return CTE._construct(
+ self, name=name, recursive=recursive, nesting=nesting
+ )
+
+
+class Subquery(AliasedReturnsRows):
+ """Represent a subquery of a SELECT.
+
+ A :class:`.Subquery` is created by invoking the
+ :meth:`_expression.SelectBase.subquery` method, or for convenience the
+ :meth:`_expression.SelectBase.alias` method, on any
+ :class:`_expression.SelectBase` subclass
+ which includes :class:`_expression.Select`,
+ :class:`_expression.CompoundSelect`, and
+ :class:`_expression.TextualSelect`. As rendered in a FROM clause,
+ it represents the
+ body of the SELECT statement inside of parenthesis, followed by the usual
+ "AS <somename>" that defines all "alias" objects.
+
+ The :class:`.Subquery` object is very similar to the
+ :class:`_expression.Alias`
+ object and can be used in an equivalent way. The difference between
+ :class:`_expression.Alias` and :class:`.Subquery` is that
+ :class:`_expression.Alias` always
+ contains a :class:`_expression.FromClause` object whereas
+ :class:`.Subquery`
+ always contains a :class:`_expression.SelectBase` object.
+
+ .. versionadded:: 1.4 The :class:`.Subquery` class was added which now
+ serves the purpose of providing an aliased version of a SELECT
+ statement.
+
+ """
+
+ __visit_name__ = "subquery"
+
+ _is_subquery = True
+
+ inherit_cache = True
+
+ element: SelectBase
+
+ @classmethod
+ def _factory(
+ cls, selectable: SelectBase, name: Optional[str] = None
+ ) -> Subquery:
+ """Return a :class:`.Subquery` object."""
+
+ return coercions.expect(
+ roles.SelectStatementRole, selectable
+ ).subquery(name=name)
+
+ @util.deprecated(
+ "1.4",
+ "The :meth:`.Subquery.as_scalar` method, which was previously "
+ "``Alias.as_scalar()`` prior to version 1.4, is deprecated and "
+ "will be removed in a future release; Please use the "
+ ":meth:`_expression.Select.scalar_subquery` method of the "
+ ":func:`_expression.select` "
+ "construct before constructing a subquery object, or with the ORM "
+ "use the :meth:`_query.Query.scalar_subquery` method.",
+ )
+ def as_scalar(self) -> ScalarSelect[Any]:
+ return self.element.set_label_style(LABEL_STYLE_NONE).scalar_subquery()
+
+
+class FromGrouping(GroupedElement, FromClause):
+ """Represent a grouping of a FROM clause"""
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("element", InternalTraversal.dp_clauseelement)
+ ]
+
+ element: FromClause
+
+ def __init__(self, element: FromClause):
+ self.element = coercions.expect(roles.FromClauseRole, element)
+
+ def _init_collections(self) -> None:
+ pass
+
+ @util.ro_non_memoized_property
+ def columns(
+ self,
+ ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+ return self.element.columns
+
+ @util.ro_non_memoized_property
+ def c(self) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+ return self.element.columns
+
+ @property
+ def primary_key(self) -> Iterable[NamedColumn[Any]]:
+ return self.element.primary_key
+
+ @property
+ def foreign_keys(self) -> Iterable[ForeignKey]:
+ return self.element.foreign_keys
+
+ def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
+ return self.element.is_derived_from(fromclause)
+
+ def alias(
+ self, name: Optional[str] = None, flat: bool = False
+ ) -> NamedFromGrouping:
+ return NamedFromGrouping(self.element.alias(name=name, flat=flat))
+
+ def _anonymous_fromclause(self, **kw: Any) -> FromGrouping:
+ return FromGrouping(self.element._anonymous_fromclause(**kw))
+
+ @util.ro_non_memoized_property
+ def _hide_froms(self) -> Iterable[FromClause]:
+ return self.element._hide_froms
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return self.element._from_objects
+
+ def __getstate__(self) -> Dict[str, FromClause]:
+ return {"element": self.element}
+
+ def __setstate__(self, state: Dict[str, FromClause]) -> None:
+ self.element = state["element"]
+
+
+class NamedFromGrouping(FromGrouping, NamedFromClause):
+ """represent a grouping of a named FROM clause
+
+ .. versionadded:: 2.0
+
+ """
+
+ inherit_cache = True
+
+
+class TableClause(roles.DMLTableRole, Immutable, NamedFromClause):
+ """Represents a minimal "table" construct.
+
+ This is a lightweight table object that has only a name, a
+ collection of columns, which are typically produced
+ by the :func:`_expression.column` function, and a schema::
+
+ from sqlalchemy import table, column
+
+ user = table("user",
+ column("id"),
+ column("name"),
+ column("description"),
+ )
+
+ The :class:`_expression.TableClause` construct serves as the base for
+ the more commonly used :class:`_schema.Table` object, providing
+ the usual set of :class:`_expression.FromClause` services including
+ the ``.c.`` collection and statement generation methods.
+
+ It does **not** provide all the additional schema-level services
+ of :class:`_schema.Table`, including constraints, references to other
+ tables, or support for :class:`_schema.MetaData`-level services.
+ It's useful
+ on its own as an ad-hoc construct used to generate quick SQL
+ statements when a more fully fledged :class:`_schema.Table`
+ is not on hand.
+
+ """
+
+ __visit_name__ = "table"
+
+ _traverse_internals: _TraverseInternalsType = [
+ (
+ "columns",
+ InternalTraversal.dp_fromclause_canonical_column_collection,
+ ),
+ ("name", InternalTraversal.dp_string),
+ ("schema", InternalTraversal.dp_string),
+ ]
+
+ _is_table = True
+
+ fullname: str
+
+ implicit_returning = False
+ """:class:`_expression.TableClause`
+ doesn't support having a primary key or column
+ -level defaults, so implicit returning doesn't apply."""
+
+ @util.ro_memoized_property
+ def _autoincrement_column(self) -> Optional[ColumnClause[Any]]:
+ """No PK or default support so no autoincrement column."""
+ return None
+
+ def __init__(self, name: str, *columns: ColumnClause[Any], **kw: Any):
+ super().__init__()
+ self.name = name
+ self._columns = DedupeColumnCollection()
+ self.primary_key = ColumnSet() # type: ignore
+ self.foreign_keys = set() # type: ignore
+ for c in columns:
+ self.append_column(c)
+
+ schema = kw.pop("schema", None)
+ if schema is not None:
+ self.schema = schema
+ if self.schema is not None:
+ self.fullname = "%s.%s" % (self.schema, self.name)
+ else:
+ self.fullname = self.name
+ if kw:
+ raise exc.ArgumentError("Unsupported argument(s): %s" % list(kw))
+
+ if TYPE_CHECKING:
+
+ @util.ro_non_memoized_property
+ def columns(
+ self,
+ ) -> ReadOnlyColumnCollection[str, ColumnClause[Any]]: ...
+
+ @util.ro_non_memoized_property
+ def c(self) -> ReadOnlyColumnCollection[str, ColumnClause[Any]]: ...
+
+ def __str__(self) -> str:
+ if self.schema is not None:
+ return self.schema + "." + self.name
+ else:
+ return self.name
+
+ def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+ pass
+
+ def _init_collections(self) -> None:
+ pass
+
+ @util.ro_memoized_property
+ def description(self) -> str:
+ return self.name
+
+ def append_column(self, c: ColumnClause[Any]) -> None:
+ existing = c.table
+ if existing is not None and existing is not self:
+ raise exc.ArgumentError(
+ "column object '%s' already assigned to table '%s'"
+ % (c.key, existing)
+ )
+
+ self._columns.add(c)
+ c.table = self
+
+ @util.preload_module("sqlalchemy.sql.dml")
+ def insert(self) -> util.preloaded.sql_dml.Insert:
+ """Generate an :class:`_sql.Insert` construct against this
+ :class:`_expression.TableClause`.
+
+ E.g.::
+
+ table.insert().values(name='foo')
+
+ See :func:`_expression.insert` for argument and usage information.
+
+ """
+
+ return util.preloaded.sql_dml.Insert(self)
+
+ @util.preload_module("sqlalchemy.sql.dml")
+ def update(self) -> Update:
+ """Generate an :func:`_expression.update` construct against this
+ :class:`_expression.TableClause`.
+
+ E.g.::
+
+ table.update().where(table.c.id==7).values(name='foo')
+
+ See :func:`_expression.update` for argument and usage information.
+
+ """
+ return util.preloaded.sql_dml.Update(
+ self,
+ )
+
+ @util.preload_module("sqlalchemy.sql.dml")
+ def delete(self) -> Delete:
+ """Generate a :func:`_expression.delete` construct against this
+ :class:`_expression.TableClause`.
+
+ E.g.::
+
+ table.delete().where(table.c.id==7)
+
+ See :func:`_expression.delete` for argument and usage information.
+
+ """
+ return util.preloaded.sql_dml.Delete(self)
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return [self]
+
+
+ForUpdateParameter = Union["ForUpdateArg", None, bool, Dict[str, Any]]
+
+
+class ForUpdateArg(ClauseElement):
+ _traverse_internals: _TraverseInternalsType = [
+ ("of", InternalTraversal.dp_clauseelement_list),
+ ("nowait", InternalTraversal.dp_boolean),
+ ("read", InternalTraversal.dp_boolean),
+ ("skip_locked", InternalTraversal.dp_boolean),
+ ]
+
+ of: Optional[Sequence[ClauseElement]]
+ nowait: bool
+ read: bool
+ skip_locked: bool
+
+ @classmethod
+ def _from_argument(
+ cls, with_for_update: ForUpdateParameter
+ ) -> Optional[ForUpdateArg]:
+ if isinstance(with_for_update, ForUpdateArg):
+ return with_for_update
+ elif with_for_update in (None, False):
+ return None
+ elif with_for_update is True:
+ return ForUpdateArg()
+ else:
+ return ForUpdateArg(**cast("Dict[str, Any]", with_for_update))
+
+ def __eq__(self, other: Any) -> bool:
+ return (
+ isinstance(other, ForUpdateArg)
+ and other.nowait == self.nowait
+ and other.read == self.read
+ and other.skip_locked == self.skip_locked
+ and other.key_share == self.key_share
+ and other.of is self.of
+ )
+
+ def __ne__(self, other: Any) -> bool:
+ return not self.__eq__(other)
+
+ def __hash__(self) -> int:
+ return id(self)
+
+ def __init__(
+ self,
+ *,
+ nowait: bool = False,
+ read: bool = False,
+ of: Optional[_ForUpdateOfArgument] = None,
+ skip_locked: bool = False,
+ key_share: bool = False,
+ ):
+ """Represents arguments specified to
+ :meth:`_expression.Select.for_update`.
+
+ """
+
+ self.nowait = nowait
+ self.read = read
+ self.skip_locked = skip_locked
+ self.key_share = key_share
+ if of is not None:
+ self.of = [
+ coercions.expect(roles.ColumnsClauseRole, elem)
+ for elem in util.to_list(of)
+ ]
+ else:
+ self.of = None
+
+
+class Values(roles.InElementRole, Generative, LateralFromClause):
+ """Represent a ``VALUES`` construct that can be used as a FROM element
+ in a statement.
+
+ The :class:`_expression.Values` object is created from the
+ :func:`_expression.values` function.
+
+ .. versionadded:: 1.4
+
+ """
+
+ __visit_name__ = "values"
+
+ _data: Tuple[Sequence[Tuple[Any, ...]], ...] = ()
+
+ _unnamed: bool
+ _traverse_internals: _TraverseInternalsType = [
+ ("_column_args", InternalTraversal.dp_clauseelement_list),
+ ("_data", InternalTraversal.dp_dml_multi_values),
+ ("name", InternalTraversal.dp_string),
+ ("literal_binds", InternalTraversal.dp_boolean),
+ ]
+
+ def __init__(
+ self,
+ *columns: ColumnClause[Any],
+ name: Optional[str] = None,
+ literal_binds: bool = False,
+ ):
+ super().__init__()
+ self._column_args = columns
+
+ if name is None:
+ self._unnamed = True
+ self.name = _anonymous_label.safe_construct(id(self), "anon")
+ else:
+ self._unnamed = False
+ self.name = name
+ self.literal_binds = literal_binds
+ self.named_with_column = not self._unnamed
+
+ @property
+ def _column_types(self) -> List[TypeEngine[Any]]:
+ return [col.type for col in self._column_args]
+
+ @_generative
+ def alias(self, name: Optional[str] = None, flat: bool = False) -> Self:
+ """Return a new :class:`_expression.Values`
+ construct that is a copy of this
+ one with the given name.
+
+ This method is a VALUES-specific specialization of the
+ :meth:`_expression.FromClause.alias` method.
+
+ .. seealso::
+
+ :ref:`tutorial_using_aliases`
+
+ :func:`_expression.alias`
+
+ """
+ non_none_name: str
+
+ if name is None:
+ non_none_name = _anonymous_label.safe_construct(id(self), "anon")
+ else:
+ non_none_name = name
+
+ self.name = non_none_name
+ self.named_with_column = True
+ self._unnamed = False
+ return self
+
+ @_generative
+ def lateral(self, name: Optional[str] = None) -> LateralFromClause:
+ """Return a new :class:`_expression.Values` with the lateral flag set,
+ so that
+ it renders as LATERAL.
+
+ .. seealso::
+
+ :func:`_expression.lateral`
+
+ """
+ non_none_name: str
+
+ if name is None:
+ non_none_name = self.name
+ else:
+ non_none_name = name
+
+ self._is_lateral = True
+ self.name = non_none_name
+ self._unnamed = False
+ return self
+
+ @_generative
+ def data(self, values: Sequence[Tuple[Any, ...]]) -> Self:
+ """Return a new :class:`_expression.Values` construct,
+ adding the given data to the data list.
+
+ E.g.::
+
+ my_values = my_values.data([(1, 'value 1'), (2, 'value2')])
+
+ :param values: a sequence (i.e. list) of tuples that map to the
+ column expressions given in the :class:`_expression.Values`
+ constructor.
+
+ """
+
+ self._data += (values,)
+ return self
+
+ def scalar_values(self) -> ScalarValues:
+ """Returns a scalar ``VALUES`` construct that can be used as a
+ COLUMN element in a statement.
+
+ .. versionadded:: 2.0.0b4
+
+ """
+ return ScalarValues(self._column_args, self._data, self.literal_binds)
+
+ def _populate_column_collection(self) -> None:
+ for c in self._column_args:
+ if c.table is not None and c.table is not self:
+ _, c = c._make_proxy(self)
+ else:
+ # if the column was used in other contexts, ensure
+ # no memoizations of other FROM clauses.
+ # see test_values.py -> test_auto_proxy_select_direct_col
+ c._reset_memoizations()
+ self._columns.add(c)
+ c.table = self
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return [self]
+
+
+class ScalarValues(roles.InElementRole, GroupedElement, ColumnElement[Any]):
+ """Represent a scalar ``VALUES`` construct that can be used as a
+ COLUMN element in a statement.
+
+ The :class:`_expression.ScalarValues` object is created from the
+ :meth:`_expression.Values.scalar_values` method. It's also
+ automatically generated when a :class:`_expression.Values` is used in
+ an ``IN`` or ``NOT IN`` condition.
+
+ .. versionadded:: 2.0.0b4
+
+ """
+
+ __visit_name__ = "scalar_values"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("_column_args", InternalTraversal.dp_clauseelement_list),
+ ("_data", InternalTraversal.dp_dml_multi_values),
+ ("literal_binds", InternalTraversal.dp_boolean),
+ ]
+
+ def __init__(
+ self,
+ columns: Sequence[ColumnClause[Any]],
+ data: Tuple[Sequence[Tuple[Any, ...]], ...],
+ literal_binds: bool,
+ ):
+ super().__init__()
+ self._column_args = columns
+ self._data = data
+ self.literal_binds = literal_binds
+
+ @property
+ def _column_types(self) -> List[TypeEngine[Any]]:
+ return [col.type for col in self._column_args]
+
+ def __clause_element__(self) -> ScalarValues:
+ return self
+
+
+class SelectBase(
+ roles.SelectStatementRole,
+ roles.DMLSelectRole,
+ roles.CompoundElementRole,
+ roles.InElementRole,
+ HasCTE,
+ SupportsCloneAnnotations,
+ Selectable,
+):
+ """Base class for SELECT statements.
+
+
+ This includes :class:`_expression.Select`,
+ :class:`_expression.CompoundSelect` and
+ :class:`_expression.TextualSelect`.
+
+
+ """
+
+ _is_select_base = True
+ is_select = True
+
+ _label_style: SelectLabelStyle = LABEL_STYLE_NONE
+
+ def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+ self._reset_memoizations()
+
+ @util.ro_non_memoized_property
+ def selected_columns(
+ self,
+ ) -> ColumnCollection[str, ColumnElement[Any]]:
+ """A :class:`_expression.ColumnCollection`
+ representing the columns that
+ this SELECT statement or similar construct returns in its result set.
+
+ This collection differs from the :attr:`_expression.FromClause.columns`
+ collection of a :class:`_expression.FromClause` in that the columns
+ within this collection cannot be directly nested inside another SELECT
+ statement; a subquery must be applied first which provides for the
+ necessary parenthesization required by SQL.
+
+ .. note::
+
+ The :attr:`_sql.SelectBase.selected_columns` collection does not
+ include expressions established in the columns clause using the
+ :func:`_sql.text` construct; these are silently omitted from the
+ collection. To use plain textual column expressions inside of a
+ :class:`_sql.Select` construct, use the :func:`_sql.literal_column`
+ construct.
+
+ .. seealso::
+
+ :attr:`_sql.Select.selected_columns`
+
+ .. versionadded:: 1.4
+
+ """
+ raise NotImplementedError()
+
+ def _generate_fromclause_column_proxies(
+ self,
+ subquery: FromClause,
+ *,
+ proxy_compound_columns: Optional[
+ Iterable[Sequence[ColumnElement[Any]]]
+ ] = None,
+ ) -> None:
+ raise NotImplementedError()
+
+ @util.ro_non_memoized_property
+ def _all_selected_columns(self) -> _SelectIterable:
+ """A sequence of expressions that correspond to what is rendered
+ in the columns clause, including :class:`_sql.TextClause`
+ constructs.
+
+ .. versionadded:: 1.4.12
+
+ .. seealso::
+
+ :attr:`_sql.SelectBase.exported_columns`
+
+ """
+ raise NotImplementedError()
+
+ @property
+ def exported_columns(
+ self,
+ ) -> ReadOnlyColumnCollection[str, ColumnElement[Any]]:
+ """A :class:`_expression.ColumnCollection`
+ that represents the "exported"
+ columns of this :class:`_expression.Selectable`, not including
+ :class:`_sql.TextClause` constructs.
+
+ The "exported" columns for a :class:`_expression.SelectBase`
+ object are synonymous
+ with the :attr:`_expression.SelectBase.selected_columns` collection.
+
+ .. versionadded:: 1.4
+
+ .. seealso::
+
+ :attr:`_expression.Select.exported_columns`
+
+ :attr:`_expression.Selectable.exported_columns`
+
+ :attr:`_expression.FromClause.exported_columns`
+
+
+ """
+ return self.selected_columns.as_readonly()
+
+ @property
+ @util.deprecated(
+ "1.4",
+ "The :attr:`_expression.SelectBase.c` and "
+ ":attr:`_expression.SelectBase.columns` attributes "
+ "are deprecated and will be removed in a future release; these "
+ "attributes implicitly create a subquery that should be explicit. "
+ "Please call :meth:`_expression.SelectBase.subquery` "
+ "first in order to create "
+ "a subquery, which then contains this attribute. To access the "
+ "columns that this SELECT object SELECTs "
+ "from, use the :attr:`_expression.SelectBase.selected_columns` "
+ "attribute.",
+ )
+ def c(self) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+ return self._implicit_subquery.columns
+
+ @property
+ def columns(
+ self,
+ ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+ return self.c
+
+ def get_label_style(self) -> SelectLabelStyle:
+ """
+ Retrieve the current label style.
+
+ Implemented by subclasses.
+
+ """
+ raise NotImplementedError()
+
+ def set_label_style(self, style: SelectLabelStyle) -> Self:
+ """Return a new selectable with the specified label style.
+
+ Implemented by subclasses.
+
+ """
+
+ raise NotImplementedError()
+
+ @util.deprecated(
+ "1.4",
+ "The :meth:`_expression.SelectBase.select` method is deprecated "
+ "and will be removed in a future release; this method implicitly "
+ "creates a subquery that should be explicit. "
+ "Please call :meth:`_expression.SelectBase.subquery` "
+ "first in order to create "
+ "a subquery, which then can be selected.",
+ )
+ def select(self, *arg: Any, **kw: Any) -> Select[Any]:
+ return self._implicit_subquery.select(*arg, **kw)
+
+ @HasMemoized.memoized_attribute
+ def _implicit_subquery(self) -> Subquery:
+ return self.subquery()
+
+ def _scalar_type(self) -> TypeEngine[Any]:
+ raise NotImplementedError()
+
+ @util.deprecated(
+ "1.4",
+ "The :meth:`_expression.SelectBase.as_scalar` "
+ "method is deprecated and will be "
+ "removed in a future release. Please refer to "
+ ":meth:`_expression.SelectBase.scalar_subquery`.",
+ )
+ def as_scalar(self) -> ScalarSelect[Any]:
+ return self.scalar_subquery()
+
+ def exists(self) -> Exists:
+ """Return an :class:`_sql.Exists` representation of this selectable,
+ which can be used as a column expression.
+
+ The returned object is an instance of :class:`_sql.Exists`.
+
+ .. seealso::
+
+ :func:`_sql.exists`
+
+ :ref:`tutorial_exists` - in the :term:`2.0 style` tutorial.
+
+ .. versionadded:: 1.4
+
+ """
+ return Exists(self)
+
+ def scalar_subquery(self) -> ScalarSelect[Any]:
+ """Return a 'scalar' representation of this selectable, which can be
+ used as a column expression.
+
+ The returned object is an instance of :class:`_sql.ScalarSelect`.
+
+ Typically, a select statement which has only one column in its columns
+ clause is eligible to be used as a scalar expression. The scalar
+ subquery can then be used in the WHERE clause or columns clause of
+ an enclosing SELECT.
+
+ Note that the scalar subquery differentiates from the FROM-level
+ subquery that can be produced using the
+ :meth:`_expression.SelectBase.subquery`
+ method.
+
+ .. versionchanged: 1.4 - the ``.as_scalar()`` method was renamed to
+ :meth:`_expression.SelectBase.scalar_subquery`.
+
+ .. seealso::
+
+ :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
+
+ """
+ if self._label_style is not LABEL_STYLE_NONE:
+ self = self.set_label_style(LABEL_STYLE_NONE)
+
+ return ScalarSelect(self)
+
+ def label(self, name: Optional[str]) -> Label[Any]:
+ """Return a 'scalar' representation of this selectable, embedded as a
+ subquery with a label.
+
+ .. seealso::
+
+ :meth:`_expression.SelectBase.scalar_subquery`.
+
+ """
+ return self.scalar_subquery().label(name)
+
+ def lateral(self, name: Optional[str] = None) -> LateralFromClause:
+ """Return a LATERAL alias of this :class:`_expression.Selectable`.
+
+ The return value is the :class:`_expression.Lateral` construct also
+ provided by the top-level :func:`_expression.lateral` function.
+
+ .. seealso::
+
+ :ref:`tutorial_lateral_correlation` - overview of usage.
+
+ """
+ return Lateral._factory(self, name)
+
+ def subquery(self, name: Optional[str] = None) -> Subquery:
+ """Return a subquery of this :class:`_expression.SelectBase`.
+
+ A subquery is from a SQL perspective a parenthesized, named
+ construct that can be placed in the FROM clause of another
+ SELECT statement.
+
+ Given a SELECT statement such as::
+
+ stmt = select(table.c.id, table.c.name)
+
+ The above statement might look like::
+
+ SELECT table.id, table.name FROM table
+
+ The subquery form by itself renders the same way, however when
+ embedded into the FROM clause of another SELECT statement, it becomes
+ a named sub-element::
+
+ subq = stmt.subquery()
+ new_stmt = select(subq)
+
+ The above renders as::
+
+ SELECT anon_1.id, anon_1.name
+ FROM (SELECT table.id, table.name FROM table) AS anon_1
+
+ Historically, :meth:`_expression.SelectBase.subquery`
+ is equivalent to calling
+ the :meth:`_expression.FromClause.alias`
+ method on a FROM object; however,
+ as a :class:`_expression.SelectBase`
+ object is not directly FROM object,
+ the :meth:`_expression.SelectBase.subquery`
+ method provides clearer semantics.
+
+ .. versionadded:: 1.4
+
+ """
+
+ return Subquery._construct(
+ self._ensure_disambiguated_names(), name=name
+ )
+
+ def _ensure_disambiguated_names(self) -> Self:
+ """Ensure that the names generated by this selectbase will be
+ disambiguated in some way, if possible.
+
+ """
+
+ raise NotImplementedError()
+
+ def alias(
+ self, name: Optional[str] = None, flat: bool = False
+ ) -> Subquery:
+ """Return a named subquery against this
+ :class:`_expression.SelectBase`.
+
+ For a :class:`_expression.SelectBase` (as opposed to a
+ :class:`_expression.FromClause`),
+ this returns a :class:`.Subquery` object which behaves mostly the
+ same as the :class:`_expression.Alias` object that is used with a
+ :class:`_expression.FromClause`.
+
+ .. versionchanged:: 1.4 The :meth:`_expression.SelectBase.alias`
+ method is now
+ a synonym for the :meth:`_expression.SelectBase.subquery` method.
+
+ """
+ return self.subquery(name=name)
+
+
+_SB = TypeVar("_SB", bound=SelectBase)
+
+
+class SelectStatementGrouping(GroupedElement, SelectBase, Generic[_SB]):
+ """Represent a grouping of a :class:`_expression.SelectBase`.
+
+ This differs from :class:`.Subquery` in that we are still
+ an "inner" SELECT statement, this is strictly for grouping inside of
+ compound selects.
+
+ """
+
+ __visit_name__ = "select_statement_grouping"
+ _traverse_internals: _TraverseInternalsType = [
+ ("element", InternalTraversal.dp_clauseelement)
+ ]
+
+ _is_select_container = True
+
+ element: _SB
+
+ def __init__(self, element: _SB) -> None:
+ self.element = cast(
+ _SB, coercions.expect(roles.SelectStatementRole, element)
+ )
+
+ def _ensure_disambiguated_names(self) -> SelectStatementGrouping[_SB]:
+ new_element = self.element._ensure_disambiguated_names()
+ if new_element is not self.element:
+ return SelectStatementGrouping(new_element)
+ else:
+ return self
+
+ def get_label_style(self) -> SelectLabelStyle:
+ return self.element.get_label_style()
+
+ def set_label_style(
+ self, label_style: SelectLabelStyle
+ ) -> SelectStatementGrouping[_SB]:
+ return SelectStatementGrouping(
+ self.element.set_label_style(label_style)
+ )
+
+ @property
+ def select_statement(self) -> _SB:
+ return self.element
+
+ def self_group(self, against: Optional[OperatorType] = None) -> Self:
+ ...
+ return self
+
+ if TYPE_CHECKING:
+
+ def _ungroup(self) -> _SB: ...
+
+ # def _generate_columns_plus_names(
+ # self, anon_for_dupe_key: bool
+ # ) -> List[Tuple[str, str, str, ColumnElement[Any], bool]]:
+ # return self.element._generate_columns_plus_names(anon_for_dupe_key)
+
+ def _generate_fromclause_column_proxies(
+ self,
+ subquery: FromClause,
+ *,
+ proxy_compound_columns: Optional[
+ Iterable[Sequence[ColumnElement[Any]]]
+ ] = None,
+ ) -> None:
+ self.element._generate_fromclause_column_proxies(
+ subquery, proxy_compound_columns=proxy_compound_columns
+ )
+
+ @util.ro_non_memoized_property
+ def _all_selected_columns(self) -> _SelectIterable:
+ return self.element._all_selected_columns
+
+ @util.ro_non_memoized_property
+ def selected_columns(self) -> ColumnCollection[str, ColumnElement[Any]]:
+ """A :class:`_expression.ColumnCollection`
+ representing the columns that
+ the embedded SELECT statement returns in its result set, not including
+ :class:`_sql.TextClause` constructs.
+
+ .. versionadded:: 1.4
+
+ .. seealso::
+
+ :attr:`_sql.Select.selected_columns`
+
+ """
+ return self.element.selected_columns
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return self.element._from_objects
+
+
+class GenerativeSelect(SelectBase, Generative):
+ """Base class for SELECT statements where additional elements can be
+ added.
+
+ This serves as the base for :class:`_expression.Select` and
+ :class:`_expression.CompoundSelect`
+ where elements such as ORDER BY, GROUP BY can be added and column
+ rendering can be controlled. Compare to
+ :class:`_expression.TextualSelect`, which,
+ while it subclasses :class:`_expression.SelectBase`
+ and is also a SELECT construct,
+ represents a fixed textual string which cannot be altered at this level,
+ only wrapped as a subquery.
+
+ """
+
+ _order_by_clauses: Tuple[ColumnElement[Any], ...] = ()
+ _group_by_clauses: Tuple[ColumnElement[Any], ...] = ()
+ _limit_clause: Optional[ColumnElement[Any]] = None
+ _offset_clause: Optional[ColumnElement[Any]] = None
+ _fetch_clause: Optional[ColumnElement[Any]] = None
+ _fetch_clause_options: Optional[Dict[str, bool]] = None
+ _for_update_arg: Optional[ForUpdateArg] = None
+
+ def __init__(self, _label_style: SelectLabelStyle = LABEL_STYLE_DEFAULT):
+ self._label_style = _label_style
+
+ @_generative
+ def with_for_update(
+ self,
+ *,
+ nowait: bool = False,
+ read: bool = False,
+ of: Optional[_ForUpdateOfArgument] = None,
+ skip_locked: bool = False,
+ key_share: bool = False,
+ ) -> Self:
+ """Specify a ``FOR UPDATE`` clause for this
+ :class:`_expression.GenerativeSelect`.
+
+ E.g.::
+
+ stmt = select(table).with_for_update(nowait=True)
+
+ On a database like PostgreSQL or Oracle, the above would render a
+ statement like::
+
+ SELECT table.a, table.b FROM table FOR UPDATE NOWAIT
+
+ on other backends, the ``nowait`` option is ignored and instead
+ would produce::
+
+ SELECT table.a, table.b FROM table FOR UPDATE
+
+ When called with no arguments, the statement will render with
+ the suffix ``FOR UPDATE``. Additional arguments can then be
+ provided which allow for common database-specific
+ variants.
+
+ :param nowait: boolean; will render ``FOR UPDATE NOWAIT`` on Oracle
+ and PostgreSQL dialects.
+
+ :param read: boolean; will render ``LOCK IN SHARE MODE`` on MySQL,
+ ``FOR SHARE`` on PostgreSQL. On PostgreSQL, when combined with
+ ``nowait``, will render ``FOR SHARE NOWAIT``.
+
+ :param of: SQL expression or list of SQL expression elements,
+ (typically :class:`_schema.Column` objects or a compatible expression,
+ for some backends may also be a table expression) which will render
+ into a ``FOR UPDATE OF`` clause; supported by PostgreSQL, Oracle, some
+ MySQL versions and possibly others. May render as a table or as a
+ column depending on backend.
+
+ :param skip_locked: boolean, will render ``FOR UPDATE SKIP LOCKED``
+ on Oracle and PostgreSQL dialects or ``FOR SHARE SKIP LOCKED`` if
+ ``read=True`` is also specified.
+
+ :param key_share: boolean, will render ``FOR NO KEY UPDATE``,
+ or if combined with ``read=True`` will render ``FOR KEY SHARE``,
+ on the PostgreSQL dialect.
+
+ """
+ self._for_update_arg = ForUpdateArg(
+ nowait=nowait,
+ read=read,
+ of=of,
+ skip_locked=skip_locked,
+ key_share=key_share,
+ )
+ return self
+
+ def get_label_style(self) -> SelectLabelStyle:
+ """
+ Retrieve the current label style.
+
+ .. versionadded:: 1.4
+
+ """
+ return self._label_style
+
+ def set_label_style(self, style: SelectLabelStyle) -> Self:
+ """Return a new selectable with the specified label style.
+
+ There are three "label styles" available,
+ :attr:`_sql.SelectLabelStyle.LABEL_STYLE_DISAMBIGUATE_ONLY`,
+ :attr:`_sql.SelectLabelStyle.LABEL_STYLE_TABLENAME_PLUS_COL`, and
+ :attr:`_sql.SelectLabelStyle.LABEL_STYLE_NONE`. The default style is
+ :attr:`_sql.SelectLabelStyle.LABEL_STYLE_TABLENAME_PLUS_COL`.
+
+ In modern SQLAlchemy, there is not generally a need to change the
+ labeling style, as per-expression labels are more effectively used by
+ making use of the :meth:`_sql.ColumnElement.label` method. In past
+ versions, :data:`_sql.LABEL_STYLE_TABLENAME_PLUS_COL` was used to
+ disambiguate same-named columns from different tables, aliases, or
+ subqueries; the newer :data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY` now
+ applies labels only to names that conflict with an existing name so
+ that the impact of this labeling is minimal.
+
+ The rationale for disambiguation is mostly so that all column
+ expressions are available from a given :attr:`_sql.FromClause.c`
+ collection when a subquery is created.
+
+ .. versionadded:: 1.4 - the
+ :meth:`_sql.GenerativeSelect.set_label_style` method replaces the
+ previous combination of ``.apply_labels()``, ``.with_labels()`` and
+ ``use_labels=True`` methods and/or parameters.
+
+ .. seealso::
+
+ :data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY`
+
+ :data:`_sql.LABEL_STYLE_TABLENAME_PLUS_COL`
+
+ :data:`_sql.LABEL_STYLE_NONE`
+
+ :data:`_sql.LABEL_STYLE_DEFAULT`
+
+ """
+ if self._label_style is not style:
+ self = self._generate()
+ self._label_style = style
+ return self
+
+ @property
+ def _group_by_clause(self) -> ClauseList:
+ """ClauseList access to group_by_clauses for legacy dialects"""
+ return ClauseList._construct_raw(
+ operators.comma_op, self._group_by_clauses
+ )
+
+ @property
+ def _order_by_clause(self) -> ClauseList:
+ """ClauseList access to order_by_clauses for legacy dialects"""
+ return ClauseList._construct_raw(
+ operators.comma_op, self._order_by_clauses
+ )
+
+ def _offset_or_limit_clause(
+ self,
+ element: _LimitOffsetType,
+ name: Optional[str] = None,
+ type_: Optional[_TypeEngineArgument[int]] = None,
+ ) -> ColumnElement[Any]:
+ """Convert the given value to an "offset or limit" clause.
+
+ This handles incoming integers and converts to an expression; if
+ an expression is already given, it is passed through.
+
+ """
+ return coercions.expect(
+ roles.LimitOffsetRole, element, name=name, type_=type_
+ )
+
+ @overload
+ def _offset_or_limit_clause_asint(
+ self, clause: ColumnElement[Any], attrname: str
+ ) -> NoReturn: ...
+
+ @overload
+ def _offset_or_limit_clause_asint(
+ self, clause: Optional[_OffsetLimitParam], attrname: str
+ ) -> Optional[int]: ...
+
+ def _offset_or_limit_clause_asint(
+ self, clause: Optional[ColumnElement[Any]], attrname: str
+ ) -> Union[NoReturn, Optional[int]]:
+ """Convert the "offset or limit" clause of a select construct to an
+ integer.
+
+ This is only possible if the value is stored as a simple bound
+ parameter. Otherwise, a compilation error is raised.
+
+ """
+ if clause is None:
+ return None
+ try:
+ value = clause._limit_offset_value
+ except AttributeError as err:
+ raise exc.CompileError(
+ "This SELECT structure does not use a simple "
+ "integer value for %s" % attrname
+ ) from err
+ else:
+ return util.asint(value)
+
+ @property
+ def _limit(self) -> Optional[int]:
+ """Get an integer value for the limit. This should only be used
+ by code that cannot support a limit as a BindParameter or
+ other custom clause as it will throw an exception if the limit
+ isn't currently set to an integer.
+
+ """
+ return self._offset_or_limit_clause_asint(self._limit_clause, "limit")
+
+ def _simple_int_clause(self, clause: ClauseElement) -> bool:
+ """True if the clause is a simple integer, False
+ if it is not present or is a SQL expression.
+ """
+ return isinstance(clause, _OffsetLimitParam)
+
+ @property
+ def _offset(self) -> Optional[int]:
+ """Get an integer value for the offset. This should only be used
+ by code that cannot support an offset as a BindParameter or
+ other custom clause as it will throw an exception if the
+ offset isn't currently set to an integer.
+
+ """
+ return self._offset_or_limit_clause_asint(
+ self._offset_clause, "offset"
+ )
+
+ @property
+ def _has_row_limiting_clause(self) -> bool:
+ return (
+ self._limit_clause is not None
+ or self._offset_clause is not None
+ or self._fetch_clause is not None
+ )
+
+ @_generative
+ def limit(self, limit: _LimitOffsetType) -> Self:
+ """Return a new selectable with the given LIMIT criterion
+ applied.
+
+ This is a numerical value which usually renders as a ``LIMIT``
+ expression in the resulting select. Backends that don't
+ support ``LIMIT`` will attempt to provide similar
+ functionality.
+
+ .. note::
+
+ The :meth:`_sql.GenerativeSelect.limit` method will replace
+ any clause applied with :meth:`_sql.GenerativeSelect.fetch`.
+
+ :param limit: an integer LIMIT parameter, or a SQL expression
+ that provides an integer result. Pass ``None`` to reset it.
+
+ .. seealso::
+
+ :meth:`_sql.GenerativeSelect.fetch`
+
+ :meth:`_sql.GenerativeSelect.offset`
+
+ """
+
+ self._fetch_clause = self._fetch_clause_options = None
+ self._limit_clause = self._offset_or_limit_clause(limit)
+ return self
+
+ @_generative
+ def fetch(
+ self,
+ count: _LimitOffsetType,
+ with_ties: bool = False,
+ percent: bool = False,
+ ) -> Self:
+ """Return a new selectable with the given FETCH FIRST criterion
+ applied.
+
+ This is a numeric value which usually renders as
+ ``FETCH {FIRST | NEXT} [ count ] {ROW | ROWS} {ONLY | WITH TIES}``
+ expression in the resulting select. This functionality is
+ is currently implemented for Oracle, PostgreSQL, MSSQL.
+
+ Use :meth:`_sql.GenerativeSelect.offset` to specify the offset.
+
+ .. note::
+
+ The :meth:`_sql.GenerativeSelect.fetch` method will replace
+ any clause applied with :meth:`_sql.GenerativeSelect.limit`.
+
+ .. versionadded:: 1.4
+
+ :param count: an integer COUNT parameter, or a SQL expression
+ that provides an integer result. When ``percent=True`` this will
+ represent the percentage of rows to return, not the absolute value.
+ Pass ``None`` to reset it.
+
+ :param with_ties: When ``True``, the WITH TIES option is used
+ to return any additional rows that tie for the last place in the
+ result set according to the ``ORDER BY`` clause. The
+ ``ORDER BY`` may be mandatory in this case. Defaults to ``False``
+
+ :param percent: When ``True``, ``count`` represents the percentage
+ of the total number of selected rows to return. Defaults to ``False``
+
+ .. seealso::
+
+ :meth:`_sql.GenerativeSelect.limit`
+
+ :meth:`_sql.GenerativeSelect.offset`
+
+ """
+
+ self._limit_clause = None
+ if count is None:
+ self._fetch_clause = self._fetch_clause_options = None
+ else:
+ self._fetch_clause = self._offset_or_limit_clause(count)
+ self._fetch_clause_options = {
+ "with_ties": with_ties,
+ "percent": percent,
+ }
+ return self
+
+ @_generative
+ def offset(self, offset: _LimitOffsetType) -> Self:
+ """Return a new selectable with the given OFFSET criterion
+ applied.
+
+
+ This is a numeric value which usually renders as an ``OFFSET``
+ expression in the resulting select. Backends that don't
+ support ``OFFSET`` will attempt to provide similar
+ functionality.
+
+ :param offset: an integer OFFSET parameter, or a SQL expression
+ that provides an integer result. Pass ``None`` to reset it.
+
+ .. seealso::
+
+ :meth:`_sql.GenerativeSelect.limit`
+
+ :meth:`_sql.GenerativeSelect.fetch`
+
+ """
+
+ self._offset_clause = self._offset_or_limit_clause(offset)
+ return self
+
+ @_generative
+ @util.preload_module("sqlalchemy.sql.util")
+ def slice(
+ self,
+ start: int,
+ stop: int,
+ ) -> Self:
+ """Apply LIMIT / OFFSET to this statement based on a slice.
+
+ The start and stop indices behave like the argument to Python's
+ built-in :func:`range` function. This method provides an
+ alternative to using ``LIMIT``/``OFFSET`` to get a slice of the
+ query.
+
+ For example, ::
+
+ stmt = select(User).order_by(User).id.slice(1, 3)
+
+ renders as
+
+ .. sourcecode:: sql
+
+ SELECT users.id AS users_id,
+ users.name AS users_name
+ FROM users ORDER BY users.id
+ LIMIT ? OFFSET ?
+ (2, 1)
+
+ .. note::
+
+ The :meth:`_sql.GenerativeSelect.slice` method will replace
+ any clause applied with :meth:`_sql.GenerativeSelect.fetch`.
+
+ .. versionadded:: 1.4 Added the :meth:`_sql.GenerativeSelect.slice`
+ method generalized from the ORM.
+
+ .. seealso::
+
+ :meth:`_sql.GenerativeSelect.limit`
+
+ :meth:`_sql.GenerativeSelect.offset`
+
+ :meth:`_sql.GenerativeSelect.fetch`
+
+ """
+ sql_util = util.preloaded.sql_util
+ self._fetch_clause = self._fetch_clause_options = None
+ self._limit_clause, self._offset_clause = sql_util._make_slice(
+ self._limit_clause, self._offset_clause, start, stop
+ )
+ return self
+
+ @_generative
+ def order_by(
+ self,
+ __first: Union[
+ Literal[None, _NoArg.NO_ARG],
+ _ColumnExpressionOrStrLabelArgument[Any],
+ ] = _NoArg.NO_ARG,
+ *clauses: _ColumnExpressionOrStrLabelArgument[Any],
+ ) -> Self:
+ r"""Return a new selectable with the given list of ORDER BY
+ criteria applied.
+
+ e.g.::
+
+ stmt = select(table).order_by(table.c.id, table.c.name)
+
+ 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)
+
+ :param \*clauses: a series of :class:`_expression.ColumnElement`
+ constructs
+ which will be used to generate an ORDER BY clause.
+
+ .. seealso::
+
+ :ref:`tutorial_order_by` - in the :ref:`unified_tutorial`
+
+ :ref:`tutorial_order_by_label` - in the :ref:`unified_tutorial`
+
+ """
+
+ if not clauses and __first is None:
+ self._order_by_clauses = ()
+ elif __first is not _NoArg.NO_ARG:
+ self._order_by_clauses += tuple(
+ coercions.expect(
+ roles.OrderByRole, clause, apply_propagate_attrs=self
+ )
+ for clause in (__first,) + clauses
+ )
+ return self
+
+ @_generative
+ def group_by(
+ self,
+ __first: Union[
+ Literal[None, _NoArg.NO_ARG],
+ _ColumnExpressionOrStrLabelArgument[Any],
+ ] = _NoArg.NO_ARG,
+ *clauses: _ColumnExpressionOrStrLabelArgument[Any],
+ ) -> Self:
+ r"""Return a new selectable with the given list of GROUP BY
+ criterion applied.
+
+ All existing GROUP BY settings can be suppressed by passing ``None``.
+
+ e.g.::
+
+ stmt = select(table.c.name, func.max(table.c.stat)).\
+ group_by(table.c.name)
+
+ :param \*clauses: a series of :class:`_expression.ColumnElement`
+ constructs
+ which will be used to generate an GROUP BY clause.
+
+ .. seealso::
+
+ :ref:`tutorial_group_by_w_aggregates` - in the
+ :ref:`unified_tutorial`
+
+ :ref:`tutorial_order_by_label` - in the :ref:`unified_tutorial`
+
+ """
+
+ if not clauses and __first is None:
+ self._group_by_clauses = ()
+ elif __first is not _NoArg.NO_ARG:
+ self._group_by_clauses += tuple(
+ coercions.expect(
+ roles.GroupByRole, clause, apply_propagate_attrs=self
+ )
+ for clause in (__first,) + clauses
+ )
+ return self
+
+
+@CompileState.plugin_for("default", "compound_select")
+class CompoundSelectState(CompileState):
+ @util.memoized_property
+ def _label_resolve_dict(
+ self,
+ ) -> Tuple[
+ Dict[str, ColumnElement[Any]],
+ Dict[str, ColumnElement[Any]],
+ Dict[str, ColumnElement[Any]],
+ ]:
+ # TODO: this is hacky and slow
+ hacky_subquery = self.statement.subquery()
+ hacky_subquery.named_with_column = False
+ d = {c.key: c for c in hacky_subquery.c}
+ return d, d, d
+
+
+class _CompoundSelectKeyword(Enum):
+ UNION = "UNION"
+ UNION_ALL = "UNION ALL"
+ EXCEPT = "EXCEPT"
+ EXCEPT_ALL = "EXCEPT ALL"
+ INTERSECT = "INTERSECT"
+ INTERSECT_ALL = "INTERSECT ALL"
+
+
+class CompoundSelect(HasCompileState, GenerativeSelect, ExecutableReturnsRows):
+ """Forms the basis of ``UNION``, ``UNION ALL``, and other
+ SELECT-based set operations.
+
+
+ .. seealso::
+
+ :func:`_expression.union`
+
+ :func:`_expression.union_all`
+
+ :func:`_expression.intersect`
+
+ :func:`_expression.intersect_all`
+
+ :func:`_expression.except`
+
+ :func:`_expression.except_all`
+
+ """
+
+ __visit_name__ = "compound_select"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("selects", InternalTraversal.dp_clauseelement_list),
+ ("_limit_clause", InternalTraversal.dp_clauseelement),
+ ("_offset_clause", InternalTraversal.dp_clauseelement),
+ ("_fetch_clause", InternalTraversal.dp_clauseelement),
+ ("_fetch_clause_options", InternalTraversal.dp_plain_dict),
+ ("_order_by_clauses", InternalTraversal.dp_clauseelement_list),
+ ("_group_by_clauses", InternalTraversal.dp_clauseelement_list),
+ ("_for_update_arg", InternalTraversal.dp_clauseelement),
+ ("keyword", InternalTraversal.dp_string),
+ ] + SupportsCloneAnnotations._clone_annotations_traverse_internals
+
+ selects: List[SelectBase]
+
+ _is_from_container = True
+ _auto_correlate = False
+
+ def __init__(
+ self,
+ keyword: _CompoundSelectKeyword,
+ *selects: _SelectStatementForCompoundArgument,
+ ):
+ self.keyword = keyword
+ self.selects = [
+ coercions.expect(
+ roles.CompoundElementRole, s, apply_propagate_attrs=self
+ ).self_group(against=self)
+ for s in selects
+ ]
+
+ GenerativeSelect.__init__(self)
+
+ @classmethod
+ def _create_union(
+ cls, *selects: _SelectStatementForCompoundArgument
+ ) -> CompoundSelect:
+ return CompoundSelect(_CompoundSelectKeyword.UNION, *selects)
+
+ @classmethod
+ def _create_union_all(
+ cls, *selects: _SelectStatementForCompoundArgument
+ ) -> CompoundSelect:
+ return CompoundSelect(_CompoundSelectKeyword.UNION_ALL, *selects)
+
+ @classmethod
+ def _create_except(
+ cls, *selects: _SelectStatementForCompoundArgument
+ ) -> CompoundSelect:
+ return CompoundSelect(_CompoundSelectKeyword.EXCEPT, *selects)
+
+ @classmethod
+ def _create_except_all(
+ cls, *selects: _SelectStatementForCompoundArgument
+ ) -> CompoundSelect:
+ return CompoundSelect(_CompoundSelectKeyword.EXCEPT_ALL, *selects)
+
+ @classmethod
+ def _create_intersect(
+ cls, *selects: _SelectStatementForCompoundArgument
+ ) -> CompoundSelect:
+ return CompoundSelect(_CompoundSelectKeyword.INTERSECT, *selects)
+
+ @classmethod
+ def _create_intersect_all(
+ cls, *selects: _SelectStatementForCompoundArgument
+ ) -> CompoundSelect:
+ return CompoundSelect(_CompoundSelectKeyword.INTERSECT_ALL, *selects)
+
+ def _scalar_type(self) -> TypeEngine[Any]:
+ return self.selects[0]._scalar_type()
+
+ def self_group(
+ self, against: Optional[OperatorType] = None
+ ) -> GroupedElement:
+ return SelectStatementGrouping(self)
+
+ def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
+ for s in self.selects:
+ if s.is_derived_from(fromclause):
+ return True
+ return False
+
+ def set_label_style(self, style: SelectLabelStyle) -> CompoundSelect:
+ if self._label_style is not style:
+ self = self._generate()
+ select_0 = self.selects[0].set_label_style(style)
+ self.selects = [select_0] + self.selects[1:]
+
+ return self
+
+ def _ensure_disambiguated_names(self) -> CompoundSelect:
+ new_select = self.selects[0]._ensure_disambiguated_names()
+ if new_select is not self.selects[0]:
+ self = self._generate()
+ self.selects = [new_select] + self.selects[1:]
+
+ return self
+
+ def _generate_fromclause_column_proxies(
+ self,
+ subquery: FromClause,
+ *,
+ proxy_compound_columns: Optional[
+ Iterable[Sequence[ColumnElement[Any]]]
+ ] = None,
+ ) -> None:
+ # this is a slightly hacky thing - the union exports a
+ # column that resembles just that of the *first* selectable.
+ # to get at a "composite" column, particularly foreign keys,
+ # you have to dig through the proxies collection which we
+ # generate below.
+ select_0 = self.selects[0]
+
+ if self._label_style is not LABEL_STYLE_DEFAULT:
+ select_0 = select_0.set_label_style(self._label_style)
+
+ # hand-construct the "_proxies" collection to include all
+ # derived columns place a 'weight' annotation corresponding
+ # to how low in the list of select()s the column occurs, so
+ # that the corresponding_column() operation can resolve
+ # conflicts
+ extra_col_iterator = zip(
+ *[
+ [
+ c._annotate(dd)
+ for c in stmt._all_selected_columns
+ if is_column_element(c)
+ ]
+ for dd, stmt in [
+ ({"weight": i + 1}, stmt)
+ for i, stmt in enumerate(self.selects)
+ ]
+ ]
+ )
+
+ # the incoming proxy_compound_columns can be present also if this is
+ # a compound embedded in a compound. it's probably more appropriate
+ # that we generate new weights local to this nested compound, though
+ # i haven't tried to think what it means for compound nested in
+ # compound
+ select_0._generate_fromclause_column_proxies(
+ subquery, proxy_compound_columns=extra_col_iterator
+ )
+
+ def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+ super()._refresh_for_new_column(column)
+ for select in self.selects:
+ select._refresh_for_new_column(column)
+
+ @util.ro_non_memoized_property
+ def _all_selected_columns(self) -> _SelectIterable:
+ return self.selects[0]._all_selected_columns
+
+ @util.ro_non_memoized_property
+ def selected_columns(
+ self,
+ ) -> ColumnCollection[str, ColumnElement[Any]]:
+ """A :class:`_expression.ColumnCollection`
+ representing the columns that
+ this SELECT statement or similar construct returns in its result set,
+ not including :class:`_sql.TextClause` constructs.
+
+ For a :class:`_expression.CompoundSelect`, the
+ :attr:`_expression.CompoundSelect.selected_columns`
+ attribute returns the selected
+ columns of the first SELECT statement contained within the series of
+ statements within the set operation.
+
+ .. seealso::
+
+ :attr:`_sql.Select.selected_columns`
+
+ .. versionadded:: 1.4
+
+ """
+ return self.selects[0].selected_columns
+
+
+# backwards compat
+for elem in _CompoundSelectKeyword:
+ setattr(CompoundSelect, elem.name, elem)
+
+
+@CompileState.plugin_for("default", "select")
+class SelectState(util.MemoizedSlots, CompileState):
+ __slots__ = (
+ "from_clauses",
+ "froms",
+ "columns_plus_names",
+ "_label_resolve_dict",
+ )
+
+ if TYPE_CHECKING:
+ default_select_compile_options: CacheableOptions
+ else:
+
+ class default_select_compile_options(CacheableOptions):
+ _cache_key_traversal = []
+
+ if TYPE_CHECKING:
+
+ @classmethod
+ def get_plugin_class(
+ cls, statement: Executable
+ ) -> Type[SelectState]: ...
+
+ def __init__(
+ self,
+ statement: Select[Any],
+ compiler: Optional[SQLCompiler],
+ **kw: Any,
+ ):
+ self.statement = statement
+ self.from_clauses = statement._from_obj
+
+ for memoized_entities in statement._memoized_select_entities:
+ self._setup_joins(
+ memoized_entities._setup_joins, memoized_entities._raw_columns
+ )
+
+ if statement._setup_joins:
+ self._setup_joins(statement._setup_joins, statement._raw_columns)
+
+ self.froms = self._get_froms(statement)
+
+ self.columns_plus_names = statement._generate_columns_plus_names(True)
+
+ @classmethod
+ def _plugin_not_implemented(cls) -> NoReturn:
+ raise NotImplementedError(
+ "The default SELECT construct without plugins does not "
+ "implement this method."
+ )
+
+ @classmethod
+ def get_column_descriptions(
+ cls, statement: Select[Any]
+ ) -> List[Dict[str, Any]]:
+ return [
+ {
+ "name": name,
+ "type": element.type,
+ "expr": element,
+ }
+ for _, name, _, element, _ in (
+ statement._generate_columns_plus_names(False)
+ )
+ ]
+
+ @classmethod
+ def from_statement(
+ cls, statement: Select[Any], from_statement: roles.ReturnsRowsRole
+ ) -> ExecutableReturnsRows:
+ cls._plugin_not_implemented()
+
+ @classmethod
+ def get_columns_clause_froms(
+ cls, statement: Select[Any]
+ ) -> List[FromClause]:
+ return cls._normalize_froms(
+ itertools.chain.from_iterable(
+ element._from_objects for element in statement._raw_columns
+ )
+ )
+
+ @classmethod
+ def _column_naming_convention(
+ cls, label_style: SelectLabelStyle
+ ) -> _LabelConventionCallable:
+ table_qualified = label_style is LABEL_STYLE_TABLENAME_PLUS_COL
+ dedupe = label_style is not LABEL_STYLE_NONE
+
+ pa = prefix_anon_map()
+ names = set()
+
+ def go(
+ c: Union[ColumnElement[Any], TextClause],
+ col_name: Optional[str] = None,
+ ) -> Optional[str]:
+ if is_text_clause(c):
+ return None
+ elif TYPE_CHECKING:
+ assert is_column_element(c)
+
+ if not dedupe:
+ name = c._proxy_key
+ if name is None:
+ name = "_no_label"
+ return name
+
+ name = c._tq_key_label if table_qualified else c._proxy_key
+
+ if name is None:
+ name = "_no_label"
+ if name in names:
+ return c._anon_label(name) % pa
+ else:
+ names.add(name)
+ return name
+
+ elif name in names:
+ return (
+ c._anon_tq_key_label % pa
+ if table_qualified
+ else c._anon_key_label % pa
+ )
+ else:
+ names.add(name)
+ return name
+
+ return go
+
+ def _get_froms(self, statement: Select[Any]) -> List[FromClause]:
+ ambiguous_table_name_map: _AmbiguousTableNameMap
+ self._ambiguous_table_name_map = ambiguous_table_name_map = {}
+
+ return self._normalize_froms(
+ itertools.chain(
+ self.from_clauses,
+ itertools.chain.from_iterable(
+ [
+ element._from_objects
+ for element in statement._raw_columns
+ ]
+ ),
+ itertools.chain.from_iterable(
+ [
+ element._from_objects
+ for element in statement._where_criteria
+ ]
+ ),
+ ),
+ check_statement=statement,
+ ambiguous_table_name_map=ambiguous_table_name_map,
+ )
+
+ @classmethod
+ def _normalize_froms(
+ cls,
+ iterable_of_froms: Iterable[FromClause],
+ check_statement: Optional[Select[Any]] = None,
+ ambiguous_table_name_map: Optional[_AmbiguousTableNameMap] = None,
+ ) -> List[FromClause]:
+ """given an iterable of things to select FROM, reduce them to what
+ would actually render in the FROM clause of a SELECT.
+
+ This does the job of checking for JOINs, tables, etc. that are in fact
+ overlapping due to cloning, adaption, present in overlapping joins,
+ etc.
+
+ """
+ seen: Set[FromClause] = set()
+ froms: List[FromClause] = []
+
+ for item in iterable_of_froms:
+ if is_subquery(item) and item.element is check_statement:
+ raise exc.InvalidRequestError(
+ "select() construct refers to itself as a FROM"
+ )
+
+ if not seen.intersection(item._cloned_set):
+ froms.append(item)
+ seen.update(item._cloned_set)
+
+ if froms:
+ toremove = set(
+ itertools.chain.from_iterable(
+ [_expand_cloned(f._hide_froms) for f in froms]
+ )
+ )
+ if toremove:
+ # filter out to FROM clauses not in the list,
+ # using a list to maintain ordering
+ froms = [f for f in froms if f not in toremove]
+
+ if ambiguous_table_name_map is not None:
+ ambiguous_table_name_map.update(
+ (
+ fr.name,
+ _anonymous_label.safe_construct(
+ hash(fr.name), fr.name
+ ),
+ )
+ for item in froms
+ for fr in item._from_objects
+ if is_table(fr)
+ and fr.schema
+ and fr.name not in ambiguous_table_name_map
+ )
+
+ return froms
+
+ def _get_display_froms(
+ self,
+ explicit_correlate_froms: Optional[Sequence[FromClause]] = None,
+ implicit_correlate_froms: Optional[Sequence[FromClause]] = None,
+ ) -> List[FromClause]:
+ """Return the full list of 'from' clauses to be displayed.
+
+ Takes into account a set of existing froms which may be
+ rendered in the FROM clause of enclosing selects; this Select
+ may want to leave those absent if it is automatically
+ correlating.
+
+ """
+
+ froms = self.froms
+
+ if self.statement._correlate:
+ to_correlate = self.statement._correlate
+ if to_correlate:
+ froms = [
+ f
+ for f in froms
+ if f
+ not in _cloned_intersection(
+ _cloned_intersection(
+ froms, explicit_correlate_froms or ()
+ ),
+ to_correlate,
+ )
+ ]
+
+ if self.statement._correlate_except is not None:
+ froms = [
+ f
+ for f in froms
+ if f
+ not in _cloned_difference(
+ _cloned_intersection(
+ froms, explicit_correlate_froms or ()
+ ),
+ self.statement._correlate_except,
+ )
+ ]
+
+ if (
+ self.statement._auto_correlate
+ and implicit_correlate_froms
+ and len(froms) > 1
+ ):
+ froms = [
+ f
+ for f in froms
+ if f
+ not in _cloned_intersection(froms, implicit_correlate_froms)
+ ]
+
+ if not len(froms):
+ raise exc.InvalidRequestError(
+ "Select statement '%r"
+ "' returned no FROM clauses "
+ "due to auto-correlation; "
+ "specify correlate(<tables>) "
+ "to control correlation "
+ "manually." % self.statement
+ )
+
+ return froms
+
+ def _memoized_attr__label_resolve_dict(
+ self,
+ ) -> Tuple[
+ Dict[str, ColumnElement[Any]],
+ Dict[str, ColumnElement[Any]],
+ Dict[str, ColumnElement[Any]],
+ ]:
+ with_cols: Dict[str, ColumnElement[Any]] = {
+ c._tq_label or c.key: c
+ for c in self.statement._all_selected_columns
+ if c._allow_label_resolve
+ }
+ only_froms: Dict[str, ColumnElement[Any]] = {
+ c.key: c # type: ignore
+ for c in _select_iterables(self.froms)
+ if c._allow_label_resolve
+ }
+ only_cols: Dict[str, ColumnElement[Any]] = with_cols.copy()
+ for key, value in only_froms.items():
+ with_cols.setdefault(key, value)
+
+ return with_cols, only_froms, only_cols
+
+ @classmethod
+ def determine_last_joined_entity(
+ cls, stmt: Select[Any]
+ ) -> Optional[_JoinTargetElement]:
+ if stmt._setup_joins:
+ return stmt._setup_joins[-1][0]
+ else:
+ return None
+
+ @classmethod
+ def all_selected_columns(cls, statement: Select[Any]) -> _SelectIterable:
+ return [c for c in _select_iterables(statement._raw_columns)]
+
+ def _setup_joins(
+ self,
+ args: Tuple[_SetupJoinsElement, ...],
+ raw_columns: List[_ColumnsClauseElement],
+ ) -> None:
+ for right, onclause, left, flags in args:
+ if TYPE_CHECKING:
+ if onclause is not None:
+ assert isinstance(onclause, ColumnElement)
+
+ isouter = flags["isouter"]
+ full = flags["full"]
+
+ if left is None:
+ (
+ left,
+ replace_from_obj_index,
+ ) = self._join_determine_implicit_left_side(
+ raw_columns, left, right, onclause
+ )
+ else:
+ (replace_from_obj_index) = self._join_place_explicit_left_side(
+ left
+ )
+
+ # these assertions can be made here, as if the right/onclause
+ # contained ORM elements, the select() statement would have been
+ # upgraded to an ORM select, and this method would not be called;
+ # orm.context.ORMSelectCompileState._join() would be
+ # used instead.
+ if TYPE_CHECKING:
+ assert isinstance(right, FromClause)
+ if onclause is not None:
+ assert isinstance(onclause, ColumnElement)
+
+ if replace_from_obj_index is not None:
+ # splice into an existing element in the
+ # self._from_obj list
+ left_clause = self.from_clauses[replace_from_obj_index]
+
+ self.from_clauses = (
+ self.from_clauses[:replace_from_obj_index]
+ + (
+ Join(
+ left_clause,
+ right,
+ onclause,
+ isouter=isouter,
+ full=full,
+ ),
+ )
+ + self.from_clauses[replace_from_obj_index + 1 :]
+ )
+ else:
+ assert left is not None
+ self.from_clauses = self.from_clauses + (
+ Join(left, right, onclause, isouter=isouter, full=full),
+ )
+
+ @util.preload_module("sqlalchemy.sql.util")
+ def _join_determine_implicit_left_side(
+ self,
+ raw_columns: List[_ColumnsClauseElement],
+ left: Optional[FromClause],
+ right: _JoinTargetElement,
+ onclause: Optional[ColumnElement[Any]],
+ ) -> Tuple[Optional[FromClause], Optional[int]]:
+ """When join conditions don't express the left side explicitly,
+ determine if an existing FROM or entity in this query
+ can serve as the left hand side.
+
+ """
+
+ sql_util = util.preloaded.sql_util
+
+ replace_from_obj_index: Optional[int] = None
+
+ from_clauses = self.from_clauses
+
+ if from_clauses:
+ indexes: List[int] = sql_util.find_left_clause_to_join_from(
+ from_clauses, right, onclause
+ )
+
+ if len(indexes) == 1:
+ replace_from_obj_index = indexes[0]
+ left = from_clauses[replace_from_obj_index]
+ else:
+ potential = {}
+ statement = self.statement
+
+ for from_clause in itertools.chain(
+ itertools.chain.from_iterable(
+ [element._from_objects for element in raw_columns]
+ ),
+ itertools.chain.from_iterable(
+ [
+ element._from_objects
+ for element in statement._where_criteria
+ ]
+ ),
+ ):
+ potential[from_clause] = ()
+
+ all_clauses = list(potential.keys())
+ indexes = sql_util.find_left_clause_to_join_from(
+ all_clauses, right, onclause
+ )
+
+ if len(indexes) == 1:
+ left = all_clauses[indexes[0]]
+
+ if len(indexes) > 1:
+ raise exc.InvalidRequestError(
+ "Can't determine which FROM clause to join "
+ "from, there are multiple FROMS which can "
+ "join to this entity. Please use the .select_from() "
+ "method to establish an explicit left side, as well as "
+ "providing an explicit ON clause if not present already to "
+ "help resolve the ambiguity."
+ )
+ elif not indexes:
+ raise exc.InvalidRequestError(
+ "Don't know how to join to %r. "
+ "Please use the .select_from() "
+ "method to establish an explicit left side, as well as "
+ "providing an explicit ON clause if not present already to "
+ "help resolve the ambiguity." % (right,)
+ )
+ return left, replace_from_obj_index
+
+ @util.preload_module("sqlalchemy.sql.util")
+ def _join_place_explicit_left_side(
+ self, left: FromClause
+ ) -> Optional[int]:
+ replace_from_obj_index: Optional[int] = None
+
+ sql_util = util.preloaded.sql_util
+
+ from_clauses = list(self.statement._iterate_from_elements())
+
+ if from_clauses:
+ indexes: List[int] = sql_util.find_left_clause_that_matches_given(
+ self.from_clauses, left
+ )
+ else:
+ indexes = []
+
+ if len(indexes) > 1:
+ raise exc.InvalidRequestError(
+ "Can't identify which entity in which to assign the "
+ "left side of this join. Please use a more specific "
+ "ON clause."
+ )
+
+ # have an index, means the left side is already present in
+ # an existing FROM in the self._from_obj tuple
+ if indexes:
+ replace_from_obj_index = indexes[0]
+
+ # no index, means we need to add a new element to the
+ # self._from_obj tuple
+
+ return replace_from_obj_index
+
+
+class _SelectFromElements:
+ __slots__ = ()
+
+ _raw_columns: List[_ColumnsClauseElement]
+ _where_criteria: Tuple[ColumnElement[Any], ...]
+ _from_obj: Tuple[FromClause, ...]
+
+ def _iterate_from_elements(self) -> Iterator[FromClause]:
+ # note this does not include elements
+ # in _setup_joins
+
+ seen = set()
+ for element in self._raw_columns:
+ for fr in element._from_objects:
+ if fr in seen:
+ continue
+ seen.add(fr)
+ yield fr
+ for element in self._where_criteria:
+ for fr in element._from_objects:
+ if fr in seen:
+ continue
+ seen.add(fr)
+ yield fr
+ for element in self._from_obj:
+ if element in seen:
+ continue
+ seen.add(element)
+ yield element
+
+
+class _MemoizedSelectEntities(
+ cache_key.HasCacheKey, traversals.HasCopyInternals, visitors.Traversible
+):
+ """represents partial state from a Select object, for the case
+ where Select.columns() has redefined the set of columns/entities the
+ statement will be SELECTing from. This object represents
+ the entities from the SELECT before that transformation was applied,
+ so that transformations that were made in terms of the SELECT at that
+ time, such as join() as well as options(), can access the correct context.
+
+ In previous SQLAlchemy versions, this wasn't needed because these
+ constructs calculated everything up front, like when you called join()
+ or options(), it did everything to figure out how that would translate
+ into specific SQL constructs that would be ready to send directly to the
+ SQL compiler when needed. But as of
+ 1.4, all of that stuff is done in the compilation phase, during the
+ "compile state" portion of the process, so that the work can all be
+ cached. So it needs to be able to resolve joins/options2 based on what
+ the list of entities was when those methods were called.
+
+
+ """
+
+ __visit_name__ = "memoized_select_entities"
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("_raw_columns", InternalTraversal.dp_clauseelement_list),
+ ("_setup_joins", InternalTraversal.dp_setup_join_tuple),
+ ("_with_options", InternalTraversal.dp_executable_options),
+ ]
+
+ _is_clone_of: Optional[ClauseElement]
+ _raw_columns: List[_ColumnsClauseElement]
+ _setup_joins: Tuple[_SetupJoinsElement, ...]
+ _with_options: Tuple[ExecutableOption, ...]
+
+ _annotations = util.EMPTY_DICT
+
+ def _clone(self, **kw: Any) -> Self:
+ c = self.__class__.__new__(self.__class__)
+ c.__dict__ = {k: v for k, v in self.__dict__.items()}
+
+ c._is_clone_of = self.__dict__.get("_is_clone_of", self)
+ return c
+
+ @classmethod
+ def _generate_for_statement(cls, select_stmt: Select[Any]) -> None:
+ if select_stmt._setup_joins or select_stmt._with_options:
+ self = _MemoizedSelectEntities()
+ self._raw_columns = select_stmt._raw_columns
+ self._setup_joins = select_stmt._setup_joins
+ self._with_options = select_stmt._with_options
+
+ select_stmt._memoized_select_entities += (self,)
+ select_stmt._raw_columns = []
+ select_stmt._setup_joins = select_stmt._with_options = ()
+
+
+class Select(
+ HasPrefixes,
+ HasSuffixes,
+ HasHints,
+ HasCompileState,
+ _SelectFromElements,
+ GenerativeSelect,
+ TypedReturnsRows[_TP],
+):
+ """Represents a ``SELECT`` statement.
+
+ The :class:`_sql.Select` object is normally constructed using the
+ :func:`_sql.select` function. See that function for details.
+
+ .. seealso::
+
+ :func:`_sql.select`
+
+ :ref:`tutorial_selecting_data` - in the 2.0 tutorial
+
+ """
+
+ __visit_name__ = "select"
+
+ _setup_joins: Tuple[_SetupJoinsElement, ...] = ()
+ _memoized_select_entities: Tuple[TODO_Any, ...] = ()
+
+ _raw_columns: List[_ColumnsClauseElement]
+
+ _distinct: bool = False
+ _distinct_on: Tuple[ColumnElement[Any], ...] = ()
+ _correlate: Tuple[FromClause, ...] = ()
+ _correlate_except: Optional[Tuple[FromClause, ...]] = None
+ _where_criteria: Tuple[ColumnElement[Any], ...] = ()
+ _having_criteria: Tuple[ColumnElement[Any], ...] = ()
+ _from_obj: Tuple[FromClause, ...] = ()
+ _auto_correlate = True
+ _is_select_statement = True
+ _compile_options: CacheableOptions = (
+ SelectState.default_select_compile_options
+ )
+
+ _traverse_internals: _TraverseInternalsType = (
+ [
+ ("_raw_columns", InternalTraversal.dp_clauseelement_list),
+ (
+ "_memoized_select_entities",
+ InternalTraversal.dp_memoized_select_entities,
+ ),
+ ("_from_obj", InternalTraversal.dp_clauseelement_list),
+ ("_where_criteria", InternalTraversal.dp_clauseelement_tuple),
+ ("_having_criteria", InternalTraversal.dp_clauseelement_tuple),
+ ("_order_by_clauses", InternalTraversal.dp_clauseelement_tuple),
+ ("_group_by_clauses", InternalTraversal.dp_clauseelement_tuple),
+ ("_setup_joins", InternalTraversal.dp_setup_join_tuple),
+ ("_correlate", InternalTraversal.dp_clauseelement_tuple),
+ ("_correlate_except", InternalTraversal.dp_clauseelement_tuple),
+ ("_limit_clause", InternalTraversal.dp_clauseelement),
+ ("_offset_clause", InternalTraversal.dp_clauseelement),
+ ("_fetch_clause", InternalTraversal.dp_clauseelement),
+ ("_fetch_clause_options", InternalTraversal.dp_plain_dict),
+ ("_for_update_arg", InternalTraversal.dp_clauseelement),
+ ("_distinct", InternalTraversal.dp_boolean),
+ ("_distinct_on", InternalTraversal.dp_clauseelement_tuple),
+ ("_label_style", InternalTraversal.dp_plain_obj),
+ ]
+ + HasCTE._has_ctes_traverse_internals
+ + HasPrefixes._has_prefixes_traverse_internals
+ + HasSuffixes._has_suffixes_traverse_internals
+ + HasHints._has_hints_traverse_internals
+ + SupportsCloneAnnotations._clone_annotations_traverse_internals
+ + Executable._executable_traverse_internals
+ )
+
+ _cache_key_traversal: _CacheKeyTraversalType = _traverse_internals + [
+ ("_compile_options", InternalTraversal.dp_has_cache_key)
+ ]
+
+ _compile_state_factory: Type[SelectState]
+
+ @classmethod
+ def _create_raw_select(cls, **kw: Any) -> Select[Any]:
+ """Create a :class:`.Select` using raw ``__new__`` with no coercions.
+
+ Used internally to build up :class:`.Select` constructs with
+ pre-established state.
+
+ """
+
+ stmt = Select.__new__(Select)
+ stmt.__dict__.update(kw)
+ return stmt
+
+ def __init__(self, *entities: _ColumnsClauseArgument[Any]):
+ r"""Construct a new :class:`_expression.Select`.
+
+ The public constructor for :class:`_expression.Select` is the
+ :func:`_sql.select` function.
+
+ """
+ self._raw_columns = [
+ coercions.expect(
+ roles.ColumnsClauseRole, ent, apply_propagate_attrs=self
+ )
+ for ent in entities
+ ]
+
+ GenerativeSelect.__init__(self)
+
+ def _scalar_type(self) -> TypeEngine[Any]:
+ if not self._raw_columns:
+ return NULLTYPE
+ elem = self._raw_columns[0]
+ cols = list(elem._select_iterable)
+ return cols[0].type
+
+ def filter(self, *criteria: _ColumnExpressionArgument[bool]) -> Self:
+ """A synonym for the :meth:`_sql.Select.where` method."""
+
+ return self.where(*criteria)
+
+ def _filter_by_zero(
+ self,
+ ) -> Union[
+ FromClause, _JoinTargetProtocol, ColumnElement[Any], TextClause
+ ]:
+ if self._setup_joins:
+ meth = SelectState.get_plugin_class(
+ self
+ ).determine_last_joined_entity
+ _last_joined_entity = meth(self)
+ if _last_joined_entity is not None:
+ return _last_joined_entity
+
+ if self._from_obj:
+ return self._from_obj[0]
+
+ return self._raw_columns[0]
+
+ if TYPE_CHECKING:
+
+ @overload
+ def scalar_subquery(
+ self: Select[Tuple[_MAYBE_ENTITY]],
+ ) -> ScalarSelect[Any]: ...
+
+ @overload
+ def scalar_subquery(
+ self: Select[Tuple[_NOT_ENTITY]],
+ ) -> ScalarSelect[_NOT_ENTITY]: ...
+
+ @overload
+ def scalar_subquery(self) -> ScalarSelect[Any]: ...
+
+ def scalar_subquery(self) -> ScalarSelect[Any]: ...
+
+ def filter_by(self, **kwargs: Any) -> Self:
+ r"""apply the given filtering criterion as a WHERE clause
+ to this select.
+
+ """
+ from_entity = self._filter_by_zero()
+
+ clauses = [
+ _entity_namespace_key(from_entity, key) == value
+ for key, value in kwargs.items()
+ ]
+ return self.filter(*clauses)
+
+ @property
+ def column_descriptions(self) -> Any:
+ """Return a :term:`plugin-enabled` 'column descriptions' structure
+ referring to the columns which are SELECTed by this statement.
+
+ This attribute is generally useful when using the ORM, as an
+ extended structure which includes information about mapped
+ entities is returned. The section :ref:`queryguide_inspection`
+ contains more background.
+
+ For a Core-only statement, the structure returned by this accessor
+ is derived from the same objects that are returned by the
+ :attr:`.Select.selected_columns` accessor, formatted as a list of
+ dictionaries which contain the keys ``name``, ``type`` and ``expr``,
+ which indicate the column expressions to be selected::
+
+ >>> stmt = select(user_table)
+ >>> stmt.column_descriptions
+ [
+ {
+ 'name': 'id',
+ 'type': Integer(),
+ 'expr': Column('id', Integer(), ...)},
+ {
+ 'name': 'name',
+ 'type': String(length=30),
+ 'expr': Column('name', String(length=30), ...)}
+ ]
+
+ .. versionchanged:: 1.4.33 The :attr:`.Select.column_descriptions`
+ attribute returns a structure for a Core-only set of entities,
+ not just ORM-only entities.
+
+ .. seealso::
+
+ :attr:`.UpdateBase.entity_description` - entity information for
+ an :func:`.insert`, :func:`.update`, or :func:`.delete`
+
+ :ref:`queryguide_inspection` - ORM background
+
+ """
+ meth = SelectState.get_plugin_class(self).get_column_descriptions
+ return meth(self)
+
+ def from_statement(
+ self, statement: roles.ReturnsRowsRole
+ ) -> ExecutableReturnsRows:
+ """Apply the columns which this :class:`.Select` would select
+ onto another statement.
+
+ This operation is :term:`plugin-specific` and will raise a not
+ supported exception if this :class:`_sql.Select` does not select from
+ plugin-enabled entities.
+
+
+ The statement is typically either a :func:`_expression.text` or
+ :func:`_expression.select` construct, and should return the set of
+ columns appropriate to the entities represented by this
+ :class:`.Select`.
+
+ .. seealso::
+
+ :ref:`orm_queryguide_selecting_text` - usage examples in the
+ ORM Querying Guide
+
+ """
+ meth = SelectState.get_plugin_class(self).from_statement
+ return meth(self, statement)
+
+ @_generative
+ def join(
+ self,
+ target: _JoinTargetArgument,
+ onclause: Optional[_OnClauseArgument] = None,
+ *,
+ isouter: bool = False,
+ full: bool = False,
+ ) -> Self:
+ r"""Create a SQL JOIN against this :class:`_expression.Select`
+ object's criterion
+ and apply generatively, returning the newly resulting
+ :class:`_expression.Select`.
+
+ E.g.::
+
+ stmt = select(user_table).join(address_table, user_table.c.id == address_table.c.user_id)
+
+ The above statement generates SQL similar to::
+
+ SELECT user.id, user.name FROM user JOIN address ON user.id = address.user_id
+
+ .. versionchanged:: 1.4 :meth:`_expression.Select.join` now creates
+ a :class:`_sql.Join` object between a :class:`_sql.FromClause`
+ source that is within the FROM clause of the existing SELECT,
+ and a given target :class:`_sql.FromClause`, and then adds
+ this :class:`_sql.Join` to the FROM clause of the newly generated
+ SELECT statement. This is completely reworked from the behavior
+ in 1.3, which would instead create a subquery of the entire
+ :class:`_expression.Select` and then join that subquery to the
+ target.
+
+ This is a **backwards incompatible change** as the previous behavior
+ was mostly useless, producing an unnamed subquery rejected by
+ most databases in any case. The new behavior is modeled after
+ that of the very successful :meth:`_orm.Query.join` method in the
+ ORM, in order to support the functionality of :class:`_orm.Query`
+ being available by using a :class:`_sql.Select` object with an
+ :class:`_orm.Session`.
+
+ See the notes for this change at :ref:`change_select_join`.
+
+
+ :param target: target table to join towards
+
+ :param onclause: ON clause of the join. If omitted, an ON clause
+ is generated automatically based on the :class:`_schema.ForeignKey`
+ linkages between the two tables, if one can be unambiguously
+ determined, otherwise an error is raised.
+
+ :param isouter: if True, generate LEFT OUTER join. Same as
+ :meth:`_expression.Select.outerjoin`.
+
+ :param full: if True, generate FULL OUTER join.
+
+ .. seealso::
+
+ :ref:`tutorial_select_join` - in the :doc:`/tutorial/index`
+
+ :ref:`orm_queryguide_joins` - in the :ref:`queryguide_toplevel`
+
+ :meth:`_expression.Select.join_from`
+
+ :meth:`_expression.Select.outerjoin`
+
+ """ # noqa: E501
+ join_target = coercions.expect(
+ roles.JoinTargetRole, target, apply_propagate_attrs=self
+ )
+ if onclause is not None:
+ onclause_element = coercions.expect(roles.OnClauseRole, onclause)
+ else:
+ onclause_element = None
+
+ self._setup_joins += (
+ (
+ join_target,
+ onclause_element,
+ None,
+ {"isouter": isouter, "full": full},
+ ),
+ )
+ return self
+
+ def outerjoin_from(
+ self,
+ from_: _FromClauseArgument,
+ target: _JoinTargetArgument,
+ onclause: Optional[_OnClauseArgument] = None,
+ *,
+ full: bool = False,
+ ) -> Self:
+ r"""Create a SQL LEFT OUTER JOIN against this
+ :class:`_expression.Select` object's criterion and apply generatively,
+ returning the newly resulting :class:`_expression.Select`.
+
+ Usage is the same as that of :meth:`_selectable.Select.join_from`.
+
+ """
+ return self.join_from(
+ from_, target, onclause=onclause, isouter=True, full=full
+ )
+
+ @_generative
+ def join_from(
+ self,
+ from_: _FromClauseArgument,
+ target: _JoinTargetArgument,
+ onclause: Optional[_OnClauseArgument] = None,
+ *,
+ isouter: bool = False,
+ full: bool = False,
+ ) -> Self:
+ r"""Create a SQL JOIN against this :class:`_expression.Select`
+ object's criterion
+ and apply generatively, returning the newly resulting
+ :class:`_expression.Select`.
+
+ E.g.::
+
+ stmt = select(user_table, address_table).join_from(
+ user_table, address_table, user_table.c.id == address_table.c.user_id
+ )
+
+ The above statement generates SQL similar to::
+
+ SELECT user.id, user.name, address.id, address.email, address.user_id
+ FROM user JOIN address ON user.id = address.user_id
+
+ .. versionadded:: 1.4
+
+ :param from\_: the left side of the join, will be rendered in the
+ FROM clause and is roughly equivalent to using the
+ :meth:`.Select.select_from` method.
+
+ :param target: target table to join towards
+
+ :param onclause: ON clause of the join.
+
+ :param isouter: if True, generate LEFT OUTER join. Same as
+ :meth:`_expression.Select.outerjoin`.
+
+ :param full: if True, generate FULL OUTER join.
+
+ .. seealso::
+
+ :ref:`tutorial_select_join` - in the :doc:`/tutorial/index`
+
+ :ref:`orm_queryguide_joins` - in the :ref:`queryguide_toplevel`
+
+ :meth:`_expression.Select.join`
+
+ """ # noqa: E501
+
+ # note the order of parsing from vs. target is important here, as we
+ # are also deriving the source of the plugin (i.e. the subject mapper
+ # in an ORM query) which should favor the "from_" over the "target"
+
+ from_ = coercions.expect(
+ roles.FromClauseRole, from_, apply_propagate_attrs=self
+ )
+ join_target = coercions.expect(
+ roles.JoinTargetRole, target, apply_propagate_attrs=self
+ )
+ if onclause is not None:
+ onclause_element = coercions.expect(roles.OnClauseRole, onclause)
+ else:
+ onclause_element = None
+
+ self._setup_joins += (
+ (
+ join_target,
+ onclause_element,
+ from_,
+ {"isouter": isouter, "full": full},
+ ),
+ )
+ return self
+
+ def outerjoin(
+ self,
+ target: _JoinTargetArgument,
+ onclause: Optional[_OnClauseArgument] = None,
+ *,
+ full: bool = False,
+ ) -> Self:
+ """Create a left outer join.
+
+ Parameters are the same as that of :meth:`_expression.Select.join`.
+
+ .. versionchanged:: 1.4 :meth:`_expression.Select.outerjoin` now
+ creates a :class:`_sql.Join` object between a
+ :class:`_sql.FromClause` source that is within the FROM clause of
+ the existing SELECT, and a given target :class:`_sql.FromClause`,
+ and then adds this :class:`_sql.Join` to the FROM clause of the
+ newly generated SELECT statement. This is completely reworked
+ from the behavior in 1.3, which would instead create a subquery of
+ the entire
+ :class:`_expression.Select` and then join that subquery to the
+ target.
+
+ This is a **backwards incompatible change** as the previous behavior
+ was mostly useless, producing an unnamed subquery rejected by
+ most databases in any case. The new behavior is modeled after
+ that of the very successful :meth:`_orm.Query.join` method in the
+ ORM, in order to support the functionality of :class:`_orm.Query`
+ being available by using a :class:`_sql.Select` object with an
+ :class:`_orm.Session`.
+
+ See the notes for this change at :ref:`change_select_join`.
+
+ .. seealso::
+
+ :ref:`tutorial_select_join` - in the :doc:`/tutorial/index`
+
+ :ref:`orm_queryguide_joins` - in the :ref:`queryguide_toplevel`
+
+ :meth:`_expression.Select.join`
+
+ """
+ return self.join(target, onclause=onclause, isouter=True, full=full)
+
+ def get_final_froms(self) -> Sequence[FromClause]:
+ """Compute the final displayed list of :class:`_expression.FromClause`
+ elements.
+
+ This method will run through the full computation required to
+ determine what FROM elements will be displayed in the resulting
+ SELECT statement, including shadowing individual tables with
+ JOIN objects, as well as full computation for ORM use cases including
+ eager loading clauses.
+
+ For ORM use, this accessor returns the **post compilation**
+ list of FROM objects; this collection will include elements such as
+ eagerly loaded tables and joins. The objects will **not** be
+ ORM enabled and not work as a replacement for the
+ :meth:`_sql.Select.select_froms` collection; additionally, the
+ method is not well performing for an ORM enabled statement as it
+ will incur the full ORM construction process.
+
+ To retrieve the FROM list that's implied by the "columns" collection
+ passed to the :class:`_sql.Select` originally, use the
+ :attr:`_sql.Select.columns_clause_froms` accessor.
+
+ To select from an alternative set of columns while maintaining the
+ FROM list, use the :meth:`_sql.Select.with_only_columns` method and
+ pass the
+ :paramref:`_sql.Select.with_only_columns.maintain_column_froms`
+ parameter.
+
+ .. versionadded:: 1.4.23 - the :meth:`_sql.Select.get_final_froms`
+ method replaces the previous :attr:`_sql.Select.froms` accessor,
+ which is deprecated.
+
+ .. seealso::
+
+ :attr:`_sql.Select.columns_clause_froms`
+
+ """
+
+ return self._compile_state_factory(self, None)._get_display_froms()
+
+ @property
+ @util.deprecated(
+ "1.4.23",
+ "The :attr:`_expression.Select.froms` attribute is moved to "
+ "the :meth:`_expression.Select.get_final_froms` method.",
+ )
+ def froms(self) -> Sequence[FromClause]:
+ """Return the displayed list of :class:`_expression.FromClause`
+ elements.
+
+
+ """
+ return self.get_final_froms()
+
+ @property
+ def columns_clause_froms(self) -> List[FromClause]:
+ """Return the set of :class:`_expression.FromClause` objects implied
+ by the columns clause of this SELECT statement.
+
+ .. versionadded:: 1.4.23
+
+ .. seealso::
+
+ :attr:`_sql.Select.froms` - "final" FROM list taking the full
+ statement into account
+
+ :meth:`_sql.Select.with_only_columns` - makes use of this
+ collection to set up a new FROM list
+
+ """
+
+ return SelectState.get_plugin_class(self).get_columns_clause_froms(
+ self
+ )
+
+ @property
+ def inner_columns(self) -> _SelectIterable:
+ """An iterator of all :class:`_expression.ColumnElement`
+ expressions which would
+ be rendered into the columns clause of the resulting SELECT statement.
+
+ This method is legacy as of 1.4 and is superseded by the
+ :attr:`_expression.Select.exported_columns` collection.
+
+ """
+
+ return iter(self._all_selected_columns)
+
+ def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
+ if fromclause is not None and self in fromclause._cloned_set:
+ return True
+
+ for f in self._iterate_from_elements():
+ if f.is_derived_from(fromclause):
+ return True
+ return False
+
+ def _copy_internals(
+ self, clone: _CloneCallableType = _clone, **kw: Any
+ ) -> None:
+ # Select() object has been cloned and probably adapted by the
+ # given clone function. Apply the cloning function to internal
+ # objects
+
+ # 1. keep a dictionary of the froms we've cloned, and what
+ # they've become. This allows us to ensure the same cloned from
+ # is used when other items such as columns are "cloned"
+
+ all_the_froms = set(
+ itertools.chain(
+ _from_objects(*self._raw_columns),
+ _from_objects(*self._where_criteria),
+ _from_objects(*[elem[0] for elem in self._setup_joins]),
+ )
+ )
+
+ # do a clone for the froms we've gathered. what is important here
+ # is if any of the things we are selecting from, like tables,
+ # were converted into Join objects. if so, these need to be
+ # added to _from_obj explicitly, because otherwise they won't be
+ # part of the new state, as they don't associate themselves with
+ # their columns.
+ new_froms = {f: clone(f, **kw) for f in all_the_froms}
+
+ # 2. copy FROM collections, adding in joins that we've created.
+ existing_from_obj = [clone(f, **kw) for f in self._from_obj]
+ add_froms = (
+ {f for f in new_froms.values() if isinstance(f, Join)}
+ .difference(all_the_froms)
+ .difference(existing_from_obj)
+ )
+
+ self._from_obj = tuple(existing_from_obj) + tuple(add_froms)
+
+ # 3. clone everything else, making sure we use columns
+ # corresponding to the froms we just made.
+ def replace(
+ obj: Union[BinaryExpression[Any], ColumnClause[Any]],
+ **kw: Any,
+ ) -> Optional[KeyedColumnElement[ColumnElement[Any]]]:
+ if isinstance(obj, ColumnClause) and obj.table in new_froms:
+ newelem = new_froms[obj.table].corresponding_column(obj)
+ return newelem
+ return None
+
+ kw["replace"] = replace
+
+ # copy everything else. for table-ish things like correlate,
+ # correlate_except, setup_joins, these clone normally. For
+ # column-expression oriented things like raw_columns, where_criteria,
+ # order by, we get this from the new froms.
+ super()._copy_internals(clone=clone, omit_attrs=("_from_obj",), **kw)
+
+ self._reset_memoizations()
+
+ def get_children(self, **kw: Any) -> Iterable[ClauseElement]:
+ return itertools.chain(
+ super().get_children(
+ omit_attrs=("_from_obj", "_correlate", "_correlate_except"),
+ **kw,
+ ),
+ self._iterate_from_elements(),
+ )
+
+ @_generative
+ def add_columns(
+ self, *entities: _ColumnsClauseArgument[Any]
+ ) -> Select[Any]:
+ r"""Return a new :func:`_expression.select` construct with
+ the given entities appended to its columns clause.
+
+ E.g.::
+
+ my_select = my_select.add_columns(table.c.new_column)
+
+ The original expressions in the columns clause remain in place.
+ To replace the original expressions with new ones, see the method
+ :meth:`_expression.Select.with_only_columns`.
+
+ :param \*entities: column, table, or other entity expressions to be
+ added to the columns clause
+
+ .. seealso::
+
+ :meth:`_expression.Select.with_only_columns` - replaces existing
+ expressions rather than appending.
+
+ :ref:`orm_queryguide_select_multiple_entities` - ORM-centric
+ example
+
+ """
+ self._reset_memoizations()
+
+ self._raw_columns = self._raw_columns + [
+ coercions.expect(
+ roles.ColumnsClauseRole, column, apply_propagate_attrs=self
+ )
+ for column in entities
+ ]
+ return self
+
+ def _set_entities(
+ self, entities: Iterable[_ColumnsClauseArgument[Any]]
+ ) -> None:
+ self._raw_columns = [
+ coercions.expect(
+ roles.ColumnsClauseRole, ent, apply_propagate_attrs=self
+ )
+ for ent in util.to_list(entities)
+ ]
+
+ @util.deprecated(
+ "1.4",
+ "The :meth:`_expression.Select.column` method is deprecated and will "
+ "be removed in a future release. Please use "
+ ":meth:`_expression.Select.add_columns`",
+ )
+ def column(self, column: _ColumnsClauseArgument[Any]) -> Select[Any]:
+ """Return a new :func:`_expression.select` construct with
+ the given column expression added to its columns clause.
+
+ E.g.::
+
+ my_select = my_select.column(table.c.new_column)
+
+ See the documentation for
+ :meth:`_expression.Select.with_only_columns`
+ for guidelines on adding /replacing the columns of a
+ :class:`_expression.Select` object.
+
+ """
+ return self.add_columns(column)
+
+ @util.preload_module("sqlalchemy.sql.util")
+ def reduce_columns(self, only_synonyms: bool = True) -> Select[Any]:
+ """Return a new :func:`_expression.select` construct with redundantly
+ named, equivalently-valued columns removed from the columns clause.
+
+ "Redundant" here means two columns where one refers to the
+ other either based on foreign key, or via a simple equality
+ comparison in the WHERE clause of the statement. The primary purpose
+ of this method is to automatically construct a select statement
+ with all uniquely-named columns, without the need to use
+ table-qualified labels as
+ :meth:`_expression.Select.set_label_style`
+ does.
+
+ When columns are omitted based on foreign key, the referred-to
+ column is the one that's kept. When columns are omitted based on
+ WHERE equivalence, the first column in the columns clause is the
+ one that's kept.
+
+ :param only_synonyms: when True, limit the removal of columns
+ to those which have the same name as the equivalent. Otherwise,
+ all columns that are equivalent to another are removed.
+
+ """
+ woc: Select[Any]
+ woc = self.with_only_columns(
+ *util.preloaded.sql_util.reduce_columns(
+ self._all_selected_columns,
+ only_synonyms=only_synonyms,
+ *(self._where_criteria + self._from_obj),
+ )
+ )
+ return woc
+
+ # START OVERLOADED FUNCTIONS self.with_only_columns Select 8
+
+ # code within this block is **programmatically,
+ # statically generated** by tools/generate_sel_v1_overloads.py
+
+ @overload
+ def with_only_columns(self, __ent0: _TCCA[_T0]) -> Select[Tuple[_T0]]: ...
+
+ @overload
+ def with_only_columns(
+ self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1]
+ ) -> Select[Tuple[_T0, _T1]]: ...
+
+ @overload
+ def with_only_columns(
+ self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2]
+ ) -> Select[Tuple[_T0, _T1, _T2]]: ...
+
+ @overload
+ def with_only_columns(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ ) -> Select[Tuple[_T0, _T1, _T2, _T3]]: ...
+
+ @overload
+ def with_only_columns(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ ) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4]]: ...
+
+ @overload
+ def with_only_columns(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ ) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]: ...
+
+ @overload
+ def with_only_columns(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ __ent6: _TCCA[_T6],
+ ) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]: ...
+
+ @overload
+ def with_only_columns(
+ self,
+ __ent0: _TCCA[_T0],
+ __ent1: _TCCA[_T1],
+ __ent2: _TCCA[_T2],
+ __ent3: _TCCA[_T3],
+ __ent4: _TCCA[_T4],
+ __ent5: _TCCA[_T5],
+ __ent6: _TCCA[_T6],
+ __ent7: _TCCA[_T7],
+ ) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]: ...
+
+ # END OVERLOADED FUNCTIONS self.with_only_columns
+
+ @overload
+ def with_only_columns(
+ self,
+ *entities: _ColumnsClauseArgument[Any],
+ maintain_column_froms: bool = False,
+ **__kw: Any,
+ ) -> Select[Any]: ...
+
+ @_generative
+ def with_only_columns(
+ self,
+ *entities: _ColumnsClauseArgument[Any],
+ maintain_column_froms: bool = False,
+ **__kw: Any,
+ ) -> Select[Any]:
+ r"""Return a new :func:`_expression.select` construct with its columns
+ clause replaced with the given entities.
+
+ By default, this method is exactly equivalent to as if the original
+ :func:`_expression.select` had been called with the given entities.
+ E.g. a statement::
+
+ s = select(table1.c.a, table1.c.b)
+ s = s.with_only_columns(table1.c.b)
+
+ should be exactly equivalent to::
+
+ s = select(table1.c.b)
+
+ In this mode of operation, :meth:`_sql.Select.with_only_columns`
+ will also dynamically alter the FROM clause of the
+ statement if it is not explicitly stated.
+ To maintain the existing set of FROMs including those implied by the
+ current columns clause, add the
+ :paramref:`_sql.Select.with_only_columns.maintain_column_froms`
+ parameter::
+
+ s = select(table1.c.a, table2.c.b)
+ s = s.with_only_columns(table1.c.a, maintain_column_froms=True)
+
+ The above parameter performs a transfer of the effective FROMs
+ in the columns collection to the :meth:`_sql.Select.select_from`
+ method, as though the following were invoked::
+
+ s = select(table1.c.a, table2.c.b)
+ s = s.select_from(table1, table2).with_only_columns(table1.c.a)
+
+ The :paramref:`_sql.Select.with_only_columns.maintain_column_froms`
+ parameter makes use of the :attr:`_sql.Select.columns_clause_froms`
+ collection and performs an operation equivalent to the following::
+
+ s = select(table1.c.a, table2.c.b)
+ s = s.select_from(*s.columns_clause_froms).with_only_columns(table1.c.a)
+
+ :param \*entities: column expressions to be used.
+
+ :param maintain_column_froms: boolean parameter that will ensure the
+ FROM list implied from the current columns clause will be transferred
+ to the :meth:`_sql.Select.select_from` method first.
+
+ .. versionadded:: 1.4.23
+
+ """ # noqa: E501
+
+ if __kw:
+ raise _no_kw()
+
+ # memoizations should be cleared here as of
+ # I95c560ffcbfa30b26644999412fb6a385125f663 , asserting this
+ # is the case for now.
+ self._assert_no_memoizations()
+
+ if maintain_column_froms:
+ self.select_from.non_generative( # type: ignore
+ self, *self.columns_clause_froms
+ )
+
+ # then memoize the FROMs etc.
+ _MemoizedSelectEntities._generate_for_statement(self)
+
+ self._raw_columns = [
+ coercions.expect(roles.ColumnsClauseRole, c)
+ for c in coercions._expression_collection_was_a_list(
+ "entities", "Select.with_only_columns", entities
+ )
+ ]
+ return self
+
+ @property
+ def whereclause(self) -> Optional[ColumnElement[Any]]:
+ """Return the completed WHERE clause for this
+ :class:`_expression.Select` statement.
+
+ This assembles the current collection of WHERE criteria
+ into a single :class:`_expression.BooleanClauseList` construct.
+
+
+ .. versionadded:: 1.4
+
+ """
+
+ return BooleanClauseList._construct_for_whereclause(
+ self._where_criteria
+ )
+
+ _whereclause = whereclause
+
+ @_generative
+ def where(self, *whereclause: _ColumnExpressionArgument[bool]) -> Self:
+ """Return a new :func:`_expression.select` construct with
+ the given expression added to
+ its WHERE clause, joined to the existing clause via AND, if any.
+
+ """
+
+ assert isinstance(self._where_criteria, tuple)
+
+ for criterion in whereclause:
+ where_criteria: ColumnElement[Any] = coercions.expect(
+ roles.WhereHavingRole, criterion, apply_propagate_attrs=self
+ )
+ self._where_criteria += (where_criteria,)
+ return self
+
+ @_generative
+ def having(self, *having: _ColumnExpressionArgument[bool]) -> Self:
+ """Return a new :func:`_expression.select` construct with
+ the given expression added to
+ its HAVING clause, joined to the existing clause via AND, if any.
+
+ """
+
+ for criterion in having:
+ having_criteria = coercions.expect(
+ roles.WhereHavingRole, criterion, apply_propagate_attrs=self
+ )
+ self._having_criteria += (having_criteria,)
+ return self
+
+ @_generative
+ def distinct(self, *expr: _ColumnExpressionArgument[Any]) -> Self:
+ r"""Return a new :func:`_expression.select` construct which
+ will apply DISTINCT to its columns clause.
+
+ :param \*expr: optional column expressions. When present,
+ the PostgreSQL dialect will render a ``DISTINCT ON (<expressions>>)``
+ construct.
+
+ .. deprecated:: 1.4 Using \*expr in other dialects is deprecated
+ and will raise :class:`_exc.CompileError` in a future version.
+
+ """
+ if expr:
+ self._distinct = True
+ self._distinct_on = self._distinct_on + tuple(
+ coercions.expect(roles.ByOfRole, e, apply_propagate_attrs=self)
+ for e in expr
+ )
+ else:
+ self._distinct = True
+ return self
+
+ @_generative
+ def select_from(self, *froms: _FromClauseArgument) -> Self:
+ r"""Return a new :func:`_expression.select` construct with the
+ given FROM expression(s)
+ merged into its list of FROM objects.
+
+ E.g.::
+
+ table1 = table('t1', column('a'))
+ table2 = table('t2', column('b'))
+ s = select(table1.c.a).\
+ select_from(
+ table1.join(table2, table1.c.a==table2.c.b)
+ )
+
+ The "from" list is a unique set on the identity of each element,
+ so adding an already present :class:`_schema.Table`
+ or other selectable
+ will have no effect. Passing a :class:`_expression.Join` that refers
+ to an already present :class:`_schema.Table`
+ or other selectable will have
+ the effect of concealing the presence of that selectable as
+ an individual element in the rendered FROM list, instead
+ rendering it into a JOIN clause.
+
+ While the typical purpose of :meth:`_expression.Select.select_from`
+ is to
+ replace the default, derived FROM clause with a join, it can
+ also be called with individual table elements, multiple times
+ if desired, in the case that the FROM clause cannot be fully
+ derived from the columns clause::
+
+ select(func.count('*')).select_from(table1)
+
+ """
+
+ self._from_obj += tuple(
+ coercions.expect(
+ roles.FromClauseRole, fromclause, apply_propagate_attrs=self
+ )
+ for fromclause in froms
+ )
+ return self
+
+ @_generative
+ def correlate(
+ self,
+ *fromclauses: Union[Literal[None, False], _FromClauseArgument],
+ ) -> Self:
+ r"""Return a new :class:`_expression.Select`
+ which will correlate the given FROM
+ clauses to that of an enclosing :class:`_expression.Select`.
+
+ Calling this method turns off the :class:`_expression.Select` object's
+ default behavior of "auto-correlation". Normally, FROM elements
+ which appear in a :class:`_expression.Select`
+ that encloses this one via
+ its :term:`WHERE clause`, ORDER BY, HAVING or
+ :term:`columns clause` will be omitted from this
+ :class:`_expression.Select`
+ object's :term:`FROM clause`.
+ Setting an explicit correlation collection using the
+ :meth:`_expression.Select.correlate`
+ method provides a fixed list of FROM objects
+ that can potentially take place in this process.
+
+ When :meth:`_expression.Select.correlate`
+ is used to apply specific FROM clauses
+ for correlation, the FROM elements become candidates for
+ correlation regardless of how deeply nested this
+ :class:`_expression.Select`
+ object is, relative to an enclosing :class:`_expression.Select`
+ which refers to
+ the same FROM object. This is in contrast to the behavior of
+ "auto-correlation" which only correlates to an immediate enclosing
+ :class:`_expression.Select`.
+ Multi-level correlation ensures that the link
+ between enclosed and enclosing :class:`_expression.Select`
+ is always via
+ at least one WHERE/ORDER BY/HAVING/columns clause in order for
+ correlation to take place.
+
+ If ``None`` is passed, the :class:`_expression.Select`
+ object will correlate
+ none of its FROM entries, and all will render unconditionally
+ in the local FROM clause.
+
+ :param \*fromclauses: one or more :class:`.FromClause` or other
+ FROM-compatible construct such as an ORM mapped entity to become part
+ of the correlate collection; alternatively pass a single value
+ ``None`` to remove all existing correlations.
+
+ .. seealso::
+
+ :meth:`_expression.Select.correlate_except`
+
+ :ref:`tutorial_scalar_subquery`
+
+ """
+
+ # tests failing when we try to change how these
+ # arguments are passed
+
+ self._auto_correlate = False
+ if not fromclauses or fromclauses[0] in {None, False}:
+ if len(fromclauses) > 1:
+ raise exc.ArgumentError(
+ "additional FROM objects not accepted when "
+ "passing None/False to correlate()"
+ )
+ self._correlate = ()
+ else:
+ self._correlate = self._correlate + tuple(
+ coercions.expect(roles.FromClauseRole, f) for f in fromclauses
+ )
+ return self
+
+ @_generative
+ def correlate_except(
+ self,
+ *fromclauses: Union[Literal[None, False], _FromClauseArgument],
+ ) -> Self:
+ r"""Return a new :class:`_expression.Select`
+ which will omit the given FROM
+ clauses from the auto-correlation process.
+
+ Calling :meth:`_expression.Select.correlate_except` turns off the
+ :class:`_expression.Select` object's default behavior of
+ "auto-correlation" for the given FROM elements. An element
+ specified here will unconditionally appear in the FROM list, while
+ all other FROM elements remain subject to normal auto-correlation
+ behaviors.
+
+ If ``None`` is passed, or no arguments are passed,
+ the :class:`_expression.Select` object will correlate all of its
+ FROM entries.
+
+ :param \*fromclauses: a list of one or more
+ :class:`_expression.FromClause`
+ constructs, or other compatible constructs (i.e. ORM-mapped
+ classes) to become part of the correlate-exception collection.
+
+ .. seealso::
+
+ :meth:`_expression.Select.correlate`
+
+ :ref:`tutorial_scalar_subquery`
+
+ """
+
+ self._auto_correlate = False
+ if not fromclauses or fromclauses[0] in {None, False}:
+ if len(fromclauses) > 1:
+ raise exc.ArgumentError(
+ "additional FROM objects not accepted when "
+ "passing None/False to correlate_except()"
+ )
+ self._correlate_except = ()
+ else:
+ self._correlate_except = (self._correlate_except or ()) + tuple(
+ coercions.expect(roles.FromClauseRole, f) for f in fromclauses
+ )
+
+ return self
+
+ @HasMemoized_ro_memoized_attribute
+ def selected_columns(
+ self,
+ ) -> ColumnCollection[str, ColumnElement[Any]]:
+ """A :class:`_expression.ColumnCollection`
+ representing the columns that
+ this SELECT statement or similar construct returns in its result set,
+ not including :class:`_sql.TextClause` constructs.
+
+ This collection differs from the :attr:`_expression.FromClause.columns`
+ collection of a :class:`_expression.FromClause` in that the columns
+ within this collection cannot be directly nested inside another SELECT
+ statement; a subquery must be applied first which provides for the
+ necessary parenthesization required by SQL.
+
+ For a :func:`_expression.select` construct, the collection here is
+ exactly what would be rendered inside the "SELECT" statement, and the
+ :class:`_expression.ColumnElement` objects are directly present as they
+ were given, e.g.::
+
+ col1 = column('q', Integer)
+ col2 = column('p', Integer)
+ stmt = select(col1, col2)
+
+ Above, ``stmt.selected_columns`` would be a collection that contains
+ the ``col1`` and ``col2`` objects directly. For a statement that is
+ against a :class:`_schema.Table` or other
+ :class:`_expression.FromClause`, the collection will use the
+ :class:`_expression.ColumnElement` objects that are in the
+ :attr:`_expression.FromClause.c` collection of the from element.
+
+ A use case for the :attr:`_sql.Select.selected_columns` collection is
+ to allow the existing columns to be referenced when adding additional
+ criteria, e.g.::
+
+ def filter_on_id(my_select, id):
+ return my_select.where(my_select.selected_columns['id'] == id)
+
+ stmt = select(MyModel)
+
+ # adds "WHERE id=:param" to the statement
+ stmt = filter_on_id(stmt, 42)
+
+ .. note::
+
+ The :attr:`_sql.Select.selected_columns` collection does not
+ include expressions established in the columns clause using the
+ :func:`_sql.text` construct; these are silently omitted from the
+ collection. To use plain textual column expressions inside of a
+ :class:`_sql.Select` construct, use the :func:`_sql.literal_column`
+ construct.
+
+
+ .. versionadded:: 1.4
+
+ """
+
+ # compare to SelectState._generate_columns_plus_names, which
+ # generates the actual names used in the SELECT string. that
+ # method is more complex because it also renders columns that are
+ # fully ambiguous, e.g. same column more than once.
+ conv = cast(
+ "Callable[[Any], str]",
+ SelectState._column_naming_convention(self._label_style),
+ )
+
+ cc: ColumnCollection[str, ColumnElement[Any]] = ColumnCollection(
+ [
+ (conv(c), c)
+ for c in self._all_selected_columns
+ if is_column_element(c)
+ ]
+ )
+ return cc.as_readonly()
+
+ @HasMemoized_ro_memoized_attribute
+ def _all_selected_columns(self) -> _SelectIterable:
+ meth = SelectState.get_plugin_class(self).all_selected_columns
+ return list(meth(self))
+
+ def _ensure_disambiguated_names(self) -> Select[Any]:
+ if self._label_style is LABEL_STYLE_NONE:
+ self = self.set_label_style(LABEL_STYLE_DISAMBIGUATE_ONLY)
+ return self
+
+ def _generate_fromclause_column_proxies(
+ self,
+ subquery: FromClause,
+ *,
+ proxy_compound_columns: Optional[
+ Iterable[Sequence[ColumnElement[Any]]]
+ ] = None,
+ ) -> None:
+ """Generate column proxies to place in the exported ``.c``
+ collection of a subquery."""
+
+ if proxy_compound_columns:
+ extra_col_iterator = proxy_compound_columns
+ prox = [
+ c._make_proxy(
+ subquery,
+ key=proxy_key,
+ name=required_label_name,
+ name_is_truncatable=True,
+ compound_select_cols=extra_cols,
+ )
+ for (
+ (
+ required_label_name,
+ proxy_key,
+ fallback_label_name,
+ c,
+ repeated,
+ ),
+ extra_cols,
+ ) in (
+ zip(
+ self._generate_columns_plus_names(False),
+ extra_col_iterator,
+ )
+ )
+ if is_column_element(c)
+ ]
+ else:
+ prox = [
+ c._make_proxy(
+ subquery,
+ key=proxy_key,
+ name=required_label_name,
+ name_is_truncatable=True,
+ )
+ for (
+ required_label_name,
+ proxy_key,
+ fallback_label_name,
+ c,
+ repeated,
+ ) in (self._generate_columns_plus_names(False))
+ if is_column_element(c)
+ ]
+
+ subquery._columns._populate_separate_keys(prox)
+
+ def _needs_parens_for_grouping(self) -> bool:
+ return self._has_row_limiting_clause or bool(
+ self._order_by_clause.clauses
+ )
+
+ def self_group(
+ self, against: Optional[OperatorType] = None
+ ) -> Union[SelectStatementGrouping[Self], Self]:
+ ...
+ """Return a 'grouping' construct as per the
+ :class:`_expression.ClauseElement` specification.
+
+ This produces an element that can be embedded in an expression. Note
+ that this method is called automatically as needed when constructing
+ expressions and should not require explicit use.
+
+ """
+ if (
+ isinstance(against, CompoundSelect)
+ and not self._needs_parens_for_grouping()
+ ):
+ return self
+ else:
+ return SelectStatementGrouping(self)
+
+ def union(
+ self, *other: _SelectStatementForCompoundArgument
+ ) -> CompoundSelect:
+ r"""Return a SQL ``UNION`` of this select() construct against
+ the given selectables provided as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
+
+ .. versionchanged:: 1.4.28
+
+ multiple elements are now accepted.
+
+ :param \**kwargs: keyword arguments are forwarded to the constructor
+ for the newly created :class:`_sql.CompoundSelect` object.
+
+ """
+ return CompoundSelect._create_union(self, *other)
+
+ def union_all(
+ self, *other: _SelectStatementForCompoundArgument
+ ) -> CompoundSelect:
+ r"""Return a SQL ``UNION ALL`` of this select() construct against
+ the given selectables provided as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
+
+ .. versionchanged:: 1.4.28
+
+ multiple elements are now accepted.
+
+ :param \**kwargs: keyword arguments are forwarded to the constructor
+ for the newly created :class:`_sql.CompoundSelect` object.
+
+ """
+ return CompoundSelect._create_union_all(self, *other)
+
+ def except_(
+ self, *other: _SelectStatementForCompoundArgument
+ ) -> CompoundSelect:
+ r"""Return a SQL ``EXCEPT`` of this select() construct against
+ the given selectable provided as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
+
+ .. versionchanged:: 1.4.28
+
+ multiple elements are now accepted.
+
+ """
+ return CompoundSelect._create_except(self, *other)
+
+ def except_all(
+ self, *other: _SelectStatementForCompoundArgument
+ ) -> CompoundSelect:
+ r"""Return a SQL ``EXCEPT ALL`` of this select() construct against
+ the given selectables provided as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
+
+ .. versionchanged:: 1.4.28
+
+ multiple elements are now accepted.
+
+ """
+ return CompoundSelect._create_except_all(self, *other)
+
+ def intersect(
+ self, *other: _SelectStatementForCompoundArgument
+ ) -> CompoundSelect:
+ r"""Return a SQL ``INTERSECT`` of this select() construct against
+ the given selectables provided as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
+
+ .. versionchanged:: 1.4.28
+
+ multiple elements are now accepted.
+
+ :param \**kwargs: keyword arguments are forwarded to the constructor
+ for the newly created :class:`_sql.CompoundSelect` object.
+
+ """
+ return CompoundSelect._create_intersect(self, *other)
+
+ def intersect_all(
+ self, *other: _SelectStatementForCompoundArgument
+ ) -> CompoundSelect:
+ r"""Return a SQL ``INTERSECT ALL`` of this select() construct
+ against the given selectables provided as positional arguments.
+
+ :param \*other: one or more elements with which to create a
+ UNION.
+
+ .. versionchanged:: 1.4.28
+
+ multiple elements are now accepted.
+
+ :param \**kwargs: keyword arguments are forwarded to the constructor
+ for the newly created :class:`_sql.CompoundSelect` object.
+
+ """
+ return CompoundSelect._create_intersect_all(self, *other)
+
+
+class ScalarSelect(
+ roles.InElementRole, Generative, GroupedElement, ColumnElement[_T]
+):
+ """Represent a scalar subquery.
+
+
+ A :class:`_sql.ScalarSelect` is created by invoking the
+ :meth:`_sql.SelectBase.scalar_subquery` method. The object
+ then participates in other SQL expressions as a SQL column expression
+ within the :class:`_sql.ColumnElement` hierarchy.
+
+ .. seealso::
+
+ :meth:`_sql.SelectBase.scalar_subquery`
+
+ :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
+
+ """
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("element", InternalTraversal.dp_clauseelement),
+ ("type", InternalTraversal.dp_type),
+ ]
+
+ _from_objects: List[FromClause] = []
+ _is_from_container = True
+ if not TYPE_CHECKING:
+ _is_implicitly_boolean = False
+ inherit_cache = True
+
+ element: SelectBase
+
+ def __init__(self, element: SelectBase) -> None:
+ self.element = element
+ self.type = element._scalar_type()
+ self._propagate_attrs = element._propagate_attrs
+
+ def __getattr__(self, attr: str) -> Any:
+ return getattr(self.element, attr)
+
+ def __getstate__(self) -> Dict[str, Any]:
+ return {"element": self.element, "type": self.type}
+
+ def __setstate__(self, state: Dict[str, Any]) -> None:
+ self.element = state["element"]
+ self.type = state["type"]
+
+ @property
+ def columns(self) -> NoReturn:
+ raise exc.InvalidRequestError(
+ "Scalar Select expression has no "
+ "columns; use this object directly "
+ "within a column-level expression."
+ )
+
+ c = columns
+
+ @_generative
+ def where(self, crit: _ColumnExpressionArgument[bool]) -> Self:
+ """Apply a WHERE clause to the SELECT statement referred to
+ by this :class:`_expression.ScalarSelect`.
+
+ """
+ self.element = cast("Select[Any]", self.element).where(crit)
+ return self
+
+ @overload
+ def self_group(
+ self: ScalarSelect[Any], against: Optional[OperatorType] = None
+ ) -> ScalarSelect[Any]: ...
+
+ @overload
+ def self_group(
+ self: ColumnElement[Any], against: Optional[OperatorType] = None
+ ) -> ColumnElement[Any]: ...
+
+ def self_group(
+ self, against: Optional[OperatorType] = None
+ ) -> ColumnElement[Any]:
+ return self
+
+ if TYPE_CHECKING:
+
+ def _ungroup(self) -> Select[Any]: ...
+
+ @_generative
+ def correlate(
+ self,
+ *fromclauses: Union[Literal[None, False], _FromClauseArgument],
+ ) -> Self:
+ r"""Return a new :class:`_expression.ScalarSelect`
+ which will correlate the given FROM
+ clauses to that of an enclosing :class:`_expression.Select`.
+
+ This method is mirrored from the :meth:`_sql.Select.correlate` method
+ of the underlying :class:`_sql.Select`. The method applies the
+ :meth:_sql.Select.correlate` method, then returns a new
+ :class:`_sql.ScalarSelect` against that statement.
+
+ .. versionadded:: 1.4 Previously, the
+ :meth:`_sql.ScalarSelect.correlate`
+ method was only available from :class:`_sql.Select`.
+
+ :param \*fromclauses: a list of one or more
+ :class:`_expression.FromClause`
+ constructs, or other compatible constructs (i.e. ORM-mapped
+ classes) to become part of the correlate collection.
+
+ .. seealso::
+
+ :meth:`_expression.ScalarSelect.correlate_except`
+
+ :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
+
+
+ """
+ self.element = cast("Select[Any]", self.element).correlate(
+ *fromclauses
+ )
+ return self
+
+ @_generative
+ def correlate_except(
+ self,
+ *fromclauses: Union[Literal[None, False], _FromClauseArgument],
+ ) -> Self:
+ r"""Return a new :class:`_expression.ScalarSelect`
+ which will omit the given FROM
+ clauses from the auto-correlation process.
+
+ This method is mirrored from the
+ :meth:`_sql.Select.correlate_except` method of the underlying
+ :class:`_sql.Select`. The method applies the
+ :meth:_sql.Select.correlate_except` method, then returns a new
+ :class:`_sql.ScalarSelect` against that statement.
+
+ .. versionadded:: 1.4 Previously, the
+ :meth:`_sql.ScalarSelect.correlate_except`
+ method was only available from :class:`_sql.Select`.
+
+ :param \*fromclauses: a list of one or more
+ :class:`_expression.FromClause`
+ constructs, or other compatible constructs (i.e. ORM-mapped
+ classes) to become part of the correlate-exception collection.
+
+ .. seealso::
+
+ :meth:`_expression.ScalarSelect.correlate`
+
+ :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
+
+
+ """
+
+ self.element = cast("Select[Any]", self.element).correlate_except(
+ *fromclauses
+ )
+ return self
+
+
+class Exists(UnaryExpression[bool]):
+ """Represent an ``EXISTS`` clause.
+
+ See :func:`_sql.exists` for a description of usage.
+
+ An ``EXISTS`` clause can also be constructed from a :func:`_sql.select`
+ instance by calling :meth:`_sql.SelectBase.exists`.
+
+ """
+
+ inherit_cache = True
+ element: Union[SelectStatementGrouping[Select[Any]], ScalarSelect[Any]]
+
+ def __init__(
+ self,
+ __argument: Optional[
+ Union[_ColumnsClauseArgument[Any], SelectBase, ScalarSelect[Any]]
+ ] = None,
+ ):
+ s: ScalarSelect[Any]
+
+ # TODO: this seems like we should be using coercions for this
+ if __argument is None:
+ s = Select(literal_column("*")).scalar_subquery()
+ elif isinstance(__argument, SelectBase):
+ s = __argument.scalar_subquery()
+ s._propagate_attrs = __argument._propagate_attrs
+ elif isinstance(__argument, ScalarSelect):
+ s = __argument
+ else:
+ s = Select(__argument).scalar_subquery()
+
+ UnaryExpression.__init__(
+ self,
+ s,
+ operator=operators.exists,
+ type_=type_api.BOOLEANTYPE,
+ wraps_column_expression=True,
+ )
+
+ @util.ro_non_memoized_property
+ def _from_objects(self) -> List[FromClause]:
+ return []
+
+ def _regroup(
+ self, fn: Callable[[Select[Any]], Select[Any]]
+ ) -> SelectStatementGrouping[Select[Any]]:
+ element = self.element._ungroup()
+ new_element = fn(element)
+
+ return_value = new_element.self_group(against=operators.exists)
+ assert isinstance(return_value, SelectStatementGrouping)
+ return return_value
+
+ def select(self) -> Select[Any]:
+ r"""Return a SELECT of this :class:`_expression.Exists`.
+
+ e.g.::
+
+ stmt = exists(some_table.c.id).where(some_table.c.id == 5).select()
+
+ This will produce a statement resembling::
+
+ SELECT EXISTS (SELECT id FROM some_table WHERE some_table = :param) AS anon_1
+
+ .. seealso::
+
+ :func:`_expression.select` - general purpose
+ method which allows for arbitrary column lists.
+
+ """ # noqa
+
+ return Select(self)
+
+ def correlate(
+ self,
+ *fromclauses: Union[Literal[None, False], _FromClauseArgument],
+ ) -> Self:
+ """Apply correlation to the subquery noted by this
+ :class:`_sql.Exists`.
+
+ .. seealso::
+
+ :meth:`_sql.ScalarSelect.correlate`
+
+ """
+ e = self._clone()
+ e.element = self._regroup(
+ lambda element: element.correlate(*fromclauses)
+ )
+ return e
+
+ def correlate_except(
+ self,
+ *fromclauses: Union[Literal[None, False], _FromClauseArgument],
+ ) -> Self:
+ """Apply correlation to the subquery noted by this
+ :class:`_sql.Exists`.
+
+ .. seealso::
+
+ :meth:`_sql.ScalarSelect.correlate_except`
+
+ """
+
+ e = self._clone()
+ e.element = self._regroup(
+ lambda element: element.correlate_except(*fromclauses)
+ )
+ return e
+
+ def select_from(self, *froms: _FromClauseArgument) -> Self:
+ """Return a new :class:`_expression.Exists` construct,
+ applying the given
+ expression to the :meth:`_expression.Select.select_from`
+ method of the select
+ statement contained.
+
+ .. note:: it is typically preferable to build a :class:`_sql.Select`
+ statement first, including the desired WHERE clause, then use the
+ :meth:`_sql.SelectBase.exists` method to produce an
+ :class:`_sql.Exists` object at once.
+
+ """
+ e = self._clone()
+ e.element = self._regroup(lambda element: element.select_from(*froms))
+ return e
+
+ def where(self, *clause: _ColumnExpressionArgument[bool]) -> Self:
+ """Return a new :func:`_expression.exists` construct with the
+ given expression added to
+ its WHERE clause, joined to the existing clause via AND, if any.
+
+
+ .. note:: it is typically preferable to build a :class:`_sql.Select`
+ statement first, including the desired WHERE clause, then use the
+ :meth:`_sql.SelectBase.exists` method to produce an
+ :class:`_sql.Exists` object at once.
+
+ """
+ e = self._clone()
+ e.element = self._regroup(lambda element: element.where(*clause))
+ return e
+
+
+class TextualSelect(SelectBase, ExecutableReturnsRows, Generative):
+ """Wrap a :class:`_expression.TextClause` construct within a
+ :class:`_expression.SelectBase`
+ interface.
+
+ This allows the :class:`_expression.TextClause` object to gain a
+ ``.c`` collection
+ and other FROM-like capabilities such as
+ :meth:`_expression.FromClause.alias`,
+ :meth:`_expression.SelectBase.cte`, etc.
+
+ The :class:`_expression.TextualSelect` construct is produced via the
+ :meth:`_expression.TextClause.columns`
+ method - see that method for details.
+
+ .. versionchanged:: 1.4 the :class:`_expression.TextualSelect`
+ class was renamed
+ from ``TextAsFrom``, to more correctly suit its role as a
+ SELECT-oriented object and not a FROM clause.
+
+ .. seealso::
+
+ :func:`_expression.text`
+
+ :meth:`_expression.TextClause.columns` - primary creation interface.
+
+ """
+
+ __visit_name__ = "textual_select"
+
+ _label_style = LABEL_STYLE_NONE
+
+ _traverse_internals: _TraverseInternalsType = [
+ ("element", InternalTraversal.dp_clauseelement),
+ ("column_args", InternalTraversal.dp_clauseelement_list),
+ ] + SupportsCloneAnnotations._clone_annotations_traverse_internals
+
+ _is_textual = True
+
+ is_text = True
+ is_select = True
+
+ def __init__(
+ self,
+ text: TextClause,
+ columns: List[_ColumnExpressionArgument[Any]],
+ positional: bool = False,
+ ) -> None:
+ self._init(
+ text,
+ # convert for ORM attributes->columns, etc
+ [
+ coercions.expect(roles.LabeledColumnExprRole, c)
+ for c in columns
+ ],
+ positional,
+ )
+
+ def _init(
+ self,
+ text: TextClause,
+ columns: List[NamedColumn[Any]],
+ positional: bool = False,
+ ) -> None:
+ self.element = text
+ self.column_args = columns
+ self.positional = positional
+
+ @HasMemoized_ro_memoized_attribute
+ def selected_columns(
+ self,
+ ) -> ColumnCollection[str, KeyedColumnElement[Any]]:
+ """A :class:`_expression.ColumnCollection`
+ representing the columns that
+ this SELECT statement or similar construct returns in its result set,
+ not including :class:`_sql.TextClause` constructs.
+
+ This collection differs from the :attr:`_expression.FromClause.columns`
+ collection of a :class:`_expression.FromClause` in that the columns
+ within this collection cannot be directly nested inside another SELECT
+ statement; a subquery must be applied first which provides for the
+ necessary parenthesization required by SQL.
+
+ For a :class:`_expression.TextualSelect` construct, the collection
+ contains the :class:`_expression.ColumnElement` objects that were
+ passed to the constructor, typically via the
+ :meth:`_expression.TextClause.columns` method.
+
+
+ .. versionadded:: 1.4
+
+ """
+ return ColumnCollection(
+ (c.key, c) for c in self.column_args
+ ).as_readonly()
+
+ @util.ro_non_memoized_property
+ def _all_selected_columns(self) -> _SelectIterable:
+ return self.column_args
+
+ def set_label_style(self, style: SelectLabelStyle) -> TextualSelect:
+ return self
+
+ def _ensure_disambiguated_names(self) -> TextualSelect:
+ return self
+
+ @_generative
+ def bindparams(
+ self,
+ *binds: BindParameter[Any],
+ **bind_as_values: Any,
+ ) -> Self:
+ self.element = self.element.bindparams(*binds, **bind_as_values)
+ return self
+
+ def _generate_fromclause_column_proxies(
+ self,
+ fromclause: FromClause,
+ *,
+ proxy_compound_columns: Optional[
+ Iterable[Sequence[ColumnElement[Any]]]
+ ] = None,
+ ) -> None:
+ if TYPE_CHECKING:
+ assert isinstance(fromclause, Subquery)
+
+ if proxy_compound_columns:
+ fromclause._columns._populate_separate_keys(
+ c._make_proxy(fromclause, compound_select_cols=extra_cols)
+ for c, extra_cols in zip(
+ self.column_args, proxy_compound_columns
+ )
+ )
+ else:
+ fromclause._columns._populate_separate_keys(
+ c._make_proxy(fromclause) for c in self.column_args
+ )
+
+ def _scalar_type(self) -> Union[TypeEngine[Any], Any]:
+ return self.column_args[0].type
+
+
+TextAsFrom = TextualSelect
+"""Backwards compatibility with the previous name"""
+
+
+class AnnotatedFromClause(Annotated):
+ def _copy_internals(self, **kw: Any) -> None:
+ super()._copy_internals(**kw)
+ if kw.get("ind_cols_on_fromclause", False):
+ ee = self._Annotated__element # type: ignore
+
+ self.c = ee.__class__.c.fget(self) # type: ignore
+
+ @util.ro_memoized_property
+ def c(self) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+ """proxy the .c collection of the underlying FromClause.
+
+ Originally implemented in 2008 as a simple load of the .c collection
+ when the annotated construct was created (see d3621ae961a), in modern
+ SQLAlchemy versions this can be expensive for statements constructed
+ with ORM aliases. So for #8796 SQLAlchemy 2.0 we instead proxy
+ it, which works just as well.
+
+ Two different use cases seem to require the collection either copied
+ from the underlying one, or unique to this AnnotatedFromClause.
+
+ See test_selectable->test_annotated_corresponding_column
+
+ """
+ ee = self._Annotated__element # type: ignore
+ return ee.c # type: ignore
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/sqltypes.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/sqltypes.py
new file mode 100644
index 0000000..1af3c5e
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/sqltypes.py
@@ -0,0 +1,3786 @@
+# sql/sqltypes.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+# mypy: allow-untyped-defs, allow-untyped-calls
+
+"""SQL specific types.
+
+"""
+from __future__ import annotations
+
+import collections.abc as collections_abc
+import datetime as dt
+import decimal
+import enum
+import json
+import pickle
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import overload
+from typing import Sequence
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+from uuid import UUID as _python_UUID
+
+from . import coercions
+from . import elements
+from . import operators
+from . import roles
+from . import type_api
+from .base import _NONE_NAME
+from .base import NO_ARG
+from .base import SchemaEventTarget
+from .cache_key import HasCacheKey
+from .elements import quoted_name
+from .elements import Slice
+from .elements import TypeCoerce as type_coerce # noqa
+from .type_api import Emulated
+from .type_api import NativeForEmulated # noqa
+from .type_api import to_instance as to_instance
+from .type_api import TypeDecorator as TypeDecorator
+from .type_api import TypeEngine as TypeEngine
+from .type_api import TypeEngineMixin
+from .type_api import Variant # noqa
+from .visitors import InternalTraversal
+from .. import event
+from .. import exc
+from .. import inspection
+from .. import util
+from ..engine import processors
+from ..util import langhelpers
+from ..util import OrderedDict
+from ..util.typing import is_literal
+from ..util.typing import Literal
+from ..util.typing import typing_get_args
+
+if TYPE_CHECKING:
+ from ._typing import _ColumnExpressionArgument
+ from ._typing import _TypeEngineArgument
+ from .operators import OperatorType
+ from .schema import MetaData
+ from .type_api import _BindProcessorType
+ from .type_api import _ComparatorFactory
+ from .type_api import _MatchedOnType
+ from .type_api import _ResultProcessorType
+ from ..engine.interfaces import Dialect
+
+_T = TypeVar("_T", bound="Any")
+_CT = TypeVar("_CT", bound=Any)
+_TE = TypeVar("_TE", bound="TypeEngine[Any]")
+
+
+class HasExpressionLookup(TypeEngineMixin):
+ """Mixin expression adaptations based on lookup tables.
+
+ These rules are currently used by the numeric, integer and date types
+ which have detailed cross-expression coercion rules.
+
+ """
+
+ @property
+ def _expression_adaptations(self):
+ raise NotImplementedError()
+
+ class Comparator(TypeEngine.Comparator[_CT]):
+ __slots__ = ()
+
+ _blank_dict = util.EMPTY_DICT
+
+ def _adapt_expression(
+ self,
+ op: OperatorType,
+ other_comparator: TypeEngine.Comparator[Any],
+ ) -> Tuple[OperatorType, TypeEngine[Any]]:
+ othertype = other_comparator.type._type_affinity
+ if TYPE_CHECKING:
+ assert isinstance(self.type, HasExpressionLookup)
+ lookup = self.type._expression_adaptations.get(
+ op, self._blank_dict
+ ).get(othertype, self.type)
+ if lookup is othertype:
+ return (op, other_comparator.type)
+ elif lookup is self.type._type_affinity:
+ return (op, self.type)
+ else:
+ return (op, to_instance(lookup))
+
+ comparator_factory: _ComparatorFactory[Any] = Comparator
+
+
+class Concatenable(TypeEngineMixin):
+ """A mixin that marks a type as supporting 'concatenation',
+ typically strings."""
+
+ class Comparator(TypeEngine.Comparator[_T]):
+ __slots__ = ()
+
+ def _adapt_expression(
+ self,
+ op: OperatorType,
+ other_comparator: TypeEngine.Comparator[Any],
+ ) -> Tuple[OperatorType, TypeEngine[Any]]:
+ if op is operators.add and isinstance(
+ other_comparator,
+ (Concatenable.Comparator, NullType.Comparator),
+ ):
+ return operators.concat_op, self.expr.type
+ else:
+ return super()._adapt_expression(op, other_comparator)
+
+ comparator_factory: _ComparatorFactory[Any] = Comparator
+
+
+class Indexable(TypeEngineMixin):
+ """A mixin that marks a type as supporting indexing operations,
+ such as array or JSON structures.
+
+ """
+
+ class Comparator(TypeEngine.Comparator[_T]):
+ __slots__ = ()
+
+ def _setup_getitem(self, index):
+ raise NotImplementedError()
+
+ def __getitem__(self, index):
+ (
+ adjusted_op,
+ adjusted_right_expr,
+ result_type,
+ ) = self._setup_getitem(index)
+ return self.operate(
+ adjusted_op, adjusted_right_expr, result_type=result_type
+ )
+
+ comparator_factory: _ComparatorFactory[Any] = Comparator
+
+
+class String(Concatenable, TypeEngine[str]):
+ """The base for all string and character types.
+
+ In SQL, corresponds to VARCHAR.
+
+ The `length` field is usually required when the `String` type is
+ used within a CREATE TABLE statement, as VARCHAR requires a length
+ on most databases.
+
+ """
+
+ __visit_name__ = "string"
+
+ def __init__(
+ self,
+ length: Optional[int] = None,
+ collation: Optional[str] = None,
+ ):
+ """
+ Create a string-holding type.
+
+ :param length: optional, a length for the column for use in
+ DDL and CAST expressions. May be safely omitted if no ``CREATE
+ TABLE`` will be issued. Certain databases may require a
+ ``length`` for use in DDL, and will raise an exception when
+ the ``CREATE TABLE`` DDL is issued if a ``VARCHAR``
+ with no length is included. Whether the value is
+ interpreted as bytes or characters is database specific.
+
+ :param collation: Optional, a column-level collation for
+ use in DDL and CAST expressions. Renders using the
+ COLLATE keyword supported by SQLite, MySQL, and PostgreSQL.
+ E.g.:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import cast, select, String
+ >>> print(select(cast('some string', String(collation='utf8'))))
+ {printsql}SELECT CAST(:param_1 AS VARCHAR COLLATE utf8) AS anon_1
+
+ .. note::
+
+ In most cases, the :class:`.Unicode` or :class:`.UnicodeText`
+ datatypes should be used for a :class:`_schema.Column` that expects
+ to store non-ascii data. These datatypes will ensure that the
+ correct types are used on the database.
+
+ """
+
+ self.length = length
+ self.collation = collation
+
+ def _resolve_for_literal(self, value):
+ # I was SO PROUD of my regex trick, but we dont need it.
+ # re.search(r"[^\u0000-\u007F]", value)
+
+ if value.isascii():
+ return _STRING
+ else:
+ return _UNICODE
+
+ def literal_processor(self, dialect):
+ def process(value):
+ value = value.replace("'", "''")
+
+ if dialect.identifier_preparer._double_percents:
+ value = value.replace("%", "%%")
+
+ return "'%s'" % value
+
+ return process
+
+ def bind_processor(self, dialect):
+ return None
+
+ def result_processor(self, dialect, coltype):
+ return None
+
+ @property
+ def python_type(self):
+ return str
+
+ def get_dbapi_type(self, dbapi):
+ return dbapi.STRING
+
+
+class Text(String):
+ """A variably sized string type.
+
+ In SQL, usually corresponds to CLOB or TEXT. In general, TEXT objects
+ do not have a length; while some databases will accept a length
+ argument here, it will be rejected by others.
+
+ """
+
+ __visit_name__ = "text"
+
+
+class Unicode(String):
+ """A variable length Unicode string type.
+
+ The :class:`.Unicode` type is a :class:`.String` subclass that assumes
+ input and output strings that may contain non-ASCII characters, and for
+ some backends implies an underlying column type that is explicitly
+ supporting of non-ASCII data, such as ``NVARCHAR`` on Oracle and SQL
+ Server. This will impact the output of ``CREATE TABLE`` statements and
+ ``CAST`` functions at the dialect level.
+
+ The character encoding used by the :class:`.Unicode` type that is used to
+ transmit and receive data to the database is usually determined by the
+ DBAPI itself. All modern DBAPIs accommodate non-ASCII strings but may have
+ different methods of managing database encodings; if necessary, this
+ encoding should be configured as detailed in the notes for the target DBAPI
+ in the :ref:`dialect_toplevel` section.
+
+ In modern SQLAlchemy, use of the :class:`.Unicode` datatype does not
+ imply any encoding/decoding behavior within SQLAlchemy itself. In Python
+ 3, all string objects are inherently Unicode capable, and SQLAlchemy
+ does not produce bytestring objects nor does it accommodate a DBAPI that
+ does not return Python Unicode objects in result sets for string values.
+
+ .. warning:: Some database backends, particularly SQL Server with pyodbc,
+ are known to have undesirable behaviors regarding data that is noted
+ as being of ``NVARCHAR`` type as opposed to ``VARCHAR``, including
+ datatype mismatch errors and non-use of indexes. See the section
+ on :meth:`.DialectEvents.do_setinputsizes` for background on working
+ around unicode character issues for backends like SQL Server with
+ pyodbc as well as cx_Oracle.
+
+ .. seealso::
+
+ :class:`.UnicodeText` - unlengthed textual counterpart
+ to :class:`.Unicode`.
+
+ :meth:`.DialectEvents.do_setinputsizes`
+
+
+ """
+
+ __visit_name__ = "unicode"
+
+
+class UnicodeText(Text):
+ """An unbounded-length Unicode string type.
+
+ See :class:`.Unicode` for details on the unicode
+ behavior of this object.
+
+ Like :class:`.Unicode`, usage the :class:`.UnicodeText` type implies a
+ unicode-capable type being used on the backend, such as
+ ``NCLOB``, ``NTEXT``.
+
+ """
+
+ __visit_name__ = "unicode_text"
+
+
+class Integer(HasExpressionLookup, TypeEngine[int]):
+ """A type for ``int`` integers."""
+
+ __visit_name__ = "integer"
+
+ if TYPE_CHECKING:
+
+ @util.ro_memoized_property
+ def _type_affinity(self) -> Type[Integer]: ...
+
+ def get_dbapi_type(self, dbapi):
+ return dbapi.NUMBER
+
+ @property
+ def python_type(self):
+ return int
+
+ def _resolve_for_literal(self, value):
+ if value.bit_length() >= 32:
+ return _BIGINTEGER
+ else:
+ return self
+
+ def literal_processor(self, dialect):
+ def process(value):
+ return str(int(value))
+
+ return process
+
+ @util.memoized_property
+ def _expression_adaptations(self):
+ return {
+ operators.add: {
+ Date: Date,
+ Integer: self.__class__,
+ Numeric: Numeric,
+ },
+ operators.mul: {
+ Interval: Interval,
+ Integer: self.__class__,
+ Numeric: Numeric,
+ },
+ operators.truediv: {Integer: Numeric, Numeric: Numeric},
+ operators.floordiv: {Integer: self.__class__, Numeric: Numeric},
+ operators.sub: {Integer: self.__class__, Numeric: Numeric},
+ }
+
+
+class SmallInteger(Integer):
+ """A type for smaller ``int`` integers.
+
+ Typically generates a ``SMALLINT`` in DDL, and otherwise acts like
+ a normal :class:`.Integer` on the Python side.
+
+ """
+
+ __visit_name__ = "small_integer"
+
+
+class BigInteger(Integer):
+ """A type for bigger ``int`` integers.
+
+ Typically generates a ``BIGINT`` in DDL, and otherwise acts like
+ a normal :class:`.Integer` on the Python side.
+
+ """
+
+ __visit_name__ = "big_integer"
+
+
+_N = TypeVar("_N", bound=Union[decimal.Decimal, float])
+
+
+class Numeric(HasExpressionLookup, TypeEngine[_N]):
+ """Base for non-integer numeric types, such as
+ ``NUMERIC``, ``FLOAT``, ``DECIMAL``, and other variants.
+
+ The :class:`.Numeric` datatype when used directly will render DDL
+ corresponding to precision numerics if available, such as
+ ``NUMERIC(precision, scale)``. The :class:`.Float` subclass will
+ attempt to render a floating-point datatype such as ``FLOAT(precision)``.
+
+ :class:`.Numeric` returns Python ``decimal.Decimal`` objects by default,
+ based on the default value of ``True`` for the
+ :paramref:`.Numeric.asdecimal` parameter. If this parameter is set to
+ False, returned values are coerced to Python ``float`` objects.
+
+ The :class:`.Float` subtype, being more specific to floating point,
+ defaults the :paramref:`.Float.asdecimal` flag to False so that the
+ default Python datatype is ``float``.
+
+ .. note::
+
+ When using a :class:`.Numeric` datatype against a database type that
+ returns Python floating point values to the driver, the accuracy of the
+ decimal conversion indicated by :paramref:`.Numeric.asdecimal` may be
+ limited. The behavior of specific numeric/floating point datatypes
+ is a product of the SQL datatype in use, the Python :term:`DBAPI`
+ in use, as well as strategies that may be present within
+ the SQLAlchemy dialect in use. Users requiring specific precision/
+ scale are encouraged to experiment with the available datatypes
+ in order to determine the best results.
+
+ """
+
+ __visit_name__ = "numeric"
+
+ if TYPE_CHECKING:
+
+ @util.ro_memoized_property
+ def _type_affinity(self) -> Type[Numeric[_N]]: ...
+
+ _default_decimal_return_scale = 10
+
+ @overload
+ def __init__(
+ self: Numeric[decimal.Decimal],
+ precision: Optional[int] = ...,
+ scale: Optional[int] = ...,
+ decimal_return_scale: Optional[int] = ...,
+ asdecimal: Literal[True] = ...,
+ ): ...
+
+ @overload
+ def __init__(
+ self: Numeric[float],
+ precision: Optional[int] = ...,
+ scale: Optional[int] = ...,
+ decimal_return_scale: Optional[int] = ...,
+ asdecimal: Literal[False] = ...,
+ ): ...
+
+ def __init__(
+ self,
+ precision: Optional[int] = None,
+ scale: Optional[int] = None,
+ decimal_return_scale: Optional[int] = None,
+ asdecimal: bool = True,
+ ):
+ """
+ Construct a Numeric.
+
+ :param precision: the numeric precision for use in DDL ``CREATE
+ TABLE``.
+
+ :param scale: the numeric scale for use in DDL ``CREATE TABLE``.
+
+ :param asdecimal: default True. Return whether or not
+ values should be sent as Python Decimal objects, or
+ as floats. Different DBAPIs send one or the other based on
+ datatypes - the Numeric type will ensure that return values
+ are one or the other across DBAPIs consistently.
+
+ :param decimal_return_scale: Default scale to use when converting
+ from floats to Python decimals. Floating point values will typically
+ be much longer due to decimal inaccuracy, and most floating point
+ database types don't have a notion of "scale", so by default the
+ float type looks for the first ten decimal places when converting.
+ Specifying this value will override that length. Types which
+ do include an explicit ".scale" value, such as the base
+ :class:`.Numeric` as well as the MySQL float types, will use the
+ value of ".scale" as the default for decimal_return_scale, if not
+ otherwise specified.
+
+ When using the ``Numeric`` type, care should be taken to ensure
+ that the asdecimal setting is appropriate for the DBAPI in use -
+ when Numeric applies a conversion from Decimal->float or float->
+ Decimal, this conversion incurs an additional performance overhead
+ for all result columns received.
+
+ DBAPIs that return Decimal natively (e.g. psycopg2) will have
+ better accuracy and higher performance with a setting of ``True``,
+ as the native translation to Decimal reduces the amount of floating-
+ point issues at play, and the Numeric type itself doesn't need
+ to apply any further conversions. However, another DBAPI which
+ returns floats natively *will* incur an additional conversion
+ overhead, and is still subject to floating point data loss - in
+ which case ``asdecimal=False`` will at least remove the extra
+ conversion overhead.
+
+ """
+ self.precision = precision
+ self.scale = scale
+ self.decimal_return_scale = decimal_return_scale
+ self.asdecimal = asdecimal
+
+ @property
+ def _effective_decimal_return_scale(self):
+ if self.decimal_return_scale is not None:
+ return self.decimal_return_scale
+ elif getattr(self, "scale", None) is not None:
+ return self.scale
+ else:
+ return self._default_decimal_return_scale
+
+ def get_dbapi_type(self, dbapi):
+ return dbapi.NUMBER
+
+ def literal_processor(self, dialect):
+ def process(value):
+ return str(value)
+
+ return process
+
+ @property
+ def python_type(self):
+ if self.asdecimal:
+ return decimal.Decimal
+ else:
+ return float
+
+ def bind_processor(self, dialect):
+ if dialect.supports_native_decimal:
+ return None
+ else:
+ return processors.to_float
+
+ def result_processor(self, dialect, coltype):
+ if self.asdecimal:
+ if dialect.supports_native_decimal:
+ # we're a "numeric", DBAPI will give us Decimal directly
+ return None
+ else:
+ # we're a "numeric", DBAPI returns floats, convert.
+ return processors.to_decimal_processor_factory(
+ decimal.Decimal,
+ (
+ self.scale
+ if self.scale is not None
+ else self._default_decimal_return_scale
+ ),
+ )
+ else:
+ if dialect.supports_native_decimal:
+ return processors.to_float
+ else:
+ return None
+
+ @util.memoized_property
+ def _expression_adaptations(self):
+ return {
+ operators.mul: {
+ Interval: Interval,
+ Numeric: self.__class__,
+ Integer: self.__class__,
+ },
+ operators.truediv: {
+ Numeric: self.__class__,
+ Integer: self.__class__,
+ },
+ operators.add: {Numeric: self.__class__, Integer: self.__class__},
+ operators.sub: {Numeric: self.__class__, Integer: self.__class__},
+ }
+
+
+class Float(Numeric[_N]):
+ """Type representing floating point types, such as ``FLOAT`` or ``REAL``.
+
+ This type returns Python ``float`` objects by default, unless the
+ :paramref:`.Float.asdecimal` flag is set to ``True``, in which case they
+ are coerced to ``decimal.Decimal`` objects.
+
+ When a :paramref:`.Float.precision` is not provided in a
+ :class:`_types.Float` type some backend may compile this type as
+ an 8 bytes / 64 bit float datatype. To use a 4 bytes / 32 bit float
+ datatype a precision <= 24 can usually be provided or the
+ :class:`_types.REAL` type can be used.
+ This is known to be the case in the PostgreSQL and MSSQL dialects
+ that render the type as ``FLOAT`` that's in both an alias of
+ ``DOUBLE PRECISION``. Other third party dialects may have similar
+ behavior.
+ """
+
+ __visit_name__ = "float"
+
+ scale = None
+
+ @overload
+ def __init__(
+ self: Float[float],
+ precision: Optional[int] = ...,
+ asdecimal: Literal[False] = ...,
+ decimal_return_scale: Optional[int] = ...,
+ ): ...
+
+ @overload
+ def __init__(
+ self: Float[decimal.Decimal],
+ precision: Optional[int] = ...,
+ asdecimal: Literal[True] = ...,
+ decimal_return_scale: Optional[int] = ...,
+ ): ...
+
+ def __init__(
+ self: Float[_N],
+ precision: Optional[int] = None,
+ asdecimal: bool = False,
+ decimal_return_scale: Optional[int] = None,
+ ):
+ r"""
+ Construct a Float.
+
+ :param precision: the numeric precision for use in DDL ``CREATE
+ TABLE``. Backends **should** attempt to ensure this precision
+ indicates a number of digits for the generic
+ :class:`_sqltypes.Float` datatype.
+
+ .. note:: For the Oracle backend, the
+ :paramref:`_sqltypes.Float.precision` parameter is not accepted
+ when rendering DDL, as Oracle does not support float precision
+ specified as a number of decimal places. Instead, use the
+ Oracle-specific :class:`_oracle.FLOAT` datatype and specify the
+ :paramref:`_oracle.FLOAT.binary_precision` parameter. This is new
+ in version 2.0 of SQLAlchemy.
+
+ To create a database agnostic :class:`_types.Float` that
+ separately specifies binary precision for Oracle, use
+ :meth:`_types.TypeEngine.with_variant` as follows::
+
+ from sqlalchemy import Column
+ from sqlalchemy import Float
+ from sqlalchemy.dialects import oracle
+
+ Column(
+ "float_data",
+ Float(5).with_variant(oracle.FLOAT(binary_precision=16), "oracle")
+ )
+
+ :param asdecimal: the same flag as that of :class:`.Numeric`, but
+ defaults to ``False``. Note that setting this flag to ``True``
+ results in floating point conversion.
+
+ :param decimal_return_scale: Default scale to use when converting
+ from floats to Python decimals. Floating point values will typically
+ be much longer due to decimal inaccuracy, and most floating point
+ database types don't have a notion of "scale", so by default the
+ float type looks for the first ten decimal places when converting.
+ Specifying this value will override that length. Note that the
+ MySQL float types, which do include "scale", will use "scale"
+ as the default for decimal_return_scale, if not otherwise specified.
+
+ """ # noqa: E501
+ self.precision = precision
+ self.asdecimal = asdecimal
+ self.decimal_return_scale = decimal_return_scale
+
+ def result_processor(self, dialect, coltype):
+ if self.asdecimal:
+ return processors.to_decimal_processor_factory(
+ decimal.Decimal, self._effective_decimal_return_scale
+ )
+ elif dialect.supports_native_decimal:
+ return processors.to_float
+ else:
+ return None
+
+
+class Double(Float[_N]):
+ """A type for double ``FLOAT`` floating point types.
+
+ Typically generates a ``DOUBLE`` or ``DOUBLE_PRECISION`` in DDL,
+ and otherwise acts like a normal :class:`.Float` on the Python
+ side.
+
+ .. versionadded:: 2.0
+
+ """
+
+ __visit_name__ = "double"
+
+
+class _RenderISO8601NoT:
+ def _literal_processor_datetime(self, dialect):
+ return self._literal_processor_portion(dialect, None)
+
+ def _literal_processor_date(self, dialect):
+ return self._literal_processor_portion(dialect, 0)
+
+ def _literal_processor_time(self, dialect):
+ return self._literal_processor_portion(dialect, -1)
+
+ def _literal_processor_portion(self, dialect, _portion=None):
+ assert _portion in (None, 0, -1)
+ if _portion is not None:
+
+ def process(value):
+ return f"""'{value.isoformat().split("T")[_portion]}'"""
+
+ else:
+
+ def process(value):
+ return f"""'{value.isoformat().replace("T", " ")}'"""
+
+ return process
+
+
+class DateTime(
+ _RenderISO8601NoT, HasExpressionLookup, TypeEngine[dt.datetime]
+):
+ """A type for ``datetime.datetime()`` objects.
+
+ Date and time types return objects from the Python ``datetime``
+ module. Most DBAPIs have built in support for the datetime
+ module, with the noted exception of SQLite. In the case of
+ SQLite, date and time types are stored as strings which are then
+ converted back to datetime objects when rows are returned.
+
+ For the time representation within the datetime type, some
+ backends include additional options, such as timezone support and
+ fractional seconds support. For fractional seconds, use the
+ dialect-specific datatype, such as :class:`.mysql.TIME`. For
+ timezone support, use at least the :class:`_types.TIMESTAMP` datatype,
+ if not the dialect-specific datatype object.
+
+ """
+
+ __visit_name__ = "datetime"
+
+ def __init__(self, timezone: bool = False):
+ """Construct a new :class:`.DateTime`.
+
+ :param timezone: boolean. Indicates that the datetime type should
+ enable timezone support, if available on the
+ **base date/time-holding type only**. It is recommended
+ to make use of the :class:`_types.TIMESTAMP` datatype directly when
+ using this flag, as some databases include separate generic
+ date/time-holding types distinct from the timezone-capable
+ TIMESTAMP datatype, such as Oracle.
+
+
+ """
+ self.timezone = timezone
+
+ def get_dbapi_type(self, dbapi):
+ return dbapi.DATETIME
+
+ def _resolve_for_literal(self, value):
+ with_timezone = value.tzinfo is not None
+ if with_timezone and not self.timezone:
+ return DATETIME_TIMEZONE
+ else:
+ return self
+
+ def literal_processor(self, dialect):
+ return self._literal_processor_datetime(dialect)
+
+ @property
+ def python_type(self):
+ return dt.datetime
+
+ @util.memoized_property
+ def _expression_adaptations(self):
+ # Based on
+ # https://www.postgresql.org/docs/current/static/functions-datetime.html.
+
+ return {
+ operators.add: {Interval: self.__class__},
+ operators.sub: {Interval: self.__class__, DateTime: Interval},
+ }
+
+
+class Date(_RenderISO8601NoT, HasExpressionLookup, TypeEngine[dt.date]):
+ """A type for ``datetime.date()`` objects."""
+
+ __visit_name__ = "date"
+
+ def get_dbapi_type(self, dbapi):
+ return dbapi.DATETIME
+
+ @property
+ def python_type(self):
+ return dt.date
+
+ def literal_processor(self, dialect):
+ return self._literal_processor_date(dialect)
+
+ @util.memoized_property
+ def _expression_adaptations(self):
+ # Based on
+ # https://www.postgresql.org/docs/current/static/functions-datetime.html.
+
+ return {
+ operators.add: {
+ Integer: self.__class__,
+ Interval: DateTime,
+ Time: DateTime,
+ },
+ operators.sub: {
+ # date - integer = date
+ Integer: self.__class__,
+ # date - date = integer.
+ Date: Integer,
+ Interval: DateTime,
+ # date - datetime = interval,
+ # this one is not in the PG docs
+ # but works
+ DateTime: Interval,
+ },
+ }
+
+
+class Time(_RenderISO8601NoT, HasExpressionLookup, TypeEngine[dt.time]):
+ """A type for ``datetime.time()`` objects."""
+
+ __visit_name__ = "time"
+
+ def __init__(self, timezone: bool = False):
+ self.timezone = timezone
+
+ def get_dbapi_type(self, dbapi):
+ return dbapi.DATETIME
+
+ @property
+ def python_type(self):
+ return dt.time
+
+ def _resolve_for_literal(self, value):
+ with_timezone = value.tzinfo is not None
+ if with_timezone and not self.timezone:
+ return TIME_TIMEZONE
+ else:
+ return self
+
+ @util.memoized_property
+ def _expression_adaptations(self):
+ # Based on
+ # https://www.postgresql.org/docs/current/static/functions-datetime.html.
+
+ return {
+ operators.add: {Date: DateTime, Interval: self.__class__},
+ operators.sub: {Time: Interval, Interval: self.__class__},
+ }
+
+ def literal_processor(self, dialect):
+ return self._literal_processor_time(dialect)
+
+
+class _Binary(TypeEngine[bytes]):
+ """Define base behavior for binary types."""
+
+ def __init__(self, length: Optional[int] = None):
+ self.length = length
+
+ def literal_processor(self, dialect):
+ def process(value):
+ # TODO: this is useless for real world scenarios; implement
+ # real binary literals
+ value = value.decode(
+ dialect._legacy_binary_type_literal_encoding
+ ).replace("'", "''")
+ return "'%s'" % value
+
+ return process
+
+ @property
+ def python_type(self):
+ return bytes
+
+ # Python 3 - sqlite3 doesn't need the `Binary` conversion
+ # here, though pg8000 does to indicate "bytea"
+ def bind_processor(self, dialect):
+ if dialect.dbapi is None:
+ return None
+
+ DBAPIBinary = dialect.dbapi.Binary
+
+ def process(value):
+ if value is not None:
+ return DBAPIBinary(value)
+ else:
+ return None
+
+ return process
+
+ # Python 3 has native bytes() type
+ # both sqlite3 and pg8000 seem to return it,
+ # psycopg2 as of 2.5 returns 'memoryview'
+ def result_processor(self, dialect, coltype):
+ if dialect.returns_native_bytes:
+ return None
+
+ def process(value):
+ if value is not None:
+ value = bytes(value)
+ return value
+
+ return process
+
+ def coerce_compared_value(self, op, value):
+ """See :meth:`.TypeEngine.coerce_compared_value` for a description."""
+
+ if isinstance(value, str):
+ return self
+ else:
+ return super().coerce_compared_value(op, value)
+
+ def get_dbapi_type(self, dbapi):
+ return dbapi.BINARY
+
+
+class LargeBinary(_Binary):
+ """A type for large binary byte data.
+
+ The :class:`.LargeBinary` type corresponds to a large and/or unlengthed
+ binary type for the target platform, such as BLOB on MySQL and BYTEA for
+ PostgreSQL. It also handles the necessary conversions for the DBAPI.
+
+ """
+
+ __visit_name__ = "large_binary"
+
+ def __init__(self, length: Optional[int] = None):
+ """
+ Construct a LargeBinary type.
+
+ :param length: optional, a length for the column for use in
+ DDL statements, for those binary types that accept a length,
+ such as the MySQL BLOB type.
+
+ """
+ _Binary.__init__(self, length=length)
+
+
+class SchemaType(SchemaEventTarget, TypeEngineMixin):
+ """Add capabilities to a type which allow for schema-level DDL to be
+ associated with a type.
+
+ Supports types that must be explicitly created/dropped (i.e. PG ENUM type)
+ as well as types that are complimented by table or schema level
+ constraints, triggers, and other rules.
+
+ :class:`.SchemaType` classes can also be targets for the
+ :meth:`.DDLEvents.before_parent_attach` and
+ :meth:`.DDLEvents.after_parent_attach` events, where the events fire off
+ surrounding the association of the type object with a parent
+ :class:`_schema.Column`.
+
+ .. seealso::
+
+ :class:`.Enum`
+
+ :class:`.Boolean`
+
+
+ """
+
+ _use_schema_map = True
+
+ name: Optional[str]
+
+ def __init__(
+ self,
+ name: Optional[str] = None,
+ schema: Optional[str] = None,
+ metadata: Optional[MetaData] = None,
+ inherit_schema: bool = False,
+ quote: Optional[bool] = None,
+ _create_events: bool = True,
+ _adapted_from: Optional[SchemaType] = None,
+ ):
+ if name is not None:
+ self.name = quoted_name(name, quote)
+ else:
+ self.name = None
+ self.schema = schema
+ self.metadata = metadata
+ self.inherit_schema = inherit_schema
+ self._create_events = _create_events
+
+ if _create_events and self.metadata:
+ event.listen(
+ self.metadata,
+ "before_create",
+ util.portable_instancemethod(self._on_metadata_create),
+ )
+ event.listen(
+ self.metadata,
+ "after_drop",
+ util.portable_instancemethod(self._on_metadata_drop),
+ )
+
+ if _adapted_from:
+ self.dispatch = self.dispatch._join(_adapted_from.dispatch)
+
+ def _set_parent(self, column, **kw):
+ # set parent hook is when this type is associated with a column.
+ # Column calls it for all SchemaEventTarget instances, either the
+ # base type and/or variants in _variant_mapping.
+
+ # we want to register a second hook to trigger when that column is
+ # associated with a table. in that event, we and all of our variants
+ # may want to set up some state on the table such as a CheckConstraint
+ # that will conditionally render at DDL render time.
+
+ # the base SchemaType also sets up events for
+ # on_table/metadata_create/drop in this method, which is used by
+ # "native" types with a separate CREATE/DROP e.g. Postgresql.ENUM
+
+ column._on_table_attach(util.portable_instancemethod(self._set_table))
+
+ def _variant_mapping_for_set_table(self, column):
+ if column.type._variant_mapping:
+ variant_mapping = dict(column.type._variant_mapping)
+ variant_mapping["_default"] = column.type
+ else:
+ variant_mapping = None
+ return variant_mapping
+
+ def _set_table(self, column, table):
+ if self.inherit_schema:
+ self.schema = table.schema
+ elif self.metadata and self.schema is None and self.metadata.schema:
+ self.schema = self.metadata.schema
+
+ if not self._create_events:
+ return
+
+ variant_mapping = self._variant_mapping_for_set_table(column)
+
+ event.listen(
+ table,
+ "before_create",
+ util.portable_instancemethod(
+ self._on_table_create, {"variant_mapping": variant_mapping}
+ ),
+ )
+ event.listen(
+ table,
+ "after_drop",
+ util.portable_instancemethod(
+ self._on_table_drop, {"variant_mapping": variant_mapping}
+ ),
+ )
+ if self.metadata is None:
+ # if SchemaType were created w/ a metadata argument, these
+ # events would already have been associated with that metadata
+ # and would preclude an association with table.metadata
+ event.listen(
+ table.metadata,
+ "before_create",
+ util.portable_instancemethod(
+ self._on_metadata_create,
+ {"variant_mapping": variant_mapping},
+ ),
+ )
+ event.listen(
+ table.metadata,
+ "after_drop",
+ util.portable_instancemethod(
+ self._on_metadata_drop,
+ {"variant_mapping": variant_mapping},
+ ),
+ )
+
+ def copy(self, **kw):
+ return self.adapt(
+ cast("Type[TypeEngine[Any]]", self.__class__),
+ _create_events=True,
+ )
+
+ @overload
+ def adapt(self, cls: Type[_TE], **kw: Any) -> _TE: ...
+
+ @overload
+ def adapt(
+ self, cls: Type[TypeEngineMixin], **kw: Any
+ ) -> TypeEngine[Any]: ...
+
+ def adapt(
+ self, cls: Type[Union[TypeEngine[Any], TypeEngineMixin]], **kw: Any
+ ) -> TypeEngine[Any]:
+ kw.setdefault("_create_events", False)
+ kw.setdefault("_adapted_from", self)
+ return super().adapt(cls, **kw)
+
+ def create(self, bind, checkfirst=False):
+ """Issue CREATE DDL for this type, if applicable."""
+
+ t = self.dialect_impl(bind.dialect)
+ if isinstance(t, SchemaType) and t.__class__ is not self.__class__:
+ t.create(bind, checkfirst=checkfirst)
+
+ def drop(self, bind, checkfirst=False):
+ """Issue DROP DDL for this type, if applicable."""
+
+ t = self.dialect_impl(bind.dialect)
+ if isinstance(t, SchemaType) and t.__class__ is not self.__class__:
+ t.drop(bind, checkfirst=checkfirst)
+
+ def _on_table_create(self, target, bind, **kw):
+ if not self._is_impl_for_variant(bind.dialect, kw):
+ return
+
+ t = self.dialect_impl(bind.dialect)
+ if isinstance(t, SchemaType) and t.__class__ is not self.__class__:
+ t._on_table_create(target, bind, **kw)
+
+ def _on_table_drop(self, target, bind, **kw):
+ if not self._is_impl_for_variant(bind.dialect, kw):
+ return
+
+ t = self.dialect_impl(bind.dialect)
+ if isinstance(t, SchemaType) and t.__class__ is not self.__class__:
+ t._on_table_drop(target, bind, **kw)
+
+ def _on_metadata_create(self, target, bind, **kw):
+ if not self._is_impl_for_variant(bind.dialect, kw):
+ return
+
+ t = self.dialect_impl(bind.dialect)
+ if isinstance(t, SchemaType) and t.__class__ is not self.__class__:
+ t._on_metadata_create(target, bind, **kw)
+
+ def _on_metadata_drop(self, target, bind, **kw):
+ if not self._is_impl_for_variant(bind.dialect, kw):
+ return
+
+ t = self.dialect_impl(bind.dialect)
+ if isinstance(t, SchemaType) and t.__class__ is not self.__class__:
+ t._on_metadata_drop(target, bind, **kw)
+
+ def _is_impl_for_variant(self, dialect, kw):
+ variant_mapping = kw.pop("variant_mapping", None)
+
+ if not variant_mapping:
+ return True
+
+ # for types that have _variant_mapping, all the impls in the map
+ # that are SchemaEventTarget subclasses get set up as event holders.
+ # this is so that constructs that need
+ # to be associated with the Table at dialect-agnostic time etc. like
+ # CheckConstraints can be set up with that table. they then add
+ # to these constraints a DDL check_rule that among other things
+ # will check this _is_impl_for_variant() method to determine when
+ # the dialect is known that we are part of the table's DDL sequence.
+
+ # since PostgreSQL is the only DB that has ARRAY this can only
+ # be integration tested by PG-specific tests
+ def _we_are_the_impl(typ):
+ return (
+ typ is self
+ or isinstance(typ, ARRAY)
+ and typ.item_type is self # type: ignore[comparison-overlap]
+ )
+
+ if dialect.name in variant_mapping and _we_are_the_impl(
+ variant_mapping[dialect.name]
+ ):
+ return True
+ elif dialect.name not in variant_mapping:
+ return _we_are_the_impl(variant_mapping["_default"])
+
+
+class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]):
+ """Generic Enum Type.
+
+ The :class:`.Enum` type provides a set of possible string values
+ which the column is constrained towards.
+
+ The :class:`.Enum` type will make use of the backend's native "ENUM"
+ type if one is available; otherwise, it uses a VARCHAR datatype.
+ An option also exists to automatically produce a CHECK constraint
+ when the VARCHAR (so called "non-native") variant is produced;
+ see the :paramref:`.Enum.create_constraint` flag.
+
+ The :class:`.Enum` type also provides in-Python validation of string
+ values during both read and write operations. When reading a value
+ from the database in a result set, the string value is always checked
+ against the list of possible values and a ``LookupError`` is raised
+ if no match is found. When passing a value to the database as a
+ plain string within a SQL statement, if the
+ :paramref:`.Enum.validate_strings` parameter is
+ set to True, a ``LookupError`` is raised for any string value that's
+ not located in the given list of possible values; note that this
+ impacts usage of LIKE expressions with enumerated values (an unusual
+ use case).
+
+ The source of enumerated values may be a list of string values, or
+ alternatively a PEP-435-compliant enumerated class. For the purposes
+ of the :class:`.Enum` datatype, this class need only provide a
+ ``__members__`` method.
+
+ When using an enumerated class, the enumerated objects are used
+ both for input and output, rather than strings as is the case with
+ a plain-string enumerated type::
+
+ import enum
+ from sqlalchemy import Enum
+
+ class MyEnum(enum.Enum):
+ one = 1
+ two = 2
+ three = 3
+
+ t = Table(
+ 'data', MetaData(),
+ Column('value', Enum(MyEnum))
+ )
+
+ connection.execute(t.insert(), {"value": MyEnum.two})
+ assert connection.scalar(t.select()) is MyEnum.two
+
+ Above, the string names of each element, e.g. "one", "two", "three",
+ are persisted to the database; the values of the Python Enum, here
+ indicated as integers, are **not** used; the value of each enum can
+ therefore be any kind of Python object whether or not it is persistable.
+
+ In order to persist the values and not the names, the
+ :paramref:`.Enum.values_callable` parameter may be used. The value of
+ this parameter is a user-supplied callable, which is intended to be used
+ with a PEP-435-compliant enumerated class and returns a list of string
+ values to be persisted. For a simple enumeration that uses string values,
+ a callable such as ``lambda x: [e.value for e in x]`` is sufficient.
+
+ .. seealso::
+
+ :ref:`orm_declarative_mapped_column_enums` - background on using
+ the :class:`_sqltypes.Enum` datatype with the ORM's
+ :ref:`ORM Annotated Declarative <orm_declarative_mapped_column>`
+ feature.
+
+ :class:`_postgresql.ENUM` - PostgreSQL-specific type,
+ which has additional functionality.
+
+ :class:`.mysql.ENUM` - MySQL-specific type
+
+ """
+
+ __visit_name__ = "enum"
+
+ def __init__(self, *enums: object, **kw: Any):
+ r"""Construct an enum.
+
+ Keyword arguments which don't apply to a specific backend are ignored
+ by that backend.
+
+ :param \*enums: either exactly one PEP-435 compliant enumerated type
+ or one or more string labels.
+
+ :param create_constraint: defaults to False. When creating a
+ non-native enumerated type, also build a CHECK constraint on the
+ database against the valid values.
+
+ .. note:: it is strongly recommended that the CHECK constraint
+ have an explicit name in order to support schema-management
+ concerns. This can be established either by setting the
+ :paramref:`.Enum.name` parameter or by setting up an
+ appropriate naming convention; see
+ :ref:`constraint_naming_conventions` for background.
+
+ .. versionchanged:: 1.4 - this flag now defaults to False, meaning
+ no CHECK constraint is generated for a non-native enumerated
+ type.
+
+ :param metadata: Associate this type directly with a ``MetaData``
+ object. For types that exist on the target database as an
+ independent schema construct (PostgreSQL), this type will be
+ created and dropped within ``create_all()`` and ``drop_all()``
+ operations. If the type is not associated with any ``MetaData``
+ object, it will associate itself with each ``Table`` in which it is
+ used, and will be created when any of those individual tables are
+ created, after a check is performed for its existence. The type is
+ only dropped when ``drop_all()`` is called for that ``Table``
+ object's metadata, however.
+
+ The value of the :paramref:`_schema.MetaData.schema` parameter of
+ the :class:`_schema.MetaData` object, if set, will be used as the
+ default value of the :paramref:`_types.Enum.schema` on this object
+ if an explicit value is not otherwise supplied.
+
+ .. versionchanged:: 1.4.12 :class:`_types.Enum` inherits the
+ :paramref:`_schema.MetaData.schema` parameter of the
+ :class:`_schema.MetaData` object if present, when passed using
+ the :paramref:`_types.Enum.metadata` parameter.
+
+ :param name: The name of this type. This is required for PostgreSQL
+ and any future supported database which requires an explicitly
+ named type, or an explicitly named constraint in order to generate
+ the type and/or a table that uses it. If a PEP-435 enumerated
+ class was used, its name (converted to lower case) is used by
+ default.
+
+ :param native_enum: Use the database's native ENUM type when
+ available. Defaults to True. When False, uses VARCHAR + check
+ constraint for all backends. When False, the VARCHAR length can be
+ controlled with :paramref:`.Enum.length`; currently "length" is
+ ignored if native_enum=True.
+
+ :param length: Allows specifying a custom length for the VARCHAR
+ when a non-native enumeration datatype is used. By default it uses
+ the length of the longest value.
+
+ .. versionchanged:: 2.0.0 The :paramref:`.Enum.length` parameter
+ is used unconditionally for ``VARCHAR`` rendering regardless of
+ the :paramref:`.Enum.native_enum` parameter, for those backends
+ where ``VARCHAR`` is used for enumerated datatypes.
+
+
+ :param schema: Schema name of this type. For types that exist on the
+ target database as an independent schema construct (PostgreSQL),
+ this parameter specifies the named schema in which the type is
+ present.
+
+ If not present, the schema name will be taken from the
+ :class:`_schema.MetaData` collection if passed as
+ :paramref:`_types.Enum.metadata`, for a :class:`_schema.MetaData`
+ that includes the :paramref:`_schema.MetaData.schema` parameter.
+
+ .. versionchanged:: 1.4.12 :class:`_types.Enum` inherits the
+ :paramref:`_schema.MetaData.schema` parameter of the
+ :class:`_schema.MetaData` object if present, when passed using
+ the :paramref:`_types.Enum.metadata` parameter.
+
+ Otherwise, if the :paramref:`_types.Enum.inherit_schema` flag is set
+ to ``True``, the schema will be inherited from the associated
+ :class:`_schema.Table` object if any; when
+ :paramref:`_types.Enum.inherit_schema` is at its default of
+ ``False``, the owning table's schema is **not** used.
+
+
+ :param quote: Set explicit quoting preferences for the type's name.
+
+ :param inherit_schema: When ``True``, the "schema" from the owning
+ :class:`_schema.Table`
+ will be copied to the "schema" attribute of this
+ :class:`.Enum`, replacing whatever value was passed for the
+ ``schema`` attribute. This also takes effect when using the
+ :meth:`_schema.Table.to_metadata` operation.
+
+ :param validate_strings: when True, string values that are being
+ passed to the database in a SQL statement will be checked
+ for validity against the list of enumerated values. Unrecognized
+ values will result in a ``LookupError`` being raised.
+
+ :param values_callable: A callable which will be passed the PEP-435
+ compliant enumerated type, which should then return a list of string
+ values to be persisted. This allows for alternate usages such as
+ using the string value of an enum to be persisted to the database
+ instead of its name. The callable must return the values to be
+ persisted in the same order as iterating through the Enum's
+ ``__member__`` attribute. For example
+ ``lambda x: [i.value for i in x]``.
+
+ .. versionadded:: 1.2.3
+
+ :param sort_key_function: a Python callable which may be used as the
+ "key" argument in the Python ``sorted()`` built-in. The SQLAlchemy
+ ORM requires that primary key columns which are mapped must
+ be sortable in some way. When using an unsortable enumeration
+ object such as a Python 3 ``Enum`` object, this parameter may be
+ used to set a default sort key function for the objects. By
+ default, the database value of the enumeration is used as the
+ sorting function.
+
+ .. versionadded:: 1.3.8
+
+ :param omit_aliases: A boolean that when true will remove aliases from
+ pep 435 enums. defaults to ``True``.
+
+ .. versionchanged:: 2.0 This parameter now defaults to True.
+
+ """
+ self._enum_init(enums, kw)
+
+ @property
+ def _enums_argument(self):
+ if self.enum_class is not None:
+ return [self.enum_class]
+ else:
+ return self.enums
+
+ def _enum_init(self, enums, kw):
+ """internal init for :class:`.Enum` and subclasses.
+
+ friendly init helper used by subclasses to remove
+ all the Enum-specific keyword arguments from kw. Allows all
+ other arguments in kw to pass through.
+
+ """
+ self.native_enum = kw.pop("native_enum", True)
+ self.create_constraint = kw.pop("create_constraint", False)
+ self.values_callable = kw.pop("values_callable", None)
+ self._sort_key_function = kw.pop("sort_key_function", NO_ARG)
+ length_arg = kw.pop("length", NO_ARG)
+ self._omit_aliases = kw.pop("omit_aliases", True)
+ _disable_warnings = kw.pop("_disable_warnings", False)
+ values, objects = self._parse_into_values(enums, kw)
+ self._setup_for_values(values, objects, kw)
+
+ self.validate_strings = kw.pop("validate_strings", False)
+
+ if self.enums:
+ self._default_length = length = max(len(x) for x in self.enums)
+ else:
+ self._default_length = length = 0
+
+ if length_arg is not NO_ARG:
+ if (
+ not _disable_warnings
+ and length_arg is not None
+ and length_arg < length
+ ):
+ raise ValueError(
+ "When provided, length must be larger or equal"
+ " than the length of the longest enum value. %s < %s"
+ % (length_arg, length)
+ )
+ length = length_arg
+
+ self._valid_lookup[None] = self._object_lookup[None] = None
+
+ super().__init__(length=length)
+
+ # assign name to the given enum class if no other name, and this
+ # enum is not an "empty" enum. if the enum is "empty" we assume
+ # this is a template enum that will be used to generate
+ # new Enum classes.
+ if self.enum_class and values:
+ kw.setdefault("name", self.enum_class.__name__.lower())
+ SchemaType.__init__(
+ self,
+ name=kw.pop("name", None),
+ schema=kw.pop("schema", None),
+ metadata=kw.pop("metadata", None),
+ inherit_schema=kw.pop("inherit_schema", False),
+ quote=kw.pop("quote", None),
+ _create_events=kw.pop("_create_events", True),
+ _adapted_from=kw.pop("_adapted_from", None),
+ )
+
+ def _parse_into_values(self, enums, kw):
+ if not enums and "_enums" in kw:
+ enums = kw.pop("_enums")
+
+ if len(enums) == 1 and hasattr(enums[0], "__members__"):
+ self.enum_class = enums[0]
+
+ _members = self.enum_class.__members__
+
+ if self._omit_aliases is True:
+ # remove aliases
+ members = OrderedDict(
+ (n, v) for n, v in _members.items() if v.name == n
+ )
+ else:
+ members = _members
+ if self.values_callable:
+ values = self.values_callable(self.enum_class)
+ else:
+ values = list(members)
+ objects = [members[k] for k in members]
+ return values, objects
+ else:
+ self.enum_class = None
+ return enums, enums
+
+ def _resolve_for_literal(self, value: Any) -> Enum:
+ tv = type(value)
+ typ = self._resolve_for_python_type(tv, tv, tv)
+ assert typ is not None
+ return typ
+
+ def _resolve_for_python_type(
+ self,
+ python_type: Type[Any],
+ matched_on: _MatchedOnType,
+ matched_on_flattened: Type[Any],
+ ) -> Optional[Enum]:
+ # "generic form" indicates we were placed in a type map
+ # as ``sqlalchemy.Enum(enum.Enum)`` which indicates we need to
+ # get enumerated values from the datatype
+ we_are_generic_form = self._enums_argument == [enum.Enum]
+
+ native_enum = None
+
+ if not we_are_generic_form and python_type is matched_on:
+ # if we have enumerated values, and the incoming python
+ # type is exactly the one that matched in the type map,
+ # then we use these enumerated values and dont try to parse
+ # what's incoming
+ enum_args = self._enums_argument
+
+ elif is_literal(python_type):
+ # for a literal, where we need to get its contents, parse it out.
+ enum_args = typing_get_args(python_type)
+ bad_args = [arg for arg in enum_args if not isinstance(arg, str)]
+ if bad_args:
+ raise exc.ArgumentError(
+ f"Can't create string-based Enum datatype from non-string "
+ f"values: {', '.join(repr(x) for x in bad_args)}. Please "
+ f"provide an explicit Enum datatype for this Python type"
+ )
+ native_enum = False
+ elif isinstance(python_type, type) and issubclass(
+ python_type, enum.Enum
+ ):
+ # same for an enum.Enum
+ enum_args = [python_type]
+
+ else:
+ enum_args = self._enums_argument
+
+ # make a new Enum that looks like this one.
+ # arguments or other rules
+ kw = self._make_enum_kw({})
+
+ if native_enum is False:
+ kw["native_enum"] = False
+
+ kw["length"] = NO_ARG if self.length == 0 else self.length
+ return cast(
+ Enum,
+ self._generic_type_affinity(_enums=enum_args, **kw), # type: ignore # noqa: E501
+ )
+
+ def _setup_for_values(self, values, objects, kw):
+ self.enums = list(values)
+
+ self._valid_lookup = dict(zip(reversed(objects), reversed(values)))
+
+ self._object_lookup = dict(zip(values, objects))
+
+ self._valid_lookup.update(
+ [
+ (value, self._valid_lookup[self._object_lookup[value]])
+ for value in values
+ ]
+ )
+
+ @property
+ def sort_key_function(self):
+ if self._sort_key_function is NO_ARG:
+ return self._db_value_for_elem
+ else:
+ return self._sort_key_function
+
+ @property
+ def native(self):
+ return self.native_enum
+
+ def _db_value_for_elem(self, elem):
+ try:
+ return self._valid_lookup[elem]
+ except KeyError as err:
+ # for unknown string values, we return as is. While we can
+ # validate these if we wanted, that does not allow for lesser-used
+ # end-user use cases, such as using a LIKE comparison with an enum,
+ # or for an application that wishes to apply string tests to an
+ # ENUM (see [ticket:3725]). While we can decide to differentiate
+ # here between an INSERT statement and a criteria used in a SELECT,
+ # for now we're staying conservative w/ behavioral changes (perhaps
+ # someone has a trigger that handles strings on INSERT)
+ if not self.validate_strings and isinstance(elem, str):
+ return elem
+ else:
+ raise LookupError(
+ "'%s' is not among the defined enum values. "
+ "Enum name: %s. Possible values: %s"
+ % (
+ elem,
+ self.name,
+ langhelpers.repr_tuple_names(self.enums),
+ )
+ ) from err
+
+ class Comparator(String.Comparator[str]):
+ __slots__ = ()
+
+ type: String
+
+ def _adapt_expression(
+ self,
+ op: OperatorType,
+ other_comparator: TypeEngine.Comparator[Any],
+ ) -> Tuple[OperatorType, TypeEngine[Any]]:
+ op, typ = super()._adapt_expression(op, other_comparator)
+ if op is operators.concat_op:
+ typ = String(self.type.length)
+ return op, typ
+
+ comparator_factory = Comparator
+
+ def _object_value_for_elem(self, elem):
+ try:
+ return self._object_lookup[elem]
+ except KeyError as err:
+ raise LookupError(
+ "'%s' is not among the defined enum values. "
+ "Enum name: %s. Possible values: %s"
+ % (
+ elem,
+ self.name,
+ langhelpers.repr_tuple_names(self.enums),
+ )
+ ) from err
+
+ def __repr__(self):
+ return util.generic_repr(
+ self,
+ additional_kw=[
+ ("native_enum", True),
+ ("create_constraint", False),
+ ("length", self._default_length),
+ ],
+ to_inspect=[Enum, SchemaType],
+ )
+
+ def as_generic(self, allow_nulltype=False):
+ try:
+ args = self.enums
+ except AttributeError:
+ raise NotImplementedError(
+ "TypeEngine.as_generic() heuristic "
+ "is undefined for types that inherit Enum but do not have "
+ "an `enums` attribute."
+ ) from None
+
+ return util.constructor_copy(
+ self, self._generic_type_affinity, *args, _disable_warnings=True
+ )
+
+ def _make_enum_kw(self, kw):
+ kw.setdefault("validate_strings", self.validate_strings)
+ if self.name:
+ kw.setdefault("name", self.name)
+ kw.setdefault("schema", self.schema)
+ kw.setdefault("inherit_schema", self.inherit_schema)
+ kw.setdefault("metadata", self.metadata)
+ kw.setdefault("native_enum", self.native_enum)
+ kw.setdefault("values_callable", self.values_callable)
+ kw.setdefault("create_constraint", self.create_constraint)
+ kw.setdefault("length", self.length)
+ kw.setdefault("omit_aliases", self._omit_aliases)
+ return kw
+
+ def adapt_to_emulated(self, impltype, **kw):
+ self._make_enum_kw(kw)
+ kw["_disable_warnings"] = True
+ kw.setdefault("_create_events", False)
+ assert "_enums" in kw
+ return impltype(**kw)
+
+ def adapt(self, impltype, **kw):
+ kw["_enums"] = self._enums_argument
+ kw["_disable_warnings"] = True
+ return super().adapt(impltype, **kw)
+
+ def _should_create_constraint(self, compiler, **kw):
+ if not self._is_impl_for_variant(compiler.dialect, kw):
+ return False
+ return (
+ not self.native_enum or not compiler.dialect.supports_native_enum
+ )
+
+ @util.preload_module("sqlalchemy.sql.schema")
+ def _set_table(self, column, table):
+ schema = util.preloaded.sql_schema
+ SchemaType._set_table(self, column, table)
+
+ if not self.create_constraint:
+ return
+
+ variant_mapping = self._variant_mapping_for_set_table(column)
+
+ e = schema.CheckConstraint(
+ type_coerce(column, String()).in_(self.enums),
+ name=_NONE_NAME if self.name is None else self.name,
+ _create_rule=util.portable_instancemethod(
+ self._should_create_constraint,
+ {"variant_mapping": variant_mapping},
+ ),
+ _type_bound=True,
+ )
+ assert e.table is table
+
+ def literal_processor(self, dialect):
+ parent_processor = super().literal_processor(dialect)
+
+ def process(value):
+ value = self._db_value_for_elem(value)
+ if parent_processor:
+ value = parent_processor(value)
+ return value
+
+ return process
+
+ def bind_processor(self, dialect):
+ parent_processor = super().bind_processor(dialect)
+
+ def process(value):
+ value = self._db_value_for_elem(value)
+ if parent_processor:
+ value = parent_processor(value)
+ return value
+
+ return process
+
+ def result_processor(self, dialect, coltype):
+ parent_processor = super().result_processor(dialect, coltype)
+
+ def process(value):
+ if parent_processor:
+ value = parent_processor(value)
+
+ value = self._object_value_for_elem(value)
+ return value
+
+ return process
+
+ def copy(self, **kw):
+ return SchemaType.copy(self, **kw)
+
+ @property
+ def python_type(self):
+ if self.enum_class:
+ return self.enum_class
+ else:
+ return super().python_type
+
+
+class PickleType(TypeDecorator[object]):
+ """Holds Python objects, which are serialized using pickle.
+
+ PickleType builds upon the Binary type to apply Python's
+ ``pickle.dumps()`` to incoming objects, and ``pickle.loads()`` on
+ the way out, allowing any pickleable Python object to be stored as
+ a serialized binary field.
+
+ To allow ORM change events to propagate for elements associated
+ with :class:`.PickleType`, see :ref:`mutable_toplevel`.
+
+ """
+
+ impl = LargeBinary
+ cache_ok = True
+
+ def __init__(
+ self,
+ protocol: int = pickle.HIGHEST_PROTOCOL,
+ pickler: Any = None,
+ comparator: Optional[Callable[[Any, Any], bool]] = None,
+ impl: Optional[_TypeEngineArgument[Any]] = None,
+ ):
+ """
+ Construct a PickleType.
+
+ :param protocol: defaults to ``pickle.HIGHEST_PROTOCOL``.
+
+ :param pickler: defaults to pickle. May be any object with
+ pickle-compatible ``dumps`` and ``loads`` methods.
+
+ :param comparator: a 2-arg callable predicate used
+ to compare values of this type. If left as ``None``,
+ the Python "equals" operator is used to compare values.
+
+ :param impl: A binary-storing :class:`_types.TypeEngine` class or
+ instance to use in place of the default :class:`_types.LargeBinary`.
+ For example the :class: `_mysql.LONGBLOB` class may be more effective
+ when using MySQL.
+
+ .. versionadded:: 1.4.20
+
+ """
+ self.protocol = protocol
+ self.pickler = pickler or pickle
+ self.comparator = comparator
+ super().__init__()
+
+ if impl:
+ # custom impl is not necessarily a LargeBinary subclass.
+ # make an exception to typing for this
+ self.impl = to_instance(impl) # type: ignore
+
+ def __reduce__(self):
+ return PickleType, (self.protocol, None, self.comparator)
+
+ def bind_processor(self, dialect):
+ impl_processor = self.impl_instance.bind_processor(dialect)
+ dumps = self.pickler.dumps
+ protocol = self.protocol
+ if impl_processor:
+ fixed_impl_processor = impl_processor
+
+ def process(value):
+ if value is not None:
+ value = dumps(value, protocol)
+ return fixed_impl_processor(value)
+
+ else:
+
+ def process(value):
+ if value is not None:
+ value = dumps(value, protocol)
+ return value
+
+ return process
+
+ def result_processor(self, dialect, coltype):
+ impl_processor = self.impl_instance.result_processor(dialect, coltype)
+ loads = self.pickler.loads
+ if impl_processor:
+ fixed_impl_processor = impl_processor
+
+ def process(value):
+ value = fixed_impl_processor(value)
+ if value is None:
+ return None
+ return loads(value)
+
+ else:
+
+ def process(value):
+ if value is None:
+ return None
+ return loads(value)
+
+ return process
+
+ def compare_values(self, x, y):
+ if self.comparator:
+ return self.comparator(x, y)
+ else:
+ return x == y
+
+
+class Boolean(SchemaType, Emulated, TypeEngine[bool]):
+ """A bool datatype.
+
+ :class:`.Boolean` typically uses BOOLEAN or SMALLINT on the DDL side,
+ and on the Python side deals in ``True`` or ``False``.
+
+ The :class:`.Boolean` datatype currently has two levels of assertion
+ that the values persisted are simple true/false values. For all
+ backends, only the Python values ``None``, ``True``, ``False``, ``1``
+ or ``0`` are accepted as parameter values. For those backends that
+ don't support a "native boolean" datatype, an option exists to
+ also create a CHECK constraint on the target column
+
+ .. versionchanged:: 1.2 the :class:`.Boolean` datatype now asserts that
+ incoming Python values are already in pure boolean form.
+
+
+ """
+
+ __visit_name__ = "boolean"
+ native = True
+
+ def __init__(
+ self,
+ create_constraint: bool = False,
+ name: Optional[str] = None,
+ _create_events: bool = True,
+ _adapted_from: Optional[SchemaType] = None,
+ ):
+ """Construct a Boolean.
+
+ :param create_constraint: defaults to False. If the boolean
+ is generated as an int/smallint, also create a CHECK constraint
+ on the table that ensures 1 or 0 as a value.
+
+ .. note:: it is strongly recommended that the CHECK constraint
+ have an explicit name in order to support schema-management
+ concerns. This can be established either by setting the
+ :paramref:`.Boolean.name` parameter or by setting up an
+ appropriate naming convention; see
+ :ref:`constraint_naming_conventions` for background.
+
+ .. versionchanged:: 1.4 - this flag now defaults to False, meaning
+ no CHECK constraint is generated for a non-native enumerated
+ type.
+
+ :param name: if a CHECK constraint is generated, specify
+ the name of the constraint.
+
+ """
+ self.create_constraint = create_constraint
+ self.name = name
+ self._create_events = _create_events
+ if _adapted_from:
+ self.dispatch = self.dispatch._join(_adapted_from.dispatch)
+
+ def _should_create_constraint(self, compiler, **kw):
+ if not self._is_impl_for_variant(compiler.dialect, kw):
+ return False
+ return (
+ not compiler.dialect.supports_native_boolean
+ and compiler.dialect.non_native_boolean_check_constraint
+ )
+
+ @util.preload_module("sqlalchemy.sql.schema")
+ def _set_table(self, column, table):
+ schema = util.preloaded.sql_schema
+ if not self.create_constraint:
+ return
+
+ variant_mapping = self._variant_mapping_for_set_table(column)
+
+ e = schema.CheckConstraint(
+ type_coerce(column, self).in_([0, 1]),
+ name=_NONE_NAME if self.name is None else self.name,
+ _create_rule=util.portable_instancemethod(
+ self._should_create_constraint,
+ {"variant_mapping": variant_mapping},
+ ),
+ _type_bound=True,
+ )
+ assert e.table is table
+
+ @property
+ def python_type(self):
+ return bool
+
+ _strict_bools = frozenset([None, True, False])
+
+ def _strict_as_bool(self, value):
+ if value not in self._strict_bools:
+ if not isinstance(value, int):
+ raise TypeError("Not a boolean value: %r" % (value,))
+ else:
+ raise ValueError(
+ "Value %r is not None, True, or False" % (value,)
+ )
+ return value
+
+ def literal_processor(self, dialect):
+ compiler = dialect.statement_compiler(dialect, None)
+ true = compiler.visit_true(None)
+ false = compiler.visit_false(None)
+
+ def process(value):
+ return true if self._strict_as_bool(value) else false
+
+ return process
+
+ def bind_processor(self, dialect):
+ _strict_as_bool = self._strict_as_bool
+
+ _coerce: Union[Type[bool], Type[int]]
+
+ if dialect.supports_native_boolean:
+ _coerce = bool
+ else:
+ _coerce = int
+
+ def process(value):
+ value = _strict_as_bool(value)
+ if value is not None:
+ value = _coerce(value)
+ return value
+
+ return process
+
+ def result_processor(self, dialect, coltype):
+ if dialect.supports_native_boolean:
+ return None
+ else:
+ return processors.int_to_boolean
+
+
+class _AbstractInterval(HasExpressionLookup, TypeEngine[dt.timedelta]):
+ @util.memoized_property
+ def _expression_adaptations(self):
+ # Based on
+ # https://www.postgresql.org/docs/current/static/functions-datetime.html.
+
+ return {
+ operators.add: {
+ Date: DateTime,
+ Interval: self.__class__,
+ DateTime: DateTime,
+ Time: Time,
+ },
+ operators.sub: {Interval: self.__class__},
+ operators.mul: {Numeric: self.__class__},
+ operators.truediv: {Numeric: self.__class__},
+ }
+
+ @util.ro_non_memoized_property
+ def _type_affinity(self) -> Type[Interval]:
+ return Interval
+
+
+class Interval(Emulated, _AbstractInterval, TypeDecorator[dt.timedelta]):
+ """A type for ``datetime.timedelta()`` objects.
+
+ The Interval type deals with ``datetime.timedelta`` objects. In
+ PostgreSQL and Oracle, the native ``INTERVAL`` type is used; for others,
+ the value is stored as a date which is relative to the "epoch"
+ (Jan. 1, 1970).
+
+ Note that the ``Interval`` type does not currently provide date arithmetic
+ operations on platforms which do not support interval types natively. Such
+ operations usually require transformation of both sides of the expression
+ (such as, conversion of both sides into integer epoch values first) which
+ currently is a manual procedure (such as via
+ :attr:`~sqlalchemy.sql.expression.func`).
+
+ """
+
+ impl = DateTime
+ epoch = dt.datetime.fromtimestamp(0, dt.timezone.utc).replace(tzinfo=None)
+ cache_ok = True
+
+ def __init__(
+ self,
+ native: bool = True,
+ second_precision: Optional[int] = None,
+ day_precision: Optional[int] = None,
+ ):
+ """Construct an Interval object.
+
+ :param native: when True, use the actual
+ INTERVAL type provided by the database, if
+ supported (currently PostgreSQL, Oracle).
+ Otherwise, represent the interval data as
+ an epoch value regardless.
+
+ :param second_precision: For native interval types
+ which support a "fractional seconds precision" parameter,
+ i.e. Oracle and PostgreSQL
+
+ :param day_precision: for native interval types which
+ support a "day precision" parameter, i.e. Oracle.
+
+ """
+ super().__init__()
+ self.native = native
+ self.second_precision = second_precision
+ self.day_precision = day_precision
+
+ class Comparator(
+ TypeDecorator.Comparator[_CT],
+ _AbstractInterval.Comparator[_CT],
+ ):
+ __slots__ = ()
+
+ comparator_factory = Comparator
+
+ @property
+ def python_type(self):
+ return dt.timedelta
+
+ def adapt_to_emulated(self, impltype, **kw):
+ return _AbstractInterval.adapt(self, impltype, **kw)
+
+ def coerce_compared_value(self, op, value):
+ return self.impl_instance.coerce_compared_value(op, value)
+
+ def bind_processor(
+ self, dialect: Dialect
+ ) -> _BindProcessorType[dt.timedelta]:
+ if TYPE_CHECKING:
+ assert isinstance(self.impl_instance, DateTime)
+ impl_processor = self.impl_instance.bind_processor(dialect)
+ epoch = self.epoch
+ if impl_processor:
+ fixed_impl_processor = impl_processor
+
+ def process(
+ value: Optional[dt.timedelta],
+ ) -> Any:
+ if value is not None:
+ dt_value = epoch + value
+ else:
+ dt_value = None
+ return fixed_impl_processor(dt_value)
+
+ else:
+
+ def process(
+ value: Optional[dt.timedelta],
+ ) -> Any:
+ if value is not None:
+ dt_value = epoch + value
+ else:
+ dt_value = None
+ return dt_value
+
+ return process
+
+ def result_processor(
+ self, dialect: Dialect, coltype: Any
+ ) -> _ResultProcessorType[dt.timedelta]:
+ if TYPE_CHECKING:
+ assert isinstance(self.impl_instance, DateTime)
+ impl_processor = self.impl_instance.result_processor(dialect, coltype)
+ epoch = self.epoch
+ if impl_processor:
+ fixed_impl_processor = impl_processor
+
+ def process(value: Any) -> Optional[dt.timedelta]:
+ dt_value = fixed_impl_processor(value)
+ if dt_value is None:
+ return None
+ return dt_value - epoch
+
+ else:
+
+ def process(value: Any) -> Optional[dt.timedelta]:
+ if value is None:
+ return None
+ return value - epoch # type: ignore
+
+ return process
+
+
+class JSON(Indexable, TypeEngine[Any]):
+ """Represent a SQL JSON type.
+
+ .. note:: :class:`_types.JSON`
+ is provided as a facade for vendor-specific
+ JSON types. Since it supports JSON SQL operations, it only
+ works on backends that have an actual JSON type, currently:
+
+ * PostgreSQL - see :class:`sqlalchemy.dialects.postgresql.JSON` and
+ :class:`sqlalchemy.dialects.postgresql.JSONB` for backend-specific
+ notes
+
+ * MySQL - see
+ :class:`sqlalchemy.dialects.mysql.JSON` for backend-specific notes
+
+ * SQLite as of version 3.9 - see
+ :class:`sqlalchemy.dialects.sqlite.JSON` for backend-specific notes
+
+ * Microsoft SQL Server 2016 and later - see
+ :class:`sqlalchemy.dialects.mssql.JSON` for backend-specific notes
+
+ :class:`_types.JSON` is part of the Core in support of the growing
+ popularity of native JSON datatypes.
+
+ The :class:`_types.JSON` type stores arbitrary JSON format data, e.g.::
+
+ data_table = Table('data_table', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('data', JSON)
+ )
+
+ with engine.connect() as conn:
+ conn.execute(
+ data_table.insert(),
+ {"data": {"key1": "value1", "key2": "value2"}}
+ )
+
+ **JSON-Specific Expression Operators**
+
+ The :class:`_types.JSON`
+ datatype provides these additional SQL operations:
+
+ * Keyed index operations::
+
+ data_table.c.data['some key']
+
+ * Integer index operations::
+
+ data_table.c.data[3]
+
+ * Path index operations::
+
+ data_table.c.data[('key_1', 'key_2', 5, ..., 'key_n')]
+
+ * Data casters for specific JSON element types, subsequent to an index
+ or path operation being invoked::
+
+ data_table.c.data["some key"].as_integer()
+
+ .. versionadded:: 1.3.11
+
+ Additional operations may be available from the dialect-specific versions
+ of :class:`_types.JSON`, such as
+ :class:`sqlalchemy.dialects.postgresql.JSON` and
+ :class:`sqlalchemy.dialects.postgresql.JSONB` which both offer additional
+ PostgreSQL-specific operations.
+
+ **Casting JSON Elements to Other Types**
+
+ Index operations, i.e. those invoked by calling upon the expression using
+ the Python bracket operator as in ``some_column['some key']``, return an
+ expression object whose type defaults to :class:`_types.JSON` by default,
+ so that
+ further JSON-oriented instructions may be called upon the result type.
+ However, it is likely more common that an index operation is expected
+ to return a specific scalar element, such as a string or integer. In
+ order to provide access to these elements in a backend-agnostic way,
+ a series of data casters are provided:
+
+ * :meth:`.JSON.Comparator.as_string` - return the element as a string
+
+ * :meth:`.JSON.Comparator.as_boolean` - return the element as a boolean
+
+ * :meth:`.JSON.Comparator.as_float` - return the element as a float
+
+ * :meth:`.JSON.Comparator.as_integer` - return the element as an integer
+
+ These data casters are implemented by supporting dialects in order to
+ assure that comparisons to the above types will work as expected, such as::
+
+ # integer comparison
+ data_table.c.data["some_integer_key"].as_integer() == 5
+
+ # boolean comparison
+ data_table.c.data["some_boolean"].as_boolean() == True
+
+ .. versionadded:: 1.3.11 Added type-specific casters for the basic JSON
+ data element types.
+
+ .. note::
+
+ The data caster functions are new in version 1.3.11, and supersede
+ the previous documented approaches of using CAST; for reference,
+ this looked like::
+
+ from sqlalchemy import cast, type_coerce
+ from sqlalchemy import String, JSON
+ cast(
+ data_table.c.data['some_key'], String
+ ) == type_coerce(55, JSON)
+
+ The above case now works directly as::
+
+ data_table.c.data['some_key'].as_integer() == 5
+
+ For details on the previous comparison approach within the 1.3.x
+ series, see the documentation for SQLAlchemy 1.2 or the included HTML
+ files in the doc/ directory of the version's distribution.
+
+ **Detecting Changes in JSON columns when using the ORM**
+
+ The :class:`_types.JSON` type, when used with the SQLAlchemy ORM, does not
+ detect in-place mutations to the structure. In order to detect these, the
+ :mod:`sqlalchemy.ext.mutable` extension must be used, most typically
+ using the :class:`.MutableDict` class. This extension will
+ allow "in-place" changes to the datastructure to produce events which
+ will be detected by the unit of work. See the example at :class:`.HSTORE`
+ for a simple example involving a dictionary.
+
+ Alternatively, assigning a JSON structure to an ORM element that
+ replaces the old one will always trigger a change event.
+
+ **Support for JSON null vs. SQL NULL**
+
+ When working with NULL values, the :class:`_types.JSON` type recommends the
+ use of two specific constants in order to differentiate between a column
+ that evaluates to SQL NULL, e.g. no value, vs. the JSON-encoded string of
+ ``"null"``. To insert or select against a value that is SQL NULL, use the
+ constant :func:`.null`. This symbol may be passed as a parameter value
+ specifically when using the :class:`_types.JSON` datatype, which contains
+ special logic that interprets this symbol to mean that the column value
+ should be SQL NULL as opposed to JSON ``"null"``::
+
+ from sqlalchemy import null
+ conn.execute(table.insert(), {"json_value": null()})
+
+ To insert or select against a value that is JSON ``"null"``, use the
+ constant :attr:`_types.JSON.NULL`::
+
+ conn.execute(table.insert(), {"json_value": JSON.NULL})
+
+ The :class:`_types.JSON` type supports a flag
+ :paramref:`_types.JSON.none_as_null` which when set to True will result
+ in the Python constant ``None`` evaluating to the value of SQL
+ NULL, and when set to False results in the Python constant
+ ``None`` evaluating to the value of JSON ``"null"``. The Python
+ value ``None`` may be used in conjunction with either
+ :attr:`_types.JSON.NULL` and :func:`.null` in order to indicate NULL
+ values, but care must be taken as to the value of the
+ :paramref:`_types.JSON.none_as_null` in these cases.
+
+ **Customizing the JSON Serializer**
+
+ The JSON serializer and deserializer used by :class:`_types.JSON`
+ defaults to
+ Python's ``json.dumps`` and ``json.loads`` functions; in the case of the
+ psycopg2 dialect, psycopg2 may be using its own custom loader function.
+
+ In order to affect the serializer / deserializer, they are currently
+ configurable at the :func:`_sa.create_engine` level via the
+ :paramref:`_sa.create_engine.json_serializer` and
+ :paramref:`_sa.create_engine.json_deserializer` parameters. For example,
+ to turn off ``ensure_ascii``::
+
+ engine = create_engine(
+ "sqlite://",
+ json_serializer=lambda obj: json.dumps(obj, ensure_ascii=False))
+
+ .. versionchanged:: 1.3.7
+
+ SQLite dialect's ``json_serializer`` and ``json_deserializer``
+ parameters renamed from ``_json_serializer`` and
+ ``_json_deserializer``.
+
+ .. seealso::
+
+ :class:`sqlalchemy.dialects.postgresql.JSON`
+
+ :class:`sqlalchemy.dialects.postgresql.JSONB`
+
+ :class:`sqlalchemy.dialects.mysql.JSON`
+
+ :class:`sqlalchemy.dialects.sqlite.JSON`
+
+ """
+
+ __visit_name__ = "JSON"
+
+ hashable = False
+ NULL = util.symbol("JSON_NULL")
+ """Describe the json value of NULL.
+
+ This value is used to force the JSON value of ``"null"`` to be
+ used as the value. A value of Python ``None`` will be recognized
+ either as SQL NULL or JSON ``"null"``, based on the setting
+ of the :paramref:`_types.JSON.none_as_null` flag; the
+ :attr:`_types.JSON.NULL`
+ constant can be used to always resolve to JSON ``"null"`` regardless
+ of this setting. This is in contrast to the :func:`_expression.null`
+ construct,
+ which always resolves to SQL NULL. E.g.::
+
+ from sqlalchemy import null
+ from sqlalchemy.dialects.postgresql import JSON
+
+ # will *always* insert SQL NULL
+ obj1 = MyObject(json_value=null())
+
+ # will *always* insert JSON string "null"
+ obj2 = MyObject(json_value=JSON.NULL)
+
+ session.add_all([obj1, obj2])
+ session.commit()
+
+ In order to set JSON NULL as a default value for a column, the most
+ transparent method is to use :func:`_expression.text`::
+
+ Table(
+ 'my_table', metadata,
+ Column('json_data', JSON, default=text("'null'"))
+ )
+
+ While it is possible to use :attr:`_types.JSON.NULL` in this context, the
+ :attr:`_types.JSON.NULL` value will be returned as the value of the
+ column,
+ which in the context of the ORM or other repurposing of the default
+ value, may not be desirable. Using a SQL expression means the value
+ will be re-fetched from the database within the context of retrieving
+ generated defaults.
+
+
+ """
+
+ def __init__(self, none_as_null: bool = False):
+ """Construct a :class:`_types.JSON` type.
+
+ :param none_as_null=False: if True, persist the value ``None`` as a
+ SQL NULL value, not the JSON encoding of ``null``. Note that when this
+ flag is False, the :func:`.null` construct can still be used to
+ persist a NULL value, which may be passed directly as a parameter
+ value that is specially interpreted by the :class:`_types.JSON` type
+ as SQL NULL::
+
+ from sqlalchemy import null
+ conn.execute(table.insert(), {"data": null()})
+
+ .. note::
+
+ :paramref:`_types.JSON.none_as_null` does **not** apply to the
+ values passed to :paramref:`_schema.Column.default` and
+ :paramref:`_schema.Column.server_default`; a value of ``None``
+ passed for these parameters means "no default present".
+
+ Additionally, when used in SQL comparison expressions, the
+ Python value ``None`` continues to refer to SQL null, and not
+ JSON NULL. The :paramref:`_types.JSON.none_as_null` flag refers
+ explicitly to the **persistence** of the value within an
+ INSERT or UPDATE statement. The :attr:`_types.JSON.NULL`
+ value should be used for SQL expressions that wish to compare to
+ JSON null.
+
+ .. seealso::
+
+ :attr:`.types.JSON.NULL`
+
+ """
+ self.none_as_null = none_as_null
+
+ class JSONElementType(TypeEngine[Any]):
+ """Common function for index / path elements in a JSON expression."""
+
+ _integer = Integer()
+ _string = String()
+
+ def string_bind_processor(self, dialect):
+ return self._string._cached_bind_processor(dialect)
+
+ def string_literal_processor(self, dialect):
+ return self._string._cached_literal_processor(dialect)
+
+ def bind_processor(self, dialect):
+ int_processor = self._integer._cached_bind_processor(dialect)
+ string_processor = self.string_bind_processor(dialect)
+
+ def process(value):
+ if int_processor and isinstance(value, int):
+ value = int_processor(value)
+ elif string_processor and isinstance(value, str):
+ value = string_processor(value)
+ return value
+
+ return process
+
+ def literal_processor(self, dialect):
+ int_processor = self._integer._cached_literal_processor(dialect)
+ string_processor = self.string_literal_processor(dialect)
+
+ def process(value):
+ if int_processor and isinstance(value, int):
+ value = int_processor(value)
+ elif string_processor and isinstance(value, str):
+ value = string_processor(value)
+ else:
+ raise NotImplementedError()
+
+ return value
+
+ return process
+
+ class JSONIndexType(JSONElementType):
+ """Placeholder for the datatype of a JSON index value.
+
+ This allows execution-time processing of JSON index values
+ for special syntaxes.
+
+ """
+
+ class JSONIntIndexType(JSONIndexType):
+ """Placeholder for the datatype of a JSON index value.
+
+ This allows execution-time processing of JSON index values
+ for special syntaxes.
+
+ """
+
+ class JSONStrIndexType(JSONIndexType):
+ """Placeholder for the datatype of a JSON index value.
+
+ This allows execution-time processing of JSON index values
+ for special syntaxes.
+
+ """
+
+ class JSONPathType(JSONElementType):
+ """Placeholder type for JSON path operations.
+
+ This allows execution-time processing of a path-based
+ index value into a specific SQL syntax.
+
+ """
+
+ __visit_name__ = "json_path"
+
+ class Comparator(Indexable.Comparator[_T], Concatenable.Comparator[_T]):
+ """Define comparison operations for :class:`_types.JSON`."""
+
+ __slots__ = ()
+
+ def _setup_getitem(self, index):
+ if not isinstance(index, str) and isinstance(
+ index, collections_abc.Sequence
+ ):
+ index = coercions.expect(
+ roles.BinaryElementRole,
+ index,
+ expr=self.expr,
+ operator=operators.json_path_getitem_op,
+ bindparam_type=JSON.JSONPathType,
+ )
+
+ operator = operators.json_path_getitem_op
+ else:
+ index = coercions.expect(
+ roles.BinaryElementRole,
+ index,
+ expr=self.expr,
+ operator=operators.json_getitem_op,
+ bindparam_type=(
+ JSON.JSONIntIndexType
+ if isinstance(index, int)
+ else JSON.JSONStrIndexType
+ ),
+ )
+ operator = operators.json_getitem_op
+
+ return operator, index, self.type
+
+ def as_boolean(self):
+ """Cast an indexed value as boolean.
+
+ e.g.::
+
+ stmt = select(
+ mytable.c.json_column['some_data'].as_boolean()
+ ).where(
+ mytable.c.json_column['some_data'].as_boolean() == True
+ )
+
+ .. versionadded:: 1.3.11
+
+ """
+ return self._binary_w_type(Boolean(), "as_boolean")
+
+ def as_string(self):
+ """Cast an indexed value as string.
+
+ e.g.::
+
+ stmt = select(
+ mytable.c.json_column['some_data'].as_string()
+ ).where(
+ mytable.c.json_column['some_data'].as_string() ==
+ 'some string'
+ )
+
+ .. versionadded:: 1.3.11
+
+ """
+ return self._binary_w_type(Unicode(), "as_string")
+
+ def as_integer(self):
+ """Cast an indexed value as integer.
+
+ e.g.::
+
+ stmt = select(
+ mytable.c.json_column['some_data'].as_integer()
+ ).where(
+ mytable.c.json_column['some_data'].as_integer() == 5
+ )
+
+ .. versionadded:: 1.3.11
+
+ """
+ return self._binary_w_type(Integer(), "as_integer")
+
+ def as_float(self):
+ """Cast an indexed value as float.
+
+ e.g.::
+
+ stmt = select(
+ mytable.c.json_column['some_data'].as_float()
+ ).where(
+ mytable.c.json_column['some_data'].as_float() == 29.75
+ )
+
+ .. versionadded:: 1.3.11
+
+ """
+ return self._binary_w_type(Float(), "as_float")
+
+ def as_numeric(self, precision, scale, asdecimal=True):
+ """Cast an indexed value as numeric/decimal.
+
+ e.g.::
+
+ stmt = select(
+ mytable.c.json_column['some_data'].as_numeric(10, 6)
+ ).where(
+ mytable.c.
+ json_column['some_data'].as_numeric(10, 6) == 29.75
+ )
+
+ .. versionadded:: 1.4.0b2
+
+ """
+ return self._binary_w_type(
+ Numeric(precision, scale, asdecimal=asdecimal), "as_numeric"
+ )
+
+ def as_json(self):
+ """Cast an indexed value as JSON.
+
+ e.g.::
+
+ stmt = select(mytable.c.json_column['some_data'].as_json())
+
+ This is typically the default behavior of indexed elements in any
+ case.
+
+ Note that comparison of full JSON structures may not be
+ supported by all backends.
+
+ .. versionadded:: 1.3.11
+
+ """
+ return self.expr
+
+ def _binary_w_type(self, typ, method_name):
+ if not isinstance(
+ self.expr, elements.BinaryExpression
+ ) or self.expr.operator not in (
+ operators.json_getitem_op,
+ operators.json_path_getitem_op,
+ ):
+ raise exc.InvalidRequestError(
+ "The JSON cast operator JSON.%s() only works with a JSON "
+ "index expression e.g. col['q'].%s()"
+ % (method_name, method_name)
+ )
+ expr = self.expr._clone()
+ expr.type = typ
+ return expr
+
+ comparator_factory = Comparator
+
+ @property
+ def python_type(self):
+ return dict
+
+ @property # type: ignore # mypy property bug
+ def should_evaluate_none(self):
+ """Alias of :attr:`_types.JSON.none_as_null`"""
+ return not self.none_as_null
+
+ @should_evaluate_none.setter
+ def should_evaluate_none(self, value):
+ self.none_as_null = not value
+
+ @util.memoized_property
+ def _str_impl(self):
+ return String()
+
+ def _make_bind_processor(self, string_process, json_serializer):
+ if string_process:
+
+ def process(value):
+ if value is self.NULL:
+ value = None
+ elif isinstance(value, elements.Null) or (
+ value is None and self.none_as_null
+ ):
+ return None
+
+ serialized = json_serializer(value)
+ return string_process(serialized)
+
+ else:
+
+ def process(value):
+ if value is self.NULL:
+ value = None
+ elif isinstance(value, elements.Null) or (
+ value is None and self.none_as_null
+ ):
+ return None
+
+ return json_serializer(value)
+
+ return process
+
+ def bind_processor(self, dialect):
+ string_process = self._str_impl.bind_processor(dialect)
+ json_serializer = dialect._json_serializer or json.dumps
+
+ return self._make_bind_processor(string_process, json_serializer)
+
+ def result_processor(self, dialect, coltype):
+ string_process = self._str_impl.result_processor(dialect, coltype)
+ json_deserializer = dialect._json_deserializer or json.loads
+
+ def process(value):
+ if value is None:
+ return None
+ if string_process:
+ value = string_process(value)
+ return json_deserializer(value)
+
+ return process
+
+
+class ARRAY(
+ SchemaEventTarget, Indexable, Concatenable, TypeEngine[Sequence[Any]]
+):
+ """Represent a SQL Array type.
+
+ .. note:: This type serves as the basis for all ARRAY operations.
+ However, currently **only the PostgreSQL backend has support for SQL
+ arrays in SQLAlchemy**. It is recommended to use the PostgreSQL-specific
+ :class:`sqlalchemy.dialects.postgresql.ARRAY` type directly when using
+ ARRAY types with PostgreSQL, as it provides additional operators
+ specific to that backend.
+
+ :class:`_types.ARRAY` is part of the Core in support of various SQL
+ standard functions such as :class:`_functions.array_agg`
+ which explicitly involve
+ arrays; however, with the exception of the PostgreSQL backend and possibly
+ some third-party dialects, no other SQLAlchemy built-in dialect has support
+ for this type.
+
+ An :class:`_types.ARRAY` type is constructed given the "type"
+ of element::
+
+ mytable = Table("mytable", metadata,
+ Column("data", ARRAY(Integer))
+ )
+
+ The above type represents an N-dimensional array,
+ meaning a supporting backend such as PostgreSQL will interpret values
+ with any number of dimensions automatically. To produce an INSERT
+ construct that passes in a 1-dimensional array of integers::
+
+ connection.execute(
+ mytable.insert(),
+ {"data": [1,2,3]}
+ )
+
+ The :class:`_types.ARRAY` type can be constructed given a fixed number
+ of dimensions::
+
+ mytable = Table("mytable", metadata,
+ Column("data", ARRAY(Integer, dimensions=2))
+ )
+
+ Sending a number of dimensions is optional, but recommended if the
+ datatype is to represent arrays of more than one dimension. This number
+ is used:
+
+ * When emitting the type declaration itself to the database, e.g.
+ ``INTEGER[][]``
+
+ * When translating Python values to database values, and vice versa, e.g.
+ an ARRAY of :class:`.Unicode` objects uses this number to efficiently
+ access the string values inside of array structures without resorting
+ to per-row type inspection
+
+ * When used with the Python ``getitem`` accessor, the number of dimensions
+ serves to define the kind of type that the ``[]`` operator should
+ return, e.g. for an ARRAY of INTEGER with two dimensions::
+
+ >>> expr = table.c.column[5] # returns ARRAY(Integer, dimensions=1)
+ >>> expr = expr[6] # returns Integer
+
+ For 1-dimensional arrays, an :class:`_types.ARRAY` instance with no
+ dimension parameter will generally assume single-dimensional behaviors.
+
+ SQL expressions of type :class:`_types.ARRAY` have support for "index" and
+ "slice" behavior. The ``[]`` operator produces expression
+ constructs which will produce the appropriate SQL, both for
+ SELECT statements::
+
+ select(mytable.c.data[5], mytable.c.data[2:7])
+
+ as well as UPDATE statements when the :meth:`_expression.Update.values`
+ method is used::
+
+ mytable.update().values({
+ mytable.c.data[5]: 7,
+ mytable.c.data[2:7]: [1, 2, 3]
+ })
+
+ Indexed access is one-based by default;
+ for zero-based index conversion, set :paramref:`_types.ARRAY.zero_indexes`.
+
+ The :class:`_types.ARRAY` type also provides for the operators
+ :meth:`.types.ARRAY.Comparator.any` and
+ :meth:`.types.ARRAY.Comparator.all`. The PostgreSQL-specific version of
+ :class:`_types.ARRAY` also provides additional operators.
+
+ .. container:: topic
+
+ **Detecting Changes in ARRAY columns when using the ORM**
+
+ The :class:`_sqltypes.ARRAY` type, when used with the SQLAlchemy ORM,
+ does not detect in-place mutations to the array. In order to detect
+ these, the :mod:`sqlalchemy.ext.mutable` extension must be used, using
+ the :class:`.MutableList` class::
+
+ from sqlalchemy import ARRAY
+ from sqlalchemy.ext.mutable import MutableList
+
+ class SomeOrmClass(Base):
+ # ...
+
+ data = Column(MutableList.as_mutable(ARRAY(Integer)))
+
+ This extension will allow "in-place" changes such to the array
+ such as ``.append()`` to produce events which will be detected by the
+ unit of work. Note that changes to elements **inside** the array,
+ including subarrays that are mutated in place, are **not** detected.
+
+ Alternatively, assigning a new array value to an ORM element that
+ replaces the old one will always trigger a change event.
+
+ .. seealso::
+
+ :class:`sqlalchemy.dialects.postgresql.ARRAY`
+
+ """
+
+ __visit_name__ = "ARRAY"
+
+ _is_array = True
+
+ zero_indexes = False
+ """If True, Python zero-based indexes should be interpreted as one-based
+ on the SQL expression side."""
+
+ def __init__(
+ self,
+ item_type: _TypeEngineArgument[Any],
+ as_tuple: bool = False,
+ dimensions: Optional[int] = None,
+ zero_indexes: bool = False,
+ ):
+ """Construct an :class:`_types.ARRAY`.
+
+ E.g.::
+
+ Column('myarray', ARRAY(Integer))
+
+ Arguments are:
+
+ :param item_type: The data type of items of this array. Note that
+ dimensionality is irrelevant here, so multi-dimensional arrays like
+ ``INTEGER[][]``, are constructed as ``ARRAY(Integer)``, not as
+ ``ARRAY(ARRAY(Integer))`` or such.
+
+ :param as_tuple=False: Specify whether return results
+ should be converted to tuples from lists. This parameter is
+ not generally needed as a Python list corresponds well
+ to a SQL array.
+
+ :param dimensions: if non-None, the ARRAY will assume a fixed
+ number of dimensions. This impacts how the array is declared
+ on the database, how it goes about interpreting Python and
+ result values, as well as how expression behavior in conjunction
+ with the "getitem" operator works. See the description at
+ :class:`_types.ARRAY` for additional detail.
+
+ :param zero_indexes=False: when True, index values will be converted
+ between Python zero-based and SQL one-based indexes, e.g.
+ a value of one will be added to all index values before passing
+ to the database.
+
+ """
+ if isinstance(item_type, ARRAY):
+ raise ValueError(
+ "Do not nest ARRAY types; ARRAY(basetype) "
+ "handles multi-dimensional arrays of basetype"
+ )
+ if isinstance(item_type, type):
+ item_type = item_type()
+ self.item_type = item_type
+ self.as_tuple = as_tuple
+ self.dimensions = dimensions
+ self.zero_indexes = zero_indexes
+
+ class Comparator(
+ Indexable.Comparator[Sequence[Any]],
+ Concatenable.Comparator[Sequence[Any]],
+ ):
+ """Define comparison operations for :class:`_types.ARRAY`.
+
+ More operators are available on the dialect-specific form
+ of this type. See :class:`.postgresql.ARRAY.Comparator`.
+
+ """
+
+ __slots__ = ()
+
+ type: ARRAY
+
+ def _setup_getitem(self, index):
+ arr_type = self.type
+
+ return_type: TypeEngine[Any]
+
+ if isinstance(index, slice):
+ return_type = arr_type
+ if arr_type.zero_indexes:
+ index = slice(index.start + 1, index.stop + 1, index.step)
+ slice_ = Slice(
+ index.start, index.stop, index.step, _name=self.expr.key
+ )
+ return operators.getitem, slice_, return_type
+ else:
+ if arr_type.zero_indexes:
+ index += 1
+ if arr_type.dimensions is None or arr_type.dimensions == 1:
+ return_type = arr_type.item_type
+ else:
+ adapt_kw = {"dimensions": arr_type.dimensions - 1}
+ return_type = arr_type.adapt(
+ arr_type.__class__, **adapt_kw
+ )
+
+ return operators.getitem, index, return_type
+
+ def contains(self, *arg, **kw):
+ """``ARRAY.contains()`` not implemented for the base ARRAY type.
+ Use the dialect-specific ARRAY type.
+
+ .. seealso::
+
+ :class:`_postgresql.ARRAY` - PostgreSQL specific version.
+ """
+ raise NotImplementedError(
+ "ARRAY.contains() not implemented for the base "
+ "ARRAY type; please use the dialect-specific ARRAY type"
+ )
+
+ @util.preload_module("sqlalchemy.sql.elements")
+ def any(self, other, operator=None):
+ """Return ``other operator ANY (array)`` clause.
+
+ .. legacy:: This method is an :class:`_types.ARRAY` - specific
+ construct that is now superseded by the :func:`_sql.any_`
+ function, which features a different calling style. The
+ :func:`_sql.any_` function is also mirrored at the method level
+ via the :meth:`_sql.ColumnOperators.any_` method.
+
+ Usage of array-specific :meth:`_types.ARRAY.Comparator.any`
+ is as follows::
+
+ from sqlalchemy.sql import operators
+
+ conn.execute(
+ select(table.c.data).where(
+ table.c.data.any(7, operator=operators.lt)
+ )
+ )
+
+ :param other: expression to be compared
+ :param operator: an operator object from the
+ :mod:`sqlalchemy.sql.operators`
+ package, defaults to :func:`.operators.eq`.
+
+ .. seealso::
+
+ :func:`_expression.any_`
+
+ :meth:`.types.ARRAY.Comparator.all`
+
+ """
+ elements = util.preloaded.sql_elements
+ operator = operator if operator else operators.eq
+
+ arr_type = self.type
+
+ return elements.CollectionAggregate._create_any(self.expr).operate(
+ operators.mirror(operator),
+ coercions.expect(
+ roles.BinaryElementRole,
+ element=other,
+ operator=operator,
+ expr=self.expr,
+ bindparam_type=arr_type.item_type,
+ ),
+ )
+
+ @util.preload_module("sqlalchemy.sql.elements")
+ def all(self, other, operator=None):
+ """Return ``other operator ALL (array)`` clause.
+
+ .. legacy:: This method is an :class:`_types.ARRAY` - specific
+ construct that is now superseded by the :func:`_sql.all_`
+ function, which features a different calling style. The
+ :func:`_sql.all_` function is also mirrored at the method level
+ via the :meth:`_sql.ColumnOperators.all_` method.
+
+ Usage of array-specific :meth:`_types.ARRAY.Comparator.all`
+ is as follows::
+
+ from sqlalchemy.sql import operators
+
+ conn.execute(
+ select(table.c.data).where(
+ table.c.data.all(7, operator=operators.lt)
+ )
+ )
+
+ :param other: expression to be compared
+ :param operator: an operator object from the
+ :mod:`sqlalchemy.sql.operators`
+ package, defaults to :func:`.operators.eq`.
+
+ .. seealso::
+
+ :func:`_expression.all_`
+
+ :meth:`.types.ARRAY.Comparator.any`
+
+ """
+ elements = util.preloaded.sql_elements
+ operator = operator if operator else operators.eq
+
+ arr_type = self.type
+
+ return elements.CollectionAggregate._create_all(self.expr).operate(
+ operators.mirror(operator),
+ coercions.expect(
+ roles.BinaryElementRole,
+ element=other,
+ operator=operator,
+ expr=self.expr,
+ bindparam_type=arr_type.item_type,
+ ),
+ )
+
+ comparator_factory = Comparator
+
+ @property
+ def hashable(self):
+ return self.as_tuple
+
+ @property
+ def python_type(self):
+ return list
+
+ def compare_values(self, x, y):
+ return x == y
+
+ def _set_parent(self, column, outer=False, **kw):
+ """Support SchemaEventTarget"""
+
+ if not outer and isinstance(self.item_type, SchemaEventTarget):
+ self.item_type._set_parent(column, **kw)
+
+ def _set_parent_with_dispatch(self, parent):
+ """Support SchemaEventTarget"""
+
+ super()._set_parent_with_dispatch(parent, outer=True)
+
+ if isinstance(self.item_type, SchemaEventTarget):
+ self.item_type._set_parent_with_dispatch(parent)
+
+ def literal_processor(self, dialect):
+ item_proc = self.item_type.dialect_impl(dialect).literal_processor(
+ dialect
+ )
+ if item_proc is None:
+ return None
+
+ def to_str(elements):
+ return f"[{', '.join(elements)}]"
+
+ def process(value):
+ inner = self._apply_item_processor(
+ value, item_proc, self.dimensions, to_str
+ )
+ return inner
+
+ return process
+
+ def _apply_item_processor(self, arr, itemproc, dim, collection_callable):
+ """Helper method that can be used by bind_processor(),
+ literal_processor(), etc. to apply an item processor to elements of
+ an array value, taking into account the 'dimensions' for this
+ array type.
+
+ See the Postgresql ARRAY datatype for usage examples.
+
+ .. versionadded:: 2.0
+
+ """
+
+ if dim is None:
+ arr = list(arr)
+ if (
+ dim == 1
+ or dim is None
+ and (
+ # this has to be (list, tuple), or at least
+ # not hasattr('__iter__'), since Py3K strings
+ # etc. have __iter__
+ not arr
+ or not isinstance(arr[0], (list, tuple))
+ )
+ ):
+ if itemproc:
+ return collection_callable(itemproc(x) for x in arr)
+ else:
+ return collection_callable(arr)
+ else:
+ return collection_callable(
+ (
+ self._apply_item_processor(
+ x,
+ itemproc,
+ dim - 1 if dim is not None else None,
+ collection_callable,
+ )
+ if x is not None
+ else None
+ )
+ for x in arr
+ )
+
+
+class TupleType(TypeEngine[Tuple[Any, ...]]):
+ """represent the composite type of a Tuple."""
+
+ _is_tuple_type = True
+
+ types: List[TypeEngine[Any]]
+
+ def __init__(self, *types: _TypeEngineArgument[Any]):
+ self._fully_typed = NULLTYPE not in types
+ self.types = [
+ item_type() if isinstance(item_type, type) else item_type
+ for item_type in types
+ ]
+
+ def coerce_compared_value(
+ self, op: Optional[OperatorType], value: Any
+ ) -> TypeEngine[Any]:
+ if value is type_api._NO_VALUE_IN_LIST:
+ return super().coerce_compared_value(op, value)
+ else:
+ return TupleType(
+ *[
+ typ.coerce_compared_value(op, elem)
+ for typ, elem in zip(self.types, value)
+ ]
+ )
+
+ def _resolve_values_to_types(self, value: Any) -> TupleType:
+ if self._fully_typed:
+ return self
+ else:
+ return TupleType(
+ *[
+ _resolve_value_to_type(elem) if typ is NULLTYPE else typ
+ for typ, elem in zip(self.types, value)
+ ]
+ )
+
+ def result_processor(self, dialect, coltype):
+ raise NotImplementedError(
+ "The tuple type does not support being fetched "
+ "as a column in a result row."
+ )
+
+
+class REAL(Float[_N]):
+ """The SQL REAL type.
+
+ .. seealso::
+
+ :class:`_types.Float` - documentation for the base type.
+
+ """
+
+ __visit_name__ = "REAL"
+
+
+class FLOAT(Float[_N]):
+ """The SQL FLOAT type.
+
+ .. seealso::
+
+ :class:`_types.Float` - documentation for the base type.
+
+ """
+
+ __visit_name__ = "FLOAT"
+
+
+class DOUBLE(Double[_N]):
+ """The SQL DOUBLE type.
+
+ .. versionadded:: 2.0
+
+ .. seealso::
+
+ :class:`_types.Double` - documentation for the base type.
+
+ """
+
+ __visit_name__ = "DOUBLE"
+
+
+class DOUBLE_PRECISION(Double[_N]):
+ """The SQL DOUBLE PRECISION type.
+
+ .. versionadded:: 2.0
+
+ .. seealso::
+
+ :class:`_types.Double` - documentation for the base type.
+
+ """
+
+ __visit_name__ = "DOUBLE_PRECISION"
+
+
+class NUMERIC(Numeric[_N]):
+ """The SQL NUMERIC type.
+
+ .. seealso::
+
+ :class:`_types.Numeric` - documentation for the base type.
+
+ """
+
+ __visit_name__ = "NUMERIC"
+
+
+class DECIMAL(Numeric[_N]):
+ """The SQL DECIMAL type.
+
+ .. seealso::
+
+ :class:`_types.Numeric` - documentation for the base type.
+
+ """
+
+ __visit_name__ = "DECIMAL"
+
+
+class INTEGER(Integer):
+ """The SQL INT or INTEGER type.
+
+ .. seealso::
+
+ :class:`_types.Integer` - documentation for the base type.
+
+ """
+
+ __visit_name__ = "INTEGER"
+
+
+INT = INTEGER
+
+
+class SMALLINT(SmallInteger):
+ """The SQL SMALLINT type.
+
+ .. seealso::
+
+ :class:`_types.SmallInteger` - documentation for the base type.
+
+ """
+
+ __visit_name__ = "SMALLINT"
+
+
+class BIGINT(BigInteger):
+ """The SQL BIGINT type.
+
+ .. seealso::
+
+ :class:`_types.BigInteger` - documentation for the base type.
+
+ """
+
+ __visit_name__ = "BIGINT"
+
+
+class TIMESTAMP(DateTime):
+ """The SQL TIMESTAMP type.
+
+ :class:`_types.TIMESTAMP` datatypes have support for timezone
+ storage on some backends, such as PostgreSQL and Oracle. Use the
+ :paramref:`~types.TIMESTAMP.timezone` argument in order to enable
+ "TIMESTAMP WITH TIMEZONE" for these backends.
+
+ """
+
+ __visit_name__ = "TIMESTAMP"
+
+ def __init__(self, timezone: bool = False):
+ """Construct a new :class:`_types.TIMESTAMP`.
+
+ :param timezone: boolean. Indicates that the TIMESTAMP type should
+ enable timezone support, if available on the target database.
+ On a per-dialect basis is similar to "TIMESTAMP WITH TIMEZONE".
+ If the target database does not support timezones, this flag is
+ ignored.
+
+
+ """
+ super().__init__(timezone=timezone)
+
+ def get_dbapi_type(self, dbapi):
+ return dbapi.TIMESTAMP
+
+
+class DATETIME(DateTime):
+ """The SQL DATETIME type."""
+
+ __visit_name__ = "DATETIME"
+
+
+class DATE(Date):
+ """The SQL DATE type."""
+
+ __visit_name__ = "DATE"
+
+
+class TIME(Time):
+ """The SQL TIME type."""
+
+ __visit_name__ = "TIME"
+
+
+class TEXT(Text):
+ """The SQL TEXT type."""
+
+ __visit_name__ = "TEXT"
+
+
+class CLOB(Text):
+ """The CLOB type.
+
+ This type is found in Oracle and Informix.
+ """
+
+ __visit_name__ = "CLOB"
+
+
+class VARCHAR(String):
+ """The SQL VARCHAR type."""
+
+ __visit_name__ = "VARCHAR"
+
+
+class NVARCHAR(Unicode):
+ """The SQL NVARCHAR type."""
+
+ __visit_name__ = "NVARCHAR"
+
+
+class CHAR(String):
+ """The SQL CHAR type."""
+
+ __visit_name__ = "CHAR"
+
+
+class NCHAR(Unicode):
+ """The SQL NCHAR type."""
+
+ __visit_name__ = "NCHAR"
+
+
+class BLOB(LargeBinary):
+ """The SQL BLOB type."""
+
+ __visit_name__ = "BLOB"
+
+
+class BINARY(_Binary):
+ """The SQL BINARY type."""
+
+ __visit_name__ = "BINARY"
+
+
+class VARBINARY(_Binary):
+ """The SQL VARBINARY type."""
+
+ __visit_name__ = "VARBINARY"
+
+
+class BOOLEAN(Boolean):
+ """The SQL BOOLEAN type."""
+
+ __visit_name__ = "BOOLEAN"
+
+
+class NullType(TypeEngine[None]):
+ """An unknown type.
+
+ :class:`.NullType` is used as a default type for those cases where
+ a type cannot be determined, including:
+
+ * During table reflection, when the type of a column is not recognized
+ by the :class:`.Dialect`
+ * When constructing SQL expressions using plain Python objects of
+ unknown types (e.g. ``somecolumn == my_special_object``)
+ * When a new :class:`_schema.Column` is created,
+ and the given type is passed
+ as ``None`` or is not passed at all.
+
+ The :class:`.NullType` can be used within SQL expression invocation
+ without issue, it just has no behavior either at the expression
+ construction level or at the bind-parameter/result processing level.
+ :class:`.NullType` will result in a :exc:`.CompileError` if the compiler
+ is asked to render the type itself, such as if it is used in a
+ :func:`.cast` operation or within a schema creation operation such as that
+ invoked by :meth:`_schema.MetaData.create_all` or the
+ :class:`.CreateTable`
+ construct.
+
+ """
+
+ __visit_name__ = "null"
+
+ _isnull = True
+
+ def literal_processor(self, dialect):
+ return None
+
+ class Comparator(TypeEngine.Comparator[_T]):
+ __slots__ = ()
+
+ def _adapt_expression(
+ self,
+ op: OperatorType,
+ other_comparator: TypeEngine.Comparator[Any],
+ ) -> Tuple[OperatorType, TypeEngine[Any]]:
+ if isinstance(
+ other_comparator, NullType.Comparator
+ ) or not operators.is_commutative(op):
+ return op, self.expr.type
+ else:
+ return other_comparator._adapt_expression(op, self)
+
+ comparator_factory = Comparator
+
+
+class TableValueType(HasCacheKey, TypeEngine[Any]):
+ """Refers to a table value type."""
+
+ _is_table_value = True
+
+ _traverse_internals = [
+ ("_elements", InternalTraversal.dp_clauseelement_list),
+ ]
+
+ def __init__(self, *elements: Union[str, _ColumnExpressionArgument[Any]]):
+ self._elements = [
+ coercions.expect(roles.StrAsPlainColumnRole, elem)
+ for elem in elements
+ ]
+
+
+class MatchType(Boolean):
+ """Refers to the return type of the MATCH operator.
+
+ As the :meth:`.ColumnOperators.match` is probably the most open-ended
+ operator in generic SQLAlchemy Core, we can't assume the return type
+ at SQL evaluation time, as MySQL returns a floating point, not a boolean,
+ and other backends might do something different. So this type
+ acts as a placeholder, currently subclassing :class:`.Boolean`.
+ The type allows dialects to inject result-processing functionality
+ if needed, and on MySQL will return floating-point values.
+
+ """
+
+
+_UUID_RETURN = TypeVar("_UUID_RETURN", str, _python_UUID)
+
+
+class Uuid(Emulated, TypeEngine[_UUID_RETURN]):
+ """Represent a database agnostic UUID datatype.
+
+ For backends that have no "native" UUID datatype, the value will
+ make use of ``CHAR(32)`` and store the UUID as a 32-character alphanumeric
+ hex string.
+
+ For backends which are known to support ``UUID`` directly or a similar
+ uuid-storing datatype such as SQL Server's ``UNIQUEIDENTIFIER``, a
+ "native" mode enabled by default allows these types will be used on those
+ backends.
+
+ In its default mode of use, the :class:`_sqltypes.Uuid` datatype expects
+ **Python uuid objects**, from the Python
+ `uuid <https://docs.python.org/3/library/uuid.html>`_
+ module::
+
+ import uuid
+
+ from sqlalchemy import Uuid
+ from sqlalchemy import Table, Column, MetaData, String
+
+
+ metadata_obj = MetaData()
+
+ t = Table(
+ "t",
+ metadata_obj,
+ Column('uuid_data', Uuid, primary_key=True),
+ Column("other_data", String)
+ )
+
+ with engine.begin() as conn:
+ conn.execute(
+ t.insert(),
+ {"uuid_data": uuid.uuid4(), "other_data", "some data"}
+ )
+
+ To have the :class:`_sqltypes.Uuid` datatype work with string-based
+ Uuids (e.g. 32 character hexadecimal strings), pass the
+ :paramref:`_sqltypes.Uuid.as_uuid` parameter with the value ``False``.
+
+ .. versionadded:: 2.0
+
+ .. seealso::
+
+ :class:`_sqltypes.UUID` - represents exactly the ``UUID`` datatype
+ without any backend-agnostic behaviors.
+
+ """
+
+ __visit_name__ = "uuid"
+
+ collation: Optional[str] = None
+
+ @overload
+ def __init__(
+ self: Uuid[_python_UUID],
+ as_uuid: Literal[True] = ...,
+ native_uuid: bool = ...,
+ ): ...
+
+ @overload
+ def __init__(
+ self: Uuid[str],
+ as_uuid: Literal[False] = ...,
+ native_uuid: bool = ...,
+ ): ...
+
+ def __init__(self, as_uuid: bool = True, native_uuid: bool = True):
+ """Construct a :class:`_sqltypes.Uuid` type.
+
+ :param as_uuid=True: if True, values will be interpreted
+ as Python uuid objects, converting to/from string via the
+ DBAPI.
+
+ .. versionchanged: 2.0 ``as_uuid`` now defaults to ``True``.
+
+ :param native_uuid=True: if True, backends that support either the
+ ``UUID`` datatype directly, or a UUID-storing value
+ (such as SQL Server's ``UNIQUEIDENTIFIER`` will be used by those
+ backends. If False, a ``CHAR(32)`` datatype will be used for
+ all backends regardless of native support.
+
+ """
+ self.as_uuid = as_uuid
+ self.native_uuid = native_uuid
+
+ @property
+ def python_type(self):
+ return _python_UUID if self.as_uuid else str
+
+ @property
+ def native(self):
+ return self.native_uuid
+
+ def coerce_compared_value(self, op, value):
+ """See :meth:`.TypeEngine.coerce_compared_value` for a description."""
+
+ if isinstance(value, str):
+ return self
+ else:
+ return super().coerce_compared_value(op, value)
+
+ def bind_processor(self, dialect):
+ character_based_uuid = (
+ not dialect.supports_native_uuid or not self.native_uuid
+ )
+
+ if character_based_uuid:
+ if self.as_uuid:
+
+ def process(value):
+ if value is not None:
+ value = value.hex
+ return value
+
+ return process
+ else:
+
+ def process(value):
+ if value is not None:
+ value = value.replace("-", "")
+ return value
+
+ return process
+ else:
+ return None
+
+ def result_processor(self, dialect, coltype):
+ character_based_uuid = (
+ not dialect.supports_native_uuid or not self.native_uuid
+ )
+
+ if character_based_uuid:
+ if self.as_uuid:
+
+ def process(value):
+ if value is not None:
+ value = _python_UUID(value)
+ return value
+
+ return process
+ else:
+
+ def process(value):
+ if value is not None:
+ value = str(_python_UUID(value))
+ return value
+
+ return process
+ else:
+ if not self.as_uuid:
+
+ def process(value):
+ if value is not None:
+ value = str(value)
+ return value
+
+ return process
+ else:
+ return None
+
+ def literal_processor(self, dialect):
+ character_based_uuid = (
+ not dialect.supports_native_uuid or not self.native_uuid
+ )
+
+ if not self.as_uuid:
+
+ def process(value):
+ return f"""'{value.replace("-", "").replace("'", "''")}'"""
+
+ return process
+ else:
+ if character_based_uuid:
+
+ def process(value):
+ return f"""'{value.hex}'"""
+
+ return process
+ else:
+
+ def process(value):
+ return f"""'{str(value).replace("'", "''")}'"""
+
+ return process
+
+
+class UUID(Uuid[_UUID_RETURN], type_api.NativeForEmulated):
+ """Represent the SQL UUID type.
+
+ This is the SQL-native form of the :class:`_types.Uuid` database agnostic
+ datatype, and is backwards compatible with the previous PostgreSQL-only
+ version of ``UUID``.
+
+ The :class:`_sqltypes.UUID` datatype only works on databases that have a
+ SQL datatype named ``UUID``. It will not function for backends which don't
+ have this exact-named type, including SQL Server. For backend-agnostic UUID
+ values with native support, including for SQL Server's ``UNIQUEIDENTIFIER``
+ datatype, use the :class:`_sqltypes.Uuid` datatype.
+
+ .. versionadded:: 2.0
+
+ .. seealso::
+
+ :class:`_sqltypes.Uuid`
+
+ """
+
+ __visit_name__ = "UUID"
+
+ @overload
+ def __init__(self: UUID[_python_UUID], as_uuid: Literal[True] = ...): ...
+
+ @overload
+ def __init__(self: UUID[str], as_uuid: Literal[False] = ...): ...
+
+ def __init__(self, as_uuid: bool = True):
+ """Construct a :class:`_sqltypes.UUID` type.
+
+
+ :param as_uuid=True: if True, values will be interpreted
+ as Python uuid objects, converting to/from string via the
+ DBAPI.
+
+ .. versionchanged: 2.0 ``as_uuid`` now defaults to ``True``.
+
+ """
+ self.as_uuid = as_uuid
+ self.native_uuid = True
+
+ @classmethod
+ def adapt_emulated_to_native(cls, impl, **kw):
+ kw.setdefault("as_uuid", impl.as_uuid)
+ return cls(**kw)
+
+
+NULLTYPE = NullType()
+BOOLEANTYPE = Boolean()
+STRINGTYPE = String()
+INTEGERTYPE = Integer()
+NUMERICTYPE: Numeric[decimal.Decimal] = Numeric()
+MATCHTYPE = MatchType()
+TABLEVALUE = TableValueType()
+DATETIME_TIMEZONE = DateTime(timezone=True)
+TIME_TIMEZONE = Time(timezone=True)
+_BIGINTEGER = BigInteger()
+_DATETIME = DateTime()
+_TIME = Time()
+_STRING = String()
+_UNICODE = Unicode()
+
+_type_map: Dict[Type[Any], TypeEngine[Any]] = {
+ int: Integer(),
+ float: Float(),
+ bool: BOOLEANTYPE,
+ _python_UUID: Uuid(),
+ decimal.Decimal: Numeric(),
+ dt.date: Date(),
+ dt.datetime: _DATETIME,
+ dt.time: _TIME,
+ dt.timedelta: Interval(),
+ type(None): NULLTYPE,
+ bytes: LargeBinary(),
+ str: _STRING,
+ enum.Enum: Enum(enum.Enum),
+ Literal: Enum(enum.Enum), # type: ignore[dict-item]
+}
+
+
+_type_map_get = _type_map.get
+
+
+def _resolve_value_to_type(value: Any) -> TypeEngine[Any]:
+ _result_type = _type_map_get(type(value), False)
+
+ if _result_type is False:
+ _result_type = getattr(value, "__sa_type_engine__", False)
+
+ if _result_type is False:
+ # use inspect() to detect SQLAlchemy built-in
+ # objects.
+ insp = inspection.inspect(value, False)
+ if (
+ insp is not None
+ and
+ # foil mock.Mock() and other impostors by ensuring
+ # the inspection target itself self-inspects
+ insp.__class__ in inspection._registrars
+ ):
+ raise exc.ArgumentError(
+ "Object %r is not legal as a SQL literal value" % (value,)
+ )
+ return NULLTYPE
+ else:
+ return _result_type._resolve_for_literal( # type: ignore [union-attr]
+ value
+ )
+
+
+# back-assign to type_api
+type_api.BOOLEANTYPE = BOOLEANTYPE
+type_api.STRINGTYPE = STRINGTYPE
+type_api.INTEGERTYPE = INTEGERTYPE
+type_api.NULLTYPE = NULLTYPE
+type_api.NUMERICTYPE = NUMERICTYPE
+type_api.MATCHTYPE = MATCHTYPE
+type_api.INDEXABLE = INDEXABLE = Indexable
+type_api.TABLEVALUE = TABLEVALUE
+type_api._resolve_value_to_type = _resolve_value_to_type
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/traversals.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/traversals.py
new file mode 100644
index 0000000..3ca3caf
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/traversals.py
@@ -0,0 +1,1022 @@
+# sql/traversals.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+# mypy: allow-untyped-defs, allow-untyped-calls
+
+from __future__ import annotations
+
+from collections import deque
+import collections.abc as collections_abc
+import itertools
+from itertools import zip_longest
+import operator
+import typing
+from typing import Any
+from typing import Callable
+from typing import Deque
+from typing import Dict
+from typing import Iterable
+from typing import Optional
+from typing import Set
+from typing import Tuple
+from typing import Type
+
+from . import operators
+from .cache_key import HasCacheKey
+from .visitors import _TraverseInternalsType
+from .visitors import anon_map
+from .visitors import ExternallyTraversible
+from .visitors import HasTraversalDispatch
+from .visitors import HasTraverseInternals
+from .. import util
+from ..util import langhelpers
+from ..util.typing import Self
+
+
+SKIP_TRAVERSE = util.symbol("skip_traverse")
+COMPARE_FAILED = False
+COMPARE_SUCCEEDED = True
+
+
+def compare(obj1: Any, obj2: Any, **kw: Any) -> bool:
+ strategy: TraversalComparatorStrategy
+ if kw.get("use_proxies", False):
+ strategy = ColIdentityComparatorStrategy()
+ else:
+ strategy = TraversalComparatorStrategy()
+
+ return strategy.compare(obj1, obj2, **kw)
+
+
+def _preconfigure_traversals(target_hierarchy: Type[Any]) -> None:
+ for cls in util.walk_subclasses(target_hierarchy):
+ if hasattr(cls, "_generate_cache_attrs") and hasattr(
+ cls, "_traverse_internals"
+ ):
+ cls._generate_cache_attrs()
+ _copy_internals.generate_dispatch(
+ cls,
+ cls._traverse_internals,
+ "_generated_copy_internals_traversal",
+ )
+ _get_children.generate_dispatch(
+ cls,
+ cls._traverse_internals,
+ "_generated_get_children_traversal",
+ )
+
+
+class HasShallowCopy(HasTraverseInternals):
+ """attribute-wide operations that are useful for classes that use
+ __slots__ and therefore can't operate on their attributes in a dictionary.
+
+
+ """
+
+ __slots__ = ()
+
+ if typing.TYPE_CHECKING:
+
+ def _generated_shallow_copy_traversal(self, other: Self) -> None: ...
+
+ def _generated_shallow_from_dict_traversal(
+ self, d: Dict[str, Any]
+ ) -> None: ...
+
+ def _generated_shallow_to_dict_traversal(self) -> Dict[str, Any]: ...
+
+ @classmethod
+ def _generate_shallow_copy(
+ cls,
+ internal_dispatch: _TraverseInternalsType,
+ method_name: str,
+ ) -> Callable[[Self, Self], None]:
+ code = "\n".join(
+ f" other.{attrname} = self.{attrname}"
+ for attrname, _ in internal_dispatch
+ )
+ meth_text = f"def {method_name}(self, other):\n{code}\n"
+ return langhelpers._exec_code_in_env(meth_text, {}, method_name)
+
+ @classmethod
+ def _generate_shallow_to_dict(
+ cls,
+ internal_dispatch: _TraverseInternalsType,
+ method_name: str,
+ ) -> Callable[[Self], Dict[str, Any]]:
+ code = ",\n".join(
+ f" '{attrname}': self.{attrname}"
+ for attrname, _ in internal_dispatch
+ )
+ meth_text = f"def {method_name}(self):\n return {{{code}}}\n"
+ return langhelpers._exec_code_in_env(meth_text, {}, method_name)
+
+ @classmethod
+ def _generate_shallow_from_dict(
+ cls,
+ internal_dispatch: _TraverseInternalsType,
+ method_name: str,
+ ) -> Callable[[Self, Dict[str, Any]], None]:
+ code = "\n".join(
+ f" self.{attrname} = d['{attrname}']"
+ for attrname, _ in internal_dispatch
+ )
+ meth_text = f"def {method_name}(self, d):\n{code}\n"
+ return langhelpers._exec_code_in_env(meth_text, {}, method_name)
+
+ def _shallow_from_dict(self, d: Dict[str, Any]) -> None:
+ cls = self.__class__
+
+ shallow_from_dict: Callable[[HasShallowCopy, Dict[str, Any]], None]
+ try:
+ shallow_from_dict = cls.__dict__[
+ "_generated_shallow_from_dict_traversal"
+ ]
+ except KeyError:
+ shallow_from_dict = self._generate_shallow_from_dict(
+ cls._traverse_internals,
+ "_generated_shallow_from_dict_traversal",
+ )
+
+ cls._generated_shallow_from_dict_traversal = shallow_from_dict # type: ignore # noqa: E501
+
+ shallow_from_dict(self, d)
+
+ def _shallow_to_dict(self) -> Dict[str, Any]:
+ cls = self.__class__
+
+ shallow_to_dict: Callable[[HasShallowCopy], Dict[str, Any]]
+
+ try:
+ shallow_to_dict = cls.__dict__[
+ "_generated_shallow_to_dict_traversal"
+ ]
+ except KeyError:
+ shallow_to_dict = self._generate_shallow_to_dict(
+ cls._traverse_internals, "_generated_shallow_to_dict_traversal"
+ )
+
+ cls._generated_shallow_to_dict_traversal = shallow_to_dict # type: ignore # noqa: E501
+ return shallow_to_dict(self)
+
+ def _shallow_copy_to(self, other: Self) -> None:
+ cls = self.__class__
+
+ shallow_copy: Callable[[Self, Self], None]
+ try:
+ shallow_copy = cls.__dict__["_generated_shallow_copy_traversal"]
+ except KeyError:
+ shallow_copy = self._generate_shallow_copy(
+ cls._traverse_internals, "_generated_shallow_copy_traversal"
+ )
+
+ cls._generated_shallow_copy_traversal = shallow_copy # type: ignore # noqa: E501
+ shallow_copy(self, other)
+
+ def _clone(self, **kw: Any) -> Self:
+ """Create a shallow copy"""
+ c = self.__class__.__new__(self.__class__)
+ self._shallow_copy_to(c)
+ return c
+
+
+class GenerativeOnTraversal(HasShallowCopy):
+ """Supplies Generative behavior but making use of traversals to shallow
+ copy.
+
+ .. seealso::
+
+ :class:`sqlalchemy.sql.base.Generative`
+
+
+ """
+
+ __slots__ = ()
+
+ def _generate(self) -> Self:
+ cls = self.__class__
+ s = cls.__new__(cls)
+ self._shallow_copy_to(s)
+ return s
+
+
+def _clone(element, **kw):
+ return element._clone()
+
+
+class HasCopyInternals(HasTraverseInternals):
+ __slots__ = ()
+
+ def _clone(self, **kw):
+ raise NotImplementedError()
+
+ def _copy_internals(
+ self, *, omit_attrs: Iterable[str] = (), **kw: Any
+ ) -> None:
+ """Reassign internal elements to be clones of themselves.
+
+ Called during a copy-and-traverse operation on newly
+ shallow-copied elements to create a deep copy.
+
+ The given clone function should be used, which may be applying
+ additional transformations to the element (i.e. replacement
+ traversal, cloned traversal, annotations).
+
+ """
+
+ try:
+ traverse_internals = self._traverse_internals
+ except AttributeError:
+ # user-defined classes may not have a _traverse_internals
+ return
+
+ for attrname, obj, meth in _copy_internals.run_generated_dispatch(
+ self, traverse_internals, "_generated_copy_internals_traversal"
+ ):
+ if attrname in omit_attrs:
+ continue
+
+ if obj is not None:
+ result = meth(attrname, self, obj, **kw)
+ if result is not None:
+ setattr(self, attrname, result)
+
+
+class _CopyInternalsTraversal(HasTraversalDispatch):
+ """Generate a _copy_internals internal traversal dispatch for classes
+ with a _traverse_internals collection."""
+
+ def visit_clauseelement(
+ self, attrname, parent, element, clone=_clone, **kw
+ ):
+ return clone(element, **kw)
+
+ def visit_clauseelement_list(
+ self, attrname, parent, element, clone=_clone, **kw
+ ):
+ return [clone(clause, **kw) for clause in element]
+
+ def visit_clauseelement_tuple(
+ self, attrname, parent, element, clone=_clone, **kw
+ ):
+ return tuple([clone(clause, **kw) for clause in element])
+
+ def visit_executable_options(
+ self, attrname, parent, element, clone=_clone, **kw
+ ):
+ return tuple([clone(clause, **kw) for clause in element])
+
+ def visit_clauseelement_unordered_set(
+ self, attrname, parent, element, clone=_clone, **kw
+ ):
+ return {clone(clause, **kw) for clause in element}
+
+ def visit_clauseelement_tuples(
+ self, attrname, parent, element, clone=_clone, **kw
+ ):
+ return [
+ tuple(clone(tup_elem, **kw) for tup_elem in elem)
+ for elem in element
+ ]
+
+ def visit_string_clauseelement_dict(
+ self, attrname, parent, element, clone=_clone, **kw
+ ):
+ return {key: clone(value, **kw) for key, value in element.items()}
+
+ def visit_setup_join_tuple(
+ self, attrname, parent, element, clone=_clone, **kw
+ ):
+ return tuple(
+ (
+ clone(target, **kw) if target is not None else None,
+ clone(onclause, **kw) if onclause is not None else None,
+ clone(from_, **kw) if from_ is not None else None,
+ flags,
+ )
+ for (target, onclause, from_, flags) in element
+ )
+
+ def visit_memoized_select_entities(self, attrname, parent, element, **kw):
+ return self.visit_clauseelement_tuple(attrname, parent, element, **kw)
+
+ def visit_dml_ordered_values(
+ self, attrname, parent, element, clone=_clone, **kw
+ ):
+ # sequence of 2-tuples
+ return [
+ (
+ (
+ clone(key, **kw)
+ if hasattr(key, "__clause_element__")
+ else key
+ ),
+ clone(value, **kw),
+ )
+ for key, value in element
+ ]
+
+ def visit_dml_values(self, attrname, parent, element, clone=_clone, **kw):
+ return {
+ (
+ clone(key, **kw) if hasattr(key, "__clause_element__") else key
+ ): clone(value, **kw)
+ for key, value in element.items()
+ }
+
+ def visit_dml_multi_values(
+ self, attrname, parent, element, clone=_clone, **kw
+ ):
+ # sequence of sequences, each sequence contains a list/dict/tuple
+
+ def copy(elem):
+ if isinstance(elem, (list, tuple)):
+ return [
+ (
+ clone(value, **kw)
+ if hasattr(value, "__clause_element__")
+ else value
+ )
+ for value in elem
+ ]
+ elif isinstance(elem, dict):
+ return {
+ (
+ clone(key, **kw)
+ if hasattr(key, "__clause_element__")
+ else key
+ ): (
+ clone(value, **kw)
+ if hasattr(value, "__clause_element__")
+ else value
+ )
+ for key, value in elem.items()
+ }
+ else:
+ # TODO: use abc classes
+ assert False
+
+ return [
+ [copy(sub_element) for sub_element in sequence]
+ for sequence in element
+ ]
+
+ def visit_propagate_attrs(
+ self, attrname, parent, element, clone=_clone, **kw
+ ):
+ return element
+
+
+_copy_internals = _CopyInternalsTraversal()
+
+
+def _flatten_clauseelement(element):
+ while hasattr(element, "__clause_element__") and not getattr(
+ element, "is_clause_element", False
+ ):
+ element = element.__clause_element__()
+
+ return element
+
+
+class _GetChildrenTraversal(HasTraversalDispatch):
+ """Generate a _children_traversal internal traversal dispatch for classes
+ with a _traverse_internals collection."""
+
+ def visit_has_cache_key(self, element, **kw):
+ # the GetChildren traversal refers explicitly to ClauseElement
+ # structures. Within these, a plain HasCacheKey is not a
+ # ClauseElement, so don't include these.
+ return ()
+
+ def visit_clauseelement(self, element, **kw):
+ return (element,)
+
+ def visit_clauseelement_list(self, element, **kw):
+ return element
+
+ def visit_clauseelement_tuple(self, element, **kw):
+ return element
+
+ def visit_clauseelement_tuples(self, element, **kw):
+ return itertools.chain.from_iterable(element)
+
+ def visit_fromclause_canonical_column_collection(self, element, **kw):
+ return ()
+
+ def visit_string_clauseelement_dict(self, element, **kw):
+ return element.values()
+
+ def visit_fromclause_ordered_set(self, element, **kw):
+ return element
+
+ def visit_clauseelement_unordered_set(self, element, **kw):
+ return element
+
+ def visit_setup_join_tuple(self, element, **kw):
+ for target, onclause, from_, flags in element:
+ if from_ is not None:
+ yield from_
+
+ if not isinstance(target, str):
+ yield _flatten_clauseelement(target)
+
+ if onclause is not None and not isinstance(onclause, str):
+ yield _flatten_clauseelement(onclause)
+
+ def visit_memoized_select_entities(self, element, **kw):
+ return self.visit_clauseelement_tuple(element, **kw)
+
+ def visit_dml_ordered_values(self, element, **kw):
+ for k, v in element:
+ if hasattr(k, "__clause_element__"):
+ yield k
+ yield v
+
+ def visit_dml_values(self, element, **kw):
+ expr_values = {k for k in element if hasattr(k, "__clause_element__")}
+ str_values = expr_values.symmetric_difference(element)
+
+ for k in sorted(str_values):
+ yield element[k]
+ for k in expr_values:
+ yield k
+ yield element[k]
+
+ def visit_dml_multi_values(self, element, **kw):
+ return ()
+
+ def visit_propagate_attrs(self, element, **kw):
+ return ()
+
+
+_get_children = _GetChildrenTraversal()
+
+
+@util.preload_module("sqlalchemy.sql.elements")
+def _resolve_name_for_compare(element, name, anon_map, **kw):
+ if isinstance(name, util.preloaded.sql_elements._anonymous_label):
+ name = name.apply_map(anon_map)
+
+ return name
+
+
+class TraversalComparatorStrategy(HasTraversalDispatch, util.MemoizedSlots):
+ __slots__ = "stack", "cache", "anon_map"
+
+ def __init__(self):
+ self.stack: Deque[
+ Tuple[
+ Optional[ExternallyTraversible],
+ Optional[ExternallyTraversible],
+ ]
+ ] = deque()
+ self.cache = set()
+
+ def _memoized_attr_anon_map(self):
+ return (anon_map(), anon_map())
+
+ def compare(
+ self,
+ obj1: ExternallyTraversible,
+ obj2: ExternallyTraversible,
+ **kw: Any,
+ ) -> bool:
+ stack = self.stack
+ cache = self.cache
+
+ compare_annotations = kw.get("compare_annotations", False)
+
+ stack.append((obj1, obj2))
+
+ while stack:
+ left, right = stack.popleft()
+
+ if left is right:
+ continue
+ elif left is None or right is None:
+ # we know they are different so no match
+ return False
+ elif (left, right) in cache:
+ continue
+ cache.add((left, right))
+
+ visit_name = left.__visit_name__
+ if visit_name != right.__visit_name__:
+ return False
+
+ meth = getattr(self, "compare_%s" % visit_name, None)
+
+ if meth:
+ attributes_compared = meth(left, right, **kw)
+ if attributes_compared is COMPARE_FAILED:
+ return False
+ elif attributes_compared is SKIP_TRAVERSE:
+ continue
+
+ # attributes_compared is returned as a list of attribute
+ # names that were "handled" by the comparison method above.
+ # remaining attribute names in the _traverse_internals
+ # will be compared.
+ else:
+ attributes_compared = ()
+
+ for (
+ (left_attrname, left_visit_sym),
+ (right_attrname, right_visit_sym),
+ ) in zip_longest(
+ left._traverse_internals,
+ right._traverse_internals,
+ fillvalue=(None, None),
+ ):
+ if not compare_annotations and (
+ (left_attrname == "_annotations")
+ or (right_attrname == "_annotations")
+ ):
+ continue
+
+ if (
+ left_attrname != right_attrname
+ or left_visit_sym is not right_visit_sym
+ ):
+ return False
+ elif left_attrname in attributes_compared:
+ continue
+
+ assert left_visit_sym is not None
+ assert left_attrname is not None
+ assert right_attrname is not None
+
+ dispatch = self.dispatch(left_visit_sym)
+ assert dispatch is not None, (
+ f"{self.__class__} has no dispatch for "
+ f"'{self._dispatch_lookup[left_visit_sym]}'"
+ )
+ left_child = operator.attrgetter(left_attrname)(left)
+ right_child = operator.attrgetter(right_attrname)(right)
+ if left_child is None:
+ if right_child is not None:
+ return False
+ else:
+ continue
+
+ comparison = dispatch(
+ left_attrname, left, left_child, right, right_child, **kw
+ )
+ if comparison is COMPARE_FAILED:
+ return False
+
+ return True
+
+ def compare_inner(self, obj1, obj2, **kw):
+ comparator = self.__class__()
+ return comparator.compare(obj1, obj2, **kw)
+
+ def visit_has_cache_key(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ if left._gen_cache_key(self.anon_map[0], []) != right._gen_cache_key(
+ self.anon_map[1], []
+ ):
+ return COMPARE_FAILED
+
+ def visit_propagate_attrs(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ return self.compare_inner(
+ left.get("plugin_subject", None), right.get("plugin_subject", None)
+ )
+
+ def visit_has_cache_key_list(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ for l, r in zip_longest(left, right, fillvalue=None):
+ if l is None:
+ if r is not None:
+ return COMPARE_FAILED
+ else:
+ continue
+ elif r is None:
+ return COMPARE_FAILED
+
+ if l._gen_cache_key(self.anon_map[0], []) != r._gen_cache_key(
+ self.anon_map[1], []
+ ):
+ return COMPARE_FAILED
+
+ def visit_executable_options(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ for l, r in zip_longest(left, right, fillvalue=None):
+ if l is None:
+ if r is not None:
+ return COMPARE_FAILED
+ else:
+ continue
+ elif r is None:
+ return COMPARE_FAILED
+
+ if (
+ l._gen_cache_key(self.anon_map[0], [])
+ if l._is_has_cache_key
+ else l
+ ) != (
+ r._gen_cache_key(self.anon_map[1], [])
+ if r._is_has_cache_key
+ else r
+ ):
+ return COMPARE_FAILED
+
+ def visit_clauseelement(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ self.stack.append((left, right))
+
+ def visit_fromclause_canonical_column_collection(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ for lcol, rcol in zip_longest(left, right, fillvalue=None):
+ self.stack.append((lcol, rcol))
+
+ def visit_fromclause_derived_column_collection(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ pass
+
+ def visit_string_clauseelement_dict(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ for lstr, rstr in zip_longest(
+ sorted(left), sorted(right), fillvalue=None
+ ):
+ if lstr != rstr:
+ return COMPARE_FAILED
+ self.stack.append((left[lstr], right[rstr]))
+
+ def visit_clauseelement_tuples(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ for ltup, rtup in zip_longest(left, right, fillvalue=None):
+ if ltup is None or rtup is None:
+ return COMPARE_FAILED
+
+ for l, r in zip_longest(ltup, rtup, fillvalue=None):
+ self.stack.append((l, r))
+
+ def visit_clauseelement_list(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ for l, r in zip_longest(left, right, fillvalue=None):
+ self.stack.append((l, r))
+
+ def visit_clauseelement_tuple(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ for l, r in zip_longest(left, right, fillvalue=None):
+ self.stack.append((l, r))
+
+ def _compare_unordered_sequences(self, seq1, seq2, **kw):
+ if seq1 is None:
+ return seq2 is None
+
+ completed: Set[object] = set()
+ for clause in seq1:
+ for other_clause in set(seq2).difference(completed):
+ if self.compare_inner(clause, other_clause, **kw):
+ completed.add(other_clause)
+ break
+ return len(completed) == len(seq1) == len(seq2)
+
+ def visit_clauseelement_unordered_set(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ return self._compare_unordered_sequences(left, right, **kw)
+
+ def visit_fromclause_ordered_set(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ for l, r in zip_longest(left, right, fillvalue=None):
+ self.stack.append((l, r))
+
+ def visit_string(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ return left == right
+
+ def visit_string_list(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ return left == right
+
+ def visit_string_multi_dict(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ for lk, rk in zip_longest(
+ sorted(left.keys()), sorted(right.keys()), fillvalue=(None, None)
+ ):
+ if lk != rk:
+ return COMPARE_FAILED
+
+ lv, rv = left[lk], right[rk]
+
+ lhc = isinstance(left, HasCacheKey)
+ rhc = isinstance(right, HasCacheKey)
+ if lhc and rhc:
+ if lv._gen_cache_key(
+ self.anon_map[0], []
+ ) != rv._gen_cache_key(self.anon_map[1], []):
+ return COMPARE_FAILED
+ elif lhc != rhc:
+ return COMPARE_FAILED
+ elif lv != rv:
+ return COMPARE_FAILED
+
+ def visit_multi(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ lhc = isinstance(left, HasCacheKey)
+ rhc = isinstance(right, HasCacheKey)
+ if lhc and rhc:
+ if left._gen_cache_key(
+ self.anon_map[0], []
+ ) != right._gen_cache_key(self.anon_map[1], []):
+ return COMPARE_FAILED
+ elif lhc != rhc:
+ return COMPARE_FAILED
+ else:
+ return left == right
+
+ def visit_anon_name(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ return _resolve_name_for_compare(
+ left_parent, left, self.anon_map[0], **kw
+ ) == _resolve_name_for_compare(
+ right_parent, right, self.anon_map[1], **kw
+ )
+
+ def visit_boolean(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ return left == right
+
+ def visit_operator(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ return left == right
+
+ def visit_type(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ return left._compare_type_affinity(right)
+
+ def visit_plain_dict(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ return left == right
+
+ def visit_dialect_options(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ return left == right
+
+ def visit_annotations_key(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ if left and right:
+ return (
+ left_parent._annotations_cache_key
+ == right_parent._annotations_cache_key
+ )
+ else:
+ return left == right
+
+ def visit_with_context_options(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ return tuple((fn.__code__, c_key) for fn, c_key in left) == tuple(
+ (fn.__code__, c_key) for fn, c_key in right
+ )
+
+ def visit_plain_obj(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ return left == right
+
+ def visit_named_ddl_element(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ if left is None:
+ if right is not None:
+ return COMPARE_FAILED
+
+ return left.name == right.name
+
+ def visit_prefix_sequence(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ for (l_clause, l_str), (r_clause, r_str) in zip_longest(
+ left, right, fillvalue=(None, None)
+ ):
+ if l_str != r_str:
+ return COMPARE_FAILED
+ else:
+ self.stack.append((l_clause, r_clause))
+
+ def visit_setup_join_tuple(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ # TODO: look at attrname for "legacy_join" and use different structure
+ for (
+ (l_target, l_onclause, l_from, l_flags),
+ (r_target, r_onclause, r_from, r_flags),
+ ) in zip_longest(left, right, fillvalue=(None, None, None, None)):
+ if l_flags != r_flags:
+ return COMPARE_FAILED
+ self.stack.append((l_target, r_target))
+ self.stack.append((l_onclause, r_onclause))
+ self.stack.append((l_from, r_from))
+
+ def visit_memoized_select_entities(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ return self.visit_clauseelement_tuple(
+ attrname, left_parent, left, right_parent, right, **kw
+ )
+
+ def visit_table_hint_list(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ left_keys = sorted(left, key=lambda elem: (elem[0].fullname, elem[1]))
+ right_keys = sorted(
+ right, key=lambda elem: (elem[0].fullname, elem[1])
+ )
+ for (ltable, ldialect), (rtable, rdialect) in zip_longest(
+ left_keys, right_keys, fillvalue=(None, None)
+ ):
+ if ldialect != rdialect:
+ return COMPARE_FAILED
+ elif left[(ltable, ldialect)] != right[(rtable, rdialect)]:
+ return COMPARE_FAILED
+ else:
+ self.stack.append((ltable, rtable))
+
+ def visit_statement_hint_list(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ return left == right
+
+ def visit_unknown_structure(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ raise NotImplementedError()
+
+ def visit_dml_ordered_values(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ # sequence of tuple pairs
+
+ for (lk, lv), (rk, rv) in zip_longest(
+ left, right, fillvalue=(None, None)
+ ):
+ if not self._compare_dml_values_or_ce(lk, rk, **kw):
+ return COMPARE_FAILED
+
+ def _compare_dml_values_or_ce(self, lv, rv, **kw):
+ lvce = hasattr(lv, "__clause_element__")
+ rvce = hasattr(rv, "__clause_element__")
+ if lvce != rvce:
+ return False
+ elif lvce and not self.compare_inner(lv, rv, **kw):
+ return False
+ elif not lvce and lv != rv:
+ return False
+ elif not self.compare_inner(lv, rv, **kw):
+ return False
+
+ return True
+
+ def visit_dml_values(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ if left is None or right is None or len(left) != len(right):
+ return COMPARE_FAILED
+
+ if isinstance(left, collections_abc.Sequence):
+ for lv, rv in zip(left, right):
+ if not self._compare_dml_values_or_ce(lv, rv, **kw):
+ return COMPARE_FAILED
+ elif isinstance(right, collections_abc.Sequence):
+ return COMPARE_FAILED
+ else:
+ # dictionaries guaranteed to support insert ordering in
+ # py37 so that we can compare the keys in order. without
+ # this, we can't compare SQL expression keys because we don't
+ # know which key is which
+ for (lk, lv), (rk, rv) in zip(left.items(), right.items()):
+ if not self._compare_dml_values_or_ce(lk, rk, **kw):
+ return COMPARE_FAILED
+ if not self._compare_dml_values_or_ce(lv, rv, **kw):
+ return COMPARE_FAILED
+
+ def visit_dml_multi_values(
+ self, attrname, left_parent, left, right_parent, right, **kw
+ ):
+ for lseq, rseq in zip_longest(left, right, fillvalue=None):
+ if lseq is None or rseq is None:
+ return COMPARE_FAILED
+
+ for ld, rd in zip_longest(lseq, rseq, fillvalue=None):
+ if (
+ self.visit_dml_values(
+ attrname, left_parent, ld, right_parent, rd, **kw
+ )
+ is COMPARE_FAILED
+ ):
+ return COMPARE_FAILED
+
+ def compare_expression_clauselist(self, left, right, **kw):
+ if left.operator is right.operator:
+ if operators.is_associative(left.operator):
+ if self._compare_unordered_sequences(
+ left.clauses, right.clauses, **kw
+ ):
+ return ["operator", "clauses"]
+ else:
+ return COMPARE_FAILED
+ else:
+ return ["operator"]
+ else:
+ return COMPARE_FAILED
+
+ def compare_clauselist(self, left, right, **kw):
+ return self.compare_expression_clauselist(left, right, **kw)
+
+ def compare_binary(self, left, right, **kw):
+ if left.operator == right.operator:
+ if operators.is_commutative(left.operator):
+ if (
+ self.compare_inner(left.left, right.left, **kw)
+ and self.compare_inner(left.right, right.right, **kw)
+ ) or (
+ self.compare_inner(left.left, right.right, **kw)
+ and self.compare_inner(left.right, right.left, **kw)
+ ):
+ return ["operator", "negate", "left", "right"]
+ else:
+ return COMPARE_FAILED
+ else:
+ return ["operator", "negate"]
+ else:
+ return COMPARE_FAILED
+
+ def compare_bindparam(self, left, right, **kw):
+ compare_keys = kw.pop("compare_keys", True)
+ compare_values = kw.pop("compare_values", True)
+
+ if compare_values:
+ omit = []
+ else:
+ # this means, "skip these, we already compared"
+ omit = ["callable", "value"]
+
+ if not compare_keys:
+ omit.append("key")
+
+ return omit
+
+
+class ColIdentityComparatorStrategy(TraversalComparatorStrategy):
+ def compare_column_element(
+ self, left, right, use_proxies=True, equivalents=(), **kw
+ ):
+ """Compare ColumnElements using proxies and equivalent collections.
+
+ This is a comparison strategy specific to the ORM.
+ """
+
+ to_compare = (right,)
+ if equivalents and right in equivalents:
+ to_compare = equivalents[right].union(to_compare)
+
+ for oth in to_compare:
+ if use_proxies and left.shares_lineage(oth):
+ return SKIP_TRAVERSE
+ elif hash(left) == hash(right):
+ return SKIP_TRAVERSE
+ else:
+ return COMPARE_FAILED
+
+ def compare_column(self, left, right, **kw):
+ return self.compare_column_element(left, right, **kw)
+
+ def compare_label(self, left, right, **kw):
+ return self.compare_column_element(left, right, **kw)
+
+ def compare_table(self, left, right, **kw):
+ # tables compare on identity, since it's not really feasible to
+ # compare them column by column with the above rules
+ return SKIP_TRAVERSE if left is right else COMPARE_FAILED
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/type_api.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/type_api.py
new file mode 100644
index 0000000..4233e7f
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/type_api.py
@@ -0,0 +1,2303 @@
+# sql/type_api.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+"""Base types API.
+
+"""
+
+from __future__ import annotations
+
+from enum import Enum
+from types import ModuleType
+import typing
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import Dict
+from typing import Generic
+from typing import Mapping
+from typing import NewType
+from typing import Optional
+from typing import overload
+from typing import Sequence
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from .base import SchemaEventTarget
+from .cache_key import CacheConst
+from .cache_key import NO_CACHE
+from .operators import ColumnOperators
+from .visitors import Visitable
+from .. import exc
+from .. import util
+from ..util.typing import Protocol
+from ..util.typing import Self
+from ..util.typing import TypeAliasType
+from ..util.typing import TypedDict
+from ..util.typing import TypeGuard
+
+# these are back-assigned by sqltypes.
+if typing.TYPE_CHECKING:
+ from ._typing import _TypeEngineArgument
+ from .elements import BindParameter
+ from .elements import ColumnElement
+ from .operators import OperatorType
+ from .sqltypes import _resolve_value_to_type as _resolve_value_to_type
+ from .sqltypes import BOOLEANTYPE as BOOLEANTYPE # noqa: F401
+ from .sqltypes import INDEXABLE as INDEXABLE # noqa: F401
+ from .sqltypes import INTEGERTYPE as INTEGERTYPE # noqa: F401
+ from .sqltypes import MATCHTYPE as MATCHTYPE # noqa: F401
+ from .sqltypes import NULLTYPE as NULLTYPE
+ from .sqltypes import NUMERICTYPE as NUMERICTYPE # noqa: F401
+ from .sqltypes import STRINGTYPE as STRINGTYPE # noqa: F401
+ from .sqltypes import TABLEVALUE as TABLEVALUE # noqa: F401
+ from ..engine.interfaces import Dialect
+ from ..util.typing import GenericProtocol
+
+_T = TypeVar("_T", bound=Any)
+_T_co = TypeVar("_T_co", bound=Any, covariant=True)
+_T_con = TypeVar("_T_con", bound=Any, contravariant=True)
+_O = TypeVar("_O", bound=object)
+_TE = TypeVar("_TE", bound="TypeEngine[Any]")
+_CT = TypeVar("_CT", bound=Any)
+
+_MatchedOnType = Union[
+ "GenericProtocol[Any]", TypeAliasType, NewType, Type[Any]
+]
+
+
+class _NoValueInList(Enum):
+ NO_VALUE_IN_LIST = 0
+ """indicates we are trying to determine the type of an expression
+ against an empty list."""
+
+
+_NO_VALUE_IN_LIST = _NoValueInList.NO_VALUE_IN_LIST
+
+
+class _LiteralProcessorType(Protocol[_T_co]):
+ def __call__(self, value: Any) -> str: ...
+
+
+class _BindProcessorType(Protocol[_T_con]):
+ def __call__(self, value: Optional[_T_con]) -> Any: ...
+
+
+class _ResultProcessorType(Protocol[_T_co]):
+ def __call__(self, value: Any) -> Optional[_T_co]: ...
+
+
+class _SentinelProcessorType(Protocol[_T_co]):
+ def __call__(self, value: Any) -> Optional[_T_co]: ...
+
+
+class _BaseTypeMemoDict(TypedDict):
+ impl: TypeEngine[Any]
+ result: Dict[Any, Optional[_ResultProcessorType[Any]]]
+
+
+class _TypeMemoDict(_BaseTypeMemoDict, total=False):
+ literal: Optional[_LiteralProcessorType[Any]]
+ bind: Optional[_BindProcessorType[Any]]
+ sentinel: Optional[_SentinelProcessorType[Any]]
+ custom: Dict[Any, object]
+
+
+class _ComparatorFactory(Protocol[_T]):
+ def __call__(
+ self, expr: ColumnElement[_T]
+ ) -> TypeEngine.Comparator[_T]: ...
+
+
+class TypeEngine(Visitable, Generic[_T]):
+ """The ultimate base class for all SQL datatypes.
+
+ Common subclasses of :class:`.TypeEngine` include
+ :class:`.String`, :class:`.Integer`, and :class:`.Boolean`.
+
+ For an overview of the SQLAlchemy typing system, see
+ :ref:`types_toplevel`.
+
+ .. seealso::
+
+ :ref:`types_toplevel`
+
+ """
+
+ _sqla_type = True
+ _isnull = False
+ _is_tuple_type = False
+ _is_table_value = False
+ _is_array = False
+ _is_type_decorator = False
+
+ render_bind_cast = False
+ """Render bind casts for :attr:`.BindTyping.RENDER_CASTS` mode.
+
+ If True, this type (usually a dialect level impl type) signals
+ to the compiler that a cast should be rendered around a bound parameter
+ for this type.
+
+ .. versionadded:: 2.0
+
+ .. seealso::
+
+ :class:`.BindTyping`
+
+ """
+
+ render_literal_cast = False
+ """render casts when rendering a value as an inline literal,
+ e.g. with :meth:`.TypeEngine.literal_processor`.
+
+ .. versionadded:: 2.0
+
+ """
+
+ class Comparator(
+ ColumnOperators,
+ Generic[_CT],
+ ):
+ """Base class for custom comparison operations defined at the
+ type level. See :attr:`.TypeEngine.comparator_factory`.
+
+
+ """
+
+ __slots__ = "expr", "type"
+
+ expr: ColumnElement[_CT]
+ type: TypeEngine[_CT]
+
+ def __clause_element__(self) -> ColumnElement[_CT]:
+ return self.expr
+
+ def __init__(self, expr: ColumnElement[_CT]):
+ self.expr = expr
+ self.type = expr.type
+
+ @util.preload_module("sqlalchemy.sql.default_comparator")
+ def operate(
+ self, op: OperatorType, *other: Any, **kwargs: Any
+ ) -> ColumnElement[_CT]:
+ default_comparator = util.preloaded.sql_default_comparator
+ op_fn, addtl_kw = default_comparator.operator_lookup[op.__name__]
+ if kwargs:
+ addtl_kw = addtl_kw.union(kwargs)
+ return op_fn(self.expr, op, *other, **addtl_kw)
+
+ @util.preload_module("sqlalchemy.sql.default_comparator")
+ def reverse_operate(
+ self, op: OperatorType, other: Any, **kwargs: Any
+ ) -> ColumnElement[_CT]:
+ default_comparator = util.preloaded.sql_default_comparator
+ op_fn, addtl_kw = default_comparator.operator_lookup[op.__name__]
+ if kwargs:
+ addtl_kw = addtl_kw.union(kwargs)
+ return op_fn(self.expr, op, other, reverse=True, **addtl_kw)
+
+ def _adapt_expression(
+ self,
+ op: OperatorType,
+ other_comparator: TypeEngine.Comparator[Any],
+ ) -> Tuple[OperatorType, TypeEngine[Any]]:
+ """evaluate the return type of <self> <op> <othertype>,
+ and apply any adaptations to the given operator.
+
+ This method determines the type of a resulting binary expression
+ given two source types and an operator. For example, two
+ :class:`_schema.Column` objects, both of the type
+ :class:`.Integer`, will
+ produce a :class:`.BinaryExpression` that also has the type
+ :class:`.Integer` when compared via the addition (``+``) operator.
+ However, using the addition operator with an :class:`.Integer`
+ and a :class:`.Date` object will produce a :class:`.Date`, assuming
+ "days delta" behavior by the database (in reality, most databases
+ other than PostgreSQL don't accept this particular operation).
+
+ The method returns a tuple of the form <operator>, <type>.
+ The resulting operator and type will be those applied to the
+ resulting :class:`.BinaryExpression` as the final operator and the
+ right-hand side of the expression.
+
+ Note that only a subset of operators make usage of
+ :meth:`._adapt_expression`,
+ including math operators and user-defined operators, but not
+ boolean comparison or special SQL keywords like MATCH or BETWEEN.
+
+ """
+
+ return op, self.type
+
+ hashable = True
+ """Flag, if False, means values from this type aren't hashable.
+
+ Used by the ORM when uniquing result lists.
+
+ """
+
+ comparator_factory: _ComparatorFactory[Any] = Comparator
+ """A :class:`.TypeEngine.Comparator` class which will apply
+ to operations performed by owning :class:`_expression.ColumnElement`
+ objects.
+
+ The :attr:`.comparator_factory` attribute is a hook consulted by
+ the core expression system when column and SQL expression operations
+ are performed. When a :class:`.TypeEngine.Comparator` class is
+ associated with this attribute, it allows custom re-definition of
+ all existing operators, as well as definition of new operators.
+ Existing operators include those provided by Python operator overloading
+ such as :meth:`.operators.ColumnOperators.__add__` and
+ :meth:`.operators.ColumnOperators.__eq__`,
+ those provided as standard
+ attributes of :class:`.operators.ColumnOperators` such as
+ :meth:`.operators.ColumnOperators.like`
+ and :meth:`.operators.ColumnOperators.in_`.
+
+ Rudimentary usage of this hook is allowed through simple subclassing
+ of existing types, or alternatively by using :class:`.TypeDecorator`.
+ See the documentation section :ref:`types_operators` for examples.
+
+ """
+
+ sort_key_function: Optional[Callable[[Any], Any]] = None
+ """A sorting function that can be passed as the key to sorted.
+
+ The default value of ``None`` indicates that the values stored by
+ this type are self-sorting.
+
+ .. versionadded:: 1.3.8
+
+ """
+
+ should_evaluate_none: bool = False
+ """If True, the Python constant ``None`` is considered to be handled
+ explicitly by this type.
+
+ The ORM uses this flag to indicate that a positive value of ``None``
+ is passed to the column in an INSERT statement, rather than omitting
+ the column from the INSERT statement which has the effect of firing
+ off column-level defaults. It also allows types which have special
+ behavior for Python None, such as a JSON type, to indicate that
+ they'd like to handle the None value explicitly.
+
+ To set this flag on an existing type, use the
+ :meth:`.TypeEngine.evaluates_none` method.
+
+ .. seealso::
+
+ :meth:`.TypeEngine.evaluates_none`
+
+ """
+
+ _variant_mapping: util.immutabledict[str, TypeEngine[Any]] = (
+ util.EMPTY_DICT
+ )
+
+ def evaluates_none(self) -> Self:
+ """Return a copy of this type which has the
+ :attr:`.should_evaluate_none` flag set to True.
+
+ E.g.::
+
+ Table(
+ 'some_table', metadata,
+ Column(
+ String(50).evaluates_none(),
+ nullable=True,
+ server_default='no value')
+ )
+
+ The ORM uses this flag to indicate that a positive value of ``None``
+ is passed to the column in an INSERT statement, rather than omitting
+ the column from the INSERT statement which has the effect of firing
+ off column-level defaults. It also allows for types which have
+ special behavior associated with the Python None value to indicate
+ that the value doesn't necessarily translate into SQL NULL; a
+ prime example of this is a JSON type which may wish to persist the
+ JSON value ``'null'``.
+
+ In all cases, the actual NULL SQL value can be always be
+ persisted in any column by using
+ the :obj:`_expression.null` SQL construct in an INSERT statement
+ or associated with an ORM-mapped attribute.
+
+ .. note::
+
+ The "evaluates none" flag does **not** apply to a value
+ of ``None`` passed to :paramref:`_schema.Column.default` or
+ :paramref:`_schema.Column.server_default`; in these cases,
+ ``None``
+ still means "no default".
+
+ .. seealso::
+
+ :ref:`session_forcing_null` - in the ORM documentation
+
+ :paramref:`.postgresql.JSON.none_as_null` - PostgreSQL JSON
+ interaction with this flag.
+
+ :attr:`.TypeEngine.should_evaluate_none` - class-level flag
+
+ """
+ typ = self.copy()
+ typ.should_evaluate_none = True
+ return typ
+
+ def copy(self, **kw: Any) -> Self:
+ return self.adapt(self.__class__)
+
+ def copy_value(self, value: Any) -> Any:
+ return value
+
+ def literal_processor(
+ self, dialect: Dialect
+ ) -> Optional[_LiteralProcessorType[_T]]:
+ """Return a conversion function for processing literal values that are
+ to be rendered directly without using binds.
+
+ This function is used when the compiler makes use of the
+ "literal_binds" flag, typically used in DDL generation as well
+ as in certain scenarios where backends don't accept bound parameters.
+
+ Returns a callable which will receive a literal Python value
+ as the sole positional argument and will return a string representation
+ to be rendered in a SQL statement.
+
+ .. note::
+
+ This method is only called relative to a **dialect specific type
+ object**, which is often **private to a dialect in use** and is not
+ the same type object as the public facing one, which means it's not
+ feasible to subclass a :class:`.types.TypeEngine` class in order to
+ provide an alternate :meth:`_types.TypeEngine.literal_processor`
+ method, unless subclassing the :class:`_types.UserDefinedType`
+ class explicitly.
+
+ To provide alternate behavior for
+ :meth:`_types.TypeEngine.literal_processor`, implement a
+ :class:`_types.TypeDecorator` class and provide an implementation
+ of :meth:`_types.TypeDecorator.process_literal_param`.
+
+ .. seealso::
+
+ :ref:`types_typedecorator`
+
+
+ """
+ return None
+
+ def bind_processor(
+ self, dialect: Dialect
+ ) -> Optional[_BindProcessorType[_T]]:
+ """Return a conversion function for processing bind values.
+
+ Returns a callable which will receive a bind parameter value
+ as the sole positional argument and will return a value to
+ send to the DB-API.
+
+ If processing is not necessary, the method should return ``None``.
+
+ .. note::
+
+ This method is only called relative to a **dialect specific type
+ object**, which is often **private to a dialect in use** and is not
+ the same type object as the public facing one, which means it's not
+ feasible to subclass a :class:`.types.TypeEngine` class in order to
+ provide an alternate :meth:`_types.TypeEngine.bind_processor`
+ method, unless subclassing the :class:`_types.UserDefinedType`
+ class explicitly.
+
+ To provide alternate behavior for
+ :meth:`_types.TypeEngine.bind_processor`, implement a
+ :class:`_types.TypeDecorator` class and provide an implementation
+ of :meth:`_types.TypeDecorator.process_bind_param`.
+
+ .. seealso::
+
+ :ref:`types_typedecorator`
+
+
+ :param dialect: Dialect instance in use.
+
+ """
+ return None
+
+ def result_processor(
+ self, dialect: Dialect, coltype: object
+ ) -> Optional[_ResultProcessorType[_T]]:
+ """Return a conversion function for processing result row values.
+
+ Returns a callable which will receive a result row column
+ value as the sole positional argument and will return a value
+ to return to the user.
+
+ If processing is not necessary, the method should return ``None``.
+
+ .. note::
+
+ This method is only called relative to a **dialect specific type
+ object**, which is often **private to a dialect in use** and is not
+ the same type object as the public facing one, which means it's not
+ feasible to subclass a :class:`.types.TypeEngine` class in order to
+ provide an alternate :meth:`_types.TypeEngine.result_processor`
+ method, unless subclassing the :class:`_types.UserDefinedType`
+ class explicitly.
+
+ To provide alternate behavior for
+ :meth:`_types.TypeEngine.result_processor`, implement a
+ :class:`_types.TypeDecorator` class and provide an implementation
+ of :meth:`_types.TypeDecorator.process_result_value`.
+
+ .. seealso::
+
+ :ref:`types_typedecorator`
+
+ :param dialect: Dialect instance in use.
+
+ :param coltype: DBAPI coltype argument received in cursor.description.
+
+ """
+ return None
+
+ def column_expression(
+ self, colexpr: ColumnElement[_T]
+ ) -> Optional[ColumnElement[_T]]:
+ """Given a SELECT column expression, return a wrapping SQL expression.
+
+ This is typically a SQL function that wraps a column expression
+ as rendered in the columns clause of a SELECT statement.
+ It is used for special data types that require
+ columns to be wrapped in some special database function in order
+ to coerce the value before being sent back to the application.
+ It is the SQL analogue of the :meth:`.TypeEngine.result_processor`
+ method.
+
+ This method is called during the **SQL compilation** phase of a
+ statement, when rendering a SQL string. It is **not** called
+ against specific values.
+
+ .. note::
+
+ This method is only called relative to a **dialect specific type
+ object**, which is often **private to a dialect in use** and is not
+ the same type object as the public facing one, which means it's not
+ feasible to subclass a :class:`.types.TypeEngine` class in order to
+ provide an alternate :meth:`_types.TypeEngine.column_expression`
+ method, unless subclassing the :class:`_types.UserDefinedType`
+ class explicitly.
+
+ To provide alternate behavior for
+ :meth:`_types.TypeEngine.column_expression`, implement a
+ :class:`_types.TypeDecorator` class and provide an implementation
+ of :meth:`_types.TypeDecorator.column_expression`.
+
+ .. seealso::
+
+ :ref:`types_typedecorator`
+
+
+ .. seealso::
+
+ :ref:`types_sql_value_processing`
+
+ """
+
+ return None
+
+ @util.memoized_property
+ def _has_column_expression(self) -> bool:
+ """memoized boolean, check if column_expression is implemented.
+
+ Allows the method to be skipped for the vast majority of expression
+ types that don't use this feature.
+
+ """
+
+ return (
+ self.__class__.column_expression.__code__
+ is not TypeEngine.column_expression.__code__
+ )
+
+ def bind_expression(
+ self, bindvalue: BindParameter[_T]
+ ) -> Optional[ColumnElement[_T]]:
+ """Given a bind value (i.e. a :class:`.BindParameter` instance),
+ return a SQL expression in its place.
+
+ This is typically a SQL function that wraps the existing bound
+ parameter within the statement. It is used for special data types
+ that require literals being wrapped in some special database function
+ in order to coerce an application-level value into a database-specific
+ format. It is the SQL analogue of the
+ :meth:`.TypeEngine.bind_processor` method.
+
+ This method is called during the **SQL compilation** phase of a
+ statement, when rendering a SQL string. It is **not** called
+ against specific values.
+
+ Note that this method, when implemented, should always return
+ the exact same structure, without any conditional logic, as it
+ may be used in an executemany() call against an arbitrary number
+ of bound parameter sets.
+
+ .. note::
+
+ This method is only called relative to a **dialect specific type
+ object**, which is often **private to a dialect in use** and is not
+ the same type object as the public facing one, which means it's not
+ feasible to subclass a :class:`.types.TypeEngine` class in order to
+ provide an alternate :meth:`_types.TypeEngine.bind_expression`
+ method, unless subclassing the :class:`_types.UserDefinedType`
+ class explicitly.
+
+ To provide alternate behavior for
+ :meth:`_types.TypeEngine.bind_expression`, implement a
+ :class:`_types.TypeDecorator` class and provide an implementation
+ of :meth:`_types.TypeDecorator.bind_expression`.
+
+ .. seealso::
+
+ :ref:`types_typedecorator`
+
+ .. seealso::
+
+ :ref:`types_sql_value_processing`
+
+ """
+ return None
+
+ @util.memoized_property
+ def _has_bind_expression(self) -> bool:
+ """memoized boolean, check if bind_expression is implemented.
+
+ Allows the method to be skipped for the vast majority of expression
+ types that don't use this feature.
+
+ """
+
+ return util.method_is_overridden(self, TypeEngine.bind_expression)
+
+ @staticmethod
+ def _to_instance(cls_or_self: Union[Type[_TE], _TE]) -> _TE:
+ return to_instance(cls_or_self)
+
+ def compare_values(self, x: Any, y: Any) -> bool:
+ """Compare two values for equality."""
+
+ return x == y # type: ignore[no-any-return]
+
+ def get_dbapi_type(self, dbapi: ModuleType) -> Optional[Any]:
+ """Return the corresponding type object from the underlying DB-API, if
+ any.
+
+ This can be useful for calling ``setinputsizes()``, for example.
+
+ """
+ return None
+
+ @property
+ def python_type(self) -> Type[Any]:
+ """Return the Python type object expected to be returned
+ by instances of this type, if known.
+
+ Basically, for those types which enforce a return type,
+ or are known across the board to do such for all common
+ DBAPIs (like ``int`` for example), will return that type.
+
+ If a return type is not defined, raises
+ ``NotImplementedError``.
+
+ Note that any type also accommodates NULL in SQL which
+ means you can also get back ``None`` from any type
+ in practice.
+
+ """
+ raise NotImplementedError()
+
+ def with_variant(
+ self,
+ type_: _TypeEngineArgument[Any],
+ *dialect_names: str,
+ ) -> Self:
+ r"""Produce a copy of this type object that will utilize the given
+ type when applied to the dialect of the given name.
+
+ e.g.::
+
+ from sqlalchemy.types import String
+ from sqlalchemy.dialects import mysql
+
+ string_type = String()
+
+ string_type = string_type.with_variant(
+ mysql.VARCHAR(collation='foo'), 'mysql', 'mariadb'
+ )
+
+ The variant mapping indicates that when this type is
+ interpreted by a specific dialect, it will instead be
+ transmuted into the given type, rather than using the
+ primary type.
+
+ .. versionchanged:: 2.0 the :meth:`_types.TypeEngine.with_variant`
+ method now works with a :class:`_types.TypeEngine` object "in
+ place", returning a copy of the original type rather than returning
+ a wrapping object; the ``Variant`` class is no longer used.
+
+ :param type\_: a :class:`.TypeEngine` that will be selected
+ as a variant from the originating type, when a dialect
+ of the given name is in use.
+ :param \*dialect_names: one or more base names of the dialect which
+ uses this type. (i.e. ``'postgresql'``, ``'mysql'``, etc.)
+
+ .. versionchanged:: 2.0 multiple dialect names can be specified
+ for one variant.
+
+ .. seealso::
+
+ :ref:`types_with_variant` - illustrates the use of
+ :meth:`_types.TypeEngine.with_variant`.
+
+ """
+
+ if not dialect_names:
+ raise exc.ArgumentError("At least one dialect name is required")
+ for dialect_name in dialect_names:
+ if dialect_name in self._variant_mapping:
+ raise exc.ArgumentError(
+ f"Dialect {dialect_name!r} is already present in "
+ f"the mapping for this {self!r}"
+ )
+ new_type = self.copy()
+ type_ = to_instance(type_)
+ if type_._variant_mapping:
+ raise exc.ArgumentError(
+ "can't pass a type that already has variants as a "
+ "dialect-level type to with_variant()"
+ )
+
+ new_type._variant_mapping = self._variant_mapping.union(
+ {dialect_name: type_ for dialect_name in dialect_names}
+ )
+ return new_type
+
+ def _resolve_for_literal(self, value: Any) -> Self:
+ """adjust this type given a literal Python value that will be
+ stored in a bound parameter.
+
+ Used exclusively by _resolve_value_to_type().
+
+ .. versionadded:: 1.4.30 or 2.0
+
+ TODO: this should be part of public API
+
+ .. seealso::
+
+ :meth:`.TypeEngine._resolve_for_python_type`
+
+ """
+ return self
+
+ def _resolve_for_python_type(
+ self,
+ python_type: Type[Any],
+ matched_on: _MatchedOnType,
+ matched_on_flattened: Type[Any],
+ ) -> Optional[Self]:
+ """given a Python type (e.g. ``int``, ``str``, etc. ) return an
+ instance of this :class:`.TypeEngine` that's appropriate for this type.
+
+ An additional argument ``matched_on`` is passed, which indicates an
+ entry from the ``__mro__`` of the given ``python_type`` that more
+ specifically matches how the caller located this :class:`.TypeEngine`
+ object. Such as, if a lookup of some kind links the ``int`` Python
+ type to the :class:`.Integer` SQL type, and the original object
+ was some custom subclass of ``int`` such as ``MyInt(int)``, the
+ arguments passed would be ``(MyInt, int)``.
+
+ If the given Python type does not correspond to this
+ :class:`.TypeEngine`, or the Python type is otherwise ambiguous, the
+ method should return None.
+
+ For simple cases, the method checks that the ``python_type``
+ and ``matched_on`` types are the same (i.e. not a subclass), and
+ returns self; for all other cases, it returns ``None``.
+
+ The initial use case here is for the ORM to link user-defined
+ Python standard library ``enum.Enum`` classes to the SQLAlchemy
+ :class:`.Enum` SQL type when constructing ORM Declarative mappings.
+
+ :param python_type: the Python type we want to use
+ :param matched_on: the Python type that led us to choose this
+ particular :class:`.TypeEngine` class, which would be a supertype
+ of ``python_type``. By default, the request is rejected if
+ ``python_type`` doesn't match ``matched_on`` (None is returned).
+
+ .. versionadded:: 2.0.0b4
+
+ TODO: this should be part of public API
+
+ .. seealso::
+
+ :meth:`.TypeEngine._resolve_for_literal`
+
+ """
+
+ if python_type is not matched_on_flattened:
+ return None
+
+ return self
+
+ @util.ro_memoized_property
+ def _type_affinity(self) -> Optional[Type[TypeEngine[_T]]]:
+ """Return a rudimental 'affinity' value expressing the general class
+ of type."""
+
+ typ = None
+ for t in self.__class__.__mro__:
+ if t is TypeEngine or TypeEngineMixin in t.__bases__:
+ return typ
+ elif issubclass(t, TypeEngine):
+ typ = t
+ else:
+ return self.__class__
+
+ @util.ro_memoized_property
+ def _generic_type_affinity(
+ self,
+ ) -> Type[TypeEngine[_T]]:
+ best_camelcase = None
+ best_uppercase = None
+
+ if not isinstance(self, TypeEngine):
+ return self.__class__
+
+ for t in self.__class__.__mro__:
+ if (
+ t.__module__
+ in (
+ "sqlalchemy.sql.sqltypes",
+ "sqlalchemy.sql.type_api",
+ )
+ and issubclass(t, TypeEngine)
+ and TypeEngineMixin not in t.__bases__
+ and t not in (TypeEngine, TypeEngineMixin)
+ and t.__name__[0] != "_"
+ ):
+ if t.__name__.isupper() and not best_uppercase:
+ best_uppercase = t
+ elif not t.__name__.isupper() and not best_camelcase:
+ best_camelcase = t
+
+ return (
+ best_camelcase
+ or best_uppercase
+ or cast("Type[TypeEngine[_T]]", NULLTYPE.__class__)
+ )
+
+ def as_generic(self, allow_nulltype: bool = False) -> TypeEngine[_T]:
+ """
+ Return an instance of the generic type corresponding to this type
+ using heuristic rule. The method may be overridden if this
+ heuristic rule is not sufficient.
+
+ >>> from sqlalchemy.dialects.mysql import INTEGER
+ >>> INTEGER(display_width=4).as_generic()
+ Integer()
+
+ >>> from sqlalchemy.dialects.mysql import NVARCHAR
+ >>> NVARCHAR(length=100).as_generic()
+ Unicode(length=100)
+
+ .. versionadded:: 1.4.0b2
+
+
+ .. seealso::
+
+ :ref:`metadata_reflection_dbagnostic_types` - describes the
+ use of :meth:`_types.TypeEngine.as_generic` in conjunction with
+ the :meth:`_sql.DDLEvents.column_reflect` event, which is its
+ intended use.
+
+ """
+ if (
+ not allow_nulltype
+ and self._generic_type_affinity == NULLTYPE.__class__
+ ):
+ raise NotImplementedError(
+ "Default TypeEngine.as_generic() "
+ "heuristic method was unsuccessful for {}. A custom "
+ "as_generic() method must be implemented for this "
+ "type class.".format(
+ self.__class__.__module__ + "." + self.__class__.__name__
+ )
+ )
+
+ return util.constructor_copy(self, self._generic_type_affinity)
+
+ def dialect_impl(self, dialect: Dialect) -> TypeEngine[_T]:
+ """Return a dialect-specific implementation for this
+ :class:`.TypeEngine`.
+
+ """
+ try:
+ tm = dialect._type_memos[self]
+ except KeyError:
+ pass
+ else:
+ return tm["impl"]
+ return self._dialect_info(dialect)["impl"]
+
+ def _unwrapped_dialect_impl(self, dialect: Dialect) -> TypeEngine[_T]:
+ """Return the 'unwrapped' dialect impl for this type.
+
+ For a type that applies wrapping logic (e.g. TypeDecorator), give
+ us the real, actual dialect-level type that is used.
+
+ This is used by TypeDecorator itself as well at least one case where
+ dialects need to check that a particular specific dialect-level
+ type is in use, within the :meth:`.DefaultDialect.set_input_sizes`
+ method.
+
+ """
+ return self.dialect_impl(dialect)
+
+ def _cached_literal_processor(
+ self, dialect: Dialect
+ ) -> Optional[_LiteralProcessorType[_T]]:
+ """Return a dialect-specific literal processor for this type."""
+
+ try:
+ return dialect._type_memos[self]["literal"]
+ except KeyError:
+ pass
+
+ # avoid KeyError context coming into literal_processor() function
+ # raises
+ d = self._dialect_info(dialect)
+ d["literal"] = lp = d["impl"].literal_processor(dialect)
+ return lp
+
+ def _cached_bind_processor(
+ self, dialect: Dialect
+ ) -> Optional[_BindProcessorType[_T]]:
+ """Return a dialect-specific bind processor for this type."""
+
+ try:
+ return dialect._type_memos[self]["bind"]
+ except KeyError:
+ pass
+
+ # avoid KeyError context coming into bind_processor() function
+ # raises
+ d = self._dialect_info(dialect)
+ d["bind"] = bp = d["impl"].bind_processor(dialect)
+ return bp
+
+ def _cached_result_processor(
+ self, dialect: Dialect, coltype: Any
+ ) -> Optional[_ResultProcessorType[_T]]:
+ """Return a dialect-specific result processor for this type."""
+
+ try:
+ return dialect._type_memos[self]["result"][coltype]
+ except KeyError:
+ pass
+
+ # avoid KeyError context coming into result_processor() function
+ # raises
+ d = self._dialect_info(dialect)
+ # key assumption: DBAPI type codes are
+ # constants. Else this dictionary would
+ # grow unbounded.
+ rp = d["impl"].result_processor(dialect, coltype)
+ d["result"][coltype] = rp
+ return rp
+
+ def _cached_custom_processor(
+ self, dialect: Dialect, key: str, fn: Callable[[TypeEngine[_T]], _O]
+ ) -> _O:
+ """return a dialect-specific processing object for
+ custom purposes.
+
+ The cx_Oracle dialect uses this at the moment.
+
+ """
+ try:
+ return cast(_O, dialect._type_memos[self]["custom"][key])
+ except KeyError:
+ pass
+ # avoid KeyError context coming into fn() function
+ # raises
+ d = self._dialect_info(dialect)
+ impl = d["impl"]
+ custom_dict = d.setdefault("custom", {})
+ custom_dict[key] = result = fn(impl)
+ return result
+
+ def _dialect_info(self, dialect: Dialect) -> _TypeMemoDict:
+ """Return a dialect-specific registry which
+ caches a dialect-specific implementation, bind processing
+ function, and one or more result processing functions."""
+
+ if self in dialect._type_memos:
+ return dialect._type_memos[self]
+ else:
+ impl = self._gen_dialect_impl(dialect)
+ if impl is self:
+ impl = self.adapt(type(self))
+ # this can't be self, else we create a cycle
+ assert impl is not self
+ d: _TypeMemoDict = {"impl": impl, "result": {}}
+ dialect._type_memos[self] = d
+ return d
+
+ def _gen_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]:
+ if dialect.name in self._variant_mapping:
+ return self._variant_mapping[dialect.name]._gen_dialect_impl(
+ dialect
+ )
+ else:
+ return dialect.type_descriptor(self)
+
+ @util.memoized_property
+ def _static_cache_key(
+ self,
+ ) -> Union[CacheConst, Tuple[Any, ...]]:
+ names = util.get_cls_kwargs(self.__class__)
+ return (self.__class__,) + tuple(
+ (
+ k,
+ (
+ self.__dict__[k]._static_cache_key
+ if isinstance(self.__dict__[k], TypeEngine)
+ else self.__dict__[k]
+ ),
+ )
+ for k in names
+ if k in self.__dict__
+ and not k.startswith("_")
+ and self.__dict__[k] is not None
+ )
+
+ @overload
+ def adapt(self, cls: Type[_TE], **kw: Any) -> _TE: ...
+
+ @overload
+ def adapt(
+ self, cls: Type[TypeEngineMixin], **kw: Any
+ ) -> TypeEngine[Any]: ...
+
+ def adapt(
+ self, cls: Type[Union[TypeEngine[Any], TypeEngineMixin]], **kw: Any
+ ) -> TypeEngine[Any]:
+ """Produce an "adapted" form of this type, given an "impl" class
+ to work with.
+
+ This method is used internally to associate generic
+ types with "implementation" types that are specific to a particular
+ dialect.
+ """
+ typ = util.constructor_copy(
+ self, cast(Type[TypeEngine[Any]], cls), **kw
+ )
+ typ._variant_mapping = self._variant_mapping
+ return typ
+
+ def coerce_compared_value(
+ self, op: Optional[OperatorType], value: Any
+ ) -> TypeEngine[Any]:
+ """Suggest a type for a 'coerced' Python value in an expression.
+
+ Given an operator and value, gives the type a chance
+ to return a type which the value should be coerced into.
+
+ The default behavior here is conservative; if the right-hand
+ side is already coerced into a SQL type based on its
+ Python type, it is usually left alone.
+
+ End-user functionality extension here should generally be via
+ :class:`.TypeDecorator`, which provides more liberal behavior in that
+ it defaults to coercing the other side of the expression into this
+ type, thus applying special Python conversions above and beyond those
+ needed by the DBAPI to both ides. It also provides the public method
+ :meth:`.TypeDecorator.coerce_compared_value` which is intended for
+ end-user customization of this behavior.
+
+ """
+ _coerced_type = _resolve_value_to_type(value)
+ if (
+ _coerced_type is NULLTYPE
+ or _coerced_type._type_affinity is self._type_affinity
+ ):
+ return self
+ else:
+ return _coerced_type
+
+ def _compare_type_affinity(self, other: TypeEngine[Any]) -> bool:
+ return self._type_affinity is other._type_affinity
+
+ def compile(self, dialect: Optional[Dialect] = None) -> str:
+ """Produce a string-compiled form of this :class:`.TypeEngine`.
+
+ When called with no arguments, uses a "default" dialect
+ to produce a string result.
+
+ :param dialect: a :class:`.Dialect` instance.
+
+ """
+ # arg, return value is inconsistent with
+ # ClauseElement.compile()....this is a mistake.
+
+ if dialect is None:
+ dialect = self._default_dialect()
+
+ return dialect.type_compiler_instance.process(self)
+
+ @util.preload_module("sqlalchemy.engine.default")
+ def _default_dialect(self) -> Dialect:
+ default = util.preloaded.engine_default
+
+ # dmypy / mypy seems to sporadically keep thinking this line is
+ # returning Any, which seems to be caused by the @deprecated_params
+ # decorator on the DefaultDialect constructor
+ return default.StrCompileDialect() # type: ignore
+
+ def __str__(self) -> str:
+ return str(self.compile())
+
+ def __repr__(self) -> str:
+ return util.generic_repr(self)
+
+
+class TypeEngineMixin:
+ """classes which subclass this can act as "mixin" classes for
+ TypeEngine."""
+
+ __slots__ = ()
+
+ if TYPE_CHECKING:
+
+ @util.memoized_property
+ def _static_cache_key(
+ self,
+ ) -> Union[CacheConst, Tuple[Any, ...]]: ...
+
+ @overload
+ def adapt(self, cls: Type[_TE], **kw: Any) -> _TE: ...
+
+ @overload
+ def adapt(
+ self, cls: Type[TypeEngineMixin], **kw: Any
+ ) -> TypeEngine[Any]: ...
+
+ def adapt(
+ self, cls: Type[Union[TypeEngine[Any], TypeEngineMixin]], **kw: Any
+ ) -> TypeEngine[Any]: ...
+
+ def dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]: ...
+
+
+class ExternalType(TypeEngineMixin):
+ """mixin that defines attributes and behaviors specific to third-party
+ datatypes.
+
+ "Third party" refers to datatypes that are defined outside the scope
+ of SQLAlchemy within either end-user application code or within
+ external extensions to SQLAlchemy.
+
+ Subclasses currently include :class:`.TypeDecorator` and
+ :class:`.UserDefinedType`.
+
+ .. versionadded:: 1.4.28
+
+ """
+
+ cache_ok: Optional[bool] = None
+ """Indicate if statements using this :class:`.ExternalType` are "safe to
+ cache".
+
+ The default value ``None`` will emit a warning and then not allow caching
+ of a statement which includes this type. Set to ``False`` to disable
+ statements using this type from being cached at all without a warning.
+ When set to ``True``, the object's class and selected elements from its
+ state will be used as part of the cache key. For example, using a
+ :class:`.TypeDecorator`::
+
+ class MyType(TypeDecorator):
+ impl = String
+
+ cache_ok = True
+
+ def __init__(self, choices):
+ self.choices = tuple(choices)
+ self.internal_only = True
+
+ The cache key for the above type would be equivalent to::
+
+ >>> MyType(["a", "b", "c"])._static_cache_key
+ (<class '__main__.MyType'>, ('choices', ('a', 'b', 'c')))
+
+ The caching scheme will extract attributes from the type that correspond
+ to the names of parameters in the ``__init__()`` method. Above, the
+ "choices" attribute becomes part of the cache key but "internal_only"
+ does not, because there is no parameter named "internal_only".
+
+ The requirements for cacheable elements is that they are hashable
+ and also that they indicate the same SQL rendered for expressions using
+ this type every time for a given cache value.
+
+ To accommodate for datatypes that refer to unhashable structures such
+ as dictionaries, sets and lists, these objects can be made "cacheable"
+ by assigning hashable structures to the attributes whose names
+ correspond with the names of the arguments. For example, a datatype
+ which accepts a dictionary of lookup values may publish this as a sorted
+ series of tuples. Given a previously un-cacheable type as::
+
+ class LookupType(UserDefinedType):
+ '''a custom type that accepts a dictionary as a parameter.
+
+ this is the non-cacheable version, as "self.lookup" is not
+ hashable.
+
+ '''
+
+ def __init__(self, lookup):
+ self.lookup = lookup
+
+ def get_col_spec(self, **kw):
+ return "VARCHAR(255)"
+
+ def bind_processor(self, dialect):
+ # ... works with "self.lookup" ...
+
+ Where "lookup" is a dictionary. The type will not be able to generate
+ a cache key::
+
+ >>> type_ = LookupType({"a": 10, "b": 20})
+ >>> type_._static_cache_key
+ <stdin>:1: SAWarning: UserDefinedType LookupType({'a': 10, 'b': 20}) will not
+ produce a cache key because the ``cache_ok`` flag is not set to True.
+ Set this flag to True if this type object's state is safe to use
+ in a cache key, or False to disable this warning.
+ symbol('no_cache')
+
+ If we **did** set up such a cache key, it wouldn't be usable. We would
+ get a tuple structure that contains a dictionary inside of it, which
+ cannot itself be used as a key in a "cache dictionary" such as SQLAlchemy's
+ statement cache, since Python dictionaries aren't hashable::
+
+ >>> # set cache_ok = True
+ >>> type_.cache_ok = True
+
+ >>> # this is the cache key it would generate
+ >>> key = type_._static_cache_key
+ >>> key
+ (<class '__main__.LookupType'>, ('lookup', {'a': 10, 'b': 20}))
+
+ >>> # however this key is not hashable, will fail when used with
+ >>> # SQLAlchemy statement cache
+ >>> some_cache = {key: "some sql value"}
+ Traceback (most recent call last): File "<stdin>", line 1,
+ in <module> TypeError: unhashable type: 'dict'
+
+ The type may be made cacheable by assigning a sorted tuple of tuples
+ to the ".lookup" attribute::
+
+ class LookupType(UserDefinedType):
+ '''a custom type that accepts a dictionary as a parameter.
+
+ The dictionary is stored both as itself in a private variable,
+ and published in a public variable as a sorted tuple of tuples,
+ which is hashable and will also return the same value for any
+ two equivalent dictionaries. Note it assumes the keys and
+ values of the dictionary are themselves hashable.
+
+ '''
+
+ cache_ok = True
+
+ def __init__(self, lookup):
+ self._lookup = lookup
+
+ # assume keys/values of "lookup" are hashable; otherwise
+ # they would also need to be converted in some way here
+ self.lookup = tuple(
+ (key, lookup[key]) for key in sorted(lookup)
+ )
+
+ def get_col_spec(self, **kw):
+ return "VARCHAR(255)"
+
+ def bind_processor(self, dialect):
+ # ... works with "self._lookup" ...
+
+ Where above, the cache key for ``LookupType({"a": 10, "b": 20})`` will be::
+
+ >>> LookupType({"a": 10, "b": 20})._static_cache_key
+ (<class '__main__.LookupType'>, ('lookup', (('a', 10), ('b', 20))))
+
+ .. versionadded:: 1.4.14 - added the ``cache_ok`` flag to allow
+ some configurability of caching for :class:`.TypeDecorator` classes.
+
+ .. versionadded:: 1.4.28 - added the :class:`.ExternalType` mixin which
+ generalizes the ``cache_ok`` flag to both the :class:`.TypeDecorator`
+ and :class:`.UserDefinedType` classes.
+
+ .. seealso::
+
+ :ref:`sql_caching`
+
+ """ # noqa: E501
+
+ @util.non_memoized_property
+ def _static_cache_key(
+ self,
+ ) -> Union[CacheConst, Tuple[Any, ...]]:
+ cache_ok = self.__class__.__dict__.get("cache_ok", None)
+
+ if cache_ok is None:
+ for subtype in self.__class__.__mro__:
+ if ExternalType in subtype.__bases__:
+ break
+ else:
+ subtype = self.__class__.__mro__[1]
+
+ util.warn(
+ "%s %r will not produce a cache key because "
+ "the ``cache_ok`` attribute is not set to True. This can "
+ "have significant performance implications including some "
+ "performance degradations in comparison to prior SQLAlchemy "
+ "versions. Set this attribute to True if this type object's "
+ "state is safe to use in a cache key, or False to "
+ "disable this warning." % (subtype.__name__, self),
+ code="cprf",
+ )
+ elif cache_ok is True:
+ return super()._static_cache_key
+
+ return NO_CACHE
+
+
+class UserDefinedType(
+ ExternalType, TypeEngineMixin, TypeEngine[_T], util.EnsureKWArg
+):
+ """Base for user defined types.
+
+ This should be the base of new types. Note that
+ for most cases, :class:`.TypeDecorator` is probably
+ more appropriate::
+
+ import sqlalchemy.types as types
+
+ class MyType(types.UserDefinedType):
+ cache_ok = True
+
+ def __init__(self, precision = 8):
+ self.precision = precision
+
+ def get_col_spec(self, **kw):
+ return "MYTYPE(%s)" % self.precision
+
+ def bind_processor(self, dialect):
+ def process(value):
+ return value
+ return process
+
+ def result_processor(self, dialect, coltype):
+ def process(value):
+ return value
+ return process
+
+ Once the type is made, it's immediately usable::
+
+ table = Table('foo', metadata_obj,
+ Column('id', Integer, primary_key=True),
+ Column('data', MyType(16))
+ )
+
+ The ``get_col_spec()`` method will in most cases receive a keyword
+ argument ``type_expression`` which refers to the owning expression
+ of the type as being compiled, such as a :class:`_schema.Column` or
+ :func:`.cast` construct. This keyword is only sent if the method
+ accepts keyword arguments (e.g. ``**kw``) in its argument signature;
+ introspection is used to check for this in order to support legacy
+ forms of this function.
+
+ The :attr:`.UserDefinedType.cache_ok` class-level flag indicates if this
+ custom :class:`.UserDefinedType` is safe to be used as part of a cache key.
+ This flag defaults to ``None`` which will initially generate a warning
+ when the SQL compiler attempts to generate a cache key for a statement
+ that uses this type. If the :class:`.UserDefinedType` is not guaranteed
+ to produce the same bind/result behavior and SQL generation
+ every time, this flag should be set to ``False``; otherwise if the
+ class produces the same behavior each time, it may be set to ``True``.
+ See :attr:`.UserDefinedType.cache_ok` for further notes on how this works.
+
+ .. versionadded:: 1.4.28 Generalized the :attr:`.ExternalType.cache_ok`
+ flag so that it is available for both :class:`.TypeDecorator` as well
+ as :class:`.UserDefinedType`.
+
+ """
+
+ __visit_name__ = "user_defined"
+
+ ensure_kwarg = "get_col_spec"
+
+ def coerce_compared_value(
+ self, op: Optional[OperatorType], value: Any
+ ) -> TypeEngine[Any]:
+ """Suggest a type for a 'coerced' Python value in an expression.
+
+ Default behavior for :class:`.UserDefinedType` is the
+ same as that of :class:`.TypeDecorator`; by default it returns
+ ``self``, assuming the compared value should be coerced into
+ the same type as this one. See
+ :meth:`.TypeDecorator.coerce_compared_value` for more detail.
+
+ """
+
+ return self
+
+
+class Emulated(TypeEngineMixin):
+ """Mixin for base types that emulate the behavior of a DB-native type.
+
+ An :class:`.Emulated` type will use an available database type
+ in conjunction with Python-side routines and/or database constraints
+ in order to approximate the behavior of a database type that is provided
+ natively by some backends. When a native-providing backend is in
+ use, the native version of the type is used. This native version
+ should include the :class:`.NativeForEmulated` mixin to allow it to be
+ distinguished from :class:`.Emulated`.
+
+ Current examples of :class:`.Emulated` are: :class:`.Interval`,
+ :class:`.Enum`, :class:`.Boolean`.
+
+ .. versionadded:: 1.2.0b3
+
+ """
+
+ native: bool
+
+ def adapt_to_emulated(
+ self,
+ impltype: Type[Union[TypeEngine[Any], TypeEngineMixin]],
+ **kw: Any,
+ ) -> TypeEngine[Any]:
+ """Given an impl class, adapt this type to the impl assuming
+ "emulated".
+
+ The impl should also be an "emulated" version of this type,
+ most likely the same class as this type itself.
+
+ e.g.: sqltypes.Enum adapts to the Enum class.
+
+ """
+ return super().adapt(impltype, **kw)
+
+ @overload
+ def adapt(self, cls: Type[_TE], **kw: Any) -> _TE: ...
+
+ @overload
+ def adapt(
+ self, cls: Type[TypeEngineMixin], **kw: Any
+ ) -> TypeEngine[Any]: ...
+
+ def adapt(
+ self, cls: Type[Union[TypeEngine[Any], TypeEngineMixin]], **kw: Any
+ ) -> TypeEngine[Any]:
+ if _is_native_for_emulated(cls):
+ if self.native:
+ # native support requested, dialect gave us a native
+ # implementor, pass control over to it
+ return cls.adapt_emulated_to_native(self, **kw)
+ else:
+ # non-native support, let the native implementor
+ # decide also, at the moment this is just to help debugging
+ # as only the default logic is implemented.
+ return cls.adapt_native_to_emulated(self, **kw)
+ else:
+ # this would be, both classes are Enum, or both classes
+ # are postgresql.ENUM
+ if issubclass(cls, self.__class__):
+ return self.adapt_to_emulated(cls, **kw)
+ else:
+ return super().adapt(cls, **kw)
+
+
+def _is_native_for_emulated(
+ typ: Type[Union[TypeEngine[Any], TypeEngineMixin]],
+) -> TypeGuard[Type[NativeForEmulated]]:
+ return hasattr(typ, "adapt_emulated_to_native")
+
+
+class NativeForEmulated(TypeEngineMixin):
+ """Indicates DB-native types supported by an :class:`.Emulated` type.
+
+ .. versionadded:: 1.2.0b3
+
+ """
+
+ @classmethod
+ def adapt_native_to_emulated(
+ cls,
+ impl: Union[TypeEngine[Any], TypeEngineMixin],
+ **kw: Any,
+ ) -> TypeEngine[Any]:
+ """Given an impl, adapt this type's class to the impl assuming
+ "emulated".
+
+
+ """
+ impltype = impl.__class__
+ return impl.adapt(impltype, **kw)
+
+ @classmethod
+ def adapt_emulated_to_native(
+ cls,
+ impl: Union[TypeEngine[Any], TypeEngineMixin],
+ **kw: Any,
+ ) -> TypeEngine[Any]:
+ """Given an impl, adapt this type's class to the impl assuming
+ "native".
+
+ The impl will be an :class:`.Emulated` class but not a
+ :class:`.NativeForEmulated`.
+
+ e.g.: postgresql.ENUM produces a type given an Enum instance.
+
+ """
+
+ # dmypy seems to crash on this
+ return cls(**kw) # type: ignore
+
+ # dmypy seems to crash with this, on repeated runs with changes
+ # if TYPE_CHECKING:
+ # def __init__(self, **kw: Any):
+ # ...
+
+
+class TypeDecorator(SchemaEventTarget, ExternalType, TypeEngine[_T]):
+ """Allows the creation of types which add additional functionality
+ to an existing type.
+
+ This method is preferred to direct subclassing of SQLAlchemy's
+ built-in types as it ensures that all required functionality of
+ the underlying type is kept in place.
+
+ Typical usage::
+
+ import sqlalchemy.types as types
+
+ class MyType(types.TypeDecorator):
+ '''Prefixes Unicode values with "PREFIX:" on the way in and
+ strips it off on the way out.
+ '''
+
+ impl = types.Unicode
+
+ cache_ok = True
+
+ def process_bind_param(self, value, dialect):
+ return "PREFIX:" + value
+
+ def process_result_value(self, value, dialect):
+ return value[7:]
+
+ def copy(self, **kw):
+ return MyType(self.impl.length)
+
+ The class-level ``impl`` attribute is required, and can reference any
+ :class:`.TypeEngine` class. Alternatively, the :meth:`load_dialect_impl`
+ method can be used to provide different type classes based on the dialect
+ given; in this case, the ``impl`` variable can reference
+ ``TypeEngine`` as a placeholder.
+
+ The :attr:`.TypeDecorator.cache_ok` class-level flag indicates if this
+ custom :class:`.TypeDecorator` is safe to be used as part of a cache key.
+ This flag defaults to ``None`` which will initially generate a warning
+ when the SQL compiler attempts to generate a cache key for a statement
+ that uses this type. If the :class:`.TypeDecorator` is not guaranteed
+ to produce the same bind/result behavior and SQL generation
+ every time, this flag should be set to ``False``; otherwise if the
+ class produces the same behavior each time, it may be set to ``True``.
+ See :attr:`.TypeDecorator.cache_ok` for further notes on how this works.
+
+ Types that receive a Python type that isn't similar to the ultimate type
+ used may want to define the :meth:`TypeDecorator.coerce_compared_value`
+ method. This is used to give the expression system a hint when coercing
+ Python objects into bind parameters within expressions. Consider this
+ expression::
+
+ mytable.c.somecol + datetime.date(2009, 5, 15)
+
+ Above, if "somecol" is an ``Integer`` variant, it makes sense that
+ we're doing date arithmetic, where above is usually interpreted
+ by databases as adding a number of days to the given date.
+ The expression system does the right thing by not attempting to
+ coerce the "date()" value into an integer-oriented bind parameter.
+
+ However, in the case of ``TypeDecorator``, we are usually changing an
+ incoming Python type to something new - ``TypeDecorator`` by default will
+ "coerce" the non-typed side to be the same type as itself. Such as below,
+ we define an "epoch" type that stores a date value as an integer::
+
+ class MyEpochType(types.TypeDecorator):
+ impl = types.Integer
+
+ cache_ok = True
+
+ epoch = datetime.date(1970, 1, 1)
+
+ def process_bind_param(self, value, dialect):
+ return (value - self.epoch).days
+
+ def process_result_value(self, value, dialect):
+ return self.epoch + timedelta(days=value)
+
+ Our expression of ``somecol + date`` with the above type will coerce the
+ "date" on the right side to also be treated as ``MyEpochType``.
+
+ This behavior can be overridden via the
+ :meth:`~TypeDecorator.coerce_compared_value` method, which returns a type
+ that should be used for the value of the expression. Below we set it such
+ that an integer value will be treated as an ``Integer``, and any other
+ value is assumed to be a date and will be treated as a ``MyEpochType``::
+
+ def coerce_compared_value(self, op, value):
+ if isinstance(value, int):
+ return Integer()
+ else:
+ return self
+
+ .. warning::
+
+ Note that the **behavior of coerce_compared_value is not inherited
+ by default from that of the base type**.
+ If the :class:`.TypeDecorator` is augmenting a
+ type that requires special logic for certain types of operators,
+ this method **must** be overridden. A key example is when decorating
+ the :class:`_postgresql.JSON` and :class:`_postgresql.JSONB` types;
+ the default rules of :meth:`.TypeEngine.coerce_compared_value` should
+ be used in order to deal with operators like index operations::
+
+ from sqlalchemy import JSON
+ from sqlalchemy import TypeDecorator
+
+ class MyJsonType(TypeDecorator):
+ impl = JSON
+
+ cache_ok = True
+
+ def coerce_compared_value(self, op, value):
+ return self.impl.coerce_compared_value(op, value)
+
+ Without the above step, index operations such as ``mycol['foo']``
+ will cause the index value ``'foo'`` to be JSON encoded.
+
+ Similarly, when working with the :class:`.ARRAY` datatype, the
+ type coercion for index operations (e.g. ``mycol[5]``) is also
+ handled by :meth:`.TypeDecorator.coerce_compared_value`, where
+ again a simple override is sufficient unless special rules are needed
+ for particular operators::
+
+ from sqlalchemy import ARRAY
+ from sqlalchemy import TypeDecorator
+
+ class MyArrayType(TypeDecorator):
+ impl = ARRAY
+
+ cache_ok = True
+
+ def coerce_compared_value(self, op, value):
+ return self.impl.coerce_compared_value(op, value)
+
+
+ """
+
+ __visit_name__ = "type_decorator"
+
+ _is_type_decorator = True
+
+ # this is that pattern I've used in a few places (Dialect.dbapi,
+ # Dialect.type_compiler) where the "cls.attr" is a class to make something,
+ # and "instance.attr" is an instance of that thing. It's such a nifty,
+ # great pattern, and there is zero chance Python typing tools will ever be
+ # OK with it. For TypeDecorator.impl, this is a highly public attribute so
+ # we really can't change its behavior without a major deprecation routine.
+ impl: Union[TypeEngine[Any], Type[TypeEngine[Any]]]
+
+ # we are changing its behavior *slightly*, which is that we now consume
+ # the instance level version from this memoized property instead, so you
+ # can't reassign "impl" on an existing TypeDecorator that's already been
+ # used (something one shouldn't do anyway) without also updating
+ # impl_instance.
+ @util.memoized_property
+ def impl_instance(self) -> TypeEngine[Any]:
+ return self.impl # type: ignore
+
+ def __init__(self, *args: Any, **kwargs: Any):
+ """Construct a :class:`.TypeDecorator`.
+
+ Arguments sent here are passed to the constructor
+ of the class assigned to the ``impl`` class level attribute,
+ assuming the ``impl`` is a callable, and the resulting
+ object is assigned to the ``self.impl`` instance attribute
+ (thus overriding the class attribute of the same name).
+
+ If the class level ``impl`` is not a callable (the unusual case),
+ it will be assigned to the same instance attribute 'as-is',
+ ignoring those arguments passed to the constructor.
+
+ Subclasses can override this to customize the generation
+ of ``self.impl`` entirely.
+
+ """
+
+ if not hasattr(self.__class__, "impl"):
+ raise AssertionError(
+ "TypeDecorator implementations "
+ "require a class-level variable "
+ "'impl' which refers to the class of "
+ "type being decorated"
+ )
+
+ self.impl = to_instance(self.__class__.impl, *args, **kwargs)
+
+ coerce_to_is_types: Sequence[Type[Any]] = (type(None),)
+ """Specify those Python types which should be coerced at the expression
+ level to "IS <constant>" when compared using ``==`` (and same for
+ ``IS NOT`` in conjunction with ``!=``).
+
+ For most SQLAlchemy types, this includes ``NoneType``, as well as
+ ``bool``.
+
+ :class:`.TypeDecorator` modifies this list to only include ``NoneType``,
+ as typedecorator implementations that deal with boolean types are common.
+
+ Custom :class:`.TypeDecorator` classes can override this attribute to
+ return an empty tuple, in which case no values will be coerced to
+ constants.
+
+ """
+
+ class Comparator(TypeEngine.Comparator[_CT]):
+ """A :class:`.TypeEngine.Comparator` that is specific to
+ :class:`.TypeDecorator`.
+
+ User-defined :class:`.TypeDecorator` classes should not typically
+ need to modify this.
+
+
+ """
+
+ __slots__ = ()
+
+ def operate(
+ self, op: OperatorType, *other: Any, **kwargs: Any
+ ) -> ColumnElement[_CT]:
+ if TYPE_CHECKING:
+ assert isinstance(self.expr.type, TypeDecorator)
+ kwargs["_python_is_types"] = self.expr.type.coerce_to_is_types
+ return super().operate(op, *other, **kwargs)
+
+ def reverse_operate(
+ self, op: OperatorType, other: Any, **kwargs: Any
+ ) -> ColumnElement[_CT]:
+ if TYPE_CHECKING:
+ assert isinstance(self.expr.type, TypeDecorator)
+ kwargs["_python_is_types"] = self.expr.type.coerce_to_is_types
+ return super().reverse_operate(op, other, **kwargs)
+
+ @property
+ def comparator_factory( # type: ignore # mypy properties bug
+ self,
+ ) -> _ComparatorFactory[Any]:
+ if TypeDecorator.Comparator in self.impl.comparator_factory.__mro__: # type: ignore # noqa: E501
+ return self.impl.comparator_factory
+ else:
+ # reconcile the Comparator class on the impl with that
+ # of TypeDecorator
+ return type(
+ "TDComparator",
+ (TypeDecorator.Comparator, self.impl.comparator_factory), # type: ignore # noqa: E501
+ {},
+ )
+
+ def _gen_dialect_impl(self, dialect: Dialect) -> TypeEngine[_T]:
+ if dialect.name in self._variant_mapping:
+ adapted = dialect.type_descriptor(
+ self._variant_mapping[dialect.name]
+ )
+ else:
+ adapted = dialect.type_descriptor(self)
+ if adapted is not self:
+ return adapted
+
+ # otherwise adapt the impl type, link
+ # to a copy of this TypeDecorator and return
+ # that.
+ typedesc = self.load_dialect_impl(dialect).dialect_impl(dialect)
+ tt = self.copy()
+ if not isinstance(tt, self.__class__):
+ raise AssertionError(
+ "Type object %s does not properly "
+ "implement the copy() method, it must "
+ "return an object of type %s" % (self, self.__class__)
+ )
+ tt.impl = tt.impl_instance = typedesc
+ return tt
+
+ @util.ro_non_memoized_property
+ def _type_affinity(self) -> Optional[Type[TypeEngine[Any]]]:
+ return self.impl_instance._type_affinity
+
+ def _set_parent(
+ self, parent: SchemaEventTarget, outer: bool = False, **kw: Any
+ ) -> None:
+ """Support SchemaEventTarget"""
+
+ super()._set_parent(parent)
+
+ if not outer and isinstance(self.impl_instance, SchemaEventTarget):
+ self.impl_instance._set_parent(parent, outer=False, **kw)
+
+ def _set_parent_with_dispatch(
+ self, parent: SchemaEventTarget, **kw: Any
+ ) -> None:
+ """Support SchemaEventTarget"""
+
+ super()._set_parent_with_dispatch(parent, outer=True, **kw)
+
+ if isinstance(self.impl_instance, SchemaEventTarget):
+ self.impl_instance._set_parent_with_dispatch(parent)
+
+ def type_engine(self, dialect: Dialect) -> TypeEngine[Any]:
+ """Return a dialect-specific :class:`.TypeEngine` instance
+ for this :class:`.TypeDecorator`.
+
+ In most cases this returns a dialect-adapted form of
+ the :class:`.TypeEngine` type represented by ``self.impl``.
+ Makes usage of :meth:`dialect_impl`.
+ Behavior can be customized here by overriding
+ :meth:`load_dialect_impl`.
+
+ """
+ adapted = dialect.type_descriptor(self)
+ if not isinstance(adapted, type(self)):
+ return adapted
+ else:
+ return self.load_dialect_impl(dialect)
+
+ def load_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]:
+ """Return a :class:`.TypeEngine` object corresponding to a dialect.
+
+ This is an end-user override hook that can be used to provide
+ differing types depending on the given dialect. It is used
+ by the :class:`.TypeDecorator` implementation of :meth:`type_engine`
+ to help determine what type should ultimately be returned
+ for a given :class:`.TypeDecorator`.
+
+ By default returns ``self.impl``.
+
+ """
+ return self.impl_instance
+
+ def _unwrapped_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]:
+ """Return the 'unwrapped' dialect impl for this type.
+
+ This is used by the :meth:`.DefaultDialect.set_input_sizes`
+ method.
+
+ """
+ # some dialects have a lookup for a TypeDecorator subclass directly.
+ # postgresql.INTERVAL being the main example
+ typ = self.dialect_impl(dialect)
+
+ # if we are still a type decorator, load the per-dialect switch
+ # (such as what Variant uses), then get the dialect impl for that.
+ if isinstance(typ, self.__class__):
+ return typ.load_dialect_impl(dialect).dialect_impl(dialect)
+ else:
+ return typ
+
+ def __getattr__(self, key: str) -> Any:
+ """Proxy all other undefined accessors to the underlying
+ implementation."""
+ return getattr(self.impl_instance, key)
+
+ def process_literal_param(
+ self, value: Optional[_T], dialect: Dialect
+ ) -> str:
+ """Receive a literal parameter value to be rendered inline within
+ a statement.
+
+ .. note::
+
+ This method is called during the **SQL compilation** phase of a
+ statement, when rendering a SQL string. Unlike other SQL
+ compilation methods, it is passed a specific Python value to be
+ rendered as a string. However it should not be confused with the
+ :meth:`_types.TypeDecorator.process_bind_param` method, which is
+ the more typical method that processes the actual value passed to a
+ particular parameter at statement execution time.
+
+ Custom subclasses of :class:`_types.TypeDecorator` should override
+ this method to provide custom behaviors for incoming data values
+ that are in the special case of being rendered as literals.
+
+ The returned string will be rendered into the output string.
+
+ """
+ raise NotImplementedError()
+
+ def process_bind_param(self, value: Optional[_T], dialect: Dialect) -> Any:
+ """Receive a bound parameter value to be converted.
+
+ Custom subclasses of :class:`_types.TypeDecorator` should override
+ this method to provide custom behaviors for incoming data values.
+ This method is called at **statement execution time** and is passed
+ the literal Python data value which is to be associated with a bound
+ parameter in the statement.
+
+ The operation could be anything desired to perform custom
+ behavior, such as transforming or serializing data.
+ This could also be used as a hook for validating logic.
+
+ :param value: Data to operate upon, of any type expected by
+ this method in the subclass. Can be ``None``.
+ :param dialect: the :class:`.Dialect` in use.
+
+ .. seealso::
+
+ :ref:`types_typedecorator`
+
+ :meth:`_types.TypeDecorator.process_result_value`
+
+ """
+
+ raise NotImplementedError()
+
+ def process_result_value(
+ self, value: Optional[Any], dialect: Dialect
+ ) -> Optional[_T]:
+ """Receive a result-row column value to be converted.
+
+ Custom subclasses of :class:`_types.TypeDecorator` should override
+ this method to provide custom behaviors for data values
+ being received in result rows coming from the database.
+ This method is called at **result fetching time** and is passed
+ the literal Python data value that's extracted from a database result
+ row.
+
+ The operation could be anything desired to perform custom
+ behavior, such as transforming or deserializing data.
+
+ :param value: Data to operate upon, of any type expected by
+ this method in the subclass. Can be ``None``.
+ :param dialect: the :class:`.Dialect` in use.
+
+ .. seealso::
+
+ :ref:`types_typedecorator`
+
+ :meth:`_types.TypeDecorator.process_bind_param`
+
+
+ """
+
+ raise NotImplementedError()
+
+ @util.memoized_property
+ def _has_bind_processor(self) -> bool:
+ """memoized boolean, check if process_bind_param is implemented.
+
+ Allows the base process_bind_param to raise
+ NotImplementedError without needing to test an expensive
+ exception throw.
+
+ """
+
+ return util.method_is_overridden(
+ self, TypeDecorator.process_bind_param
+ )
+
+ @util.memoized_property
+ def _has_literal_processor(self) -> bool:
+ """memoized boolean, check if process_literal_param is implemented."""
+
+ return util.method_is_overridden(
+ self, TypeDecorator.process_literal_param
+ )
+
+ def literal_processor(
+ self, dialect: Dialect
+ ) -> Optional[_LiteralProcessorType[_T]]:
+ """Provide a literal processing function for the given
+ :class:`.Dialect`.
+
+ This is the method that fulfills the :class:`.TypeEngine`
+ contract for literal value conversion which normally occurs via
+ the :meth:`_types.TypeEngine.literal_processor` method.
+
+ .. note::
+
+ User-defined subclasses of :class:`_types.TypeDecorator` should
+ **not** implement this method, and should instead implement
+ :meth:`_types.TypeDecorator.process_literal_param` so that the
+ "inner" processing provided by the implementing type is maintained.
+
+ """
+
+ if self._has_literal_processor:
+ process_literal_param = self.process_literal_param
+ process_bind_param = None
+ elif self._has_bind_processor:
+ # use the bind processor if dont have a literal processor,
+ # but we have an impl literal processor
+ process_literal_param = None
+ process_bind_param = self.process_bind_param
+ else:
+ process_literal_param = None
+ process_bind_param = None
+
+ if process_literal_param is not None:
+ impl_processor = self.impl_instance.literal_processor(dialect)
+ if impl_processor:
+ fixed_impl_processor = impl_processor
+ fixed_process_literal_param = process_literal_param
+
+ def process(value: Any) -> str:
+ return fixed_impl_processor(
+ fixed_process_literal_param(value, dialect)
+ )
+
+ else:
+ fixed_process_literal_param = process_literal_param
+
+ def process(value: Any) -> str:
+ return fixed_process_literal_param(value, dialect)
+
+ return process
+
+ elif process_bind_param is not None:
+ impl_processor = self.impl_instance.literal_processor(dialect)
+ if not impl_processor:
+ return None
+ else:
+ fixed_impl_processor = impl_processor
+ fixed_process_bind_param = process_bind_param
+
+ def process(value: Any) -> str:
+ return fixed_impl_processor(
+ fixed_process_bind_param(value, dialect)
+ )
+
+ return process
+ else:
+ return self.impl_instance.literal_processor(dialect)
+
+ def bind_processor(
+ self, dialect: Dialect
+ ) -> Optional[_BindProcessorType[_T]]:
+ """Provide a bound value processing function for the
+ given :class:`.Dialect`.
+
+ This is the method that fulfills the :class:`.TypeEngine`
+ contract for bound value conversion which normally occurs via
+ the :meth:`_types.TypeEngine.bind_processor` method.
+
+ .. note::
+
+ User-defined subclasses of :class:`_types.TypeDecorator` should
+ **not** implement this method, and should instead implement
+ :meth:`_types.TypeDecorator.process_bind_param` so that the "inner"
+ processing provided by the implementing type is maintained.
+
+ :param dialect: Dialect instance in use.
+
+ """
+ if self._has_bind_processor:
+ process_param = self.process_bind_param
+ impl_processor = self.impl_instance.bind_processor(dialect)
+ if impl_processor:
+ fixed_impl_processor = impl_processor
+ fixed_process_param = process_param
+
+ def process(value: Optional[_T]) -> Any:
+ return fixed_impl_processor(
+ fixed_process_param(value, dialect)
+ )
+
+ else:
+ fixed_process_param = process_param
+
+ def process(value: Optional[_T]) -> Any:
+ return fixed_process_param(value, dialect)
+
+ return process
+ else:
+ return self.impl_instance.bind_processor(dialect)
+
+ @util.memoized_property
+ def _has_result_processor(self) -> bool:
+ """memoized boolean, check if process_result_value is implemented.
+
+ Allows the base process_result_value to raise
+ NotImplementedError without needing to test an expensive
+ exception throw.
+
+ """
+
+ return util.method_is_overridden(
+ self, TypeDecorator.process_result_value
+ )
+
+ def result_processor(
+ self, dialect: Dialect, coltype: Any
+ ) -> Optional[_ResultProcessorType[_T]]:
+ """Provide a result value processing function for the given
+ :class:`.Dialect`.
+
+ This is the method that fulfills the :class:`.TypeEngine`
+ contract for bound value conversion which normally occurs via
+ the :meth:`_types.TypeEngine.result_processor` method.
+
+ .. note::
+
+ User-defined subclasses of :class:`_types.TypeDecorator` should
+ **not** implement this method, and should instead implement
+ :meth:`_types.TypeDecorator.process_result_value` so that the
+ "inner" processing provided by the implementing type is maintained.
+
+ :param dialect: Dialect instance in use.
+ :param coltype: A SQLAlchemy data type
+
+ """
+ if self._has_result_processor:
+ process_value = self.process_result_value
+ impl_processor = self.impl_instance.result_processor(
+ dialect, coltype
+ )
+ if impl_processor:
+ fixed_process_value = process_value
+ fixed_impl_processor = impl_processor
+
+ def process(value: Any) -> Optional[_T]:
+ return fixed_process_value(
+ fixed_impl_processor(value), dialect
+ )
+
+ else:
+ fixed_process_value = process_value
+
+ def process(value: Any) -> Optional[_T]:
+ return fixed_process_value(value, dialect)
+
+ return process
+ else:
+ return self.impl_instance.result_processor(dialect, coltype)
+
+ @util.memoized_property
+ def _has_bind_expression(self) -> bool:
+ return (
+ util.method_is_overridden(self, TypeDecorator.bind_expression)
+ or self.impl_instance._has_bind_expression
+ )
+
+ def bind_expression(
+ self, bindparam: BindParameter[_T]
+ ) -> Optional[ColumnElement[_T]]:
+ """Given a bind value (i.e. a :class:`.BindParameter` instance),
+ return a SQL expression which will typically wrap the given parameter.
+
+ .. note::
+
+ This method is called during the **SQL compilation** phase of a
+ statement, when rendering a SQL string. It is **not** necessarily
+ called against specific values, and should not be confused with the
+ :meth:`_types.TypeDecorator.process_bind_param` method, which is
+ the more typical method that processes the actual value passed to a
+ particular parameter at statement execution time.
+
+ Subclasses of :class:`_types.TypeDecorator` can override this method
+ to provide custom bind expression behavior for the type. This
+ implementation will **replace** that of the underlying implementation
+ type.
+
+ """
+ return self.impl_instance.bind_expression(bindparam)
+
+ @util.memoized_property
+ def _has_column_expression(self) -> bool:
+ """memoized boolean, check if column_expression is implemented.
+
+ Allows the method to be skipped for the vast majority of expression
+ types that don't use this feature.
+
+ """
+
+ return (
+ util.method_is_overridden(self, TypeDecorator.column_expression)
+ or self.impl_instance._has_column_expression
+ )
+
+ def column_expression(
+ self, column: ColumnElement[_T]
+ ) -> Optional[ColumnElement[_T]]:
+ """Given a SELECT column expression, return a wrapping SQL expression.
+
+ .. note::
+
+ This method is called during the **SQL compilation** phase of a
+ statement, when rendering a SQL string. It is **not** called
+ against specific values, and should not be confused with the
+ :meth:`_types.TypeDecorator.process_result_value` method, which is
+ the more typical method that processes the actual value returned
+ in a result row subsequent to statement execution time.
+
+ Subclasses of :class:`_types.TypeDecorator` can override this method
+ to provide custom column expression behavior for the type. This
+ implementation will **replace** that of the underlying implementation
+ type.
+
+ See the description of :meth:`_types.TypeEngine.column_expression`
+ for a complete description of the method's use.
+
+ """
+
+ return self.impl_instance.column_expression(column)
+
+ def coerce_compared_value(
+ self, op: Optional[OperatorType], value: Any
+ ) -> Any:
+ """Suggest a type for a 'coerced' Python value in an expression.
+
+ By default, returns self. This method is called by
+ the expression system when an object using this type is
+ on the left or right side of an expression against a plain Python
+ object which does not yet have a SQLAlchemy type assigned::
+
+ expr = table.c.somecolumn + 35
+
+ Where above, if ``somecolumn`` uses this type, this method will
+ be called with the value ``operator.add``
+ and ``35``. The return value is whatever SQLAlchemy type should
+ be used for ``35`` for this particular operation.
+
+ """
+ return self
+
+ def copy(self, **kw: Any) -> Self:
+ """Produce a copy of this :class:`.TypeDecorator` instance.
+
+ This is a shallow copy and is provided to fulfill part of
+ the :class:`.TypeEngine` contract. It usually does not
+ need to be overridden unless the user-defined :class:`.TypeDecorator`
+ has local state that should be deep-copied.
+
+ """
+
+ instance = self.__class__.__new__(self.__class__)
+ instance.__dict__.update(self.__dict__)
+ return instance
+
+ def get_dbapi_type(self, dbapi: ModuleType) -> Optional[Any]:
+ """Return the DBAPI type object represented by this
+ :class:`.TypeDecorator`.
+
+ By default this calls upon :meth:`.TypeEngine.get_dbapi_type` of the
+ underlying "impl".
+ """
+ return self.impl_instance.get_dbapi_type(dbapi)
+
+ def compare_values(self, x: Any, y: Any) -> bool:
+ """Given two values, compare them for equality.
+
+ By default this calls upon :meth:`.TypeEngine.compare_values`
+ of the underlying "impl", which in turn usually
+ uses the Python equals operator ``==``.
+
+ This function is used by the ORM to compare
+ an original-loaded value with an intercepted
+ "changed" value, to determine if a net change
+ has occurred.
+
+ """
+ return self.impl_instance.compare_values(x, y)
+
+ # mypy property bug
+ @property
+ def sort_key_function(self) -> Optional[Callable[[Any], Any]]: # type: ignore # noqa: E501
+ return self.impl_instance.sort_key_function
+
+ def __repr__(self) -> str:
+ return util.generic_repr(self, to_inspect=self.impl_instance)
+
+
+class Variant(TypeDecorator[_T]):
+ """deprecated. symbol is present for backwards-compatibility with
+ workaround recipes, however this actual type should not be used.
+
+ """
+
+ def __init__(self, *arg: Any, **kw: Any):
+ raise NotImplementedError(
+ "Variant is no longer used in SQLAlchemy; this is a "
+ "placeholder symbol for backwards compatibility."
+ )
+
+
+@overload
+def to_instance(
+ typeobj: Union[Type[_TE], _TE], *arg: Any, **kw: Any
+) -> _TE: ...
+
+
+@overload
+def to_instance(typeobj: None, *arg: Any, **kw: Any) -> TypeEngine[None]: ...
+
+
+def to_instance(
+ typeobj: Union[Type[_TE], _TE, None], *arg: Any, **kw: Any
+) -> Union[_TE, TypeEngine[None]]:
+ if typeobj is None:
+ return NULLTYPE
+
+ if callable(typeobj):
+ return typeobj(*arg, **kw)
+ else:
+ return typeobj
+
+
+def adapt_type(
+ typeobj: TypeEngine[Any],
+ colspecs: Mapping[Type[Any], Type[TypeEngine[Any]]],
+) -> TypeEngine[Any]:
+ if isinstance(typeobj, type):
+ typeobj = typeobj()
+ for t in typeobj.__class__.__mro__[0:-1]:
+ try:
+ impltype = colspecs[t]
+ break
+ except KeyError:
+ pass
+ else:
+ # couldn't adapt - so just return the type itself
+ # (it may be a user-defined type)
+ return typeobj
+ # if we adapted the given generic type to a database-specific type,
+ # but it turns out the originally given "generic" type
+ # is actually a subclass of our resulting type, then we were already
+ # given a more specific type than that required; so use that.
+ if issubclass(typeobj.__class__, impltype):
+ return typeobj
+ return typeobj.adapt(impltype)
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/util.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/util.py
new file mode 100644
index 0000000..4a35bb2
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/util.py
@@ -0,0 +1,1486 @@
+# sql/util.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+# mypy: allow-untyped-defs, allow-untyped-calls
+
+"""High level utilities which build upon other modules here.
+
+"""
+from __future__ import annotations
+
+from collections import deque
+import copy
+from itertools import chain
+import typing
+from typing import AbstractSet
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import Collection
+from typing import Dict
+from typing import Iterable
+from typing import Iterator
+from typing import List
+from typing import Optional
+from typing import overload
+from typing import Sequence
+from typing import Tuple
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from . import coercions
+from . import operators
+from . import roles
+from . import visitors
+from ._typing import is_text_clause
+from .annotation import _deep_annotate as _deep_annotate # noqa: F401
+from .annotation import _deep_deannotate as _deep_deannotate # noqa: F401
+from .annotation import _shallow_annotate as _shallow_annotate # noqa: F401
+from .base import _expand_cloned
+from .base import _from_objects
+from .cache_key import HasCacheKey as HasCacheKey # noqa: F401
+from .ddl import sort_tables as sort_tables # noqa: F401
+from .elements import _find_columns as _find_columns
+from .elements import _label_reference
+from .elements import _textual_label_reference
+from .elements import BindParameter
+from .elements import ClauseElement
+from .elements import ColumnClause
+from .elements import ColumnElement
+from .elements import Grouping
+from .elements import KeyedColumnElement
+from .elements import Label
+from .elements import NamedColumn
+from .elements import Null
+from .elements import UnaryExpression
+from .schema import Column
+from .selectable import Alias
+from .selectable import FromClause
+from .selectable import FromGrouping
+from .selectable import Join
+from .selectable import ScalarSelect
+from .selectable import SelectBase
+from .selectable import TableClause
+from .visitors import _ET
+from .. import exc
+from .. import util
+from ..util.typing import Literal
+from ..util.typing import Protocol
+
+if typing.TYPE_CHECKING:
+ from ._typing import _EquivalentColumnMap
+ from ._typing import _LimitOffsetType
+ from ._typing import _TypeEngineArgument
+ from .elements import BinaryExpression
+ from .elements import TextClause
+ from .selectable import _JoinTargetElement
+ from .selectable import _SelectIterable
+ from .selectable import Selectable
+ from .visitors import _TraverseCallableType
+ from .visitors import ExternallyTraversible
+ from .visitors import ExternalTraversal
+ from ..engine.interfaces import _AnyExecuteParams
+ from ..engine.interfaces import _AnyMultiExecuteParams
+ from ..engine.interfaces import _AnySingleExecuteParams
+ from ..engine.interfaces import _CoreSingleExecuteParams
+ from ..engine.row import Row
+
+_CE = TypeVar("_CE", bound="ColumnElement[Any]")
+
+
+def join_condition(
+ a: FromClause,
+ b: FromClause,
+ a_subset: Optional[FromClause] = None,
+ consider_as_foreign_keys: Optional[AbstractSet[ColumnClause[Any]]] = None,
+) -> ColumnElement[bool]:
+ """Create a join condition between two tables or selectables.
+
+ e.g.::
+
+ join_condition(tablea, tableb)
+
+ would produce an expression along the lines of::
+
+ tablea.c.id==tableb.c.tablea_id
+
+ The join is determined based on the foreign key relationships
+ between the two selectables. If there are multiple ways
+ to join, or no way to join, an error is raised.
+
+ :param a_subset: An optional expression that is a sub-component
+ of ``a``. An attempt will be made to join to just this sub-component
+ first before looking at the full ``a`` construct, and if found
+ will be successful even if there are other ways to join to ``a``.
+ This allows the "right side" of a join to be passed thereby
+ providing a "natural join".
+
+ """
+ return Join._join_condition(
+ a,
+ b,
+ a_subset=a_subset,
+ consider_as_foreign_keys=consider_as_foreign_keys,
+ )
+
+
+def find_join_source(
+ clauses: List[FromClause], join_to: FromClause
+) -> List[int]:
+ """Given a list of FROM clauses and a selectable,
+ return the first index and element from the list of
+ clauses which can be joined against the selectable. returns
+ None, None if no match is found.
+
+ e.g.::
+
+ clause1 = table1.join(table2)
+ clause2 = table4.join(table5)
+
+ join_to = table2.join(table3)
+
+ find_join_source([clause1, clause2], join_to) == clause1
+
+ """
+
+ selectables = list(_from_objects(join_to))
+ idx = []
+ for i, f in enumerate(clauses):
+ for s in selectables:
+ if f.is_derived_from(s):
+ idx.append(i)
+ return idx
+
+
+def find_left_clause_that_matches_given(
+ clauses: Sequence[FromClause], join_from: FromClause
+) -> List[int]:
+ """Given a list of FROM clauses and a selectable,
+ return the indexes from the list of
+ clauses which is derived from the selectable.
+
+ """
+
+ selectables = list(_from_objects(join_from))
+ liberal_idx = []
+ for i, f in enumerate(clauses):
+ for s in selectables:
+ # basic check, if f is derived from s.
+ # this can be joins containing a table, or an aliased table
+ # or select statement matching to a table. This check
+ # will match a table to a selectable that is adapted from
+ # that table. With Query, this suits the case where a join
+ # is being made to an adapted entity
+ if f.is_derived_from(s):
+ liberal_idx.append(i)
+ break
+
+ # in an extremely small set of use cases, a join is being made where
+ # there are multiple FROM clauses where our target table is represented
+ # in more than one, such as embedded or similar. in this case, do
+ # another pass where we try to get a more exact match where we aren't
+ # looking at adaption relationships.
+ if len(liberal_idx) > 1:
+ conservative_idx = []
+ for idx in liberal_idx:
+ f = clauses[idx]
+ for s in selectables:
+ if set(surface_selectables(f)).intersection(
+ surface_selectables(s)
+ ):
+ conservative_idx.append(idx)
+ break
+ if conservative_idx:
+ return conservative_idx
+
+ return liberal_idx
+
+
+def find_left_clause_to_join_from(
+ clauses: Sequence[FromClause],
+ join_to: _JoinTargetElement,
+ onclause: Optional[ColumnElement[Any]],
+) -> List[int]:
+ """Given a list of FROM clauses, a selectable,
+ and optional ON clause, return a list of integer indexes from the
+ clauses list indicating the clauses that can be joined from.
+
+ The presence of an "onclause" indicates that at least one clause can
+ definitely be joined from; if the list of clauses is of length one
+ and the onclause is given, returns that index. If the list of clauses
+ is more than length one, and the onclause is given, attempts to locate
+ which clauses contain the same columns.
+
+ """
+ idx = []
+ selectables = set(_from_objects(join_to))
+
+ # if we are given more than one target clause to join
+ # from, use the onclause to provide a more specific answer.
+ # otherwise, don't try to limit, after all, "ON TRUE" is a valid
+ # on clause
+ if len(clauses) > 1 and onclause is not None:
+ resolve_ambiguity = True
+ cols_in_onclause = _find_columns(onclause)
+ else:
+ resolve_ambiguity = False
+ cols_in_onclause = None
+
+ for i, f in enumerate(clauses):
+ for s in selectables.difference([f]):
+ if resolve_ambiguity:
+ assert cols_in_onclause is not None
+ if set(f.c).union(s.c).issuperset(cols_in_onclause):
+ idx.append(i)
+ break
+ elif onclause is not None or Join._can_join(f, s):
+ idx.append(i)
+ break
+
+ if len(idx) > 1:
+ # this is the same "hide froms" logic from
+ # Selectable._get_display_froms
+ toremove = set(
+ chain(*[_expand_cloned(f._hide_froms) for f in clauses])
+ )
+ idx = [i for i in idx if clauses[i] not in toremove]
+
+ # onclause was given and none of them resolved, so assume
+ # all indexes can match
+ if not idx and onclause is not None:
+ return list(range(len(clauses)))
+ else:
+ return idx
+
+
+def visit_binary_product(
+ fn: Callable[
+ [BinaryExpression[Any], ColumnElement[Any], ColumnElement[Any]], None
+ ],
+ expr: ColumnElement[Any],
+) -> None:
+ """Produce a traversal of the given expression, delivering
+ column comparisons to the given function.
+
+ The function is of the form::
+
+ def my_fn(binary, left, right)
+
+ For each binary expression located which has a
+ comparison operator, the product of "left" and
+ "right" will be delivered to that function,
+ in terms of that binary.
+
+ Hence an expression like::
+
+ and_(
+ (a + b) == q + func.sum(e + f),
+ j == r
+ )
+
+ would have the traversal::
+
+ a <eq> q
+ a <eq> e
+ a <eq> f
+ b <eq> q
+ b <eq> e
+ b <eq> f
+ j <eq> r
+
+ That is, every combination of "left" and
+ "right" that doesn't further contain
+ a binary comparison is passed as pairs.
+
+ """
+ stack: List[BinaryExpression[Any]] = []
+
+ def visit(element: ClauseElement) -> Iterator[ColumnElement[Any]]:
+ if isinstance(element, ScalarSelect):
+ # we don't want to dig into correlated subqueries,
+ # those are just column elements by themselves
+ yield element
+ elif element.__visit_name__ == "binary" and operators.is_comparison(
+ element.operator # type: ignore
+ ):
+ stack.insert(0, element) # type: ignore
+ for l in visit(element.left): # type: ignore
+ for r in visit(element.right): # type: ignore
+ fn(stack[0], l, r)
+ stack.pop(0)
+ for elem in element.get_children():
+ visit(elem)
+ else:
+ if isinstance(element, ColumnClause):
+ yield element
+ for elem in element.get_children():
+ yield from visit(elem)
+
+ list(visit(expr))
+ visit = None # type: ignore # remove gc cycles
+
+
+def find_tables(
+ clause: ClauseElement,
+ *,
+ check_columns: bool = False,
+ include_aliases: bool = False,
+ include_joins: bool = False,
+ include_selects: bool = False,
+ include_crud: bool = False,
+) -> List[TableClause]:
+ """locate Table objects within the given expression."""
+
+ tables: List[TableClause] = []
+ _visitors: Dict[str, _TraverseCallableType[Any]] = {}
+
+ if include_selects:
+ _visitors["select"] = _visitors["compound_select"] = tables.append
+
+ if include_joins:
+ _visitors["join"] = tables.append
+
+ if include_aliases:
+ _visitors["alias"] = _visitors["subquery"] = _visitors[
+ "tablesample"
+ ] = _visitors["lateral"] = tables.append
+
+ if include_crud:
+ _visitors["insert"] = _visitors["update"] = _visitors["delete"] = (
+ lambda ent: tables.append(ent.table)
+ )
+
+ if check_columns:
+
+ def visit_column(column):
+ tables.append(column.table)
+
+ _visitors["column"] = visit_column
+
+ _visitors["table"] = tables.append
+
+ visitors.traverse(clause, {}, _visitors)
+ return tables
+
+
+def unwrap_order_by(clause: Any) -> Any:
+ """Break up an 'order by' expression into individual column-expressions,
+ without DESC/ASC/NULLS FIRST/NULLS LAST"""
+
+ cols = util.column_set()
+ result = []
+ stack = deque([clause])
+
+ # examples
+ # column -> ASC/DESC == column
+ # column -> ASC/DESC -> label == column
+ # column -> label -> ASC/DESC -> label == column
+ # scalar_select -> label -> ASC/DESC == scalar_select -> label
+
+ while stack:
+ t = stack.popleft()
+ if isinstance(t, ColumnElement) and (
+ not isinstance(t, UnaryExpression)
+ or not operators.is_ordering_modifier(t.modifier) # type: ignore
+ ):
+ if isinstance(t, Label) and not isinstance(
+ t.element, ScalarSelect
+ ):
+ t = t.element
+
+ if isinstance(t, Grouping):
+ t = t.element
+
+ stack.append(t)
+ continue
+ elif isinstance(t, _label_reference):
+ t = t.element
+
+ stack.append(t)
+ continue
+ if isinstance(t, (_textual_label_reference)):
+ continue
+ if t not in cols:
+ cols.add(t)
+ result.append(t)
+
+ else:
+ for c in t.get_children():
+ stack.append(c)
+ return result
+
+
+def unwrap_label_reference(element):
+ def replace(
+ element: ExternallyTraversible, **kw: Any
+ ) -> Optional[ExternallyTraversible]:
+ if isinstance(element, _label_reference):
+ return element.element
+ elif isinstance(element, _textual_label_reference):
+ assert False, "can't unwrap a textual label reference"
+ return None
+
+ return visitors.replacement_traverse(element, {}, replace)
+
+
+def expand_column_list_from_order_by(collist, order_by):
+ """Given the columns clause and ORDER BY of a selectable,
+ return a list of column expressions that can be added to the collist
+ corresponding to the ORDER BY, without repeating those already
+ in the collist.
+
+ """
+ cols_already_present = {
+ col.element if col._order_by_label_element is not None else col
+ for col in collist
+ }
+
+ to_look_for = list(chain(*[unwrap_order_by(o) for o in order_by]))
+
+ return [col for col in to_look_for if col not in cols_already_present]
+
+
+def clause_is_present(clause, search):
+ """Given a target clause and a second to search within, return True
+ if the target is plainly present in the search without any
+ subqueries or aliases involved.
+
+ Basically descends through Joins.
+
+ """
+
+ for elem in surface_selectables(search):
+ if clause == elem: # use == here so that Annotated's compare
+ return True
+ else:
+ return False
+
+
+def tables_from_leftmost(clause: FromClause) -> Iterator[FromClause]:
+ if isinstance(clause, Join):
+ yield from tables_from_leftmost(clause.left)
+ yield from tables_from_leftmost(clause.right)
+ elif isinstance(clause, FromGrouping):
+ yield from tables_from_leftmost(clause.element)
+ else:
+ yield clause
+
+
+def surface_selectables(clause):
+ stack = [clause]
+ while stack:
+ elem = stack.pop()
+ yield elem
+ if isinstance(elem, Join):
+ stack.extend((elem.left, elem.right))
+ elif isinstance(elem, FromGrouping):
+ stack.append(elem.element)
+
+
+def surface_selectables_only(clause):
+ stack = [clause]
+ while stack:
+ elem = stack.pop()
+ if isinstance(elem, (TableClause, Alias)):
+ yield elem
+ if isinstance(elem, Join):
+ stack.extend((elem.left, elem.right))
+ elif isinstance(elem, FromGrouping):
+ stack.append(elem.element)
+ elif isinstance(elem, ColumnClause):
+ if elem.table is not None:
+ stack.append(elem.table)
+ else:
+ yield elem
+ elif elem is not None:
+ yield elem
+
+
+def extract_first_column_annotation(column, annotation_name):
+ filter_ = (FromGrouping, SelectBase)
+
+ stack = deque([column])
+ while stack:
+ elem = stack.popleft()
+ if annotation_name in elem._annotations:
+ return elem._annotations[annotation_name]
+ for sub in elem.get_children():
+ if isinstance(sub, filter_):
+ continue
+ stack.append(sub)
+ return None
+
+
+def selectables_overlap(left: FromClause, right: FromClause) -> bool:
+ """Return True if left/right have some overlapping selectable"""
+
+ return bool(
+ set(surface_selectables(left)).intersection(surface_selectables(right))
+ )
+
+
+def bind_values(clause):
+ """Return an ordered list of "bound" values in the given clause.
+
+ E.g.::
+
+ >>> expr = and_(
+ ... table.c.foo==5, table.c.foo==7
+ ... )
+ >>> bind_values(expr)
+ [5, 7]
+ """
+
+ v = []
+
+ def visit_bindparam(bind):
+ v.append(bind.effective_value)
+
+ visitors.traverse(clause, {}, {"bindparam": visit_bindparam})
+ return v
+
+
+def _quote_ddl_expr(element):
+ if isinstance(element, str):
+ element = element.replace("'", "''")
+ return "'%s'" % element
+ else:
+ return repr(element)
+
+
+class _repr_base:
+ _LIST: int = 0
+ _TUPLE: int = 1
+ _DICT: int = 2
+
+ __slots__ = ("max_chars",)
+
+ max_chars: int
+
+ def trunc(self, value: Any) -> str:
+ rep = repr(value)
+ lenrep = len(rep)
+ if lenrep > self.max_chars:
+ segment_length = self.max_chars // 2
+ rep = (
+ rep[0:segment_length]
+ + (
+ " ... (%d characters truncated) ... "
+ % (lenrep - self.max_chars)
+ )
+ + rep[-segment_length:]
+ )
+ 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."""
+
+ __slots__ = ("row",)
+
+ def __init__(self, row: Row[Any], max_chars: int = 300):
+ self.row = row
+ self.max_chars = max_chars
+
+ def __repr__(self) -> str:
+ trunc = self.trunc
+ return "(%s%s)" % (
+ ", ".join(trunc(value) for value in self.row),
+ "," if len(self.row) == 1 else "",
+ )
+
+
+class _long_statement(str):
+ def __str__(self) -> str:
+ lself = len(self)
+ if lself > 500:
+ lleft = 250
+ lright = 100
+ trunc = lself - lleft - lright
+ return (
+ f"{self[0:lleft]} ... {trunc} "
+ f"characters truncated ... {self[-lright:]}"
+ )
+ else:
+ return str.__str__(self)
+
+
+class _repr_params(_repr_base):
+ """Provide a string view of bound parameters.
+
+ Truncates display to a given number of 'multi' parameter sets,
+ as well as long values to a given number of characters.
+
+ """
+
+ __slots__ = "params", "batches", "ismulti", "max_params"
+
+ def __init__(
+ self,
+ params: Optional[_AnyExecuteParams],
+ batches: int,
+ max_params: int = 100,
+ max_chars: int = 300,
+ ismulti: Optional[bool] = None,
+ ):
+ self.params = params
+ self.ismulti = ismulti
+ self.batches = batches
+ self.max_chars = max_chars
+ self.max_params = max_params
+
+ def __repr__(self) -> str:
+ if self.ismulti is None:
+ return self.trunc(self.params)
+
+ if isinstance(self.params, list):
+ typ = self._LIST
+
+ elif isinstance(self.params, tuple):
+ typ = self._TUPLE
+ elif isinstance(self.params, dict):
+ typ = self._DICT
+ else:
+ return self.trunc(self.params)
+
+ if self.ismulti:
+ multi_params = cast(
+ "_AnyMultiExecuteParams",
+ self.params,
+ )
+
+ if len(self.params) > self.batches:
+ msg = (
+ " ... displaying %i of %i total bound parameter sets ... "
+ )
+ return " ".join(
+ (
+ self._repr_multi(
+ multi_params[: self.batches - 2],
+ typ,
+ )[0:-1],
+ msg % (self.batches, len(self.params)),
+ self._repr_multi(multi_params[-2:], typ)[1:],
+ )
+ )
+ else:
+ return self._repr_multi(multi_params, typ)
+ else:
+ return self._repr_params(
+ cast(
+ "_AnySingleExecuteParams",
+ self.params,
+ ),
+ typ,
+ )
+
+ def _repr_multi(
+ self,
+ multi_params: _AnyMultiExecuteParams,
+ typ: int,
+ ) -> str:
+ if multi_params:
+ if isinstance(multi_params[0], list):
+ elem_type = self._LIST
+ elif isinstance(multi_params[0], tuple):
+ elem_type = self._TUPLE
+ elif isinstance(multi_params[0], dict):
+ elem_type = self._DICT
+ else:
+ assert False, "Unknown parameter type %s" % (
+ type(multi_params[0])
+ )
+
+ elements = ", ".join(
+ self._repr_params(params, elem_type) for params in multi_params
+ )
+ else:
+ elements = ""
+
+ if typ == self._LIST:
+ return "[%s]" % elements
+ else:
+ return "(%s)" % elements
+
+ def _get_batches(self, params: Iterable[Any]) -> Any:
+ lparams = list(params)
+ lenparams = len(lparams)
+ if lenparams > self.max_params:
+ lleft = self.max_params // 2
+ return (
+ lparams[0:lleft],
+ lparams[-lleft:],
+ lenparams - self.max_params,
+ )
+ else:
+ return lparams, None, None
+
+ def _repr_params(
+ self,
+ params: _AnySingleExecuteParams,
+ typ: int,
+ ) -> str:
+ if typ is self._DICT:
+ return self._repr_param_dict(
+ cast("_CoreSingleExecuteParams", params)
+ )
+ elif typ is self._TUPLE:
+ return self._repr_param_tuple(cast("Sequence[Any]", params))
+ else:
+ return self._repr_param_list(params)
+
+ def _repr_param_dict(self, params: _CoreSingleExecuteParams) -> str:
+ trunc = self.trunc
+ (
+ items_first_batch,
+ items_second_batch,
+ trunclen,
+ ) = self._get_batches(params.items())
+
+ if items_second_batch:
+ text = "{%s" % (
+ ", ".join(
+ f"{key!r}: {trunc(value)}"
+ for key, value in items_first_batch
+ )
+ )
+ text += f" ... {trunclen} parameters truncated ... "
+ text += "%s}" % (
+ ", ".join(
+ f"{key!r}: {trunc(value)}"
+ for key, value in items_second_batch
+ )
+ )
+ else:
+ text = "{%s}" % (
+ ", ".join(
+ f"{key!r}: {trunc(value)}"
+ for key, value in items_first_batch
+ )
+ )
+ return text
+
+ def _repr_param_tuple(self, params: Sequence[Any]) -> str:
+ trunc = self.trunc
+
+ (
+ items_first_batch,
+ items_second_batch,
+ trunclen,
+ ) = self._get_batches(params)
+
+ if items_second_batch:
+ text = "(%s" % (
+ ", ".join(trunc(value) for value in items_first_batch)
+ )
+ text += f" ... {trunclen} parameters truncated ... "
+ text += "%s)" % (
+ ", ".join(trunc(value) for value in items_second_batch),
+ )
+ else:
+ text = "(%s%s)" % (
+ ", ".join(trunc(value) for value in items_first_batch),
+ "," if len(items_first_batch) == 1 else "",
+ )
+ return text
+
+ def _repr_param_list(self, params: _AnySingleExecuteParams) -> str:
+ trunc = self.trunc
+ (
+ items_first_batch,
+ items_second_batch,
+ trunclen,
+ ) = self._get_batches(params)
+
+ if items_second_batch:
+ text = "[%s" % (
+ ", ".join(trunc(value) for value in items_first_batch)
+ )
+ text += f" ... {trunclen} parameters truncated ... "
+ text += "%s]" % (
+ ", ".join(trunc(value) for value in items_second_batch)
+ )
+ else:
+ text = "[%s]" % (
+ ", ".join(trunc(value) for value in items_first_batch)
+ )
+ return text
+
+
+def adapt_criterion_to_null(crit: _CE, nulls: Collection[Any]) -> _CE:
+ """given criterion containing bind params, convert selected elements
+ to IS NULL.
+
+ """
+
+ def visit_binary(binary):
+ if (
+ isinstance(binary.left, BindParameter)
+ and binary.left._identifying_key in nulls
+ ):
+ # reverse order if the NULL is on the left side
+ binary.left = binary.right
+ binary.right = Null()
+ binary.operator = operators.is_
+ binary.negate = operators.is_not
+ elif (
+ isinstance(binary.right, BindParameter)
+ and binary.right._identifying_key in nulls
+ ):
+ binary.right = Null()
+ binary.operator = operators.is_
+ binary.negate = operators.is_not
+
+ return visitors.cloned_traverse(crit, {}, {"binary": visit_binary})
+
+
+def splice_joins(
+ left: Optional[FromClause],
+ right: Optional[FromClause],
+ stop_on: Optional[FromClause] = None,
+) -> Optional[FromClause]:
+ if left is None:
+ return right
+
+ stack: List[Tuple[Optional[FromClause], Optional[Join]]] = [(right, None)]
+
+ adapter = ClauseAdapter(left)
+ ret = None
+ while stack:
+ (right, prevright) = stack.pop()
+ if isinstance(right, Join) and right is not stop_on:
+ right = right._clone()
+ right.onclause = adapter.traverse(right.onclause)
+ stack.append((right.left, right))
+ else:
+ right = adapter.traverse(right)
+ if prevright is not None:
+ assert right is not None
+ prevright.left = right
+ if ret is None:
+ ret = right
+
+ return ret
+
+
+@overload
+def reduce_columns(
+ columns: Iterable[ColumnElement[Any]],
+ *clauses: Optional[ClauseElement],
+ **kw: bool,
+) -> Sequence[ColumnElement[Any]]: ...
+
+
+@overload
+def reduce_columns(
+ columns: _SelectIterable,
+ *clauses: Optional[ClauseElement],
+ **kw: bool,
+) -> Sequence[Union[ColumnElement[Any], TextClause]]: ...
+
+
+def reduce_columns(
+ columns: _SelectIterable,
+ *clauses: Optional[ClauseElement],
+ **kw: bool,
+) -> Collection[Union[ColumnElement[Any], TextClause]]:
+ r"""given a list of columns, return a 'reduced' set based on natural
+ equivalents.
+
+ the set is reduced to the smallest list of columns which have no natural
+ equivalent present in the list. A "natural equivalent" means that two
+ columns will ultimately represent the same value because they are related
+ by a foreign key.
+
+ \*clauses is an optional list of join clauses which will be traversed
+ to further identify columns that are "equivalent".
+
+ \**kw may specify 'ignore_nonexistent_tables' to ignore foreign keys
+ whose tables are not yet configured, or columns that aren't yet present.
+
+ This function is primarily used to determine the most minimal "primary
+ key" from a selectable, by reducing the set of primary key columns present
+ in the selectable to just those that are not repeated.
+
+ """
+ ignore_nonexistent_tables = kw.pop("ignore_nonexistent_tables", False)
+ only_synonyms = kw.pop("only_synonyms", False)
+
+ column_set = util.OrderedSet(columns)
+ cset_no_text: util.OrderedSet[ColumnElement[Any]] = column_set.difference(
+ c for c in column_set if is_text_clause(c) # type: ignore
+ )
+
+ omit = util.column_set()
+ for col in cset_no_text:
+ for fk in chain(*[c.foreign_keys for c in col.proxy_set]):
+ for c in cset_no_text:
+ if c is col:
+ continue
+ try:
+ fk_col = fk.column
+ except exc.NoReferencedColumnError:
+ # TODO: add specific coverage here
+ # to test/sql/test_selectable ReduceTest
+ if ignore_nonexistent_tables:
+ continue
+ else:
+ raise
+ except exc.NoReferencedTableError:
+ # TODO: add specific coverage here
+ # to test/sql/test_selectable ReduceTest
+ if ignore_nonexistent_tables:
+ continue
+ else:
+ raise
+ if fk_col.shares_lineage(c) and (
+ not only_synonyms or c.name == col.name
+ ):
+ omit.add(col)
+ break
+
+ if clauses:
+
+ def visit_binary(binary):
+ if binary.operator == operators.eq:
+ cols = util.column_set(
+ chain(
+ *[c.proxy_set for c in cset_no_text.difference(omit)]
+ )
+ )
+ if binary.left in cols and binary.right in cols:
+ for c in reversed(cset_no_text):
+ if c.shares_lineage(binary.right) and (
+ not only_synonyms or c.name == binary.left.name
+ ):
+ omit.add(c)
+ break
+
+ for clause in clauses:
+ if clause is not None:
+ visitors.traverse(clause, {}, {"binary": visit_binary})
+
+ return column_set.difference(omit)
+
+
+def criterion_as_pairs(
+ expression,
+ consider_as_foreign_keys=None,
+ consider_as_referenced_keys=None,
+ any_operator=False,
+):
+ """traverse an expression and locate binary criterion pairs."""
+
+ if consider_as_foreign_keys and consider_as_referenced_keys:
+ raise exc.ArgumentError(
+ "Can only specify one of "
+ "'consider_as_foreign_keys' or "
+ "'consider_as_referenced_keys'"
+ )
+
+ def col_is(a, b):
+ # return a is b
+ return a.compare(b)
+
+ def visit_binary(binary):
+ if not any_operator and binary.operator is not operators.eq:
+ return
+ if not isinstance(binary.left, ColumnElement) or not isinstance(
+ binary.right, ColumnElement
+ ):
+ return
+
+ if consider_as_foreign_keys:
+ if binary.left in consider_as_foreign_keys and (
+ col_is(binary.right, binary.left)
+ or binary.right not in consider_as_foreign_keys
+ ):
+ pairs.append((binary.right, binary.left))
+ elif binary.right in consider_as_foreign_keys and (
+ col_is(binary.left, binary.right)
+ or binary.left not in consider_as_foreign_keys
+ ):
+ pairs.append((binary.left, binary.right))
+ elif consider_as_referenced_keys:
+ if binary.left in consider_as_referenced_keys and (
+ col_is(binary.right, binary.left)
+ or binary.right not in consider_as_referenced_keys
+ ):
+ pairs.append((binary.left, binary.right))
+ elif binary.right in consider_as_referenced_keys and (
+ col_is(binary.left, binary.right)
+ or binary.left not in consider_as_referenced_keys
+ ):
+ pairs.append((binary.right, binary.left))
+ else:
+ if isinstance(binary.left, Column) and isinstance(
+ binary.right, Column
+ ):
+ if binary.left.references(binary.right):
+ pairs.append((binary.right, binary.left))
+ elif binary.right.references(binary.left):
+ pairs.append((binary.left, binary.right))
+
+ pairs: List[Tuple[ColumnElement[Any], ColumnElement[Any]]] = []
+ visitors.traverse(expression, {}, {"binary": visit_binary})
+ return pairs
+
+
+class ClauseAdapter(visitors.ReplacingExternalTraversal):
+ """Clones and modifies clauses based on column correspondence.
+
+ E.g.::
+
+ table1 = Table('sometable', metadata,
+ Column('col1', Integer),
+ Column('col2', Integer)
+ )
+ table2 = Table('someothertable', metadata,
+ Column('col1', Integer),
+ Column('col2', Integer)
+ )
+
+ condition = table1.c.col1 == table2.c.col1
+
+ make an alias of table1::
+
+ s = table1.alias('foo')
+
+ calling ``ClauseAdapter(s).traverse(condition)`` converts
+ condition to read::
+
+ s.c.col1 == table2.c.col1
+
+ """
+
+ __slots__ = (
+ "__traverse_options__",
+ "selectable",
+ "include_fn",
+ "exclude_fn",
+ "equivalents",
+ "adapt_on_names",
+ "adapt_from_selectables",
+ )
+
+ def __init__(
+ self,
+ selectable: Selectable,
+ equivalents: Optional[_EquivalentColumnMap] = None,
+ include_fn: Optional[Callable[[ClauseElement], bool]] = None,
+ exclude_fn: Optional[Callable[[ClauseElement], bool]] = None,
+ adapt_on_names: bool = False,
+ anonymize_labels: bool = False,
+ adapt_from_selectables: Optional[AbstractSet[FromClause]] = None,
+ ):
+ self.__traverse_options__ = {
+ "stop_on": [selectable],
+ "anonymize_labels": anonymize_labels,
+ }
+ self.selectable = selectable
+ self.include_fn = include_fn
+ self.exclude_fn = exclude_fn
+ self.equivalents = util.column_dict(equivalents or {})
+ self.adapt_on_names = adapt_on_names
+ self.adapt_from_selectables = adapt_from_selectables
+
+ if TYPE_CHECKING:
+
+ @overload
+ def traverse(self, obj: Literal[None]) -> None: ...
+
+ # note this specializes the ReplacingExternalTraversal.traverse()
+ # method to state
+ # that we will return the same kind of ExternalTraversal object as
+ # we were given. This is probably not 100% true, such as it's
+ # possible for us to swap out Alias for Table at the top level.
+ # Ideally there could be overloads specific to ColumnElement and
+ # FromClause but Mypy is not accepting those as compatible with
+ # the base ReplacingExternalTraversal
+ @overload
+ def traverse(self, obj: _ET) -> _ET: ...
+
+ def traverse(
+ self, obj: Optional[ExternallyTraversible]
+ ) -> Optional[ExternallyTraversible]: ...
+
+ def _corresponding_column(
+ self, col, require_embedded, _seen=util.EMPTY_SET
+ ):
+ newcol = self.selectable.corresponding_column(
+ col, require_embedded=require_embedded
+ )
+ if newcol is None and col in self.equivalents and col not in _seen:
+ for equiv in self.equivalents[col]:
+ newcol = self._corresponding_column(
+ equiv,
+ require_embedded=require_embedded,
+ _seen=_seen.union([col]),
+ )
+ if newcol is not None:
+ return newcol
+
+ if (
+ self.adapt_on_names
+ and newcol is None
+ and isinstance(col, NamedColumn)
+ ):
+ newcol = self.selectable.exported_columns.get(col.name)
+ return newcol
+
+ @util.preload_module("sqlalchemy.sql.functions")
+ def replace(
+ self, col: _ET, _include_singleton_constants: bool = False
+ ) -> Optional[_ET]:
+ functions = util.preloaded.sql_functions
+
+ # TODO: cython candidate
+
+ if self.include_fn and not self.include_fn(col): # type: ignore
+ return None
+ elif self.exclude_fn and self.exclude_fn(col): # type: ignore
+ return None
+
+ if isinstance(col, FromClause) and not isinstance(
+ col, functions.FunctionElement
+ ):
+ if self.selectable.is_derived_from(col):
+ if self.adapt_from_selectables:
+ for adp in self.adapt_from_selectables:
+ if adp.is_derived_from(col):
+ break
+ else:
+ return None
+ return self.selectable # type: ignore
+ elif isinstance(col, Alias) and isinstance(
+ col.element, TableClause
+ ):
+ # we are a SELECT statement and not derived from an alias of a
+ # table (which nonetheless may be a table our SELECT derives
+ # from), so return the alias to prevent further traversal
+ # or
+ # we are an alias of a table and we are not derived from an
+ # alias of a table (which nonetheless may be the same table
+ # as ours) so, same thing
+ return col # type: ignore
+ else:
+ # other cases where we are a selectable and the element
+ # is another join or selectable that contains a table which our
+ # selectable derives from, that we want to process
+ return None
+
+ elif not isinstance(col, ColumnElement):
+ return None
+ elif not _include_singleton_constants and col._is_singleton_constant:
+ # dont swap out NULL, TRUE, FALSE for a label name
+ # in a SQL statement that's being rewritten,
+ # leave them as the constant. This is first noted in #6259,
+ # however the logic to check this moved here as of #7154 so that
+ # it is made specific to SQL rewriting and not all column
+ # correspondence
+
+ return None
+
+ if "adapt_column" in col._annotations:
+ col = col._annotations["adapt_column"]
+
+ if TYPE_CHECKING:
+ assert isinstance(col, KeyedColumnElement)
+
+ if self.adapt_from_selectables and col not in self.equivalents:
+ for adp in self.adapt_from_selectables:
+ if adp.c.corresponding_column(col, False) is not None:
+ break
+ else:
+ return None
+
+ if TYPE_CHECKING:
+ assert isinstance(col, KeyedColumnElement)
+
+ return self._corresponding_column( # type: ignore
+ col, require_embedded=True
+ )
+
+
+class _ColumnLookup(Protocol):
+ @overload
+ def __getitem__(self, key: None) -> None: ...
+
+ @overload
+ def __getitem__(self, key: ColumnClause[Any]) -> ColumnClause[Any]: ...
+
+ @overload
+ def __getitem__(self, key: ColumnElement[Any]) -> ColumnElement[Any]: ...
+
+ @overload
+ def __getitem__(self, key: _ET) -> _ET: ...
+
+ def __getitem__(self, key: Any) -> Any: ...
+
+
+class ColumnAdapter(ClauseAdapter):
+ """Extends ClauseAdapter with extra utility functions.
+
+ Key aspects of ColumnAdapter include:
+
+ * Expressions that are adapted are stored in a persistent
+ .columns collection; so that an expression E adapted into
+ an expression E1, will return the same object E1 when adapted
+ a second time. This is important in particular for things like
+ Label objects that are anonymized, so that the ColumnAdapter can
+ be used to present a consistent "adapted" view of things.
+
+ * Exclusion of items from the persistent collection based on
+ include/exclude rules, but also independent of hash identity.
+ This because "annotated" items all have the same hash identity as their
+ parent.
+
+ * "wrapping" capability is added, so that the replacement of an expression
+ E can proceed through a series of adapters. This differs from the
+ visitor's "chaining" feature in that the resulting object is passed
+ through all replacing functions unconditionally, rather than stopping
+ at the first one that returns non-None.
+
+ * An adapt_required option, used by eager loading to indicate that
+ We don't trust a result row column that is not translated.
+ This is to prevent a column from being interpreted as that
+ of the child row in a self-referential scenario, see
+ inheritance/test_basic.py->EagerTargetingTest.test_adapt_stringency
+
+ """
+
+ __slots__ = (
+ "columns",
+ "adapt_required",
+ "allow_label_resolve",
+ "_wrap",
+ "__weakref__",
+ )
+
+ columns: _ColumnLookup
+
+ def __init__(
+ self,
+ selectable: Selectable,
+ equivalents: Optional[_EquivalentColumnMap] = None,
+ adapt_required: bool = False,
+ include_fn: Optional[Callable[[ClauseElement], bool]] = None,
+ exclude_fn: Optional[Callable[[ClauseElement], bool]] = None,
+ adapt_on_names: bool = False,
+ allow_label_resolve: bool = True,
+ anonymize_labels: bool = False,
+ adapt_from_selectables: Optional[AbstractSet[FromClause]] = None,
+ ):
+ super().__init__(
+ selectable,
+ equivalents,
+ include_fn=include_fn,
+ exclude_fn=exclude_fn,
+ adapt_on_names=adapt_on_names,
+ anonymize_labels=anonymize_labels,
+ adapt_from_selectables=adapt_from_selectables,
+ )
+
+ self.columns = util.WeakPopulateDict(self._locate_col) # type: ignore
+ if self.include_fn or self.exclude_fn:
+ self.columns = self._IncludeExcludeMapping(self, self.columns)
+ self.adapt_required = adapt_required
+ self.allow_label_resolve = allow_label_resolve
+ self._wrap = None
+
+ class _IncludeExcludeMapping:
+ def __init__(self, parent, columns):
+ self.parent = parent
+ self.columns = columns
+
+ def __getitem__(self, key):
+ if (
+ self.parent.include_fn and not self.parent.include_fn(key)
+ ) or (self.parent.exclude_fn and self.parent.exclude_fn(key)):
+ if self.parent._wrap:
+ return self.parent._wrap.columns[key]
+ else:
+ return key
+ return self.columns[key]
+
+ def wrap(self, adapter):
+ ac = copy.copy(self)
+ ac._wrap = adapter
+ ac.columns = util.WeakPopulateDict(ac._locate_col) # type: ignore
+ if ac.include_fn or ac.exclude_fn:
+ ac.columns = self._IncludeExcludeMapping(ac, ac.columns)
+
+ return ac
+
+ @overload
+ def traverse(self, obj: Literal[None]) -> None: ...
+
+ @overload
+ def traverse(self, obj: _ET) -> _ET: ...
+
+ def traverse(
+ self, obj: Optional[ExternallyTraversible]
+ ) -> Optional[ExternallyTraversible]:
+ return self.columns[obj]
+
+ def chain(self, visitor: ExternalTraversal) -> ColumnAdapter:
+ assert isinstance(visitor, ColumnAdapter)
+
+ return super().chain(visitor)
+
+ if TYPE_CHECKING:
+
+ @property
+ def visitor_iterator(self) -> Iterator[ColumnAdapter]: ...
+
+ adapt_clause = traverse
+ adapt_list = ClauseAdapter.copy_and_process
+
+ def adapt_check_present(
+ self, col: ColumnElement[Any]
+ ) -> Optional[ColumnElement[Any]]:
+ newcol = self.columns[col]
+
+ if newcol is col and self._corresponding_column(col, True) is None:
+ return None
+
+ return newcol
+
+ def _locate_col(
+ self, col: ColumnElement[Any]
+ ) -> Optional[ColumnElement[Any]]:
+ # both replace and traverse() are overly complicated for what
+ # we are doing here and we would do better to have an inlined
+ # version that doesn't build up as much overhead. the issue is that
+ # sometimes the lookup does in fact have to adapt the insides of
+ # say a labeled scalar subquery. However, if the object is an
+ # Immutable, i.e. Column objects, we can skip the "clone" /
+ # "copy internals" part since those will be no-ops in any case.
+ # additionally we want to catch singleton objects null/true/false
+ # and make sure they are adapted as well here.
+
+ if col._is_immutable:
+ for vis in self.visitor_iterator:
+ c = vis.replace(col, _include_singleton_constants=True)
+ if c is not None:
+ break
+ else:
+ c = col
+ else:
+ c = ClauseAdapter.traverse(self, col)
+
+ if self._wrap:
+ c2 = self._wrap._locate_col(c)
+ if c2 is not None:
+ c = c2
+
+ if self.adapt_required and c is col:
+ return None
+
+ # allow_label_resolve is consumed by one case for joined eager loading
+ # as part of its logic to prevent its own columns from being affected
+ # by .order_by(). Before full typing were applied to the ORM, this
+ # logic would set this attribute on the incoming object (which is
+ # typically a column, but we have a test for it being a non-column
+ # object) if no column were found. While this seemed to
+ # have no negative effects, this adjustment should only occur on the
+ # new column which is assumed to be local to an adapted selectable.
+ if c is not col:
+ c._allow_label_resolve = self.allow_label_resolve
+
+ return c
+
+
+def _offset_or_limit_clause(
+ element: _LimitOffsetType,
+ name: Optional[str] = None,
+ type_: Optional[_TypeEngineArgument[int]] = None,
+) -> ColumnElement[int]:
+ """Convert the given value to an "offset or limit" clause.
+
+ This handles incoming integers and converts to an expression; if
+ an expression is already given, it is passed through.
+
+ """
+ return coercions.expect(
+ roles.LimitOffsetRole, element, name=name, type_=type_
+ )
+
+
+def _offset_or_limit_clause_asint_if_possible(
+ clause: _LimitOffsetType,
+) -> _LimitOffsetType:
+ """Return the offset or limit clause as a simple integer if possible,
+ else return the clause.
+
+ """
+ if clause is None:
+ return None
+ if hasattr(clause, "_limit_offset_value"):
+ value = clause._limit_offset_value
+ return util.asint(value)
+ else:
+ return clause
+
+
+def _make_slice(
+ limit_clause: _LimitOffsetType,
+ offset_clause: _LimitOffsetType,
+ start: int,
+ stop: int,
+) -> Tuple[Optional[ColumnElement[int]], Optional[ColumnElement[int]]]:
+ """Compute LIMIT/OFFSET in terms of slice start/end"""
+
+ # for calculated limit/offset, try to do the addition of
+ # values to offset in Python, however if a SQL clause is present
+ # then the addition has to be on the SQL side.
+
+ # TODO: typing is finding a few gaps in here, see if they can be
+ # closed up
+
+ if start is not None and stop is not None:
+ offset_clause = _offset_or_limit_clause_asint_if_possible(
+ offset_clause
+ )
+ if offset_clause is None:
+ offset_clause = 0
+
+ if start != 0:
+ offset_clause = offset_clause + start # type: ignore
+
+ if offset_clause == 0:
+ offset_clause = None
+ else:
+ assert offset_clause is not None
+ offset_clause = _offset_or_limit_clause(offset_clause)
+
+ limit_clause = _offset_or_limit_clause(stop - start)
+
+ elif start is None and stop is not None:
+ limit_clause = _offset_or_limit_clause(stop)
+ elif start is not None and stop is None:
+ offset_clause = _offset_or_limit_clause_asint_if_possible(
+ offset_clause
+ )
+ if offset_clause is None:
+ offset_clause = 0
+
+ if start != 0:
+ offset_clause = offset_clause + start
+
+ if offset_clause == 0:
+ offset_clause = None
+ else:
+ offset_clause = _offset_or_limit_clause(offset_clause)
+
+ return limit_clause, offset_clause
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/sql/visitors.py b/venv/lib/python3.11/site-packages/sqlalchemy/sql/visitors.py
new file mode 100644
index 0000000..d1cd7a9
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/sqlalchemy/sql/visitors.py
@@ -0,0 +1,1165 @@
+# sql/visitors.py
+# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+"""Visitor/traversal interface and library functions.
+
+
+"""
+
+from __future__ import annotations
+
+from collections import deque
+from enum import Enum
+import itertools
+import operator
+import typing
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import ClassVar
+from typing import Dict
+from typing import Iterable
+from typing import Iterator
+from typing import List
+from typing import Mapping
+from typing import Optional
+from typing import overload
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from .. import exc
+from .. import util
+from ..util import langhelpers
+from ..util._has_cy import HAS_CYEXTENSION
+from ..util.typing import Literal
+from ..util.typing import Protocol
+from ..util.typing import Self
+
+if TYPE_CHECKING:
+ from .annotation import _AnnotationDict
+ from .elements import ColumnElement
+
+if typing.TYPE_CHECKING or not HAS_CYEXTENSION:
+ from ._py_util import prefix_anon_map as prefix_anon_map
+ from ._py_util import cache_anon_map as anon_map
+else:
+ from sqlalchemy.cyextension.util import ( # noqa: F401,E501
+ prefix_anon_map as prefix_anon_map,
+ )
+ from sqlalchemy.cyextension.util import ( # noqa: F401,E501
+ cache_anon_map as anon_map,
+ )
+
+
+__all__ = [
+ "iterate",
+ "traverse_using",
+ "traverse",
+ "cloned_traverse",
+ "replacement_traverse",
+ "Visitable",
+ "ExternalTraversal",
+ "InternalTraversal",
+ "anon_map",
+]
+
+
+class _CompilerDispatchType(Protocol):
+ def __call__(_self, self: Visitable, visitor: Any, **kw: Any) -> Any: ...
+
+
+class Visitable:
+ """Base class for visitable objects.
+
+ :class:`.Visitable` is used to implement the SQL compiler dispatch
+ functions. Other forms of traversal such as for cache key generation
+ are implemented separately using the :class:`.HasTraverseInternals`
+ interface.
+
+ .. versionchanged:: 2.0 The :class:`.Visitable` class was named
+ :class:`.Traversible` in the 1.4 series; the name is changed back
+ to :class:`.Visitable` in 2.0 which is what it was prior to 1.4.
+
+ Both names remain importable in both 1.4 and 2.0 versions.
+
+ """
+
+ __slots__ = ()
+
+ __visit_name__: str
+
+ _original_compiler_dispatch: _CompilerDispatchType
+
+ if typing.TYPE_CHECKING:
+
+ def _compiler_dispatch(self, visitor: Any, **kw: Any) -> str: ...
+
+ def __init_subclass__(cls) -> None:
+ if "__visit_name__" in cls.__dict__:
+ cls._generate_compiler_dispatch()
+ super().__init_subclass__()
+
+ @classmethod
+ def _generate_compiler_dispatch(cls) -> None:
+ visit_name = cls.__visit_name__
+
+ if "_compiler_dispatch" in cls.__dict__:
+ # class has a fixed _compiler_dispatch() method.
+ # copy it to "original" so that we can get it back if
+ # sqlalchemy.ext.compiles overrides it.
+ cls._original_compiler_dispatch = cls._compiler_dispatch
+ return
+
+ if not isinstance(visit_name, str):
+ raise exc.InvalidRequestError(
+ f"__visit_name__ on class {cls.__name__} must be a string "
+ "at the class level"
+ )
+
+ name = "visit_%s" % visit_name
+ getter = operator.attrgetter(name)
+
+ def _compiler_dispatch(
+ self: Visitable, visitor: Any, **kw: Any
+ ) -> str:
+ """Look for an attribute named "visit_<visit_name>" on the
+ visitor, and call it with the same kw params.
+
+ """
+ try:
+ meth = getter(visitor)
+ except AttributeError as err:
+ return visitor.visit_unsupported_compilation(self, err, **kw) # type: ignore # noqa: E501
+ else:
+ return meth(self, **kw) # type: ignore # noqa: E501
+
+ cls._compiler_dispatch = ( # type: ignore
+ cls._original_compiler_dispatch
+ ) = _compiler_dispatch
+
+ def __class_getitem__(cls, key: Any) -> Any:
+ # allow generic classes in py3.9+
+ return cls
+
+
+class InternalTraversal(Enum):
+ r"""Defines visitor symbols used for internal traversal.
+
+ The :class:`.InternalTraversal` class is used in two ways. One is that
+ it can serve as the superclass for an object that implements the
+ various visit methods of the class. The other is that the symbols
+ themselves of :class:`.InternalTraversal` are used within
+ the ``_traverse_internals`` collection. Such as, the :class:`.Case`
+ object defines ``_traverse_internals`` as ::
+
+ class Case(ColumnElement[_T]):
+ _traverse_internals = [
+ ("value", InternalTraversal.dp_clauseelement),
+ ("whens", InternalTraversal.dp_clauseelement_tuples),
+ ("else_", InternalTraversal.dp_clauseelement),
+ ]
+
+ Above, the :class:`.Case` class indicates its internal state as the
+ attributes named ``value``, ``whens``, and ``else_``. They each
+ link to an :class:`.InternalTraversal` method which indicates the type
+ of datastructure to which each attribute refers.
+
+ Using the ``_traverse_internals`` structure, objects of type
+ :class:`.InternalTraversible` will have the following methods automatically
+ implemented:
+
+ * :meth:`.HasTraverseInternals.get_children`
+
+ * :meth:`.HasTraverseInternals._copy_internals`
+
+ * :meth:`.HasCacheKey._gen_cache_key`
+
+ Subclasses can also implement these methods directly, particularly for the
+ :meth:`.HasTraverseInternals._copy_internals` method, when special steps
+ are needed.
+
+ .. versionadded:: 1.4
+
+ """
+
+ dp_has_cache_key = "HC"
+ """Visit a :class:`.HasCacheKey` object."""
+
+ dp_has_cache_key_list = "HL"
+ """Visit a list of :class:`.HasCacheKey` objects."""
+
+ dp_clauseelement = "CE"
+ """Visit a :class:`_expression.ClauseElement` object."""
+
+ dp_fromclause_canonical_column_collection = "FC"
+ """Visit a :class:`_expression.FromClause` object in the context of the
+ ``columns`` attribute.
+
+ The column collection is "canonical", meaning it is the originally
+ defined location of the :class:`.ColumnClause` objects. Right now
+ this means that the object being visited is a
+ :class:`_expression.TableClause`
+ or :class:`_schema.Table` object only.
+
+ """
+
+ dp_clauseelement_tuples = "CTS"
+ """Visit a list of tuples which contain :class:`_expression.ClauseElement`
+ objects.
+
+ """
+
+ dp_clauseelement_list = "CL"
+ """Visit a list of :class:`_expression.ClauseElement` objects.
+
+ """
+
+ dp_clauseelement_tuple = "CT"
+ """Visit a tuple of :class:`_expression.ClauseElement` objects.
+
+ """
+
+ dp_executable_options = "EO"
+
+ dp_with_context_options = "WC"
+
+ dp_fromclause_ordered_set = "CO"
+ """Visit an ordered set of :class:`_expression.FromClause` objects. """
+
+ dp_string = "S"
+ """Visit a plain string value.
+
+ Examples include table and column names, bound parameter keys, special
+ keywords such as "UNION", "UNION ALL".
+
+ The string value is considered to be significant for cache key
+ generation.
+
+ """
+
+ dp_string_list = "SL"
+ """Visit a list of strings."""
+
+ dp_anon_name = "AN"
+ """Visit a potentially "anonymized" string value.
+
+ The string value is considered to be significant for cache key
+ generation.
+
+ """
+
+ dp_boolean = "B"
+ """Visit a boolean value.
+
+ The boolean value is considered to be significant for cache key
+ generation.
+
+ """
+
+ dp_operator = "O"
+ """Visit an operator.
+
+ The operator is a function from the :mod:`sqlalchemy.sql.operators`
+ module.
+
+ The operator value is considered to be significant for cache key
+ generation.
+
+ """
+
+ dp_type = "T"
+ """Visit a :class:`.TypeEngine` object
+
+ The type object is considered to be significant for cache key
+ generation.
+
+ """
+
+ dp_plain_dict = "PD"
+ """Visit a dictionary with string keys.
+
+ The keys of the dictionary should be strings, the values should
+ be immutable and hashable. The dictionary is considered to be
+ significant for cache key generation.
+
+ """
+
+ dp_dialect_options = "DO"
+ """Visit a dialect options structure."""
+
+ dp_string_clauseelement_dict = "CD"
+ """Visit a dictionary of string keys to :class:`_expression.ClauseElement`
+ objects.
+
+ """
+
+ dp_string_multi_dict = "MD"
+ """Visit a dictionary of string keys to values which may either be
+ plain immutable/hashable or :class:`.HasCacheKey` objects.
+
+ """
+
+ dp_annotations_key = "AK"
+ """Visit the _annotations_cache_key element.
+
+ This is a dictionary of additional information about a ClauseElement
+ that modifies its role. It should be included when comparing or caching
+ objects, however generating this key is relatively expensive. Visitors
+ should check the "_annotations" dict for non-None first before creating
+ this key.
+
+ """
+
+ dp_plain_obj = "PO"
+ """Visit a plain python object.
+
+ The value should be immutable and hashable, such as an integer.
+ The value is considered to be significant for cache key generation.
+
+ """
+
+ dp_named_ddl_element = "DD"
+ """Visit a simple named DDL element.
+
+ The current object used by this method is the :class:`.Sequence`.
+
+ The object is only considered to be important for cache key generation
+ as far as its name, but not any other aspects of it.
+
+ """
+
+ dp_prefix_sequence = "PS"
+ """Visit the sequence represented by :class:`_expression.HasPrefixes`
+ or :class:`_expression.HasSuffixes`.
+
+ """
+
+ dp_table_hint_list = "TH"
+ """Visit the ``_hints`` collection of a :class:`_expression.Select`
+ object.
+
+ """
+
+ dp_setup_join_tuple = "SJ"
+
+ dp_memoized_select_entities = "ME"
+
+ dp_statement_hint_list = "SH"
+ """Visit the ``_statement_hints`` collection of a
+ :class:`_expression.Select`
+ object.
+
+ """
+
+ dp_unknown_structure = "UK"
+ """Visit an unknown structure.
+
+ """
+
+ dp_dml_ordered_values = "DML_OV"
+ """Visit the values() ordered tuple list of an
+ :class:`_expression.Update` object."""
+
+ dp_dml_values = "DML_V"
+ """Visit the values() dictionary of a :class:`.ValuesBase`
+ (e.g. Insert or Update) object.
+
+ """
+
+ dp_dml_multi_values = "DML_MV"
+ """Visit the values() multi-valued list of dictionaries of an
+ :class:`_expression.Insert` object.
+
+ """
+
+ dp_propagate_attrs = "PA"
+ """Visit the propagate attrs dict. This hardcodes to the particular
+ elements we care about right now."""
+
+ """Symbols that follow are additional symbols that are useful in
+ caching applications.
+
+ Traversals for :class:`_expression.ClauseElement` objects only need to use
+ those symbols present in :class:`.InternalTraversal`. However, for
+ additional caching use cases within the ORM, symbols dealing with the
+ :class:`.HasCacheKey` class are added here.
+
+ """
+
+ dp_ignore = "IG"
+ """Specify an object that should be ignored entirely.
+
+ This currently applies function call argument caching where some
+ arguments should not be considered to be part of a cache key.
+
+ """
+
+ dp_inspectable = "IS"
+ """Visit an inspectable object where the return value is a
+ :class:`.HasCacheKey` object."""
+
+ dp_multi = "M"
+ """Visit an object that may be a :class:`.HasCacheKey` or may be a
+ plain hashable object."""
+
+ dp_multi_list = "MT"
+ """Visit a tuple containing elements that may be :class:`.HasCacheKey` or
+ may be a plain hashable object."""
+
+ dp_has_cache_key_tuples = "HT"
+ """Visit a list of tuples which contain :class:`.HasCacheKey`
+ objects.
+
+ """
+
+ dp_inspectable_list = "IL"
+ """Visit a list of inspectable objects which upon inspection are
+ HasCacheKey objects."""
+
+
+_TraverseInternalsType = List[Tuple[str, InternalTraversal]]
+"""a structure that defines how a HasTraverseInternals should be
+traversed.
+
+This structure consists of a list of (attributename, internaltraversal)
+tuples, where the "attributename" refers to the name of an attribute on an
+instance of the HasTraverseInternals object, and "internaltraversal" refers
+to an :class:`.InternalTraversal` enumeration symbol defining what kind
+of data this attribute stores, which indicates to the traverser how it should
+be handled.
+
+"""
+
+
+class HasTraverseInternals:
+ """base for classes that have a "traverse internals" element,
+ which defines all kinds of ways of traversing the elements of an object.
+
+ Compared to :class:`.Visitable`, which relies upon an external visitor to
+ define how the object is travered (i.e. the :class:`.SQLCompiler`), the
+ :class:`.HasTraverseInternals` interface allows classes to define their own
+ traversal, that is, what attributes are accessed and in what order.
+
+ """
+
+ __slots__ = ()
+
+ _traverse_internals: _TraverseInternalsType
+
+ _is_immutable: bool = False
+
+ @util.preload_module("sqlalchemy.sql.traversals")
+ def get_children(
+ self, *, omit_attrs: Tuple[str, ...] = (), **kw: Any
+ ) -> Iterable[HasTraverseInternals]:
+ r"""Return immediate child :class:`.visitors.HasTraverseInternals`
+ elements of this :class:`.visitors.HasTraverseInternals`.
+
+ This is used for visit traversal.
+
+ \**kw may contain flags that change the collection that is
+ returned, for example to return a subset of items in order to
+ cut down on larger traversals, or to return child items from a
+ different context (such as schema-level collections instead of
+ clause-level).
+
+ """
+
+ traversals = util.preloaded.sql_traversals
+
+ try:
+ traverse_internals = self._traverse_internals
+ except AttributeError:
+ # user-defined classes may not have a _traverse_internals
+ return []
+
+ dispatch = traversals._get_children.run_generated_dispatch
+ return itertools.chain.from_iterable(
+ meth(obj, **kw)
+ for attrname, obj, meth in dispatch(
+ self, traverse_internals, "_generated_get_children_traversal"
+ )
+ if attrname not in omit_attrs and obj is not None
+ )
+
+
+class _InternalTraversalDispatchType(Protocol):
+ def __call__(s, self: object, visitor: HasTraversalDispatch) -> Any: ...
+
+
+class HasTraversalDispatch:
+ r"""Define infrastructure for classes that perform internal traversals
+
+ .. versionadded:: 2.0
+
+ """
+
+ __slots__ = ()
+
+ _dispatch_lookup: ClassVar[Dict[Union[InternalTraversal, str], str]] = {}
+
+ def dispatch(self, visit_symbol: InternalTraversal) -> Callable[..., Any]:
+ """Given a method from :class:`.HasTraversalDispatch`, return the
+ corresponding method on a subclass.
+
+ """
+ name = _dispatch_lookup[visit_symbol]
+ return getattr(self, name, None) # type: ignore
+
+ def run_generated_dispatch(
+ self,
+ target: object,
+ internal_dispatch: _TraverseInternalsType,
+ generate_dispatcher_name: str,
+ ) -> Any:
+ dispatcher: _InternalTraversalDispatchType
+ try:
+ dispatcher = target.__class__.__dict__[generate_dispatcher_name]
+ except KeyError:
+ # traversals.py -> _preconfigure_traversals()
+ # may be used to run these ahead of time, but
+ # is not enabled right now.
+ # this block will generate any remaining dispatchers.
+ dispatcher = self.generate_dispatch(
+ target.__class__, internal_dispatch, generate_dispatcher_name
+ )
+ return dispatcher(target, self)
+
+ def generate_dispatch(
+ self,
+ target_cls: Type[object],
+ internal_dispatch: _TraverseInternalsType,
+ generate_dispatcher_name: str,
+ ) -> _InternalTraversalDispatchType:
+ dispatcher = self._generate_dispatcher(
+ internal_dispatch, generate_dispatcher_name
+ )
+ # assert isinstance(target_cls, type)
+ setattr(target_cls, generate_dispatcher_name, dispatcher)
+ return dispatcher
+
+ def _generate_dispatcher(
+ self, internal_dispatch: _TraverseInternalsType, method_name: str
+ ) -> _InternalTraversalDispatchType:
+ names = []
+ for attrname, visit_sym in internal_dispatch:
+ meth = self.dispatch(visit_sym)
+ if meth is not None:
+ visit_name = _dispatch_lookup[visit_sym]
+ names.append((attrname, visit_name))
+
+ code = (
+ (" return [\n")
+ + (
+ ", \n".join(
+ " (%r, self.%s, visitor.%s)"
+ % (attrname, attrname, visit_name)
+ for attrname, visit_name in names
+ )
+ )
+ + ("\n ]\n")
+ )
+ meth_text = ("def %s(self, visitor):\n" % method_name) + code + "\n"
+ return cast(
+ _InternalTraversalDispatchType,
+ langhelpers._exec_code_in_env(meth_text, {}, method_name),
+ )
+
+
+ExtendedInternalTraversal = InternalTraversal
+
+
+def _generate_traversal_dispatch() -> None:
+ lookup = _dispatch_lookup
+
+ for sym in InternalTraversal:
+ key = sym.name
+ if key.startswith("dp_"):
+ visit_key = key.replace("dp_", "visit_")
+ sym_name = sym.value
+ assert sym_name not in lookup, sym_name
+ lookup[sym] = lookup[sym_name] = visit_key
+
+
+_dispatch_lookup = HasTraversalDispatch._dispatch_lookup
+_generate_traversal_dispatch()
+
+
+class ExternallyTraversible(HasTraverseInternals, Visitable):
+ __slots__ = ()
+
+ _annotations: Mapping[Any, Any] = util.EMPTY_DICT
+
+ if typing.TYPE_CHECKING:
+
+ def _annotate(self, values: _AnnotationDict) -> Self: ...
+
+ def get_children(
+ self, *, omit_attrs: Tuple[str, ...] = (), **kw: Any
+ ) -> Iterable[ExternallyTraversible]: ...
+
+ def _clone(self, **kw: Any) -> Self:
+ """clone this element"""
+ raise NotImplementedError()
+
+ def _copy_internals(
+ self, *, omit_attrs: Tuple[str, ...] = (), **kw: Any
+ ) -> None:
+ """Reassign internal elements to be clones of themselves.
+
+ Called during a copy-and-traverse operation on newly
+ shallow-copied elements to create a deep copy.
+
+ The given clone function should be used, which may be applying
+ additional transformations to the element (i.e. replacement
+ traversal, cloned traversal, annotations).
+
+ """
+ raise NotImplementedError()
+
+
+_ET = TypeVar("_ET", bound=ExternallyTraversible)
+
+_CE = TypeVar("_CE", bound="ColumnElement[Any]")
+
+_TraverseCallableType = Callable[[_ET], None]
+
+
+class _CloneCallableType(Protocol):
+ def __call__(self, element: _ET, **kw: Any) -> _ET: ...
+
+
+class _TraverseTransformCallableType(Protocol[_ET]):
+ def __call__(self, element: _ET, **kw: Any) -> Optional[_ET]: ...
+
+
+_ExtT = TypeVar("_ExtT", bound="ExternalTraversal")
+
+
+class ExternalTraversal(util.MemoizedSlots):
+ """Base class for visitor objects which can traverse externally using
+ the :func:`.visitors.traverse` function.
+
+ Direct usage of the :func:`.visitors.traverse` function is usually
+ preferred.
+
+ """
+
+ __slots__ = ("_visitor_dict", "_next")
+
+ __traverse_options__: Dict[str, Any] = {}
+ _next: Optional[ExternalTraversal]
+
+ def traverse_single(self, obj: Visitable, **kw: Any) -> Any:
+ for v in self.visitor_iterator:
+ meth = getattr(v, "visit_%s" % obj.__visit_name__, None)
+ if meth:
+ return meth(obj, **kw)
+
+ def iterate(
+ self, obj: Optional[ExternallyTraversible]
+ ) -> Iterator[ExternallyTraversible]:
+ """Traverse the given expression structure, returning an iterator
+ of all elements.
+
+ """
+ return iterate(obj, self.__traverse_options__)
+
+ @overload
+ def traverse(self, obj: Literal[None]) -> None: ...
+
+ @overload
+ def traverse(
+ self, obj: ExternallyTraversible
+ ) -> ExternallyTraversible: ...
+
+ def traverse(
+ self, obj: Optional[ExternallyTraversible]
+ ) -> Optional[ExternallyTraversible]:
+ """Traverse and visit the given expression structure."""
+
+ return traverse(obj, self.__traverse_options__, self._visitor_dict)
+
+ def _memoized_attr__visitor_dict(
+ self,
+ ) -> Dict[str, _TraverseCallableType[Any]]:
+ visitors = {}
+
+ for name in dir(self):
+ if name.startswith("visit_"):
+ visitors[name[6:]] = getattr(self, name)
+ return visitors
+
+ @property
+ def visitor_iterator(self) -> Iterator[ExternalTraversal]:
+ """Iterate through this visitor and each 'chained' visitor."""
+
+ v: Optional[ExternalTraversal] = self
+ while v:
+ yield v
+ v = getattr(v, "_next", None)
+
+ def chain(self: _ExtT, visitor: ExternalTraversal) -> _ExtT:
+ """'Chain' an additional ExternalTraversal onto this ExternalTraversal
+
+ The chained visitor will receive all visit events after this one.
+
+ """
+ tail = list(self.visitor_iterator)[-1]
+ tail._next = visitor
+ return self
+
+
+class CloningExternalTraversal(ExternalTraversal):
+ """Base class for visitor objects which can traverse using
+ the :func:`.visitors.cloned_traverse` function.
+
+ Direct usage of the :func:`.visitors.cloned_traverse` function is usually
+ preferred.
+
+
+ """
+
+ __slots__ = ()
+
+ def copy_and_process(
+ self, list_: List[ExternallyTraversible]
+ ) -> List[ExternallyTraversible]:
+ """Apply cloned traversal to the given list of elements, and return
+ the new list.
+
+ """
+ return [self.traverse(x) for x in list_]
+
+ @overload
+ def traverse(self, obj: Literal[None]) -> None: ...
+
+ @overload
+ def traverse(
+ self, obj: ExternallyTraversible
+ ) -> ExternallyTraversible: ...
+
+ def traverse(
+ self, obj: Optional[ExternallyTraversible]
+ ) -> Optional[ExternallyTraversible]:
+ """Traverse and visit the given expression structure."""
+
+ return cloned_traverse(
+ obj, self.__traverse_options__, self._visitor_dict
+ )
+
+
+class ReplacingExternalTraversal(CloningExternalTraversal):
+ """Base class for visitor objects which can traverse using
+ the :func:`.visitors.replacement_traverse` function.
+
+ Direct usage of the :func:`.visitors.replacement_traverse` function is
+ usually preferred.
+
+ """
+
+ __slots__ = ()
+
+ def replace(
+ self, elem: ExternallyTraversible
+ ) -> Optional[ExternallyTraversible]:
+ """Receive pre-copied elements during a cloning traversal.
+
+ If the method returns a new element, the element is used
+ instead of creating a simple copy of the element. Traversal
+ will halt on the newly returned element if it is re-encountered.
+ """
+ return None
+
+ @overload
+ def traverse(self, obj: Literal[None]) -> None: ...
+
+ @overload
+ def traverse(
+ self, obj: ExternallyTraversible
+ ) -> ExternallyTraversible: ...
+
+ def traverse(
+ self, obj: Optional[ExternallyTraversible]
+ ) -> Optional[ExternallyTraversible]:
+ """Traverse and visit the given expression structure."""
+
+ def replace(
+ element: ExternallyTraversible,
+ **kw: Any,
+ ) -> Optional[ExternallyTraversible]:
+ for v in self.visitor_iterator:
+ e = cast(ReplacingExternalTraversal, v).replace(element)
+ if e is not None:
+ return e
+
+ return None
+
+ return replacement_traverse(obj, self.__traverse_options__, replace)
+
+
+# backwards compatibility
+Traversible = Visitable
+
+ClauseVisitor = ExternalTraversal
+CloningVisitor = CloningExternalTraversal
+ReplacingCloningVisitor = ReplacingExternalTraversal
+
+
+def iterate(
+ obj: Optional[ExternallyTraversible],
+ opts: Mapping[str, Any] = util.EMPTY_DICT,
+) -> Iterator[ExternallyTraversible]:
+ r"""Traverse the given expression structure, returning an iterator.
+
+ Traversal is configured to be breadth-first.
+
+ The central API feature used by the :func:`.visitors.iterate`
+ function is the
+ :meth:`_expression.ClauseElement.get_children` method of
+ :class:`_expression.ClauseElement` objects. This method should return all
+ the :class:`_expression.ClauseElement` objects which are associated with a
+ particular :class:`_expression.ClauseElement` object. For example, a
+ :class:`.Case` structure will refer to a series of
+ :class:`_expression.ColumnElement` objects within its "whens" and "else\_"
+ member variables.
+
+ :param obj: :class:`_expression.ClauseElement` structure to be traversed
+
+ :param opts: dictionary of iteration options. This dictionary is usually
+ empty in modern usage.
+
+ """
+ if obj is None:
+ return
+
+ yield obj
+ children = obj.get_children(**opts)
+
+ if not children:
+ return
+
+ stack = deque([children])
+ while stack:
+ t_iterator = stack.popleft()
+ for t in t_iterator:
+ yield t
+ stack.append(t.get_children(**opts))
+
+
+@overload
+def traverse_using(
+ iterator: Iterable[ExternallyTraversible],
+ obj: Literal[None],
+ visitors: Mapping[str, _TraverseCallableType[Any]],
+) -> None: ...
+
+
+@overload
+def traverse_using(
+ iterator: Iterable[ExternallyTraversible],
+ obj: ExternallyTraversible,
+ visitors: Mapping[str, _TraverseCallableType[Any]],
+) -> ExternallyTraversible: ...
+
+
+def traverse_using(
+ iterator: Iterable[ExternallyTraversible],
+ obj: Optional[ExternallyTraversible],
+ visitors: Mapping[str, _TraverseCallableType[Any]],
+) -> Optional[ExternallyTraversible]:
+ """Visit the given expression structure using the given iterator of
+ objects.
+
+ :func:`.visitors.traverse_using` is usually called internally as the result
+ of the :func:`.visitors.traverse` function.
+
+ :param iterator: an iterable or sequence which will yield
+ :class:`_expression.ClauseElement`
+ structures; the iterator is assumed to be the
+ product of the :func:`.visitors.iterate` function.
+
+ :param obj: the :class:`_expression.ClauseElement`
+ that was used as the target of the
+ :func:`.iterate` function.
+
+ :param visitors: dictionary of visit functions. See :func:`.traverse`
+ for details on this dictionary.
+
+ .. seealso::
+
+ :func:`.traverse`
+
+
+ """
+ for target in iterator:
+ meth = visitors.get(target.__visit_name__, None)
+ if meth:
+ meth(target)
+ return obj
+
+
+@overload
+def traverse(
+ obj: Literal[None],
+ opts: Mapping[str, Any],
+ visitors: Mapping[str, _TraverseCallableType[Any]],
+) -> None: ...
+
+
+@overload
+def traverse(
+ obj: ExternallyTraversible,
+ opts: Mapping[str, Any],
+ visitors: Mapping[str, _TraverseCallableType[Any]],
+) -> ExternallyTraversible: ...
+
+
+def traverse(
+ obj: Optional[ExternallyTraversible],
+ opts: Mapping[str, Any],
+ visitors: Mapping[str, _TraverseCallableType[Any]],
+) -> Optional[ExternallyTraversible]:
+ """Traverse and visit the given expression structure using the default
+ iterator.
+
+ e.g.::
+
+ from sqlalchemy.sql import visitors
+
+ stmt = select(some_table).where(some_table.c.foo == 'bar')
+
+ def visit_bindparam(bind_param):
+ print("found bound value: %s" % bind_param.value)
+
+ visitors.traverse(stmt, {}, {"bindparam": visit_bindparam})
+
+ The iteration of objects uses the :func:`.visitors.iterate` function,
+ which does a breadth-first traversal using a stack.
+
+ :param obj: :class:`_expression.ClauseElement` structure to be traversed
+
+ :param opts: dictionary of iteration options. This dictionary is usually
+ empty in modern usage.
+
+ :param visitors: dictionary of visit functions. The dictionary should
+ have strings as keys, each of which would correspond to the
+ ``__visit_name__`` of a particular kind of SQL expression object, and
+ callable functions as values, each of which represents a visitor function
+ for that kind of object.
+
+ """
+ return traverse_using(iterate(obj, opts), obj, visitors)
+
+
+@overload
+def cloned_traverse(
+ obj: Literal[None],
+ opts: Mapping[str, Any],
+ visitors: Mapping[str, _TraverseCallableType[Any]],
+) -> None: ...
+
+
+# a bit of controversy here, as the clone of the lead element
+# *could* in theory replace with an entirely different kind of element.
+# however this is really not how cloned_traverse is ever used internally
+# at least.
+@overload
+def cloned_traverse(
+ obj: _ET,
+ opts: Mapping[str, Any],
+ visitors: Mapping[str, _TraverseCallableType[Any]],
+) -> _ET: ...
+
+
+def cloned_traverse(
+ obj: Optional[ExternallyTraversible],
+ opts: Mapping[str, Any],
+ visitors: Mapping[str, _TraverseCallableType[Any]],
+) -> Optional[ExternallyTraversible]:
+ """Clone the given expression structure, allowing modifications by
+ visitors for mutable objects.
+
+ Traversal usage is the same as that of :func:`.visitors.traverse`.
+ The visitor functions present in the ``visitors`` dictionary may also
+ modify the internals of the given structure as the traversal proceeds.
+
+ The :func:`.cloned_traverse` function does **not** provide objects that are
+ part of the :class:`.Immutable` interface to the visit methods (this
+ primarily includes :class:`.ColumnClause`, :class:`.Column`,
+ :class:`.TableClause` and :class:`.Table` objects). As this traversal is
+ only intended to allow in-place mutation of objects, :class:`.Immutable`
+ objects are skipped. The :meth:`.Immutable._clone` method is still called
+ on each object to allow for objects to replace themselves with a different
+ object based on a clone of their sub-internals (e.g. a
+ :class:`.ColumnClause` that clones its subquery to return a new
+ :class:`.ColumnClause`).
+
+ .. versionchanged:: 2.0 The :func:`.cloned_traverse` function omits
+ objects that are part of the :class:`.Immutable` interface.
+
+ The central API feature used by the :func:`.visitors.cloned_traverse`
+ and :func:`.visitors.replacement_traverse` functions, in addition to the
+ :meth:`_expression.ClauseElement.get_children`
+ function that is used to achieve
+ the iteration, is the :meth:`_expression.ClauseElement._copy_internals`
+ method.
+ For a :class:`_expression.ClauseElement`
+ structure to support cloning and replacement
+ traversals correctly, it needs to be able to pass a cloning function into
+ its internal members in order to make copies of them.
+
+ .. seealso::
+
+ :func:`.visitors.traverse`
+
+ :func:`.visitors.replacement_traverse`
+
+ """
+
+ cloned: Dict[int, ExternallyTraversible] = {}
+ stop_on = set(opts.get("stop_on", []))
+
+ def deferred_copy_internals(
+ obj: ExternallyTraversible,
+ ) -> ExternallyTraversible:
+ return cloned_traverse(obj, opts, visitors)
+
+ def clone(elem: ExternallyTraversible, **kw: Any) -> ExternallyTraversible:
+ if elem in stop_on:
+ return elem
+ else:
+ if id(elem) not in cloned:
+ if "replace" in kw:
+ newelem = cast(
+ Optional[ExternallyTraversible], kw["replace"](elem)
+ )
+ if newelem is not None:
+ cloned[id(elem)] = newelem
+ return newelem
+
+ # the _clone method for immutable normally returns "self".
+ # however, the method is still allowed to return a
+ # different object altogether; ColumnClause._clone() will
+ # based on options clone the subquery to which it is associated
+ # and return the new corresponding column.
+ cloned[id(elem)] = newelem = elem._clone(clone=clone, **kw)
+ newelem._copy_internals(clone=clone, **kw)
+
+ # however, visit methods which are tasked with in-place
+ # mutation of the object should not get access to the immutable
+ # object.
+ if not elem._is_immutable:
+ meth = visitors.get(newelem.__visit_name__, None)
+ if meth:
+ meth(newelem)
+ return cloned[id(elem)]
+
+ if obj is not None:
+ obj = clone(
+ obj, deferred_copy_internals=deferred_copy_internals, **opts
+ )
+ clone = None # type: ignore[assignment] # remove gc cycles
+ return obj
+
+
+@overload
+def replacement_traverse(
+ obj: Literal[None],
+ opts: Mapping[str, Any],
+ replace: _TraverseTransformCallableType[Any],
+) -> None: ...
+
+
+@overload
+def replacement_traverse(
+ obj: _CE,
+ opts: Mapping[str, Any],
+ replace: _TraverseTransformCallableType[Any],
+) -> _CE: ...
+
+
+@overload
+def replacement_traverse(
+ obj: ExternallyTraversible,
+ opts: Mapping[str, Any],
+ replace: _TraverseTransformCallableType[Any],
+) -> ExternallyTraversible: ...
+
+
+def replacement_traverse(
+ obj: Optional[ExternallyTraversible],
+ opts: Mapping[str, Any],
+ replace: _TraverseTransformCallableType[Any],
+) -> Optional[ExternallyTraversible]:
+ """Clone the given expression structure, allowing element
+ replacement by a given replacement function.
+
+ This function is very similar to the :func:`.visitors.cloned_traverse`
+ function, except instead of being passed a dictionary of visitors, all
+ elements are unconditionally passed into the given replace function.
+ The replace function then has the option to return an entirely new object
+ which will replace the one given. If it returns ``None``, then the object
+ is kept in place.
+
+ The difference in usage between :func:`.visitors.cloned_traverse` and
+ :func:`.visitors.replacement_traverse` is that in the former case, an
+ already-cloned object is passed to the visitor function, and the visitor
+ function can then manipulate the internal state of the object.
+ In the case of the latter, the visitor function should only return an
+ entirely different object, or do nothing.
+
+ The use case for :func:`.visitors.replacement_traverse` is that of
+ replacing a FROM clause inside of a SQL structure with a different one,
+ as is a common use case within the ORM.
+
+ """
+
+ cloned = {}
+ stop_on = {id(x) for x in opts.get("stop_on", [])}
+
+ def deferred_copy_internals(
+ obj: ExternallyTraversible,
+ ) -> ExternallyTraversible:
+ return replacement_traverse(obj, opts, replace)
+
+ def clone(elem: ExternallyTraversible, **kw: Any) -> ExternallyTraversible:
+ if (
+ id(elem) in stop_on
+ or "no_replacement_traverse" in elem._annotations
+ ):
+ return elem
+ else:
+ newelem = replace(elem)
+ if newelem is not None:
+ stop_on.add(id(newelem))
+ return newelem # type: ignore
+ else:
+ # base "already seen" on id(), not hash, so that we don't
+ # replace an Annotated element with its non-annotated one, and
+ # vice versa
+ id_elem = id(elem)
+ if id_elem not in cloned:
+ if "replace" in kw:
+ newelem = kw["replace"](elem)
+ if newelem is not None:
+ cloned[id_elem] = newelem
+ return newelem # type: ignore
+
+ cloned[id_elem] = newelem = elem._clone(**kw)
+ newelem._copy_internals(clone=clone, **kw)
+ return cloned[id_elem] # type: ignore
+
+ if obj is not None:
+ obj = clone(
+ obj, deferred_copy_internals=deferred_copy_internals, **opts
+ )
+ clone = None # type: ignore[assignment] # remove gc cycles
+ return obj