SQLAlchemy 2.0.47__cp313-cp313t-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlalchemy/__init__.py +283 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +184 -0
- sqlalchemy/connectors/asyncio.py +429 -0
- sqlalchemy/connectors/pyodbc.py +250 -0
- sqlalchemy/cyextension/__init__.py +6 -0
- sqlalchemy/cyextension/collections.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/collections.pyx +409 -0
- sqlalchemy/cyextension/immutabledict.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/immutabledict.pxd +8 -0
- sqlalchemy/cyextension/immutabledict.pyx +133 -0
- sqlalchemy/cyextension/processors.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/processors.pyx +68 -0
- sqlalchemy/cyextension/resultproxy.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/resultproxy.pyx +102 -0
- sqlalchemy/cyextension/util.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/util.pyx +90 -0
- sqlalchemy/dialects/__init__.py +62 -0
- sqlalchemy/dialects/_typing.py +30 -0
- sqlalchemy/dialects/mssql/__init__.py +88 -0
- sqlalchemy/dialects/mssql/aioodbc.py +63 -0
- sqlalchemy/dialects/mssql/base.py +4093 -0
- sqlalchemy/dialects/mssql/information_schema.py +285 -0
- sqlalchemy/dialects/mssql/json.py +129 -0
- sqlalchemy/dialects/mssql/provision.py +185 -0
- sqlalchemy/dialects/mssql/pymssql.py +126 -0
- sqlalchemy/dialects/mssql/pyodbc.py +760 -0
- sqlalchemy/dialects/mysql/__init__.py +104 -0
- sqlalchemy/dialects/mysql/aiomysql.py +250 -0
- sqlalchemy/dialects/mysql/asyncmy.py +231 -0
- sqlalchemy/dialects/mysql/base.py +3949 -0
- sqlalchemy/dialects/mysql/cymysql.py +106 -0
- sqlalchemy/dialects/mysql/dml.py +225 -0
- sqlalchemy/dialects/mysql/enumerated.py +282 -0
- sqlalchemy/dialects/mysql/expression.py +146 -0
- sqlalchemy/dialects/mysql/json.py +91 -0
- sqlalchemy/dialects/mysql/mariadb.py +72 -0
- sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
- sqlalchemy/dialects/mysql/mysqldb.py +314 -0
- sqlalchemy/dialects/mysql/provision.py +153 -0
- sqlalchemy/dialects/mysql/pymysql.py +158 -0
- sqlalchemy/dialects/mysql/pyodbc.py +157 -0
- sqlalchemy/dialects/mysql/reflection.py +727 -0
- sqlalchemy/dialects/mysql/reserved_words.py +570 -0
- sqlalchemy/dialects/mysql/types.py +835 -0
- sqlalchemy/dialects/oracle/__init__.py +81 -0
- sqlalchemy/dialects/oracle/base.py +3802 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/oracledb.py +941 -0
- sqlalchemy/dialects/oracle/provision.py +297 -0
- sqlalchemy/dialects/oracle/types.py +316 -0
- sqlalchemy/dialects/oracle/vector.py +365 -0
- sqlalchemy/dialects/postgresql/__init__.py +167 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
- sqlalchemy/dialects/postgresql/array.py +519 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
- sqlalchemy/dialects/postgresql/base.py +5378 -0
- sqlalchemy/dialects/postgresql/dml.py +339 -0
- sqlalchemy/dialects/postgresql/ext.py +540 -0
- sqlalchemy/dialects/postgresql/hstore.py +406 -0
- sqlalchemy/dialects/postgresql/json.py +404 -0
- sqlalchemy/dialects/postgresql/named_types.py +524 -0
- sqlalchemy/dialects/postgresql/operators.py +129 -0
- sqlalchemy/dialects/postgresql/pg8000.py +669 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
- sqlalchemy/dialects/postgresql/provision.py +183 -0
- sqlalchemy/dialects/postgresql/psycopg.py +862 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
- sqlalchemy/dialects/postgresql/ranges.py +1031 -0
- sqlalchemy/dialects/postgresql/types.py +313 -0
- sqlalchemy/dialects/sqlite/__init__.py +57 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
- sqlalchemy/dialects/sqlite/base.py +3056 -0
- sqlalchemy/dialects/sqlite/dml.py +263 -0
- sqlalchemy/dialects/sqlite/json.py +92 -0
- sqlalchemy/dialects/sqlite/provision.py +229 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_py_processors.py +136 -0
- sqlalchemy/engine/_py_row.py +128 -0
- sqlalchemy/engine/_py_util.py +74 -0
- sqlalchemy/engine/base.py +3390 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +893 -0
- sqlalchemy/engine/cursor.py +2298 -0
- sqlalchemy/engine/default.py +2394 -0
- sqlalchemy/engine/events.py +965 -0
- sqlalchemy/engine/interfaces.py +3471 -0
- sqlalchemy/engine/mock.py +134 -0
- sqlalchemy/engine/processors.py +61 -0
- sqlalchemy/engine/reflection.py +2102 -0
- sqlalchemy/engine/result.py +2399 -0
- sqlalchemy/engine/row.py +400 -0
- sqlalchemy/engine/strategies.py +16 -0
- sqlalchemy/engine/url.py +924 -0
- sqlalchemy/engine/util.py +167 -0
- sqlalchemy/event/__init__.py +26 -0
- sqlalchemy/event/api.py +220 -0
- sqlalchemy/event/attr.py +676 -0
- sqlalchemy/event/base.py +472 -0
- sqlalchemy/event/legacy.py +258 -0
- sqlalchemy/event/registry.py +390 -0
- sqlalchemy/events.py +17 -0
- sqlalchemy/exc.py +832 -0
- sqlalchemy/ext/__init__.py +11 -0
- sqlalchemy/ext/associationproxy.py +2027 -0
- sqlalchemy/ext/asyncio/__init__.py +25 -0
- sqlalchemy/ext/asyncio/base.py +281 -0
- sqlalchemy/ext/asyncio/engine.py +1471 -0
- sqlalchemy/ext/asyncio/exc.py +21 -0
- sqlalchemy/ext/asyncio/result.py +965 -0
- sqlalchemy/ext/asyncio/scoping.py +1599 -0
- sqlalchemy/ext/asyncio/session.py +1947 -0
- sqlalchemy/ext/automap.py +1701 -0
- sqlalchemy/ext/baked.py +570 -0
- sqlalchemy/ext/compiler.py +600 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +564 -0
- sqlalchemy/ext/horizontal_shard.py +478 -0
- sqlalchemy/ext/hybrid.py +1535 -0
- sqlalchemy/ext/indexable.py +364 -0
- sqlalchemy/ext/instrumentation.py +450 -0
- sqlalchemy/ext/mutable.py +1085 -0
- sqlalchemy/ext/mypy/__init__.py +6 -0
- sqlalchemy/ext/mypy/apply.py +324 -0
- sqlalchemy/ext/mypy/decl_class.py +515 -0
- sqlalchemy/ext/mypy/infer.py +590 -0
- sqlalchemy/ext/mypy/names.py +335 -0
- sqlalchemy/ext/mypy/plugin.py +303 -0
- sqlalchemy/ext/mypy/util.py +357 -0
- sqlalchemy/ext/orderinglist.py +439 -0
- sqlalchemy/ext/serializer.py +185 -0
- sqlalchemy/future/__init__.py +16 -0
- sqlalchemy/future/engine.py +15 -0
- sqlalchemy/inspection.py +174 -0
- sqlalchemy/log.py +288 -0
- sqlalchemy/orm/__init__.py +171 -0
- sqlalchemy/orm/_orm_constructors.py +2661 -0
- sqlalchemy/orm/_typing.py +179 -0
- sqlalchemy/orm/attributes.py +2845 -0
- sqlalchemy/orm/base.py +971 -0
- sqlalchemy/orm/bulk_persistence.py +2135 -0
- sqlalchemy/orm/clsregistry.py +571 -0
- sqlalchemy/orm/collections.py +1627 -0
- sqlalchemy/orm/context.py +3334 -0
- sqlalchemy/orm/decl_api.py +2004 -0
- sqlalchemy/orm/decl_base.py +2192 -0
- sqlalchemy/orm/dependency.py +1302 -0
- sqlalchemy/orm/descriptor_props.py +1092 -0
- sqlalchemy/orm/dynamic.py +300 -0
- sqlalchemy/orm/evaluator.py +379 -0
- sqlalchemy/orm/events.py +3252 -0
- sqlalchemy/orm/exc.py +237 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +754 -0
- sqlalchemy/orm/interfaces.py +1496 -0
- sqlalchemy/orm/loading.py +1686 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4444 -0
- sqlalchemy/orm/path_registry.py +809 -0
- sqlalchemy/orm/persistence.py +1788 -0
- sqlalchemy/orm/properties.py +935 -0
- sqlalchemy/orm/query.py +3459 -0
- sqlalchemy/orm/relationships.py +3508 -0
- sqlalchemy/orm/scoping.py +2148 -0
- sqlalchemy/orm/session.py +5280 -0
- sqlalchemy/orm/state.py +1168 -0
- sqlalchemy/orm/state_changes.py +196 -0
- sqlalchemy/orm/strategies.py +3470 -0
- sqlalchemy/orm/strategy_options.py +2568 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +796 -0
- sqlalchemy/orm/util.py +2403 -0
- sqlalchemy/orm/writeonly.py +674 -0
- sqlalchemy/pool/__init__.py +44 -0
- sqlalchemy/pool/base.py +1524 -0
- sqlalchemy/pool/events.py +375 -0
- sqlalchemy/pool/impl.py +588 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +69 -0
- sqlalchemy/sql/__init__.py +145 -0
- sqlalchemy/sql/_dml_constructors.py +132 -0
- sqlalchemy/sql/_elements_constructors.py +1872 -0
- sqlalchemy/sql/_orm_types.py +20 -0
- sqlalchemy/sql/_py_util.py +75 -0
- sqlalchemy/sql/_selectable_constructors.py +763 -0
- sqlalchemy/sql/_typing.py +482 -0
- sqlalchemy/sql/annotation.py +587 -0
- sqlalchemy/sql/base.py +2293 -0
- sqlalchemy/sql/cache_key.py +1057 -0
- sqlalchemy/sql/coercions.py +1404 -0
- sqlalchemy/sql/compiler.py +8081 -0
- sqlalchemy/sql/crud.py +1752 -0
- sqlalchemy/sql/ddl.py +1444 -0
- sqlalchemy/sql/default_comparator.py +551 -0
- sqlalchemy/sql/dml.py +1850 -0
- sqlalchemy/sql/elements.py +5589 -0
- sqlalchemy/sql/events.py +458 -0
- sqlalchemy/sql/expression.py +159 -0
- sqlalchemy/sql/functions.py +2158 -0
- sqlalchemy/sql/lambdas.py +1442 -0
- sqlalchemy/sql/naming.py +209 -0
- sqlalchemy/sql/operators.py +2623 -0
- sqlalchemy/sql/roles.py +323 -0
- sqlalchemy/sql/schema.py +6222 -0
- sqlalchemy/sql/selectable.py +7265 -0
- sqlalchemy/sql/sqltypes.py +3930 -0
- sqlalchemy/sql/traversals.py +1024 -0
- sqlalchemy/sql/type_api.py +2368 -0
- sqlalchemy/sql/util.py +1485 -0
- sqlalchemy/sql/visitors.py +1164 -0
- sqlalchemy/testing/__init__.py +96 -0
- sqlalchemy/testing/assertions.py +994 -0
- sqlalchemy/testing/assertsql.py +520 -0
- sqlalchemy/testing/asyncio.py +135 -0
- sqlalchemy/testing/config.py +434 -0
- sqlalchemy/testing/engines.py +483 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +476 -0
- sqlalchemy/testing/fixtures/__init__.py +28 -0
- sqlalchemy/testing/fixtures/base.py +384 -0
- sqlalchemy/testing/fixtures/mypy.py +332 -0
- sqlalchemy/testing/fixtures/orm.py +227 -0
- sqlalchemy/testing/fixtures/sql.py +482 -0
- sqlalchemy/testing/pickleable.py +155 -0
- sqlalchemy/testing/plugin/__init__.py +6 -0
- sqlalchemy/testing/plugin/bootstrap.py +51 -0
- sqlalchemy/testing/plugin/plugin_base.py +828 -0
- sqlalchemy/testing/plugin/pytestplugin.py +892 -0
- sqlalchemy/testing/profiling.py +329 -0
- sqlalchemy/testing/provision.py +603 -0
- sqlalchemy/testing/requirements.py +1945 -0
- sqlalchemy/testing/schema.py +198 -0
- sqlalchemy/testing/suite/__init__.py +19 -0
- sqlalchemy/testing/suite/test_cte.py +237 -0
- sqlalchemy/testing/suite/test_ddl.py +389 -0
- sqlalchemy/testing/suite/test_deprecations.py +153 -0
- sqlalchemy/testing/suite/test_dialect.py +776 -0
- sqlalchemy/testing/suite/test_insert.py +630 -0
- sqlalchemy/testing/suite/test_reflection.py +3557 -0
- sqlalchemy/testing/suite/test_results.py +504 -0
- sqlalchemy/testing/suite/test_rowcount.py +258 -0
- sqlalchemy/testing/suite/test_select.py +2010 -0
- sqlalchemy/testing/suite/test_sequence.py +317 -0
- sqlalchemy/testing/suite/test_types.py +2147 -0
- sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
- sqlalchemy/testing/suite/test_update_delete.py +139 -0
- sqlalchemy/testing/util.py +535 -0
- sqlalchemy/testing/warnings.py +52 -0
- sqlalchemy/types.py +74 -0
- sqlalchemy/util/__init__.py +162 -0
- sqlalchemy/util/_collections.py +712 -0
- sqlalchemy/util/_concurrency_py3k.py +288 -0
- sqlalchemy/util/_has_cy.py +40 -0
- sqlalchemy/util/_py_collections.py +541 -0
- sqlalchemy/util/compat.py +421 -0
- sqlalchemy/util/concurrency.py +110 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2203 -0
- sqlalchemy/util/preloaded.py +150 -0
- sqlalchemy/util/queue.py +322 -0
- sqlalchemy/util/tool_support.py +201 -0
- sqlalchemy/util/topological.py +120 -0
- sqlalchemy/util/typing.py +734 -0
- sqlalchemy-2.0.47.dist-info/METADATA +243 -0
- sqlalchemy-2.0.47.dist-info/RECORD +274 -0
- sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
- sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
- sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,3557 @@
|
|
|
1
|
+
# testing/suite/test_reflection.py
|
|
2
|
+
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of SQLAlchemy and is released under
|
|
6
|
+
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
# mypy: ignore-errors
|
|
8
|
+
|
|
9
|
+
import contextlib
|
|
10
|
+
import operator
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
import sqlalchemy as sa
|
|
14
|
+
from .. import config
|
|
15
|
+
from .. import engines
|
|
16
|
+
from .. import eq_
|
|
17
|
+
from .. import eq_regex
|
|
18
|
+
from .. import expect_raises
|
|
19
|
+
from .. import expect_raises_message
|
|
20
|
+
from .. import expect_warnings
|
|
21
|
+
from .. import fixtures
|
|
22
|
+
from .. import is_
|
|
23
|
+
from ..provision import get_temp_table_name
|
|
24
|
+
from ..provision import temp_table_keyword_args
|
|
25
|
+
from ..schema import Column
|
|
26
|
+
from ..schema import Table
|
|
27
|
+
from ... import Boolean
|
|
28
|
+
from ... import DateTime
|
|
29
|
+
from ... import event
|
|
30
|
+
from ... import ForeignKey
|
|
31
|
+
from ... import func
|
|
32
|
+
from ... import Identity
|
|
33
|
+
from ... import inspect
|
|
34
|
+
from ... import Integer
|
|
35
|
+
from ... import MetaData
|
|
36
|
+
from ... import String
|
|
37
|
+
from ... import testing
|
|
38
|
+
from ... import types as sql_types
|
|
39
|
+
from ...engine import Inspector
|
|
40
|
+
from ...engine import ObjectKind
|
|
41
|
+
from ...engine import ObjectScope
|
|
42
|
+
from ...exc import NoSuchTableError
|
|
43
|
+
from ...exc import UnreflectableTableError
|
|
44
|
+
from ...schema import DDL
|
|
45
|
+
from ...schema import Index
|
|
46
|
+
from ...sql.elements import quoted_name
|
|
47
|
+
from ...sql.schema import BLANK_SCHEMA
|
|
48
|
+
from ...testing import ComparesIndexes
|
|
49
|
+
from ...testing import ComparesTables
|
|
50
|
+
from ...testing import is_false
|
|
51
|
+
from ...testing import is_none
|
|
52
|
+
from ...testing import is_true
|
|
53
|
+
from ...testing import mock
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
metadata, users = None, None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class OneConnectionTablesTest(fixtures.TablesTest):
|
|
60
|
+
@classmethod
|
|
61
|
+
def setup_bind(cls):
|
|
62
|
+
# TODO: when temp tables are subject to server reset,
|
|
63
|
+
# this will also have to disable that server reset from
|
|
64
|
+
# happening
|
|
65
|
+
if config.requirements.independent_connections.enabled:
|
|
66
|
+
from sqlalchemy import pool
|
|
67
|
+
|
|
68
|
+
return engines.testing_engine(
|
|
69
|
+
options=dict(poolclass=pool.StaticPool, scope="class"),
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
return config.db
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class HasTableTest(OneConnectionTablesTest):
|
|
76
|
+
__sparse_driver_backend__ = True
|
|
77
|
+
|
|
78
|
+
run_deletes = None
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def define_tables(cls, metadata):
|
|
82
|
+
Table(
|
|
83
|
+
"test_table",
|
|
84
|
+
metadata,
|
|
85
|
+
Column("id", Integer, primary_key=True),
|
|
86
|
+
Column("data", String(50)),
|
|
87
|
+
)
|
|
88
|
+
if testing.requires.schemas.enabled:
|
|
89
|
+
Table(
|
|
90
|
+
"test_table_s",
|
|
91
|
+
metadata,
|
|
92
|
+
Column("id", Integer, primary_key=True),
|
|
93
|
+
Column("data", String(50)),
|
|
94
|
+
schema=config.test_schema,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if testing.requires.view_reflection:
|
|
98
|
+
cls.define_views(metadata)
|
|
99
|
+
if testing.requires.has_temp_table.enabled:
|
|
100
|
+
cls.define_temp_tables(metadata)
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def define_views(cls, metadata):
|
|
104
|
+
query = "CREATE VIEW vv AS SELECT id, data FROM test_table"
|
|
105
|
+
|
|
106
|
+
event.listen(metadata, "after_create", DDL(query))
|
|
107
|
+
event.listen(metadata, "before_drop", DDL("DROP VIEW vv"))
|
|
108
|
+
|
|
109
|
+
if testing.requires.schemas.enabled:
|
|
110
|
+
query = (
|
|
111
|
+
"CREATE VIEW %s.vv AS SELECT id, data FROM %s.test_table_s"
|
|
112
|
+
% (
|
|
113
|
+
config.test_schema,
|
|
114
|
+
config.test_schema,
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
event.listen(metadata, "after_create", DDL(query))
|
|
118
|
+
event.listen(
|
|
119
|
+
metadata,
|
|
120
|
+
"before_drop",
|
|
121
|
+
DDL("DROP VIEW %s.vv" % (config.test_schema)),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def temp_table_name(cls):
|
|
126
|
+
return get_temp_table_name(
|
|
127
|
+
config, config.db, f"user_tmp_{config.ident}"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def define_temp_tables(cls, metadata):
|
|
132
|
+
kw = temp_table_keyword_args(config, config.db)
|
|
133
|
+
table_name = cls.temp_table_name()
|
|
134
|
+
user_tmp = Table(
|
|
135
|
+
table_name,
|
|
136
|
+
metadata,
|
|
137
|
+
Column("id", sa.INT, primary_key=True),
|
|
138
|
+
Column("name", sa.VARCHAR(50)),
|
|
139
|
+
**kw,
|
|
140
|
+
)
|
|
141
|
+
if (
|
|
142
|
+
testing.requires.view_reflection.enabled
|
|
143
|
+
and testing.requires.temporary_views.enabled
|
|
144
|
+
):
|
|
145
|
+
event.listen(
|
|
146
|
+
user_tmp,
|
|
147
|
+
"after_create",
|
|
148
|
+
DDL(
|
|
149
|
+
"create temporary view user_tmp_v as "
|
|
150
|
+
"select * from user_tmp_%s" % config.ident
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
event.listen(user_tmp, "before_drop", DDL("drop view user_tmp_v"))
|
|
154
|
+
|
|
155
|
+
def test_has_table(self):
|
|
156
|
+
with config.db.begin() as conn:
|
|
157
|
+
is_true(config.db.dialect.has_table(conn, "test_table"))
|
|
158
|
+
is_false(config.db.dialect.has_table(conn, "test_table_s"))
|
|
159
|
+
is_false(config.db.dialect.has_table(conn, "nonexistent_table"))
|
|
160
|
+
|
|
161
|
+
def test_has_table_cache(self, metadata):
|
|
162
|
+
insp = inspect(config.db)
|
|
163
|
+
is_true(insp.has_table("test_table"))
|
|
164
|
+
nt = Table("new_table", metadata, Column("col", Integer))
|
|
165
|
+
is_false(insp.has_table("new_table"))
|
|
166
|
+
nt.create(config.db)
|
|
167
|
+
try:
|
|
168
|
+
is_false(insp.has_table("new_table"))
|
|
169
|
+
insp.clear_cache()
|
|
170
|
+
is_true(insp.has_table("new_table"))
|
|
171
|
+
finally:
|
|
172
|
+
nt.drop(config.db)
|
|
173
|
+
|
|
174
|
+
@testing.requires.schemas
|
|
175
|
+
def test_has_table_schema(self):
|
|
176
|
+
with config.db.begin() as conn:
|
|
177
|
+
is_false(
|
|
178
|
+
config.db.dialect.has_table(
|
|
179
|
+
conn, "test_table", schema=config.test_schema
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
is_true(
|
|
183
|
+
config.db.dialect.has_table(
|
|
184
|
+
conn, "test_table_s", schema=config.test_schema
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
is_false(
|
|
188
|
+
config.db.dialect.has_table(
|
|
189
|
+
conn, "nonexistent_table", schema=config.test_schema
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
@testing.requires.schemas
|
|
194
|
+
def test_has_table_nonexistent_schema(self):
|
|
195
|
+
with config.db.begin() as conn:
|
|
196
|
+
is_false(
|
|
197
|
+
config.db.dialect.has_table(
|
|
198
|
+
conn, "test_table", schema="nonexistent_schema"
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
@testing.requires.views
|
|
203
|
+
def test_has_table_view(self, connection):
|
|
204
|
+
insp = inspect(connection)
|
|
205
|
+
is_true(insp.has_table("vv"))
|
|
206
|
+
|
|
207
|
+
@testing.requires.has_temp_table
|
|
208
|
+
def test_has_table_temp_table(self, connection):
|
|
209
|
+
insp = inspect(connection)
|
|
210
|
+
temp_table_name = self.temp_table_name()
|
|
211
|
+
is_true(insp.has_table(temp_table_name))
|
|
212
|
+
|
|
213
|
+
@testing.requires.has_temp_table
|
|
214
|
+
@testing.requires.view_reflection
|
|
215
|
+
@testing.requires.temporary_views
|
|
216
|
+
def test_has_table_temp_view(self, connection):
|
|
217
|
+
insp = inspect(connection)
|
|
218
|
+
is_true(insp.has_table("user_tmp_v"))
|
|
219
|
+
|
|
220
|
+
@testing.requires.views
|
|
221
|
+
@testing.requires.schemas
|
|
222
|
+
def test_has_table_view_schema(self, connection):
|
|
223
|
+
insp = inspect(connection)
|
|
224
|
+
is_true(insp.has_table("vv", config.test_schema))
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class HasIndexTest(fixtures.TablesTest):
|
|
228
|
+
__sparse_driver_backend__ = True
|
|
229
|
+
__requires__ = ("index_reflection",)
|
|
230
|
+
|
|
231
|
+
@classmethod
|
|
232
|
+
def define_tables(cls, metadata):
|
|
233
|
+
tt = Table(
|
|
234
|
+
"test_table",
|
|
235
|
+
metadata,
|
|
236
|
+
Column("id", Integer, primary_key=True),
|
|
237
|
+
Column("data", String(50)),
|
|
238
|
+
Column("data2", String(50)),
|
|
239
|
+
)
|
|
240
|
+
Index("my_idx", tt.c.data)
|
|
241
|
+
|
|
242
|
+
if testing.requires.schemas.enabled:
|
|
243
|
+
tt = Table(
|
|
244
|
+
"test_table",
|
|
245
|
+
metadata,
|
|
246
|
+
Column("id", Integer, primary_key=True),
|
|
247
|
+
Column("data", String(50)),
|
|
248
|
+
schema=config.test_schema,
|
|
249
|
+
)
|
|
250
|
+
Index("my_idx_s", tt.c.data)
|
|
251
|
+
|
|
252
|
+
kind = testing.combinations("dialect", "inspector", argnames="kind")
|
|
253
|
+
|
|
254
|
+
def _has_index(self, kind, conn):
|
|
255
|
+
if kind == "dialect":
|
|
256
|
+
return lambda *a, **k: config.db.dialect.has_index(conn, *a, **k)
|
|
257
|
+
else:
|
|
258
|
+
return inspect(conn).has_index
|
|
259
|
+
|
|
260
|
+
@kind
|
|
261
|
+
def test_has_index(self, kind, connection, metadata):
|
|
262
|
+
meth = self._has_index(kind, connection)
|
|
263
|
+
assert meth("test_table", "my_idx")
|
|
264
|
+
assert not meth("test_table", "my_idx_s")
|
|
265
|
+
assert not meth("nonexistent_table", "my_idx")
|
|
266
|
+
assert not meth("test_table", "nonexistent_idx")
|
|
267
|
+
|
|
268
|
+
assert not meth("test_table", "my_idx_2")
|
|
269
|
+
assert not meth("test_table_2", "my_idx_3")
|
|
270
|
+
idx = Index("my_idx_2", self.tables.test_table.c.data2)
|
|
271
|
+
tbl = Table(
|
|
272
|
+
"test_table_2",
|
|
273
|
+
metadata,
|
|
274
|
+
Column("foo", Integer),
|
|
275
|
+
Index("my_idx_3", "foo"),
|
|
276
|
+
)
|
|
277
|
+
idx.create(connection)
|
|
278
|
+
tbl.create(connection)
|
|
279
|
+
try:
|
|
280
|
+
if kind == "inspector":
|
|
281
|
+
assert not meth("test_table", "my_idx_2")
|
|
282
|
+
assert not meth("test_table_2", "my_idx_3")
|
|
283
|
+
meth.__self__.clear_cache()
|
|
284
|
+
assert meth("test_table", "my_idx_2") is True
|
|
285
|
+
assert meth("test_table_2", "my_idx_3") is True
|
|
286
|
+
finally:
|
|
287
|
+
tbl.drop(connection)
|
|
288
|
+
idx.drop(connection)
|
|
289
|
+
|
|
290
|
+
@testing.requires.schemas
|
|
291
|
+
@kind
|
|
292
|
+
def test_has_index_schema(self, kind, connection):
|
|
293
|
+
meth = self._has_index(kind, connection)
|
|
294
|
+
assert meth("test_table", "my_idx_s", schema=config.test_schema)
|
|
295
|
+
assert not meth("test_table", "my_idx", schema=config.test_schema)
|
|
296
|
+
assert not meth(
|
|
297
|
+
"nonexistent_table", "my_idx_s", schema=config.test_schema
|
|
298
|
+
)
|
|
299
|
+
assert not meth(
|
|
300
|
+
"test_table", "nonexistent_idx_s", schema=config.test_schema
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class BizarroCharacterTest(fixtures.TestBase):
|
|
305
|
+
|
|
306
|
+
__sparse_driver_backend__ = True
|
|
307
|
+
|
|
308
|
+
def column_names():
|
|
309
|
+
return testing.combinations(
|
|
310
|
+
("plainname",),
|
|
311
|
+
("(3)",),
|
|
312
|
+
("col%p",),
|
|
313
|
+
("[brack]",),
|
|
314
|
+
argnames="columnname",
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
def table_names():
|
|
318
|
+
return testing.combinations(
|
|
319
|
+
("plain",),
|
|
320
|
+
("(2)",),
|
|
321
|
+
("per % cent",),
|
|
322
|
+
("[brackets]",),
|
|
323
|
+
argnames="tablename",
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
@testing.variation("use_composite", [True, False])
|
|
327
|
+
@column_names()
|
|
328
|
+
@table_names()
|
|
329
|
+
@testing.requires.foreign_key_constraint_reflection
|
|
330
|
+
def test_fk_ref(
|
|
331
|
+
self, connection, metadata, use_composite, tablename, columnname
|
|
332
|
+
):
|
|
333
|
+
"""tests for #10275"""
|
|
334
|
+
tt = Table(
|
|
335
|
+
tablename,
|
|
336
|
+
metadata,
|
|
337
|
+
Column(columnname, Integer, key="id", primary_key=True),
|
|
338
|
+
test_needs_fk=True,
|
|
339
|
+
)
|
|
340
|
+
if use_composite:
|
|
341
|
+
tt.append_column(Column("id2", Integer, primary_key=True))
|
|
342
|
+
|
|
343
|
+
if use_composite:
|
|
344
|
+
Table(
|
|
345
|
+
"other",
|
|
346
|
+
metadata,
|
|
347
|
+
Column("id", Integer, primary_key=True),
|
|
348
|
+
Column("ref", Integer),
|
|
349
|
+
Column("ref2", Integer),
|
|
350
|
+
sa.ForeignKeyConstraint(["ref", "ref2"], [tt.c.id, tt.c.id2]),
|
|
351
|
+
test_needs_fk=True,
|
|
352
|
+
)
|
|
353
|
+
else:
|
|
354
|
+
Table(
|
|
355
|
+
"other",
|
|
356
|
+
metadata,
|
|
357
|
+
Column("id", Integer, primary_key=True),
|
|
358
|
+
Column("ref", ForeignKey(tt.c.id)),
|
|
359
|
+
test_needs_fk=True,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
metadata.create_all(connection)
|
|
363
|
+
|
|
364
|
+
m2 = MetaData()
|
|
365
|
+
|
|
366
|
+
o2 = Table("other", m2, autoload_with=connection)
|
|
367
|
+
t1 = m2.tables[tablename]
|
|
368
|
+
|
|
369
|
+
assert o2.c.ref.references(t1.c[0])
|
|
370
|
+
if use_composite:
|
|
371
|
+
assert o2.c.ref2.references(t1.c[1])
|
|
372
|
+
|
|
373
|
+
@column_names()
|
|
374
|
+
@table_names()
|
|
375
|
+
@testing.requires.identity_columns
|
|
376
|
+
def test_reflect_identity(
|
|
377
|
+
self, tablename, columnname, connection, metadata
|
|
378
|
+
):
|
|
379
|
+
Table(
|
|
380
|
+
tablename,
|
|
381
|
+
metadata,
|
|
382
|
+
Column(columnname, Integer, Identity(), primary_key=True),
|
|
383
|
+
)
|
|
384
|
+
metadata.create_all(connection)
|
|
385
|
+
insp = inspect(connection)
|
|
386
|
+
|
|
387
|
+
eq_(insp.get_columns(tablename)[0]["identity"]["start"], 1)
|
|
388
|
+
|
|
389
|
+
@column_names()
|
|
390
|
+
@table_names()
|
|
391
|
+
@testing.requires.comment_reflection
|
|
392
|
+
def test_reflect_comments(
|
|
393
|
+
self, tablename, columnname, connection, metadata
|
|
394
|
+
):
|
|
395
|
+
Table(
|
|
396
|
+
tablename,
|
|
397
|
+
metadata,
|
|
398
|
+
Column("id", Integer, primary_key=True),
|
|
399
|
+
Column(columnname, Integer, comment="some comment"),
|
|
400
|
+
)
|
|
401
|
+
metadata.create_all(connection)
|
|
402
|
+
insp = inspect(connection)
|
|
403
|
+
|
|
404
|
+
eq_(insp.get_columns(tablename)[1]["comment"], "some comment")
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class TempTableElementsTest(fixtures.TestBase):
|
|
408
|
+
|
|
409
|
+
__sparse_driver_backend__ = True
|
|
410
|
+
|
|
411
|
+
__requires__ = ("temp_table_reflection",)
|
|
412
|
+
|
|
413
|
+
@testing.fixture
|
|
414
|
+
def tablename(self):
|
|
415
|
+
return get_temp_table_name(
|
|
416
|
+
config, config.db, f"ident_tmp_{config.ident}"
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
@testing.requires.identity_columns
|
|
420
|
+
def test_reflect_identity(self, tablename, connection, metadata):
|
|
421
|
+
Table(
|
|
422
|
+
tablename,
|
|
423
|
+
metadata,
|
|
424
|
+
Column("id", Integer, Identity(), primary_key=True),
|
|
425
|
+
)
|
|
426
|
+
metadata.create_all(connection)
|
|
427
|
+
insp = inspect(connection)
|
|
428
|
+
|
|
429
|
+
eq_(insp.get_columns(tablename)[0]["identity"]["start"], 1)
|
|
430
|
+
|
|
431
|
+
@testing.requires.temp_table_comment_reflection
|
|
432
|
+
def test_reflect_comments(self, tablename, connection, metadata):
|
|
433
|
+
Table(
|
|
434
|
+
tablename,
|
|
435
|
+
metadata,
|
|
436
|
+
Column("id", Integer, primary_key=True),
|
|
437
|
+
Column("foobar", Integer, comment="some comment"),
|
|
438
|
+
)
|
|
439
|
+
metadata.create_all(connection)
|
|
440
|
+
insp = inspect(connection)
|
|
441
|
+
|
|
442
|
+
eq_(insp.get_columns(tablename)[1]["comment"], "some comment")
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
class QuotedNameArgumentTest(fixtures.TablesTest):
|
|
446
|
+
run_create_tables = "once"
|
|
447
|
+
__sparse_driver_backend__ = True
|
|
448
|
+
|
|
449
|
+
@classmethod
|
|
450
|
+
def define_tables(cls, metadata):
|
|
451
|
+
Table(
|
|
452
|
+
"quote ' one",
|
|
453
|
+
metadata,
|
|
454
|
+
Column("id", Integer),
|
|
455
|
+
Column("name", String(50)),
|
|
456
|
+
Column("data", String(50)),
|
|
457
|
+
Column("related_id", Integer),
|
|
458
|
+
sa.PrimaryKeyConstraint("id", name="pk quote ' one"),
|
|
459
|
+
sa.Index("ix quote ' one", "name"),
|
|
460
|
+
sa.UniqueConstraint(
|
|
461
|
+
"data",
|
|
462
|
+
name="uq quote' one",
|
|
463
|
+
),
|
|
464
|
+
sa.ForeignKeyConstraint(
|
|
465
|
+
["id"], ["related.id"], name="fk quote ' one"
|
|
466
|
+
),
|
|
467
|
+
sa.CheckConstraint("name != 'foo'", name="ck quote ' one"),
|
|
468
|
+
comment=r"""quote ' one comment""",
|
|
469
|
+
test_needs_fk=True,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
if testing.requires.symbol_names_w_double_quote.enabled:
|
|
473
|
+
Table(
|
|
474
|
+
'quote " two',
|
|
475
|
+
metadata,
|
|
476
|
+
Column("id", Integer),
|
|
477
|
+
Column("name", String(50)),
|
|
478
|
+
Column("data", String(50)),
|
|
479
|
+
Column("related_id", Integer),
|
|
480
|
+
sa.PrimaryKeyConstraint("id", name='pk quote " two'),
|
|
481
|
+
sa.Index('ix quote " two', "name"),
|
|
482
|
+
sa.UniqueConstraint(
|
|
483
|
+
"data",
|
|
484
|
+
name='uq quote" two',
|
|
485
|
+
),
|
|
486
|
+
sa.ForeignKeyConstraint(
|
|
487
|
+
["id"], ["related.id"], name='fk quote " two'
|
|
488
|
+
),
|
|
489
|
+
sa.CheckConstraint("name != 'foo'", name='ck quote " two '),
|
|
490
|
+
comment=r"""quote " two comment""",
|
|
491
|
+
test_needs_fk=True,
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
Table(
|
|
495
|
+
"related",
|
|
496
|
+
metadata,
|
|
497
|
+
Column("id", Integer, primary_key=True),
|
|
498
|
+
Column("related", Integer),
|
|
499
|
+
test_needs_fk=True,
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
if testing.requires.view_column_reflection.enabled:
|
|
503
|
+
if testing.requires.symbol_names_w_double_quote.enabled:
|
|
504
|
+
names = [
|
|
505
|
+
"quote ' one",
|
|
506
|
+
'quote " two',
|
|
507
|
+
]
|
|
508
|
+
else:
|
|
509
|
+
names = [
|
|
510
|
+
"quote ' one",
|
|
511
|
+
]
|
|
512
|
+
for name in names:
|
|
513
|
+
query = "CREATE VIEW %s AS SELECT * FROM %s" % (
|
|
514
|
+
config.db.dialect.identifier_preparer.quote(
|
|
515
|
+
"view %s" % name
|
|
516
|
+
),
|
|
517
|
+
config.db.dialect.identifier_preparer.quote(name),
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
event.listen(metadata, "after_create", DDL(query))
|
|
521
|
+
event.listen(
|
|
522
|
+
metadata,
|
|
523
|
+
"before_drop",
|
|
524
|
+
DDL(
|
|
525
|
+
"DROP VIEW %s"
|
|
526
|
+
% config.db.dialect.identifier_preparer.quote(
|
|
527
|
+
"view %s" % name
|
|
528
|
+
)
|
|
529
|
+
),
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
def quote_fixtures(fn):
|
|
533
|
+
return testing.combinations(
|
|
534
|
+
("quote ' one",),
|
|
535
|
+
('quote " two', testing.requires.symbol_names_w_double_quote),
|
|
536
|
+
)(fn)
|
|
537
|
+
|
|
538
|
+
@quote_fixtures
|
|
539
|
+
def test_get_table_options(self, name):
|
|
540
|
+
insp = inspect(config.db)
|
|
541
|
+
|
|
542
|
+
if testing.requires.reflect_table_options.enabled:
|
|
543
|
+
res = insp.get_table_options(name)
|
|
544
|
+
is_true(isinstance(res, dict))
|
|
545
|
+
else:
|
|
546
|
+
with expect_raises(NotImplementedError):
|
|
547
|
+
insp.get_table_options(name)
|
|
548
|
+
|
|
549
|
+
@quote_fixtures
|
|
550
|
+
@testing.requires.view_column_reflection
|
|
551
|
+
def test_get_view_definition(self, name):
|
|
552
|
+
insp = inspect(config.db)
|
|
553
|
+
assert insp.get_view_definition("view %s" % name)
|
|
554
|
+
|
|
555
|
+
@quote_fixtures
|
|
556
|
+
def test_get_columns(self, name):
|
|
557
|
+
insp = inspect(config.db)
|
|
558
|
+
assert insp.get_columns(name)
|
|
559
|
+
|
|
560
|
+
@quote_fixtures
|
|
561
|
+
def test_get_pk_constraint(self, name):
|
|
562
|
+
insp = inspect(config.db)
|
|
563
|
+
assert insp.get_pk_constraint(name)
|
|
564
|
+
|
|
565
|
+
@quote_fixtures
|
|
566
|
+
@testing.requires.foreign_key_constraint_reflection
|
|
567
|
+
def test_get_foreign_keys(self, name):
|
|
568
|
+
insp = inspect(config.db)
|
|
569
|
+
assert insp.get_foreign_keys(name)
|
|
570
|
+
|
|
571
|
+
@quote_fixtures
|
|
572
|
+
@testing.requires.index_reflection
|
|
573
|
+
def test_get_indexes(self, name):
|
|
574
|
+
insp = inspect(config.db)
|
|
575
|
+
assert insp.get_indexes(name)
|
|
576
|
+
|
|
577
|
+
@quote_fixtures
|
|
578
|
+
@testing.requires.unique_constraint_reflection
|
|
579
|
+
def test_get_unique_constraints(self, name):
|
|
580
|
+
insp = inspect(config.db)
|
|
581
|
+
assert insp.get_unique_constraints(name)
|
|
582
|
+
|
|
583
|
+
@quote_fixtures
|
|
584
|
+
@testing.requires.comment_reflection
|
|
585
|
+
def test_get_table_comment(self, name):
|
|
586
|
+
insp = inspect(config.db)
|
|
587
|
+
assert insp.get_table_comment(name)
|
|
588
|
+
|
|
589
|
+
@quote_fixtures
|
|
590
|
+
@testing.requires.check_constraint_reflection
|
|
591
|
+
def test_get_check_constraints(self, name):
|
|
592
|
+
insp = inspect(config.db)
|
|
593
|
+
assert insp.get_check_constraints(name)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def _multi_combination(fn):
|
|
597
|
+
schema = testing.combinations(
|
|
598
|
+
None,
|
|
599
|
+
(
|
|
600
|
+
lambda: config.test_schema,
|
|
601
|
+
testing.requires.schemas,
|
|
602
|
+
),
|
|
603
|
+
argnames="schema",
|
|
604
|
+
)
|
|
605
|
+
scope = testing.combinations(
|
|
606
|
+
ObjectScope.DEFAULT,
|
|
607
|
+
ObjectScope.TEMPORARY,
|
|
608
|
+
ObjectScope.ANY,
|
|
609
|
+
argnames="scope",
|
|
610
|
+
)
|
|
611
|
+
kind = testing.combinations(
|
|
612
|
+
ObjectKind.TABLE,
|
|
613
|
+
ObjectKind.VIEW,
|
|
614
|
+
ObjectKind.MATERIALIZED_VIEW,
|
|
615
|
+
ObjectKind.ANY,
|
|
616
|
+
ObjectKind.ANY_VIEW,
|
|
617
|
+
ObjectKind.TABLE | ObjectKind.VIEW,
|
|
618
|
+
ObjectKind.TABLE | ObjectKind.MATERIALIZED_VIEW,
|
|
619
|
+
argnames="kind",
|
|
620
|
+
)
|
|
621
|
+
filter_names = testing.combinations(True, False, argnames="use_filter")
|
|
622
|
+
|
|
623
|
+
return schema(scope(kind(filter_names(fn))))
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
class ComponentReflectionTest(ComparesTables, OneConnectionTablesTest):
|
|
627
|
+
run_inserts = run_deletes = None
|
|
628
|
+
|
|
629
|
+
__sparse_driver_backend__ = True
|
|
630
|
+
|
|
631
|
+
@classmethod
|
|
632
|
+
def define_tables(cls, metadata):
|
|
633
|
+
cls.define_reflected_tables(metadata, None)
|
|
634
|
+
if testing.requires.schemas.enabled:
|
|
635
|
+
cls.define_reflected_tables(metadata, testing.config.test_schema)
|
|
636
|
+
|
|
637
|
+
@classmethod
|
|
638
|
+
def define_reflected_tables(cls, metadata, schema):
|
|
639
|
+
if schema:
|
|
640
|
+
schema_prefix = schema + "."
|
|
641
|
+
else:
|
|
642
|
+
schema_prefix = ""
|
|
643
|
+
|
|
644
|
+
if testing.requires.self_referential_foreign_keys.enabled:
|
|
645
|
+
parent_id_args = (
|
|
646
|
+
ForeignKey(
|
|
647
|
+
"%susers.user_id" % schema_prefix, name="user_id_fk"
|
|
648
|
+
),
|
|
649
|
+
)
|
|
650
|
+
else:
|
|
651
|
+
parent_id_args = ()
|
|
652
|
+
users = Table(
|
|
653
|
+
"users",
|
|
654
|
+
metadata,
|
|
655
|
+
Column("user_id", sa.INT, primary_key=True),
|
|
656
|
+
Column("test1", sa.CHAR(5), nullable=False),
|
|
657
|
+
Column("test2", sa.Float(), nullable=False),
|
|
658
|
+
Column("parent_user_id", sa.Integer, *parent_id_args),
|
|
659
|
+
sa.CheckConstraint(
|
|
660
|
+
"test2 > 0",
|
|
661
|
+
name="zz_test2_gt_zero",
|
|
662
|
+
comment="users check constraint",
|
|
663
|
+
),
|
|
664
|
+
sa.CheckConstraint("test2 <= 1000"),
|
|
665
|
+
schema=schema,
|
|
666
|
+
test_needs_fk=True,
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
Table(
|
|
670
|
+
"dingalings",
|
|
671
|
+
metadata,
|
|
672
|
+
Column("dingaling_id", sa.Integer, primary_key=True),
|
|
673
|
+
Column(
|
|
674
|
+
"address_id",
|
|
675
|
+
sa.Integer,
|
|
676
|
+
ForeignKey(
|
|
677
|
+
"%semail_addresses.address_id" % schema_prefix,
|
|
678
|
+
name="zz_email_add_id_fg",
|
|
679
|
+
comment="di fk comment",
|
|
680
|
+
),
|
|
681
|
+
),
|
|
682
|
+
Column(
|
|
683
|
+
"id_user",
|
|
684
|
+
sa.Integer,
|
|
685
|
+
ForeignKey("%susers.user_id" % schema_prefix),
|
|
686
|
+
),
|
|
687
|
+
Column("data", sa.String(30), unique=True),
|
|
688
|
+
sa.CheckConstraint(
|
|
689
|
+
"address_id > 0 AND address_id < 1000",
|
|
690
|
+
name="address_id_gt_zero",
|
|
691
|
+
),
|
|
692
|
+
sa.UniqueConstraint(
|
|
693
|
+
"address_id",
|
|
694
|
+
"dingaling_id",
|
|
695
|
+
name="zz_dingalings_multiple",
|
|
696
|
+
comment="di unique comment",
|
|
697
|
+
),
|
|
698
|
+
schema=schema,
|
|
699
|
+
test_needs_fk=True,
|
|
700
|
+
)
|
|
701
|
+
Table(
|
|
702
|
+
"email_addresses",
|
|
703
|
+
metadata,
|
|
704
|
+
Column("address_id", sa.Integer),
|
|
705
|
+
Column("remote_user_id", sa.Integer, ForeignKey(users.c.user_id)),
|
|
706
|
+
Column("email_address", sa.String(20), index=True),
|
|
707
|
+
sa.PrimaryKeyConstraint(
|
|
708
|
+
"address_id", name="email_ad_pk", comment="ea pk comment"
|
|
709
|
+
),
|
|
710
|
+
schema=schema,
|
|
711
|
+
test_needs_fk=True,
|
|
712
|
+
)
|
|
713
|
+
Table(
|
|
714
|
+
"comment_test",
|
|
715
|
+
metadata,
|
|
716
|
+
Column("id", sa.Integer, primary_key=True, comment="id comment"),
|
|
717
|
+
Column("data", sa.String(20), comment="data % comment"),
|
|
718
|
+
Column(
|
|
719
|
+
"d2",
|
|
720
|
+
sa.String(20),
|
|
721
|
+
comment=r"""Comment types type speedily ' " \ '' Fun!""",
|
|
722
|
+
),
|
|
723
|
+
Column("d3", sa.String(42), comment="Comment\nwith\rescapes"),
|
|
724
|
+
schema=schema,
|
|
725
|
+
comment=r"""the test % ' " \ table comment""",
|
|
726
|
+
)
|
|
727
|
+
Table(
|
|
728
|
+
"no_constraints",
|
|
729
|
+
metadata,
|
|
730
|
+
Column("data", sa.String(20)),
|
|
731
|
+
schema=schema,
|
|
732
|
+
comment="no\nconstraints\rhas\fescaped\vcomment",
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
if testing.requires.cross_schema_fk_reflection.enabled:
|
|
736
|
+
if schema is None:
|
|
737
|
+
Table(
|
|
738
|
+
"local_table",
|
|
739
|
+
metadata,
|
|
740
|
+
Column("id", sa.Integer, primary_key=True),
|
|
741
|
+
Column("data", sa.String(20)),
|
|
742
|
+
Column(
|
|
743
|
+
"remote_id",
|
|
744
|
+
ForeignKey(
|
|
745
|
+
"%s.remote_table_2.id" % testing.config.test_schema
|
|
746
|
+
),
|
|
747
|
+
),
|
|
748
|
+
test_needs_fk=True,
|
|
749
|
+
schema=config.db.dialect.default_schema_name,
|
|
750
|
+
)
|
|
751
|
+
else:
|
|
752
|
+
Table(
|
|
753
|
+
"remote_table",
|
|
754
|
+
metadata,
|
|
755
|
+
Column("id", sa.Integer, primary_key=True),
|
|
756
|
+
Column(
|
|
757
|
+
"local_id",
|
|
758
|
+
ForeignKey(
|
|
759
|
+
"%s.local_table.id"
|
|
760
|
+
% config.db.dialect.default_schema_name
|
|
761
|
+
),
|
|
762
|
+
),
|
|
763
|
+
Column("data", sa.String(20)),
|
|
764
|
+
schema=schema,
|
|
765
|
+
test_needs_fk=True,
|
|
766
|
+
)
|
|
767
|
+
Table(
|
|
768
|
+
"remote_table_2",
|
|
769
|
+
metadata,
|
|
770
|
+
Column("id", sa.Integer, primary_key=True),
|
|
771
|
+
Column("data", sa.String(20)),
|
|
772
|
+
schema=schema,
|
|
773
|
+
test_needs_fk=True,
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
if testing.requires.index_reflection.enabled:
|
|
777
|
+
Index("users_t_idx", users.c.test1, users.c.test2, unique=True)
|
|
778
|
+
Index(
|
|
779
|
+
"users_all_idx", users.c.user_id, users.c.test2, users.c.test1
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
if not schema:
|
|
783
|
+
# test_needs_fk is at the moment to force MySQL InnoDB
|
|
784
|
+
noncol_idx_test_nopk = Table(
|
|
785
|
+
"noncol_idx_test_nopk",
|
|
786
|
+
metadata,
|
|
787
|
+
Column("q", sa.String(5)),
|
|
788
|
+
test_needs_fk=True,
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
noncol_idx_test_pk = Table(
|
|
792
|
+
"noncol_idx_test_pk",
|
|
793
|
+
metadata,
|
|
794
|
+
Column("id", sa.Integer, primary_key=True),
|
|
795
|
+
Column("q", sa.String(5)),
|
|
796
|
+
test_needs_fk=True,
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
if (
|
|
800
|
+
testing.requires.indexes_with_ascdesc.enabled
|
|
801
|
+
and testing.requires.reflect_indexes_with_ascdesc.enabled
|
|
802
|
+
):
|
|
803
|
+
Index("noncol_idx_nopk", noncol_idx_test_nopk.c.q.desc())
|
|
804
|
+
Index("noncol_idx_pk", noncol_idx_test_pk.c.q.desc())
|
|
805
|
+
|
|
806
|
+
if testing.requires.view_column_reflection.enabled:
|
|
807
|
+
cls.define_views(metadata, schema)
|
|
808
|
+
if not schema and testing.requires.temp_table_reflection.enabled:
|
|
809
|
+
cls.define_temp_tables(metadata)
|
|
810
|
+
|
|
811
|
+
@classmethod
|
|
812
|
+
def temp_table_name(cls):
|
|
813
|
+
return get_temp_table_name(
|
|
814
|
+
config, config.db, f"user_tmp_{config.ident}"
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
@classmethod
|
|
818
|
+
def define_temp_tables(cls, metadata):
|
|
819
|
+
kw = temp_table_keyword_args(config, config.db)
|
|
820
|
+
table_name = cls.temp_table_name()
|
|
821
|
+
user_tmp = Table(
|
|
822
|
+
table_name,
|
|
823
|
+
metadata,
|
|
824
|
+
Column("id", sa.INT, primary_key=True),
|
|
825
|
+
Column("name", sa.VARCHAR(50)),
|
|
826
|
+
Column("foo", sa.INT),
|
|
827
|
+
# disambiguate temp table unique constraint names. this is
|
|
828
|
+
# pretty arbitrary for a generic dialect however we are doing
|
|
829
|
+
# it to suit SQL Server which will produce name conflicts for
|
|
830
|
+
# unique constraints created against temp tables in different
|
|
831
|
+
# databases.
|
|
832
|
+
# https://www.arbinada.com/en/node/1645
|
|
833
|
+
sa.UniqueConstraint("name", name=f"user_tmp_uq_{config.ident}"),
|
|
834
|
+
sa.Index("user_tmp_ix", "foo"),
|
|
835
|
+
**kw,
|
|
836
|
+
)
|
|
837
|
+
if (
|
|
838
|
+
testing.requires.view_reflection.enabled
|
|
839
|
+
and testing.requires.temporary_views.enabled
|
|
840
|
+
):
|
|
841
|
+
event.listen(
|
|
842
|
+
user_tmp,
|
|
843
|
+
"after_create",
|
|
844
|
+
DDL(
|
|
845
|
+
"create temporary view user_tmp_v as "
|
|
846
|
+
"select * from user_tmp_%s" % config.ident
|
|
847
|
+
),
|
|
848
|
+
)
|
|
849
|
+
event.listen(user_tmp, "before_drop", DDL("drop view user_tmp_v"))
|
|
850
|
+
|
|
851
|
+
@classmethod
|
|
852
|
+
def define_views(cls, metadata, schema):
|
|
853
|
+
if testing.requires.materialized_views.enabled:
|
|
854
|
+
materialized = {"dingalings"}
|
|
855
|
+
else:
|
|
856
|
+
materialized = set()
|
|
857
|
+
for table_name in ("users", "email_addresses", "dingalings"):
|
|
858
|
+
fullname = table_name
|
|
859
|
+
if schema:
|
|
860
|
+
fullname = f"{schema}.{table_name}"
|
|
861
|
+
view_name = fullname + "_v"
|
|
862
|
+
prefix = "MATERIALIZED " if table_name in materialized else ""
|
|
863
|
+
query = (
|
|
864
|
+
f"CREATE {prefix}VIEW {view_name} AS SELECT * FROM {fullname}"
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
event.listen(metadata, "after_create", DDL(query))
|
|
868
|
+
if table_name in materialized:
|
|
869
|
+
index_name = "mat_index"
|
|
870
|
+
if schema and testing.against("oracle"):
|
|
871
|
+
index_name = f"{schema}.{index_name}"
|
|
872
|
+
idx = f"CREATE INDEX {index_name} ON {view_name}(data)"
|
|
873
|
+
event.listen(metadata, "after_create", DDL(idx))
|
|
874
|
+
event.listen(
|
|
875
|
+
metadata, "before_drop", DDL(f"DROP {prefix}VIEW {view_name}")
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
def _resolve_kind(self, kind, tables, views, materialized):
|
|
879
|
+
res = {}
|
|
880
|
+
if ObjectKind.TABLE in kind:
|
|
881
|
+
res.update(tables)
|
|
882
|
+
if ObjectKind.VIEW in kind:
|
|
883
|
+
res.update(views)
|
|
884
|
+
if ObjectKind.MATERIALIZED_VIEW in kind:
|
|
885
|
+
res.update(materialized)
|
|
886
|
+
return res
|
|
887
|
+
|
|
888
|
+
def _resolve_views(self, views, materialized):
|
|
889
|
+
if not testing.requires.view_column_reflection.enabled:
|
|
890
|
+
materialized.clear()
|
|
891
|
+
views.clear()
|
|
892
|
+
elif not testing.requires.materialized_views.enabled:
|
|
893
|
+
views.update(materialized)
|
|
894
|
+
materialized.clear()
|
|
895
|
+
|
|
896
|
+
def _resolve_names(self, schema, scope, filter_names, values):
|
|
897
|
+
scope_filter = lambda _: True # noqa: E731
|
|
898
|
+
if scope is ObjectScope.DEFAULT:
|
|
899
|
+
scope_filter = lambda k: "tmp" not in k[1] # noqa: E731
|
|
900
|
+
if scope is ObjectScope.TEMPORARY:
|
|
901
|
+
scope_filter = lambda k: "tmp" in k[1] # noqa: E731
|
|
902
|
+
|
|
903
|
+
removed = {
|
|
904
|
+
None: {"remote_table", "remote_table_2"},
|
|
905
|
+
testing.config.test_schema: {
|
|
906
|
+
"local_table",
|
|
907
|
+
"noncol_idx_test_nopk",
|
|
908
|
+
"noncol_idx_test_pk",
|
|
909
|
+
"user_tmp_v",
|
|
910
|
+
self.temp_table_name(),
|
|
911
|
+
},
|
|
912
|
+
}
|
|
913
|
+
if not testing.requires.cross_schema_fk_reflection.enabled:
|
|
914
|
+
removed[None].add("local_table")
|
|
915
|
+
removed[testing.config.test_schema].update(
|
|
916
|
+
["remote_table", "remote_table_2"]
|
|
917
|
+
)
|
|
918
|
+
if not testing.requires.index_reflection.enabled:
|
|
919
|
+
removed[None].update(
|
|
920
|
+
["noncol_idx_test_nopk", "noncol_idx_test_pk"]
|
|
921
|
+
)
|
|
922
|
+
if (
|
|
923
|
+
not testing.requires.temp_table_reflection.enabled
|
|
924
|
+
or not testing.requires.temp_table_names.enabled
|
|
925
|
+
):
|
|
926
|
+
removed[None].update(["user_tmp_v", self.temp_table_name()])
|
|
927
|
+
if not testing.requires.temporary_views.enabled:
|
|
928
|
+
removed[None].update(["user_tmp_v"])
|
|
929
|
+
|
|
930
|
+
res = {
|
|
931
|
+
k: v
|
|
932
|
+
for k, v in values.items()
|
|
933
|
+
if scope_filter(k)
|
|
934
|
+
and k[1] not in removed[schema]
|
|
935
|
+
and (not filter_names or k[1] in filter_names)
|
|
936
|
+
}
|
|
937
|
+
return res
|
|
938
|
+
|
|
939
|
+
def exp_options(
|
|
940
|
+
self,
|
|
941
|
+
schema=None,
|
|
942
|
+
scope=ObjectScope.ANY,
|
|
943
|
+
kind=ObjectKind.ANY,
|
|
944
|
+
filter_names=None,
|
|
945
|
+
):
|
|
946
|
+
materialized = {(schema, "dingalings_v"): mock.ANY}
|
|
947
|
+
views = {
|
|
948
|
+
(schema, "email_addresses_v"): mock.ANY,
|
|
949
|
+
(schema, "users_v"): mock.ANY,
|
|
950
|
+
(schema, "user_tmp_v"): mock.ANY,
|
|
951
|
+
}
|
|
952
|
+
self._resolve_views(views, materialized)
|
|
953
|
+
tables = {
|
|
954
|
+
(schema, "users"): mock.ANY,
|
|
955
|
+
(schema, "dingalings"): mock.ANY,
|
|
956
|
+
(schema, "email_addresses"): mock.ANY,
|
|
957
|
+
(schema, "comment_test"): mock.ANY,
|
|
958
|
+
(schema, "no_constraints"): mock.ANY,
|
|
959
|
+
(schema, "local_table"): mock.ANY,
|
|
960
|
+
(schema, "remote_table"): mock.ANY,
|
|
961
|
+
(schema, "remote_table_2"): mock.ANY,
|
|
962
|
+
(schema, "noncol_idx_test_nopk"): mock.ANY,
|
|
963
|
+
(schema, "noncol_idx_test_pk"): mock.ANY,
|
|
964
|
+
(schema, self.temp_table_name()): mock.ANY,
|
|
965
|
+
}
|
|
966
|
+
res = self._resolve_kind(kind, tables, views, materialized)
|
|
967
|
+
res = self._resolve_names(schema, scope, filter_names, res)
|
|
968
|
+
return res
|
|
969
|
+
|
|
970
|
+
def exp_comments(
|
|
971
|
+
self,
|
|
972
|
+
schema=None,
|
|
973
|
+
scope=ObjectScope.ANY,
|
|
974
|
+
kind=ObjectKind.ANY,
|
|
975
|
+
filter_names=None,
|
|
976
|
+
):
|
|
977
|
+
empty = {"text": None}
|
|
978
|
+
materialized = {(schema, "dingalings_v"): empty}
|
|
979
|
+
views = {
|
|
980
|
+
(schema, "email_addresses_v"): empty,
|
|
981
|
+
(schema, "users_v"): empty,
|
|
982
|
+
(schema, "user_tmp_v"): empty,
|
|
983
|
+
}
|
|
984
|
+
self._resolve_views(views, materialized)
|
|
985
|
+
tables = {
|
|
986
|
+
(schema, "users"): empty,
|
|
987
|
+
(schema, "dingalings"): empty,
|
|
988
|
+
(schema, "email_addresses"): empty,
|
|
989
|
+
(schema, "comment_test"): {
|
|
990
|
+
"text": r"""the test % ' " \ table comment"""
|
|
991
|
+
},
|
|
992
|
+
(schema, "no_constraints"): {
|
|
993
|
+
"text": "no\nconstraints\rhas\fescaped\vcomment"
|
|
994
|
+
},
|
|
995
|
+
(schema, "local_table"): empty,
|
|
996
|
+
(schema, "remote_table"): empty,
|
|
997
|
+
(schema, "remote_table_2"): empty,
|
|
998
|
+
(schema, "noncol_idx_test_nopk"): empty,
|
|
999
|
+
(schema, "noncol_idx_test_pk"): empty,
|
|
1000
|
+
(schema, self.temp_table_name()): empty,
|
|
1001
|
+
}
|
|
1002
|
+
res = self._resolve_kind(kind, tables, views, materialized)
|
|
1003
|
+
res = self._resolve_names(schema, scope, filter_names, res)
|
|
1004
|
+
return res
|
|
1005
|
+
|
|
1006
|
+
def exp_columns(
|
|
1007
|
+
self,
|
|
1008
|
+
schema=None,
|
|
1009
|
+
scope=ObjectScope.ANY,
|
|
1010
|
+
kind=ObjectKind.ANY,
|
|
1011
|
+
filter_names=None,
|
|
1012
|
+
):
|
|
1013
|
+
def col(
|
|
1014
|
+
name, auto=False, default=mock.ANY, comment=None, nullable=True
|
|
1015
|
+
):
|
|
1016
|
+
res = {
|
|
1017
|
+
"name": name,
|
|
1018
|
+
"autoincrement": auto,
|
|
1019
|
+
"type": mock.ANY,
|
|
1020
|
+
"default": default,
|
|
1021
|
+
"comment": comment,
|
|
1022
|
+
"nullable": nullable,
|
|
1023
|
+
}
|
|
1024
|
+
if auto == "omit":
|
|
1025
|
+
res.pop("autoincrement")
|
|
1026
|
+
return res
|
|
1027
|
+
|
|
1028
|
+
def pk(name, **kw):
|
|
1029
|
+
kw = {"auto": True, "default": mock.ANY, "nullable": False, **kw}
|
|
1030
|
+
return col(name, **kw)
|
|
1031
|
+
|
|
1032
|
+
materialized = {
|
|
1033
|
+
(schema, "dingalings_v"): [
|
|
1034
|
+
col("dingaling_id", auto="omit", nullable=mock.ANY),
|
|
1035
|
+
col("address_id"),
|
|
1036
|
+
col("id_user"),
|
|
1037
|
+
col("data"),
|
|
1038
|
+
]
|
|
1039
|
+
}
|
|
1040
|
+
views = {
|
|
1041
|
+
(schema, "email_addresses_v"): [
|
|
1042
|
+
col("address_id", auto="omit", nullable=mock.ANY),
|
|
1043
|
+
col("remote_user_id"),
|
|
1044
|
+
col("email_address"),
|
|
1045
|
+
],
|
|
1046
|
+
(schema, "users_v"): [
|
|
1047
|
+
col("user_id", auto="omit", nullable=mock.ANY),
|
|
1048
|
+
col("test1", nullable=mock.ANY),
|
|
1049
|
+
col("test2", nullable=mock.ANY),
|
|
1050
|
+
col("parent_user_id"),
|
|
1051
|
+
],
|
|
1052
|
+
(schema, "user_tmp_v"): [
|
|
1053
|
+
col("id", auto="omit", nullable=mock.ANY),
|
|
1054
|
+
col("name"),
|
|
1055
|
+
col("foo"),
|
|
1056
|
+
],
|
|
1057
|
+
}
|
|
1058
|
+
self._resolve_views(views, materialized)
|
|
1059
|
+
tables = {
|
|
1060
|
+
(schema, "users"): [
|
|
1061
|
+
pk("user_id"),
|
|
1062
|
+
col("test1", nullable=False),
|
|
1063
|
+
col("test2", nullable=False),
|
|
1064
|
+
col("parent_user_id"),
|
|
1065
|
+
],
|
|
1066
|
+
(schema, "dingalings"): [
|
|
1067
|
+
pk("dingaling_id"),
|
|
1068
|
+
col("address_id"),
|
|
1069
|
+
col("id_user"),
|
|
1070
|
+
col("data"),
|
|
1071
|
+
],
|
|
1072
|
+
(schema, "email_addresses"): [
|
|
1073
|
+
pk("address_id"),
|
|
1074
|
+
col("remote_user_id"),
|
|
1075
|
+
col("email_address"),
|
|
1076
|
+
],
|
|
1077
|
+
(schema, "comment_test"): [
|
|
1078
|
+
pk("id", comment="id comment"),
|
|
1079
|
+
col("data", comment="data % comment"),
|
|
1080
|
+
col(
|
|
1081
|
+
"d2",
|
|
1082
|
+
comment=r"""Comment types type speedily ' " \ '' Fun!""",
|
|
1083
|
+
),
|
|
1084
|
+
col("d3", comment="Comment\nwith\rescapes"),
|
|
1085
|
+
],
|
|
1086
|
+
(schema, "no_constraints"): [col("data")],
|
|
1087
|
+
(schema, "local_table"): [pk("id"), col("data"), col("remote_id")],
|
|
1088
|
+
(schema, "remote_table"): [pk("id"), col("local_id"), col("data")],
|
|
1089
|
+
(schema, "remote_table_2"): [pk("id"), col("data")],
|
|
1090
|
+
(schema, "noncol_idx_test_nopk"): [col("q")],
|
|
1091
|
+
(schema, "noncol_idx_test_pk"): [pk("id"), col("q")],
|
|
1092
|
+
(schema, self.temp_table_name()): [
|
|
1093
|
+
pk("id"),
|
|
1094
|
+
col("name"),
|
|
1095
|
+
col("foo"),
|
|
1096
|
+
],
|
|
1097
|
+
}
|
|
1098
|
+
res = self._resolve_kind(kind, tables, views, materialized)
|
|
1099
|
+
res = self._resolve_names(schema, scope, filter_names, res)
|
|
1100
|
+
return res
|
|
1101
|
+
|
|
1102
|
+
@property
|
|
1103
|
+
def _required_column_keys(self):
|
|
1104
|
+
return {"name", "type", "nullable", "default"}
|
|
1105
|
+
|
|
1106
|
+
def exp_pks(
|
|
1107
|
+
self,
|
|
1108
|
+
schema=None,
|
|
1109
|
+
scope=ObjectScope.ANY,
|
|
1110
|
+
kind=ObjectKind.ANY,
|
|
1111
|
+
filter_names=None,
|
|
1112
|
+
):
|
|
1113
|
+
def pk(*cols, name=mock.ANY, comment=None):
|
|
1114
|
+
return {
|
|
1115
|
+
"constrained_columns": list(cols),
|
|
1116
|
+
"name": name,
|
|
1117
|
+
"comment": comment,
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
empty = pk(name=None)
|
|
1121
|
+
if testing.requires.materialized_views_reflect_pk.enabled:
|
|
1122
|
+
materialized = {(schema, "dingalings_v"): pk("dingaling_id")}
|
|
1123
|
+
else:
|
|
1124
|
+
materialized = {(schema, "dingalings_v"): empty}
|
|
1125
|
+
views = {
|
|
1126
|
+
(schema, "email_addresses_v"): empty,
|
|
1127
|
+
(schema, "users_v"): empty,
|
|
1128
|
+
(schema, "user_tmp_v"): empty,
|
|
1129
|
+
}
|
|
1130
|
+
self._resolve_views(views, materialized)
|
|
1131
|
+
tables = {
|
|
1132
|
+
(schema, "users"): pk("user_id"),
|
|
1133
|
+
(schema, "dingalings"): pk("dingaling_id"),
|
|
1134
|
+
(schema, "email_addresses"): pk(
|
|
1135
|
+
"address_id", name="email_ad_pk", comment="ea pk comment"
|
|
1136
|
+
),
|
|
1137
|
+
(schema, "comment_test"): pk("id"),
|
|
1138
|
+
(schema, "no_constraints"): empty,
|
|
1139
|
+
(schema, "local_table"): pk("id"),
|
|
1140
|
+
(schema, "remote_table"): pk("id"),
|
|
1141
|
+
(schema, "remote_table_2"): pk("id"),
|
|
1142
|
+
(schema, "noncol_idx_test_nopk"): empty,
|
|
1143
|
+
(schema, "noncol_idx_test_pk"): pk("id"),
|
|
1144
|
+
(schema, self.temp_table_name()): pk("id"),
|
|
1145
|
+
}
|
|
1146
|
+
if not testing.requires.reflects_pk_names.enabled:
|
|
1147
|
+
for val in tables.values():
|
|
1148
|
+
if val["name"] is not None:
|
|
1149
|
+
val["name"] = mock.ANY
|
|
1150
|
+
res = self._resolve_kind(kind, tables, views, materialized)
|
|
1151
|
+
res = self._resolve_names(schema, scope, filter_names, res)
|
|
1152
|
+
return res
|
|
1153
|
+
|
|
1154
|
+
@property
|
|
1155
|
+
def _required_pk_keys(self):
|
|
1156
|
+
return {"name", "constrained_columns"}
|
|
1157
|
+
|
|
1158
|
+
def exp_fks(
|
|
1159
|
+
self,
|
|
1160
|
+
schema=None,
|
|
1161
|
+
scope=ObjectScope.ANY,
|
|
1162
|
+
kind=ObjectKind.ANY,
|
|
1163
|
+
filter_names=None,
|
|
1164
|
+
):
|
|
1165
|
+
class tt:
|
|
1166
|
+
def __eq__(self, other):
|
|
1167
|
+
return (
|
|
1168
|
+
other is None
|
|
1169
|
+
or config.db.dialect.default_schema_name == other
|
|
1170
|
+
)
|
|
1171
|
+
|
|
1172
|
+
def fk(
|
|
1173
|
+
cols,
|
|
1174
|
+
ref_col,
|
|
1175
|
+
ref_table,
|
|
1176
|
+
ref_schema=schema,
|
|
1177
|
+
name=mock.ANY,
|
|
1178
|
+
comment=None,
|
|
1179
|
+
):
|
|
1180
|
+
return {
|
|
1181
|
+
"constrained_columns": cols,
|
|
1182
|
+
"referred_columns": ref_col,
|
|
1183
|
+
"name": name,
|
|
1184
|
+
"options": mock.ANY,
|
|
1185
|
+
"referred_schema": (
|
|
1186
|
+
ref_schema if ref_schema is not None else tt()
|
|
1187
|
+
),
|
|
1188
|
+
"referred_table": ref_table,
|
|
1189
|
+
"comment": comment,
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
materialized = {(schema, "dingalings_v"): []}
|
|
1193
|
+
views = {
|
|
1194
|
+
(schema, "email_addresses_v"): [],
|
|
1195
|
+
(schema, "users_v"): [],
|
|
1196
|
+
(schema, "user_tmp_v"): [],
|
|
1197
|
+
}
|
|
1198
|
+
self._resolve_views(views, materialized)
|
|
1199
|
+
tables = {
|
|
1200
|
+
(schema, "users"): [
|
|
1201
|
+
fk(["parent_user_id"], ["user_id"], "users", name="user_id_fk")
|
|
1202
|
+
],
|
|
1203
|
+
(schema, "dingalings"): [
|
|
1204
|
+
fk(["id_user"], ["user_id"], "users"),
|
|
1205
|
+
fk(
|
|
1206
|
+
["address_id"],
|
|
1207
|
+
["address_id"],
|
|
1208
|
+
"email_addresses",
|
|
1209
|
+
name="zz_email_add_id_fg",
|
|
1210
|
+
comment="di fk comment",
|
|
1211
|
+
),
|
|
1212
|
+
],
|
|
1213
|
+
(schema, "email_addresses"): [
|
|
1214
|
+
fk(["remote_user_id"], ["user_id"], "users")
|
|
1215
|
+
],
|
|
1216
|
+
(schema, "comment_test"): [],
|
|
1217
|
+
(schema, "no_constraints"): [],
|
|
1218
|
+
(schema, "local_table"): [
|
|
1219
|
+
fk(
|
|
1220
|
+
["remote_id"],
|
|
1221
|
+
["id"],
|
|
1222
|
+
"remote_table_2",
|
|
1223
|
+
ref_schema=config.test_schema,
|
|
1224
|
+
)
|
|
1225
|
+
],
|
|
1226
|
+
(schema, "remote_table"): [
|
|
1227
|
+
fk(["local_id"], ["id"], "local_table", ref_schema=None)
|
|
1228
|
+
],
|
|
1229
|
+
(schema, "remote_table_2"): [],
|
|
1230
|
+
(schema, "noncol_idx_test_nopk"): [],
|
|
1231
|
+
(schema, "noncol_idx_test_pk"): [],
|
|
1232
|
+
(schema, self.temp_table_name()): [],
|
|
1233
|
+
}
|
|
1234
|
+
if not testing.requires.self_referential_foreign_keys.enabled:
|
|
1235
|
+
tables[(schema, "users")].clear()
|
|
1236
|
+
if not testing.requires.named_constraints.enabled:
|
|
1237
|
+
for vals in tables.values():
|
|
1238
|
+
for val in vals:
|
|
1239
|
+
if val["name"] is not mock.ANY:
|
|
1240
|
+
val["name"] = mock.ANY
|
|
1241
|
+
|
|
1242
|
+
res = self._resolve_kind(kind, tables, views, materialized)
|
|
1243
|
+
res = self._resolve_names(schema, scope, filter_names, res)
|
|
1244
|
+
return res
|
|
1245
|
+
|
|
1246
|
+
@property
|
|
1247
|
+
def _required_fk_keys(self):
|
|
1248
|
+
return {
|
|
1249
|
+
"name",
|
|
1250
|
+
"constrained_columns",
|
|
1251
|
+
"referred_schema",
|
|
1252
|
+
"referred_table",
|
|
1253
|
+
"referred_columns",
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
def exp_indexes(
|
|
1257
|
+
self,
|
|
1258
|
+
schema=None,
|
|
1259
|
+
scope=ObjectScope.ANY,
|
|
1260
|
+
kind=ObjectKind.ANY,
|
|
1261
|
+
filter_names=None,
|
|
1262
|
+
):
|
|
1263
|
+
def idx(
|
|
1264
|
+
*cols,
|
|
1265
|
+
name,
|
|
1266
|
+
unique=False,
|
|
1267
|
+
column_sorting=None,
|
|
1268
|
+
duplicates=False,
|
|
1269
|
+
fk=False,
|
|
1270
|
+
):
|
|
1271
|
+
fk_req = testing.requires.foreign_keys_reflect_as_index
|
|
1272
|
+
dup_req = testing.requires.unique_constraints_reflect_as_index
|
|
1273
|
+
sorting_expression = (
|
|
1274
|
+
testing.requires.reflect_indexes_with_ascdesc_as_expression
|
|
1275
|
+
)
|
|
1276
|
+
|
|
1277
|
+
if (fk and not fk_req.enabled) or (
|
|
1278
|
+
duplicates and not dup_req.enabled
|
|
1279
|
+
):
|
|
1280
|
+
return ()
|
|
1281
|
+
res = {
|
|
1282
|
+
"unique": unique,
|
|
1283
|
+
"column_names": list(cols),
|
|
1284
|
+
"name": name,
|
|
1285
|
+
"dialect_options": mock.ANY,
|
|
1286
|
+
"include_columns": [],
|
|
1287
|
+
}
|
|
1288
|
+
if column_sorting:
|
|
1289
|
+
res["column_sorting"] = column_sorting
|
|
1290
|
+
if sorting_expression.enabled:
|
|
1291
|
+
res["expressions"] = orig = res["column_names"]
|
|
1292
|
+
res["column_names"] = [
|
|
1293
|
+
None if c in column_sorting else c for c in orig
|
|
1294
|
+
]
|
|
1295
|
+
|
|
1296
|
+
if duplicates:
|
|
1297
|
+
res["duplicates_constraint"] = name
|
|
1298
|
+
return [res]
|
|
1299
|
+
|
|
1300
|
+
materialized = {(schema, "dingalings_v"): []}
|
|
1301
|
+
views = {
|
|
1302
|
+
(schema, "email_addresses_v"): [],
|
|
1303
|
+
(schema, "users_v"): [],
|
|
1304
|
+
(schema, "user_tmp_v"): [],
|
|
1305
|
+
}
|
|
1306
|
+
self._resolve_views(views, materialized)
|
|
1307
|
+
if materialized:
|
|
1308
|
+
materialized[(schema, "dingalings_v")].extend(
|
|
1309
|
+
idx("data", name="mat_index")
|
|
1310
|
+
)
|
|
1311
|
+
tables = {
|
|
1312
|
+
(schema, "users"): [
|
|
1313
|
+
*idx("parent_user_id", name="user_id_fk", fk=True),
|
|
1314
|
+
*idx("user_id", "test2", "test1", name="users_all_idx"),
|
|
1315
|
+
*idx("test1", "test2", name="users_t_idx", unique=True),
|
|
1316
|
+
],
|
|
1317
|
+
(schema, "dingalings"): [
|
|
1318
|
+
*idx("data", name=mock.ANY, unique=True, duplicates=True),
|
|
1319
|
+
*idx("id_user", name=mock.ANY, fk=True),
|
|
1320
|
+
*idx(
|
|
1321
|
+
"address_id",
|
|
1322
|
+
"dingaling_id",
|
|
1323
|
+
name="zz_dingalings_multiple",
|
|
1324
|
+
unique=True,
|
|
1325
|
+
duplicates=True,
|
|
1326
|
+
),
|
|
1327
|
+
],
|
|
1328
|
+
(schema, "email_addresses"): [
|
|
1329
|
+
*idx("email_address", name=mock.ANY),
|
|
1330
|
+
*idx("remote_user_id", name=mock.ANY, fk=True),
|
|
1331
|
+
],
|
|
1332
|
+
(schema, "comment_test"): [],
|
|
1333
|
+
(schema, "no_constraints"): [],
|
|
1334
|
+
(schema, "local_table"): [
|
|
1335
|
+
*idx("remote_id", name=mock.ANY, fk=True)
|
|
1336
|
+
],
|
|
1337
|
+
(schema, "remote_table"): [
|
|
1338
|
+
*idx("local_id", name=mock.ANY, fk=True)
|
|
1339
|
+
],
|
|
1340
|
+
(schema, "remote_table_2"): [],
|
|
1341
|
+
(schema, "noncol_idx_test_nopk"): [
|
|
1342
|
+
*idx(
|
|
1343
|
+
"q",
|
|
1344
|
+
name="noncol_idx_nopk",
|
|
1345
|
+
column_sorting={"q": ("desc",)},
|
|
1346
|
+
)
|
|
1347
|
+
],
|
|
1348
|
+
(schema, "noncol_idx_test_pk"): [
|
|
1349
|
+
*idx(
|
|
1350
|
+
"q", name="noncol_idx_pk", column_sorting={"q": ("desc",)}
|
|
1351
|
+
)
|
|
1352
|
+
],
|
|
1353
|
+
(schema, self.temp_table_name()): [
|
|
1354
|
+
*idx("foo", name="user_tmp_ix"),
|
|
1355
|
+
*idx(
|
|
1356
|
+
"name",
|
|
1357
|
+
name=f"user_tmp_uq_{config.ident}",
|
|
1358
|
+
duplicates=True,
|
|
1359
|
+
unique=True,
|
|
1360
|
+
),
|
|
1361
|
+
],
|
|
1362
|
+
}
|
|
1363
|
+
if (
|
|
1364
|
+
not testing.requires.indexes_with_ascdesc.enabled
|
|
1365
|
+
or not testing.requires.reflect_indexes_with_ascdesc.enabled
|
|
1366
|
+
):
|
|
1367
|
+
tables[(schema, "noncol_idx_test_nopk")].clear()
|
|
1368
|
+
tables[(schema, "noncol_idx_test_pk")].clear()
|
|
1369
|
+
res = self._resolve_kind(kind, tables, views, materialized)
|
|
1370
|
+
res = self._resolve_names(schema, scope, filter_names, res)
|
|
1371
|
+
return res
|
|
1372
|
+
|
|
1373
|
+
@property
|
|
1374
|
+
def _required_index_keys(self):
|
|
1375
|
+
return {"name", "column_names", "unique"}
|
|
1376
|
+
|
|
1377
|
+
def exp_ucs(
|
|
1378
|
+
self,
|
|
1379
|
+
schema=None,
|
|
1380
|
+
scope=ObjectScope.ANY,
|
|
1381
|
+
kind=ObjectKind.ANY,
|
|
1382
|
+
filter_names=None,
|
|
1383
|
+
all_=False,
|
|
1384
|
+
):
|
|
1385
|
+
def uc(
|
|
1386
|
+
*cols, name, duplicates_index=None, is_index=False, comment=None
|
|
1387
|
+
):
|
|
1388
|
+
req = testing.requires.unique_index_reflect_as_unique_constraints
|
|
1389
|
+
if is_index and not req.enabled:
|
|
1390
|
+
return ()
|
|
1391
|
+
res = {
|
|
1392
|
+
"column_names": list(cols),
|
|
1393
|
+
"name": name,
|
|
1394
|
+
"comment": comment,
|
|
1395
|
+
}
|
|
1396
|
+
if duplicates_index:
|
|
1397
|
+
res["duplicates_index"] = duplicates_index
|
|
1398
|
+
return [res]
|
|
1399
|
+
|
|
1400
|
+
materialized = {(schema, "dingalings_v"): []}
|
|
1401
|
+
views = {
|
|
1402
|
+
(schema, "email_addresses_v"): [],
|
|
1403
|
+
(schema, "users_v"): [],
|
|
1404
|
+
(schema, "user_tmp_v"): [],
|
|
1405
|
+
}
|
|
1406
|
+
self._resolve_views(views, materialized)
|
|
1407
|
+
tables = {
|
|
1408
|
+
(schema, "users"): [
|
|
1409
|
+
*uc(
|
|
1410
|
+
"test1",
|
|
1411
|
+
"test2",
|
|
1412
|
+
name="users_t_idx",
|
|
1413
|
+
duplicates_index="users_t_idx",
|
|
1414
|
+
is_index=True,
|
|
1415
|
+
)
|
|
1416
|
+
],
|
|
1417
|
+
(schema, "dingalings"): [
|
|
1418
|
+
*uc("data", name=mock.ANY, duplicates_index=mock.ANY),
|
|
1419
|
+
*uc(
|
|
1420
|
+
"address_id",
|
|
1421
|
+
"dingaling_id",
|
|
1422
|
+
name="zz_dingalings_multiple",
|
|
1423
|
+
duplicates_index="zz_dingalings_multiple",
|
|
1424
|
+
comment="di unique comment",
|
|
1425
|
+
),
|
|
1426
|
+
],
|
|
1427
|
+
(schema, "email_addresses"): [],
|
|
1428
|
+
(schema, "comment_test"): [],
|
|
1429
|
+
(schema, "no_constraints"): [],
|
|
1430
|
+
(schema, "local_table"): [],
|
|
1431
|
+
(schema, "remote_table"): [],
|
|
1432
|
+
(schema, "remote_table_2"): [],
|
|
1433
|
+
(schema, "noncol_idx_test_nopk"): [],
|
|
1434
|
+
(schema, "noncol_idx_test_pk"): [],
|
|
1435
|
+
(schema, self.temp_table_name()): [
|
|
1436
|
+
*uc("name", name=f"user_tmp_uq_{config.ident}")
|
|
1437
|
+
],
|
|
1438
|
+
}
|
|
1439
|
+
if all_:
|
|
1440
|
+
return {**materialized, **views, **tables}
|
|
1441
|
+
else:
|
|
1442
|
+
res = self._resolve_kind(kind, tables, views, materialized)
|
|
1443
|
+
res = self._resolve_names(schema, scope, filter_names, res)
|
|
1444
|
+
return res
|
|
1445
|
+
|
|
1446
|
+
@property
|
|
1447
|
+
def _required_unique_cst_keys(self):
|
|
1448
|
+
return {"name", "column_names"}
|
|
1449
|
+
|
|
1450
|
+
def exp_ccs(
|
|
1451
|
+
self,
|
|
1452
|
+
schema=None,
|
|
1453
|
+
scope=ObjectScope.ANY,
|
|
1454
|
+
kind=ObjectKind.ANY,
|
|
1455
|
+
filter_names=None,
|
|
1456
|
+
):
|
|
1457
|
+
class tt(str):
|
|
1458
|
+
def __eq__(self, other):
|
|
1459
|
+
res = (
|
|
1460
|
+
other.lower()
|
|
1461
|
+
.replace("(", "")
|
|
1462
|
+
.replace(")", "")
|
|
1463
|
+
.replace("`", "")
|
|
1464
|
+
)
|
|
1465
|
+
return self in res
|
|
1466
|
+
|
|
1467
|
+
def cc(text, name, comment=None):
|
|
1468
|
+
return {"sqltext": tt(text), "name": name, "comment": comment}
|
|
1469
|
+
|
|
1470
|
+
# print({1: "test2 > (0)::double precision"} == {1: tt("test2 > 0")})
|
|
1471
|
+
# assert 0
|
|
1472
|
+
materialized = {(schema, "dingalings_v"): []}
|
|
1473
|
+
views = {
|
|
1474
|
+
(schema, "email_addresses_v"): [],
|
|
1475
|
+
(schema, "users_v"): [],
|
|
1476
|
+
(schema, "user_tmp_v"): [],
|
|
1477
|
+
}
|
|
1478
|
+
self._resolve_views(views, materialized)
|
|
1479
|
+
tables = {
|
|
1480
|
+
(schema, "users"): [
|
|
1481
|
+
cc("test2 <= 1000", mock.ANY),
|
|
1482
|
+
cc(
|
|
1483
|
+
"test2 > 0",
|
|
1484
|
+
"zz_test2_gt_zero",
|
|
1485
|
+
comment="users check constraint",
|
|
1486
|
+
),
|
|
1487
|
+
],
|
|
1488
|
+
(schema, "dingalings"): [
|
|
1489
|
+
cc(
|
|
1490
|
+
"address_id > 0 and address_id < 1000",
|
|
1491
|
+
name="address_id_gt_zero",
|
|
1492
|
+
),
|
|
1493
|
+
],
|
|
1494
|
+
(schema, "email_addresses"): [],
|
|
1495
|
+
(schema, "comment_test"): [],
|
|
1496
|
+
(schema, "no_constraints"): [],
|
|
1497
|
+
(schema, "local_table"): [],
|
|
1498
|
+
(schema, "remote_table"): [],
|
|
1499
|
+
(schema, "remote_table_2"): [],
|
|
1500
|
+
(schema, "noncol_idx_test_nopk"): [],
|
|
1501
|
+
(schema, "noncol_idx_test_pk"): [],
|
|
1502
|
+
(schema, self.temp_table_name()): [],
|
|
1503
|
+
}
|
|
1504
|
+
res = self._resolve_kind(kind, tables, views, materialized)
|
|
1505
|
+
res = self._resolve_names(schema, scope, filter_names, res)
|
|
1506
|
+
return res
|
|
1507
|
+
|
|
1508
|
+
@property
|
|
1509
|
+
def _required_cc_keys(self):
|
|
1510
|
+
return {"name", "sqltext"}
|
|
1511
|
+
|
|
1512
|
+
@testing.requires.schema_reflection
|
|
1513
|
+
def test_get_schema_names(self, connection):
|
|
1514
|
+
insp = inspect(connection)
|
|
1515
|
+
|
|
1516
|
+
is_true(testing.config.test_schema in insp.get_schema_names())
|
|
1517
|
+
|
|
1518
|
+
@testing.requires.schema_reflection
|
|
1519
|
+
def test_has_schema(self, connection):
|
|
1520
|
+
insp = inspect(connection)
|
|
1521
|
+
|
|
1522
|
+
is_true(insp.has_schema(testing.config.test_schema))
|
|
1523
|
+
is_false(insp.has_schema("sa_fake_schema_foo"))
|
|
1524
|
+
|
|
1525
|
+
@testing.requires.schema_reflection
|
|
1526
|
+
def test_get_schema_names_w_translate_map(self, connection):
|
|
1527
|
+
"""test #7300"""
|
|
1528
|
+
|
|
1529
|
+
connection = connection.execution_options(
|
|
1530
|
+
schema_translate_map={
|
|
1531
|
+
"foo": "bar",
|
|
1532
|
+
BLANK_SCHEMA: testing.config.test_schema,
|
|
1533
|
+
}
|
|
1534
|
+
)
|
|
1535
|
+
insp = inspect(connection)
|
|
1536
|
+
|
|
1537
|
+
is_true(testing.config.test_schema in insp.get_schema_names())
|
|
1538
|
+
|
|
1539
|
+
@testing.requires.schema_reflection
|
|
1540
|
+
def test_has_schema_w_translate_map(self, connection):
|
|
1541
|
+
connection = connection.execution_options(
|
|
1542
|
+
schema_translate_map={
|
|
1543
|
+
"foo": "bar",
|
|
1544
|
+
BLANK_SCHEMA: testing.config.test_schema,
|
|
1545
|
+
}
|
|
1546
|
+
)
|
|
1547
|
+
insp = inspect(connection)
|
|
1548
|
+
|
|
1549
|
+
is_true(insp.has_schema(testing.config.test_schema))
|
|
1550
|
+
is_false(insp.has_schema("sa_fake_schema_foo"))
|
|
1551
|
+
|
|
1552
|
+
@testing.requires.schema_reflection
|
|
1553
|
+
@testing.requires.schema_create_delete
|
|
1554
|
+
def test_schema_cache(self, connection):
|
|
1555
|
+
insp = inspect(connection)
|
|
1556
|
+
|
|
1557
|
+
is_false("foo_bar" in insp.get_schema_names())
|
|
1558
|
+
is_false(insp.has_schema("foo_bar"))
|
|
1559
|
+
connection.execute(DDL("CREATE SCHEMA foo_bar"))
|
|
1560
|
+
try:
|
|
1561
|
+
is_false("foo_bar" in insp.get_schema_names())
|
|
1562
|
+
is_false(insp.has_schema("foo_bar"))
|
|
1563
|
+
insp.clear_cache()
|
|
1564
|
+
is_true("foo_bar" in insp.get_schema_names())
|
|
1565
|
+
is_true(insp.has_schema("foo_bar"))
|
|
1566
|
+
finally:
|
|
1567
|
+
connection.execute(DDL("DROP SCHEMA foo_bar"))
|
|
1568
|
+
|
|
1569
|
+
@testing.requires.schema_reflection
|
|
1570
|
+
def test_dialect_initialize(self):
|
|
1571
|
+
engine = engines.testing_engine()
|
|
1572
|
+
inspect(engine)
|
|
1573
|
+
assert hasattr(engine.dialect, "default_schema_name")
|
|
1574
|
+
|
|
1575
|
+
@testing.requires.schema_reflection
|
|
1576
|
+
def test_get_default_schema_name(self, connection):
|
|
1577
|
+
insp = inspect(connection)
|
|
1578
|
+
eq_(insp.default_schema_name, connection.dialect.default_schema_name)
|
|
1579
|
+
|
|
1580
|
+
@testing.combinations(
|
|
1581
|
+
None,
|
|
1582
|
+
("foreign_key", testing.requires.foreign_key_constraint_reflection),
|
|
1583
|
+
argnames="order_by",
|
|
1584
|
+
)
|
|
1585
|
+
@testing.combinations(
|
|
1586
|
+
(True, testing.requires.schemas), False, argnames="use_schema"
|
|
1587
|
+
)
|
|
1588
|
+
def test_get_table_names(self, connection, order_by, use_schema):
|
|
1589
|
+
if use_schema:
|
|
1590
|
+
schema = config.test_schema
|
|
1591
|
+
else:
|
|
1592
|
+
schema = None
|
|
1593
|
+
|
|
1594
|
+
_ignore_tables = {
|
|
1595
|
+
"comment_test",
|
|
1596
|
+
"noncol_idx_test_pk",
|
|
1597
|
+
"noncol_idx_test_nopk",
|
|
1598
|
+
"local_table",
|
|
1599
|
+
"remote_table",
|
|
1600
|
+
"remote_table_2",
|
|
1601
|
+
"no_constraints",
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
insp = inspect(connection)
|
|
1605
|
+
|
|
1606
|
+
if order_by:
|
|
1607
|
+
tables = [
|
|
1608
|
+
rec[0]
|
|
1609
|
+
for rec in insp.get_sorted_table_and_fkc_names(schema)
|
|
1610
|
+
if rec[0]
|
|
1611
|
+
]
|
|
1612
|
+
else:
|
|
1613
|
+
tables = insp.get_table_names(schema)
|
|
1614
|
+
table_names = [t for t in tables if t not in _ignore_tables]
|
|
1615
|
+
|
|
1616
|
+
if order_by == "foreign_key":
|
|
1617
|
+
answer = ["users", "email_addresses", "dingalings"]
|
|
1618
|
+
eq_(table_names, answer)
|
|
1619
|
+
else:
|
|
1620
|
+
answer = ["dingalings", "email_addresses", "users"]
|
|
1621
|
+
eq_(sorted(table_names), answer)
|
|
1622
|
+
|
|
1623
|
+
@testing.combinations(
|
|
1624
|
+
(True, testing.requires.schemas), False, argnames="use_schema"
|
|
1625
|
+
)
|
|
1626
|
+
def test_get_view_names(self, connection, use_schema):
|
|
1627
|
+
insp = inspect(connection)
|
|
1628
|
+
if use_schema:
|
|
1629
|
+
schema = config.test_schema
|
|
1630
|
+
else:
|
|
1631
|
+
schema = None
|
|
1632
|
+
table_names = insp.get_view_names(schema)
|
|
1633
|
+
if testing.requires.materialized_views.enabled:
|
|
1634
|
+
eq_(sorted(table_names), ["email_addresses_v", "users_v"])
|
|
1635
|
+
eq_(insp.get_materialized_view_names(schema), ["dingalings_v"])
|
|
1636
|
+
else:
|
|
1637
|
+
answer = ["dingalings_v", "email_addresses_v", "users_v"]
|
|
1638
|
+
eq_(sorted(table_names), answer)
|
|
1639
|
+
|
|
1640
|
+
@testing.requires.temp_table_names
|
|
1641
|
+
def test_get_temp_table_names(self, connection):
|
|
1642
|
+
insp = inspect(connection)
|
|
1643
|
+
temp_table_names = insp.get_temp_table_names()
|
|
1644
|
+
eq_(sorted(temp_table_names), [f"user_tmp_{config.ident}"])
|
|
1645
|
+
|
|
1646
|
+
@testing.requires.view_reflection
|
|
1647
|
+
@testing.requires.temporary_views
|
|
1648
|
+
def test_get_temp_view_names(self, connection):
|
|
1649
|
+
insp = inspect(connection)
|
|
1650
|
+
temp_table_names = insp.get_temp_view_names()
|
|
1651
|
+
eq_(sorted(temp_table_names), ["user_tmp_v"])
|
|
1652
|
+
|
|
1653
|
+
@testing.requires.comment_reflection
|
|
1654
|
+
def test_get_comments(self, connection):
|
|
1655
|
+
self._test_get_comments(connection)
|
|
1656
|
+
|
|
1657
|
+
@testing.requires.comment_reflection
|
|
1658
|
+
@testing.requires.schemas
|
|
1659
|
+
def test_get_comments_with_schema(self, connection):
|
|
1660
|
+
self._test_get_comments(connection, testing.config.test_schema)
|
|
1661
|
+
|
|
1662
|
+
def _test_get_comments(self, connection, schema=None):
|
|
1663
|
+
insp = inspect(connection)
|
|
1664
|
+
exp = self.exp_comments(schema=schema)
|
|
1665
|
+
eq_(
|
|
1666
|
+
insp.get_table_comment("comment_test", schema=schema),
|
|
1667
|
+
exp[(schema, "comment_test")],
|
|
1668
|
+
)
|
|
1669
|
+
|
|
1670
|
+
eq_(
|
|
1671
|
+
insp.get_table_comment("users", schema=schema),
|
|
1672
|
+
exp[(schema, "users")],
|
|
1673
|
+
)
|
|
1674
|
+
|
|
1675
|
+
eq_(
|
|
1676
|
+
insp.get_table_comment("comment_test", schema=schema),
|
|
1677
|
+
exp[(schema, "comment_test")],
|
|
1678
|
+
)
|
|
1679
|
+
|
|
1680
|
+
no_cst = self.tables.no_constraints.name
|
|
1681
|
+
eq_(
|
|
1682
|
+
insp.get_table_comment(no_cst, schema=schema),
|
|
1683
|
+
exp[(schema, no_cst)],
|
|
1684
|
+
)
|
|
1685
|
+
|
|
1686
|
+
@testing.combinations(
|
|
1687
|
+
(False, False),
|
|
1688
|
+
(False, True, testing.requires.schemas),
|
|
1689
|
+
(True, False, testing.requires.view_reflection),
|
|
1690
|
+
(
|
|
1691
|
+
True,
|
|
1692
|
+
True,
|
|
1693
|
+
testing.requires.schemas + testing.requires.view_reflection,
|
|
1694
|
+
),
|
|
1695
|
+
argnames="use_views,use_schema",
|
|
1696
|
+
)
|
|
1697
|
+
def test_get_columns(self, connection, use_views, use_schema):
|
|
1698
|
+
if use_schema:
|
|
1699
|
+
schema = config.test_schema
|
|
1700
|
+
else:
|
|
1701
|
+
schema = None
|
|
1702
|
+
|
|
1703
|
+
users, addresses = (self.tables.users, self.tables.email_addresses)
|
|
1704
|
+
if use_views:
|
|
1705
|
+
table_names = ["users_v", "email_addresses_v", "dingalings_v"]
|
|
1706
|
+
else:
|
|
1707
|
+
table_names = ["users", "email_addresses"]
|
|
1708
|
+
|
|
1709
|
+
insp = inspect(connection)
|
|
1710
|
+
for table_name, table in zip(table_names, (users, addresses)):
|
|
1711
|
+
schema_name = schema
|
|
1712
|
+
cols = insp.get_columns(table_name, schema=schema_name)
|
|
1713
|
+
is_true(len(cols) > 0, len(cols))
|
|
1714
|
+
|
|
1715
|
+
# should be in order
|
|
1716
|
+
|
|
1717
|
+
for i, col in enumerate(table.columns):
|
|
1718
|
+
eq_(col.name, cols[i]["name"])
|
|
1719
|
+
ctype = cols[i]["type"].__class__
|
|
1720
|
+
ctype_def = col.type
|
|
1721
|
+
if isinstance(ctype_def, sa.types.TypeEngine):
|
|
1722
|
+
ctype_def = ctype_def.__class__
|
|
1723
|
+
|
|
1724
|
+
# Oracle returns Date for DateTime.
|
|
1725
|
+
|
|
1726
|
+
if testing.against("oracle") and ctype_def in (
|
|
1727
|
+
sql_types.Date,
|
|
1728
|
+
sql_types.DateTime,
|
|
1729
|
+
):
|
|
1730
|
+
ctype_def = sql_types.Date
|
|
1731
|
+
|
|
1732
|
+
# assert that the desired type and return type share
|
|
1733
|
+
# a base within one of the generic types.
|
|
1734
|
+
|
|
1735
|
+
is_true(
|
|
1736
|
+
len(
|
|
1737
|
+
set(ctype.__mro__)
|
|
1738
|
+
.intersection(ctype_def.__mro__)
|
|
1739
|
+
.intersection(
|
|
1740
|
+
[
|
|
1741
|
+
sql_types.Integer,
|
|
1742
|
+
sql_types.Numeric,
|
|
1743
|
+
sql_types.DateTime,
|
|
1744
|
+
sql_types.Date,
|
|
1745
|
+
sql_types.Time,
|
|
1746
|
+
sql_types.String,
|
|
1747
|
+
sql_types._Binary,
|
|
1748
|
+
]
|
|
1749
|
+
)
|
|
1750
|
+
)
|
|
1751
|
+
> 0,
|
|
1752
|
+
"%s(%s), %s(%s)"
|
|
1753
|
+
% (col.name, col.type, cols[i]["name"], ctype),
|
|
1754
|
+
)
|
|
1755
|
+
|
|
1756
|
+
if not col.primary_key:
|
|
1757
|
+
assert cols[i]["default"] is None
|
|
1758
|
+
|
|
1759
|
+
# The case of a table with no column
|
|
1760
|
+
# is tested below in TableNoColumnsTest
|
|
1761
|
+
|
|
1762
|
+
@testing.requires.temp_table_reflection
|
|
1763
|
+
def test_reflect_table_temp_table(self, connection):
|
|
1764
|
+
table_name = self.temp_table_name()
|
|
1765
|
+
user_tmp = self.tables[table_name]
|
|
1766
|
+
|
|
1767
|
+
reflected_user_tmp = Table(
|
|
1768
|
+
table_name, MetaData(), autoload_with=connection
|
|
1769
|
+
)
|
|
1770
|
+
self.assert_tables_equal(
|
|
1771
|
+
user_tmp, reflected_user_tmp, strict_constraints=False
|
|
1772
|
+
)
|
|
1773
|
+
|
|
1774
|
+
@testing.requires.temp_table_reflection
|
|
1775
|
+
def test_get_temp_table_columns(self, connection):
|
|
1776
|
+
table_name = self.temp_table_name()
|
|
1777
|
+
user_tmp = self.tables[table_name]
|
|
1778
|
+
insp = inspect(connection)
|
|
1779
|
+
cols = insp.get_columns(table_name)
|
|
1780
|
+
is_true(len(cols) > 0, len(cols))
|
|
1781
|
+
|
|
1782
|
+
for i, col in enumerate(user_tmp.columns):
|
|
1783
|
+
eq_(col.name, cols[i]["name"])
|
|
1784
|
+
|
|
1785
|
+
@testing.requires.temp_table_reflection
|
|
1786
|
+
@testing.requires.view_column_reflection
|
|
1787
|
+
@testing.requires.temporary_views
|
|
1788
|
+
def test_get_temp_view_columns(self, connection):
|
|
1789
|
+
insp = inspect(connection)
|
|
1790
|
+
cols = insp.get_columns("user_tmp_v")
|
|
1791
|
+
eq_([col["name"] for col in cols], ["id", "name", "foo"])
|
|
1792
|
+
|
|
1793
|
+
@testing.combinations(
|
|
1794
|
+
(False,), (True, testing.requires.schemas), argnames="use_schema"
|
|
1795
|
+
)
|
|
1796
|
+
@testing.requires.primary_key_constraint_reflection
|
|
1797
|
+
def test_get_pk_constraint(self, connection, use_schema):
|
|
1798
|
+
if use_schema:
|
|
1799
|
+
schema = testing.config.test_schema
|
|
1800
|
+
else:
|
|
1801
|
+
schema = None
|
|
1802
|
+
|
|
1803
|
+
users, addresses = self.tables.users, self.tables.email_addresses
|
|
1804
|
+
insp = inspect(connection)
|
|
1805
|
+
exp = self.exp_pks(schema=schema)
|
|
1806
|
+
|
|
1807
|
+
users_cons = insp.get_pk_constraint(users.name, schema=schema)
|
|
1808
|
+
self._check_list(
|
|
1809
|
+
[users_cons], [exp[(schema, users.name)]], self._required_pk_keys
|
|
1810
|
+
)
|
|
1811
|
+
|
|
1812
|
+
addr_cons = insp.get_pk_constraint(addresses.name, schema=schema)
|
|
1813
|
+
exp_cols = exp[(schema, addresses.name)]["constrained_columns"]
|
|
1814
|
+
eq_(addr_cons["constrained_columns"], exp_cols)
|
|
1815
|
+
|
|
1816
|
+
with testing.requires.reflects_pk_names.fail_if():
|
|
1817
|
+
eq_(addr_cons["name"], "email_ad_pk")
|
|
1818
|
+
|
|
1819
|
+
no_cst = self.tables.no_constraints.name
|
|
1820
|
+
self._check_list(
|
|
1821
|
+
[insp.get_pk_constraint(no_cst, schema=schema)],
|
|
1822
|
+
[exp[(schema, no_cst)]],
|
|
1823
|
+
self._required_pk_keys,
|
|
1824
|
+
)
|
|
1825
|
+
|
|
1826
|
+
@testing.combinations(
|
|
1827
|
+
"PK_test_table",
|
|
1828
|
+
"pk_test_table",
|
|
1829
|
+
"mixedCasePK",
|
|
1830
|
+
"pk.with.dots",
|
|
1831
|
+
argnames="pk_name",
|
|
1832
|
+
)
|
|
1833
|
+
@testing.requires.primary_key_constraint_reflection
|
|
1834
|
+
@testing.requires.reflects_pk_names
|
|
1835
|
+
def test_get_pk_constraint_quoted_name(
|
|
1836
|
+
self, connection, metadata, pk_name
|
|
1837
|
+
):
|
|
1838
|
+
"""Test that primary key constraint names with various casing are
|
|
1839
|
+
properly reflected."""
|
|
1840
|
+
|
|
1841
|
+
Table(
|
|
1842
|
+
"test_table",
|
|
1843
|
+
metadata,
|
|
1844
|
+
Column("id", Integer),
|
|
1845
|
+
Column("data", String(50)),
|
|
1846
|
+
sa.PrimaryKeyConstraint("id", name=pk_name),
|
|
1847
|
+
)
|
|
1848
|
+
|
|
1849
|
+
metadata.create_all(connection)
|
|
1850
|
+
|
|
1851
|
+
insp = inspect(connection)
|
|
1852
|
+
pk_cons = insp.get_pk_constraint("test_table")
|
|
1853
|
+
|
|
1854
|
+
eq_(pk_cons["name"], pk_name)
|
|
1855
|
+
eq_(pk_cons["constrained_columns"], ["id"])
|
|
1856
|
+
|
|
1857
|
+
@testing.combinations(
|
|
1858
|
+
(False,), (True, testing.requires.schemas), argnames="use_schema"
|
|
1859
|
+
)
|
|
1860
|
+
@testing.requires.foreign_key_constraint_reflection
|
|
1861
|
+
def test_get_foreign_keys(self, connection, use_schema):
|
|
1862
|
+
if use_schema:
|
|
1863
|
+
schema = config.test_schema
|
|
1864
|
+
else:
|
|
1865
|
+
schema = None
|
|
1866
|
+
|
|
1867
|
+
users, addresses = (self.tables.users, self.tables.email_addresses)
|
|
1868
|
+
insp = inspect(connection)
|
|
1869
|
+
expected_schema = schema
|
|
1870
|
+
# users
|
|
1871
|
+
|
|
1872
|
+
if testing.requires.self_referential_foreign_keys.enabled:
|
|
1873
|
+
users_fkeys = insp.get_foreign_keys(users.name, schema=schema)
|
|
1874
|
+
fkey1 = users_fkeys[0]
|
|
1875
|
+
|
|
1876
|
+
with testing.requires.named_constraints.fail_if():
|
|
1877
|
+
eq_(fkey1["name"], "user_id_fk")
|
|
1878
|
+
|
|
1879
|
+
eq_(fkey1["referred_schema"], expected_schema)
|
|
1880
|
+
eq_(fkey1["referred_table"], users.name)
|
|
1881
|
+
eq_(fkey1["referred_columns"], ["user_id"])
|
|
1882
|
+
eq_(fkey1["constrained_columns"], ["parent_user_id"])
|
|
1883
|
+
|
|
1884
|
+
# addresses
|
|
1885
|
+
addr_fkeys = insp.get_foreign_keys(addresses.name, schema=schema)
|
|
1886
|
+
fkey1 = addr_fkeys[0]
|
|
1887
|
+
|
|
1888
|
+
with testing.requires.implicitly_named_constraints.fail_if():
|
|
1889
|
+
is_true(fkey1["name"] is not None)
|
|
1890
|
+
|
|
1891
|
+
eq_(fkey1["referred_schema"], expected_schema)
|
|
1892
|
+
eq_(fkey1["referred_table"], users.name)
|
|
1893
|
+
eq_(fkey1["referred_columns"], ["user_id"])
|
|
1894
|
+
eq_(fkey1["constrained_columns"], ["remote_user_id"])
|
|
1895
|
+
|
|
1896
|
+
no_cst = self.tables.no_constraints.name
|
|
1897
|
+
eq_(insp.get_foreign_keys(no_cst, schema=schema), [])
|
|
1898
|
+
|
|
1899
|
+
@testing.combinations(
|
|
1900
|
+
"FK_users_id",
|
|
1901
|
+
"fk_users_id",
|
|
1902
|
+
"mixedCaseName",
|
|
1903
|
+
"fk.with.dots",
|
|
1904
|
+
argnames="fk_name",
|
|
1905
|
+
)
|
|
1906
|
+
@testing.requires.foreign_key_constraint_reflection
|
|
1907
|
+
def test_get_foreign_keys_quoted_name(self, connection, metadata, fk_name):
|
|
1908
|
+
"""Test that foreign key constraint names with various casing are
|
|
1909
|
+
properly reflected."""
|
|
1910
|
+
|
|
1911
|
+
Table(
|
|
1912
|
+
"users_ref",
|
|
1913
|
+
metadata,
|
|
1914
|
+
Column("user_id", Integer, primary_key=True),
|
|
1915
|
+
test_needs_fk=True,
|
|
1916
|
+
)
|
|
1917
|
+
|
|
1918
|
+
Table(
|
|
1919
|
+
"user_orders",
|
|
1920
|
+
metadata,
|
|
1921
|
+
Column("order_id", Integer, primary_key=True),
|
|
1922
|
+
Column("user_id", Integer),
|
|
1923
|
+
sa.ForeignKeyConstraint(
|
|
1924
|
+
["user_id"],
|
|
1925
|
+
["users_ref.user_id"],
|
|
1926
|
+
name=fk_name,
|
|
1927
|
+
),
|
|
1928
|
+
test_needs_fk=True,
|
|
1929
|
+
)
|
|
1930
|
+
|
|
1931
|
+
metadata.create_all(connection)
|
|
1932
|
+
|
|
1933
|
+
insp = inspect(connection)
|
|
1934
|
+
fkeys = insp.get_foreign_keys("user_orders")
|
|
1935
|
+
|
|
1936
|
+
eq_(len(fkeys), 1)
|
|
1937
|
+
fkey = fkeys[0]
|
|
1938
|
+
|
|
1939
|
+
with testing.requires.named_constraints.fail_if():
|
|
1940
|
+
eq_(fkey["name"], fk_name)
|
|
1941
|
+
|
|
1942
|
+
eq_(fkey["referred_table"], "users_ref")
|
|
1943
|
+
eq_(fkey["referred_columns"], ["user_id"])
|
|
1944
|
+
eq_(fkey["constrained_columns"], ["user_id"])
|
|
1945
|
+
|
|
1946
|
+
@testing.requires.cross_schema_fk_reflection
|
|
1947
|
+
@testing.requires.schemas
|
|
1948
|
+
def test_get_inter_schema_foreign_keys(self, connection):
|
|
1949
|
+
local_table, remote_table, remote_table_2 = self.tables(
|
|
1950
|
+
"%s.local_table" % connection.dialect.default_schema_name,
|
|
1951
|
+
"%s.remote_table" % testing.config.test_schema,
|
|
1952
|
+
"%s.remote_table_2" % testing.config.test_schema,
|
|
1953
|
+
)
|
|
1954
|
+
|
|
1955
|
+
insp = inspect(connection)
|
|
1956
|
+
|
|
1957
|
+
local_fkeys = insp.get_foreign_keys(local_table.name)
|
|
1958
|
+
eq_(len(local_fkeys), 1)
|
|
1959
|
+
|
|
1960
|
+
fkey1 = local_fkeys[0]
|
|
1961
|
+
eq_(fkey1["referred_schema"], testing.config.test_schema)
|
|
1962
|
+
eq_(fkey1["referred_table"], remote_table_2.name)
|
|
1963
|
+
eq_(fkey1["referred_columns"], ["id"])
|
|
1964
|
+
eq_(fkey1["constrained_columns"], ["remote_id"])
|
|
1965
|
+
|
|
1966
|
+
remote_fkeys = insp.get_foreign_keys(
|
|
1967
|
+
remote_table.name, schema=testing.config.test_schema
|
|
1968
|
+
)
|
|
1969
|
+
eq_(len(remote_fkeys), 1)
|
|
1970
|
+
|
|
1971
|
+
fkey2 = remote_fkeys[0]
|
|
1972
|
+
|
|
1973
|
+
is_true(
|
|
1974
|
+
fkey2["referred_schema"]
|
|
1975
|
+
in (
|
|
1976
|
+
None,
|
|
1977
|
+
connection.dialect.default_schema_name,
|
|
1978
|
+
)
|
|
1979
|
+
)
|
|
1980
|
+
eq_(fkey2["referred_table"], local_table.name)
|
|
1981
|
+
eq_(fkey2["referred_columns"], ["id"])
|
|
1982
|
+
eq_(fkey2["constrained_columns"], ["local_id"])
|
|
1983
|
+
|
|
1984
|
+
@testing.combinations(
|
|
1985
|
+
(False,), (True, testing.requires.schemas), argnames="use_schema"
|
|
1986
|
+
)
|
|
1987
|
+
@testing.requires.index_reflection
|
|
1988
|
+
def test_get_indexes(self, connection, use_schema):
|
|
1989
|
+
if use_schema:
|
|
1990
|
+
schema = config.test_schema
|
|
1991
|
+
else:
|
|
1992
|
+
schema = None
|
|
1993
|
+
|
|
1994
|
+
# The database may decide to create indexes for foreign keys, etc.
|
|
1995
|
+
# so there may be more indexes than expected.
|
|
1996
|
+
insp = inspect(connection)
|
|
1997
|
+
indexes = insp.get_indexes("users", schema=schema)
|
|
1998
|
+
exp = self.exp_indexes(schema=schema)
|
|
1999
|
+
self._check_list(
|
|
2000
|
+
indexes, exp[(schema, "users")], self._required_index_keys
|
|
2001
|
+
)
|
|
2002
|
+
|
|
2003
|
+
no_cst = self.tables.no_constraints.name
|
|
2004
|
+
self._check_list(
|
|
2005
|
+
insp.get_indexes(no_cst, schema=schema),
|
|
2006
|
+
exp[(schema, no_cst)],
|
|
2007
|
+
self._required_index_keys,
|
|
2008
|
+
)
|
|
2009
|
+
|
|
2010
|
+
@testing.combinations(
|
|
2011
|
+
("noncol_idx_test_nopk", "noncol_idx_nopk"),
|
|
2012
|
+
("noncol_idx_test_pk", "noncol_idx_pk"),
|
|
2013
|
+
argnames="tname,ixname",
|
|
2014
|
+
)
|
|
2015
|
+
@testing.requires.index_reflection
|
|
2016
|
+
@testing.requires.indexes_with_ascdesc
|
|
2017
|
+
@testing.requires.reflect_indexes_with_ascdesc
|
|
2018
|
+
def test_get_noncol_index(self, connection, tname, ixname):
|
|
2019
|
+
insp = inspect(connection)
|
|
2020
|
+
indexes = insp.get_indexes(tname)
|
|
2021
|
+
# reflecting an index that has "x DESC" in it as the column.
|
|
2022
|
+
# the DB may or may not give us "x", but make sure we get the index
|
|
2023
|
+
# back, it has a name, it's connected to the table.
|
|
2024
|
+
expected_indexes = self.exp_indexes()[(None, tname)]
|
|
2025
|
+
self._check_list(indexes, expected_indexes, self._required_index_keys)
|
|
2026
|
+
|
|
2027
|
+
t = Table(tname, MetaData(), autoload_with=connection)
|
|
2028
|
+
eq_(len(t.indexes), 1)
|
|
2029
|
+
is_(list(t.indexes)[0].table, t)
|
|
2030
|
+
eq_(list(t.indexes)[0].name, ixname)
|
|
2031
|
+
|
|
2032
|
+
@testing.combinations(
|
|
2033
|
+
"IX_test_data",
|
|
2034
|
+
"ix_test_data",
|
|
2035
|
+
"mixedCaseIndex",
|
|
2036
|
+
"ix.with.dots",
|
|
2037
|
+
argnames="idx_name",
|
|
2038
|
+
)
|
|
2039
|
+
@testing.requires.index_reflection
|
|
2040
|
+
def test_get_indexes_quoted_name(self, connection, metadata, idx_name):
|
|
2041
|
+
"""Test that index names with various casing are properly reflected."""
|
|
2042
|
+
|
|
2043
|
+
t = Table(
|
|
2044
|
+
"test_table",
|
|
2045
|
+
metadata,
|
|
2046
|
+
Column("id", Integer, primary_key=True),
|
|
2047
|
+
Column("data", String(50)),
|
|
2048
|
+
)
|
|
2049
|
+
Index(idx_name, t.c.data)
|
|
2050
|
+
|
|
2051
|
+
metadata.create_all(connection)
|
|
2052
|
+
|
|
2053
|
+
insp = inspect(connection)
|
|
2054
|
+
indexes = insp.get_indexes("test_table")
|
|
2055
|
+
|
|
2056
|
+
index_names = [idx["name"] for idx in indexes]
|
|
2057
|
+
assert idx_name in index_names, f"Expected {idx_name} in {index_names}"
|
|
2058
|
+
|
|
2059
|
+
# Find the specific index
|
|
2060
|
+
matching_idx = [idx for idx in indexes if idx["name"] == idx_name]
|
|
2061
|
+
eq_(len(matching_idx), 1)
|
|
2062
|
+
eq_(matching_idx[0]["column_names"], ["data"])
|
|
2063
|
+
|
|
2064
|
+
@testing.requires.temp_table_reflection
|
|
2065
|
+
@testing.requires.unique_constraint_reflection
|
|
2066
|
+
def test_get_temp_table_unique_constraints(self, connection):
|
|
2067
|
+
insp = inspect(connection)
|
|
2068
|
+
name = self.temp_table_name()
|
|
2069
|
+
reflected = insp.get_unique_constraints(name)
|
|
2070
|
+
exp = self.exp_ucs(all_=True)[(None, name)]
|
|
2071
|
+
self._check_list(reflected, exp, self._required_index_keys)
|
|
2072
|
+
|
|
2073
|
+
@testing.requires.temp_table_reflect_indexes
|
|
2074
|
+
def test_get_temp_table_indexes(self, connection):
|
|
2075
|
+
insp = inspect(connection)
|
|
2076
|
+
table_name = self.temp_table_name()
|
|
2077
|
+
indexes = insp.get_indexes(table_name)
|
|
2078
|
+
for ind in indexes:
|
|
2079
|
+
ind.pop("dialect_options", None)
|
|
2080
|
+
expected = [
|
|
2081
|
+
{"unique": False, "column_names": ["foo"], "name": "user_tmp_ix"}
|
|
2082
|
+
]
|
|
2083
|
+
if testing.requires.index_reflects_included_columns.enabled:
|
|
2084
|
+
expected[0]["include_columns"] = []
|
|
2085
|
+
eq_(
|
|
2086
|
+
[idx for idx in indexes if idx["name"] == "user_tmp_ix"],
|
|
2087
|
+
expected,
|
|
2088
|
+
)
|
|
2089
|
+
|
|
2090
|
+
@testing.combinations(
|
|
2091
|
+
(True, testing.requires.schemas), (False,), argnames="use_schema"
|
|
2092
|
+
)
|
|
2093
|
+
@testing.requires.unique_constraint_reflection
|
|
2094
|
+
def test_get_unique_constraints(self, metadata, connection, use_schema):
|
|
2095
|
+
# SQLite dialect needs to parse the names of the constraints
|
|
2096
|
+
# separately from what it gets from PRAGMA index_list(), and
|
|
2097
|
+
# then matches them up. so same set of column_names in two
|
|
2098
|
+
# constraints will confuse it. Perhaps we should no longer
|
|
2099
|
+
# bother with index_list() here since we have the whole
|
|
2100
|
+
# CREATE TABLE?
|
|
2101
|
+
|
|
2102
|
+
if use_schema:
|
|
2103
|
+
schema = config.test_schema
|
|
2104
|
+
else:
|
|
2105
|
+
schema = None
|
|
2106
|
+
uniques = sorted(
|
|
2107
|
+
[
|
|
2108
|
+
{"name": "unique_a", "column_names": ["a"]},
|
|
2109
|
+
{"name": "unique_a_b_c", "column_names": ["a", "b", "c"]},
|
|
2110
|
+
{"name": "unique_c_a_b", "column_names": ["c", "a", "b"]},
|
|
2111
|
+
{"name": "unique_asc_key", "column_names": ["asc", "key"]},
|
|
2112
|
+
{"name": "i.have.dots", "column_names": ["b"]},
|
|
2113
|
+
{"name": "i have spaces", "column_names": ["c"]},
|
|
2114
|
+
],
|
|
2115
|
+
key=operator.itemgetter("name"),
|
|
2116
|
+
)
|
|
2117
|
+
table = Table(
|
|
2118
|
+
"testtbl",
|
|
2119
|
+
metadata,
|
|
2120
|
+
Column("a", sa.String(20)),
|
|
2121
|
+
Column("b", sa.String(30)),
|
|
2122
|
+
Column("c", sa.Integer),
|
|
2123
|
+
# reserved identifiers
|
|
2124
|
+
Column("asc", sa.String(30)),
|
|
2125
|
+
Column("key", sa.String(30)),
|
|
2126
|
+
schema=schema,
|
|
2127
|
+
)
|
|
2128
|
+
for uc in uniques:
|
|
2129
|
+
table.append_constraint(
|
|
2130
|
+
sa.UniqueConstraint(*uc["column_names"], name=uc["name"])
|
|
2131
|
+
)
|
|
2132
|
+
table.create(connection)
|
|
2133
|
+
|
|
2134
|
+
insp = inspect(connection)
|
|
2135
|
+
reflected = sorted(
|
|
2136
|
+
insp.get_unique_constraints("testtbl", schema=schema),
|
|
2137
|
+
key=operator.itemgetter("name"),
|
|
2138
|
+
)
|
|
2139
|
+
|
|
2140
|
+
names_that_duplicate_index = set()
|
|
2141
|
+
|
|
2142
|
+
eq_(len(uniques), len(reflected))
|
|
2143
|
+
|
|
2144
|
+
for orig, refl in zip(uniques, reflected):
|
|
2145
|
+
# Different dialects handle duplicate index and constraints
|
|
2146
|
+
# differently, so ignore this flag
|
|
2147
|
+
dupe = refl.pop("duplicates_index", None)
|
|
2148
|
+
if dupe:
|
|
2149
|
+
names_that_duplicate_index.add(dupe)
|
|
2150
|
+
eq_(refl.pop("comment", None), None)
|
|
2151
|
+
# ignore dialect_options
|
|
2152
|
+
refl.pop("dialect_options", None)
|
|
2153
|
+
eq_(orig, refl)
|
|
2154
|
+
|
|
2155
|
+
reflected_metadata = MetaData()
|
|
2156
|
+
reflected = Table(
|
|
2157
|
+
"testtbl",
|
|
2158
|
+
reflected_metadata,
|
|
2159
|
+
autoload_with=connection,
|
|
2160
|
+
schema=schema,
|
|
2161
|
+
)
|
|
2162
|
+
|
|
2163
|
+
# test "deduplicates for index" logic. MySQL and Oracle
|
|
2164
|
+
# "unique constraints" are actually unique indexes (with possible
|
|
2165
|
+
# exception of a unique that is a dupe of another one in the case
|
|
2166
|
+
# of Oracle). make sure # they aren't duplicated.
|
|
2167
|
+
idx_names = {idx.name for idx in reflected.indexes}
|
|
2168
|
+
uq_names = {
|
|
2169
|
+
uq.name
|
|
2170
|
+
for uq in reflected.constraints
|
|
2171
|
+
if isinstance(uq, sa.UniqueConstraint)
|
|
2172
|
+
}.difference(["unique_c_a_b"])
|
|
2173
|
+
|
|
2174
|
+
assert not idx_names.intersection(uq_names)
|
|
2175
|
+
if names_that_duplicate_index:
|
|
2176
|
+
eq_(names_that_duplicate_index, idx_names)
|
|
2177
|
+
eq_(uq_names, set())
|
|
2178
|
+
|
|
2179
|
+
no_cst = self.tables.no_constraints.name
|
|
2180
|
+
eq_(insp.get_unique_constraints(no_cst, schema=schema), [])
|
|
2181
|
+
|
|
2182
|
+
@testing.combinations(
|
|
2183
|
+
"UQ_email",
|
|
2184
|
+
"uq_email",
|
|
2185
|
+
"mixedCaseUQ",
|
|
2186
|
+
"uq.with.dots",
|
|
2187
|
+
argnames="uq_name",
|
|
2188
|
+
)
|
|
2189
|
+
@testing.requires.unique_constraint_reflection
|
|
2190
|
+
def test_get_unique_constraints_quoted_name(
|
|
2191
|
+
self, connection, metadata, uq_name
|
|
2192
|
+
):
|
|
2193
|
+
"""Test that unique constraint names with various casing are
|
|
2194
|
+
properly reflected."""
|
|
2195
|
+
|
|
2196
|
+
Table(
|
|
2197
|
+
"test_table",
|
|
2198
|
+
metadata,
|
|
2199
|
+
Column("id", Integer, primary_key=True),
|
|
2200
|
+
Column("email", String(50)),
|
|
2201
|
+
sa.UniqueConstraint("email", name=uq_name),
|
|
2202
|
+
)
|
|
2203
|
+
|
|
2204
|
+
metadata.create_all(connection)
|
|
2205
|
+
|
|
2206
|
+
insp = inspect(connection)
|
|
2207
|
+
uq_cons = insp.get_unique_constraints("test_table")
|
|
2208
|
+
|
|
2209
|
+
eq_(len(uq_cons), 1)
|
|
2210
|
+
eq_(uq_cons[0]["name"], uq_name)
|
|
2211
|
+
eq_(uq_cons[0]["column_names"], ["email"])
|
|
2212
|
+
|
|
2213
|
+
@testing.requires.view_reflection
|
|
2214
|
+
@testing.combinations(
|
|
2215
|
+
(False,), (True, testing.requires.schemas), argnames="use_schema"
|
|
2216
|
+
)
|
|
2217
|
+
def test_get_view_definition(self, connection, use_schema):
|
|
2218
|
+
if use_schema:
|
|
2219
|
+
schema = config.test_schema
|
|
2220
|
+
else:
|
|
2221
|
+
schema = None
|
|
2222
|
+
insp = inspect(connection)
|
|
2223
|
+
for view in ["users_v", "email_addresses_v", "dingalings_v"]:
|
|
2224
|
+
v = insp.get_view_definition(view, schema=schema)
|
|
2225
|
+
is_true(bool(v))
|
|
2226
|
+
|
|
2227
|
+
@testing.requires.view_reflection
|
|
2228
|
+
def test_get_view_definition_does_not_exist(self, connection):
|
|
2229
|
+
insp = inspect(connection)
|
|
2230
|
+
with expect_raises(NoSuchTableError):
|
|
2231
|
+
insp.get_view_definition("view_does_not_exist")
|
|
2232
|
+
with expect_raises(NoSuchTableError):
|
|
2233
|
+
insp.get_view_definition("users") # a table
|
|
2234
|
+
|
|
2235
|
+
@testing.requires.table_reflection
|
|
2236
|
+
def test_autoincrement_col(self, connection):
|
|
2237
|
+
"""test that 'autoincrement' is reflected according to sqla's policy.
|
|
2238
|
+
|
|
2239
|
+
Don't mark this test as unsupported for any backend !
|
|
2240
|
+
|
|
2241
|
+
(technically it fails with MySQL InnoDB since "id" comes before "id2")
|
|
2242
|
+
|
|
2243
|
+
A backend is better off not returning "autoincrement" at all,
|
|
2244
|
+
instead of potentially returning "False" for an auto-incrementing
|
|
2245
|
+
primary key column.
|
|
2246
|
+
|
|
2247
|
+
"""
|
|
2248
|
+
|
|
2249
|
+
insp = inspect(connection)
|
|
2250
|
+
|
|
2251
|
+
for tname, cname in [
|
|
2252
|
+
("users", "user_id"),
|
|
2253
|
+
("email_addresses", "address_id"),
|
|
2254
|
+
("dingalings", "dingaling_id"),
|
|
2255
|
+
]:
|
|
2256
|
+
cols = insp.get_columns(tname)
|
|
2257
|
+
id_ = {c["name"]: c for c in cols}[cname]
|
|
2258
|
+
assert id_.get("autoincrement", True)
|
|
2259
|
+
|
|
2260
|
+
@testing.combinations(
|
|
2261
|
+
(True, testing.requires.schemas), (False,), argnames="use_schema"
|
|
2262
|
+
)
|
|
2263
|
+
def test_get_table_options(self, use_schema):
|
|
2264
|
+
insp = inspect(config.db)
|
|
2265
|
+
schema = config.test_schema if use_schema else None
|
|
2266
|
+
|
|
2267
|
+
if testing.requires.reflect_table_options.enabled:
|
|
2268
|
+
res = insp.get_table_options("users", schema=schema)
|
|
2269
|
+
is_true(isinstance(res, dict))
|
|
2270
|
+
# NOTE: can't really create a table with no option
|
|
2271
|
+
res = insp.get_table_options("no_constraints", schema=schema)
|
|
2272
|
+
is_true(isinstance(res, dict))
|
|
2273
|
+
else:
|
|
2274
|
+
with expect_raises(NotImplementedError):
|
|
2275
|
+
insp.get_table_options("users", schema=schema)
|
|
2276
|
+
|
|
2277
|
+
@testing.combinations((True, testing.requires.schemas), False)
|
|
2278
|
+
def test_multi_get_table_options(self, use_schema):
|
|
2279
|
+
insp = inspect(config.db)
|
|
2280
|
+
if testing.requires.reflect_table_options.enabled:
|
|
2281
|
+
schema = config.test_schema if use_schema else None
|
|
2282
|
+
res = insp.get_multi_table_options(schema=schema)
|
|
2283
|
+
|
|
2284
|
+
exp = {
|
|
2285
|
+
(schema, table): insp.get_table_options(table, schema=schema)
|
|
2286
|
+
for table in insp.get_table_names(schema=schema)
|
|
2287
|
+
}
|
|
2288
|
+
eq_(res, exp)
|
|
2289
|
+
else:
|
|
2290
|
+
with expect_raises(NotImplementedError):
|
|
2291
|
+
insp.get_multi_table_options()
|
|
2292
|
+
|
|
2293
|
+
@testing.fixture
|
|
2294
|
+
def get_multi_exp(self, connection):
|
|
2295
|
+
def provide_fixture(
|
|
2296
|
+
schema, scope, kind, use_filter, single_reflect_fn, exp_method
|
|
2297
|
+
):
|
|
2298
|
+
insp = inspect(connection)
|
|
2299
|
+
# call the reflection function at least once to avoid
|
|
2300
|
+
# "Unexpected success" errors if the result is actually empty
|
|
2301
|
+
# and NotImplementedError is not raised
|
|
2302
|
+
single_reflect_fn(insp, "email_addresses")
|
|
2303
|
+
kw = {"scope": scope, "kind": kind}
|
|
2304
|
+
if schema:
|
|
2305
|
+
schema = schema()
|
|
2306
|
+
|
|
2307
|
+
filter_names = []
|
|
2308
|
+
|
|
2309
|
+
if ObjectKind.TABLE in kind:
|
|
2310
|
+
filter_names.extend(
|
|
2311
|
+
["comment_test", "users", "does-not-exist"]
|
|
2312
|
+
)
|
|
2313
|
+
if ObjectKind.VIEW in kind:
|
|
2314
|
+
filter_names.extend(["email_addresses_v", "does-not-exist"])
|
|
2315
|
+
if ObjectKind.MATERIALIZED_VIEW in kind:
|
|
2316
|
+
filter_names.extend(["dingalings_v", "does-not-exist"])
|
|
2317
|
+
|
|
2318
|
+
if schema:
|
|
2319
|
+
kw["schema"] = schema
|
|
2320
|
+
if use_filter:
|
|
2321
|
+
kw["filter_names"] = filter_names
|
|
2322
|
+
|
|
2323
|
+
exp = exp_method(
|
|
2324
|
+
schema=schema,
|
|
2325
|
+
scope=scope,
|
|
2326
|
+
kind=kind,
|
|
2327
|
+
filter_names=kw.get("filter_names"),
|
|
2328
|
+
)
|
|
2329
|
+
kws = [kw]
|
|
2330
|
+
if scope == ObjectScope.DEFAULT:
|
|
2331
|
+
nkw = kw.copy()
|
|
2332
|
+
nkw.pop("scope")
|
|
2333
|
+
kws.append(nkw)
|
|
2334
|
+
if kind == ObjectKind.TABLE:
|
|
2335
|
+
nkw = kw.copy()
|
|
2336
|
+
nkw.pop("kind")
|
|
2337
|
+
kws.append(nkw)
|
|
2338
|
+
|
|
2339
|
+
return inspect(connection), kws, exp
|
|
2340
|
+
|
|
2341
|
+
return provide_fixture
|
|
2342
|
+
|
|
2343
|
+
@testing.requires.reflect_table_options
|
|
2344
|
+
@_multi_combination
|
|
2345
|
+
def test_multi_get_table_options_tables(
|
|
2346
|
+
self, get_multi_exp, schema, scope, kind, use_filter
|
|
2347
|
+
):
|
|
2348
|
+
insp, kws, exp = get_multi_exp(
|
|
2349
|
+
schema,
|
|
2350
|
+
scope,
|
|
2351
|
+
kind,
|
|
2352
|
+
use_filter,
|
|
2353
|
+
Inspector.get_table_options,
|
|
2354
|
+
self.exp_options,
|
|
2355
|
+
)
|
|
2356
|
+
for kw in kws:
|
|
2357
|
+
insp.clear_cache()
|
|
2358
|
+
result = insp.get_multi_table_options(**kw)
|
|
2359
|
+
eq_(result, exp)
|
|
2360
|
+
|
|
2361
|
+
@testing.requires.comment_reflection
|
|
2362
|
+
@_multi_combination
|
|
2363
|
+
def test_get_multi_table_comment(
|
|
2364
|
+
self, get_multi_exp, schema, scope, kind, use_filter
|
|
2365
|
+
):
|
|
2366
|
+
insp, kws, exp = get_multi_exp(
|
|
2367
|
+
schema,
|
|
2368
|
+
scope,
|
|
2369
|
+
kind,
|
|
2370
|
+
use_filter,
|
|
2371
|
+
Inspector.get_table_comment,
|
|
2372
|
+
self.exp_comments,
|
|
2373
|
+
)
|
|
2374
|
+
for kw in kws:
|
|
2375
|
+
insp.clear_cache()
|
|
2376
|
+
eq_(insp.get_multi_table_comment(**kw), exp)
|
|
2377
|
+
|
|
2378
|
+
def _check_expressions(self, result, exp, err_msg):
|
|
2379
|
+
def _clean(text: str):
|
|
2380
|
+
return re.sub(r"['\" ]", "", text).lower()
|
|
2381
|
+
|
|
2382
|
+
if isinstance(exp, dict):
|
|
2383
|
+
eq_({_clean(e): v for e, v in result.items()}, exp, err_msg)
|
|
2384
|
+
else:
|
|
2385
|
+
eq_([_clean(e) for e in result], exp, err_msg)
|
|
2386
|
+
|
|
2387
|
+
def _check_list(self, result, exp, req_keys=None, msg=None):
|
|
2388
|
+
if req_keys is None:
|
|
2389
|
+
eq_(result, exp, msg)
|
|
2390
|
+
else:
|
|
2391
|
+
eq_(len(result), len(exp), msg)
|
|
2392
|
+
for r, e in zip(result, exp):
|
|
2393
|
+
for k in set(r) | set(e):
|
|
2394
|
+
if k in req_keys or (k in r and k in e):
|
|
2395
|
+
err_msg = f"{msg} - {k} - {r}"
|
|
2396
|
+
if k in ("expressions", "column_sorting"):
|
|
2397
|
+
self._check_expressions(r[k], e[k], err_msg)
|
|
2398
|
+
else:
|
|
2399
|
+
eq_(r[k], e[k], err_msg)
|
|
2400
|
+
|
|
2401
|
+
def _check_table_dict(self, result, exp, req_keys=None, make_lists=False):
|
|
2402
|
+
eq_(set(result.keys()), set(exp.keys()))
|
|
2403
|
+
for k in result:
|
|
2404
|
+
r, e = result[k], exp[k]
|
|
2405
|
+
if make_lists:
|
|
2406
|
+
r, e = [r], [e]
|
|
2407
|
+
self._check_list(r, e, req_keys, k)
|
|
2408
|
+
|
|
2409
|
+
@_multi_combination
|
|
2410
|
+
def test_get_multi_columns(
|
|
2411
|
+
self, get_multi_exp, schema, scope, kind, use_filter
|
|
2412
|
+
):
|
|
2413
|
+
insp, kws, exp = get_multi_exp(
|
|
2414
|
+
schema,
|
|
2415
|
+
scope,
|
|
2416
|
+
kind,
|
|
2417
|
+
use_filter,
|
|
2418
|
+
Inspector.get_columns,
|
|
2419
|
+
self.exp_columns,
|
|
2420
|
+
)
|
|
2421
|
+
|
|
2422
|
+
for kw in kws:
|
|
2423
|
+
insp.clear_cache()
|
|
2424
|
+
result = insp.get_multi_columns(**kw)
|
|
2425
|
+
self._check_table_dict(result, exp, self._required_column_keys)
|
|
2426
|
+
|
|
2427
|
+
@testing.requires.primary_key_constraint_reflection
|
|
2428
|
+
@_multi_combination
|
|
2429
|
+
def test_get_multi_pk_constraint(
|
|
2430
|
+
self, get_multi_exp, schema, scope, kind, use_filter
|
|
2431
|
+
):
|
|
2432
|
+
insp, kws, exp = get_multi_exp(
|
|
2433
|
+
schema,
|
|
2434
|
+
scope,
|
|
2435
|
+
kind,
|
|
2436
|
+
use_filter,
|
|
2437
|
+
Inspector.get_pk_constraint,
|
|
2438
|
+
self.exp_pks,
|
|
2439
|
+
)
|
|
2440
|
+
for kw in kws:
|
|
2441
|
+
insp.clear_cache()
|
|
2442
|
+
result = insp.get_multi_pk_constraint(**kw)
|
|
2443
|
+
self._check_table_dict(
|
|
2444
|
+
result, exp, self._required_pk_keys, make_lists=True
|
|
2445
|
+
)
|
|
2446
|
+
|
|
2447
|
+
def _adjust_sort(self, result, expected, key):
|
|
2448
|
+
if not testing.requires.implicitly_named_constraints.enabled:
|
|
2449
|
+
for obj in [result, expected]:
|
|
2450
|
+
for val in obj.values():
|
|
2451
|
+
if len(val) > 1 and any(
|
|
2452
|
+
v.get("name") in (None, mock.ANY) for v in val
|
|
2453
|
+
):
|
|
2454
|
+
val.sort(key=key)
|
|
2455
|
+
|
|
2456
|
+
@testing.requires.foreign_key_constraint_reflection
|
|
2457
|
+
@_multi_combination
|
|
2458
|
+
def test_get_multi_foreign_keys(
|
|
2459
|
+
self, get_multi_exp, schema, scope, kind, use_filter
|
|
2460
|
+
):
|
|
2461
|
+
insp, kws, exp = get_multi_exp(
|
|
2462
|
+
schema,
|
|
2463
|
+
scope,
|
|
2464
|
+
kind,
|
|
2465
|
+
use_filter,
|
|
2466
|
+
Inspector.get_foreign_keys,
|
|
2467
|
+
self.exp_fks,
|
|
2468
|
+
)
|
|
2469
|
+
for kw in kws:
|
|
2470
|
+
insp.clear_cache()
|
|
2471
|
+
result = insp.get_multi_foreign_keys(**kw)
|
|
2472
|
+
self._adjust_sort(
|
|
2473
|
+
result, exp, lambda d: tuple(d["constrained_columns"])
|
|
2474
|
+
)
|
|
2475
|
+
self._check_table_dict(result, exp, self._required_fk_keys)
|
|
2476
|
+
|
|
2477
|
+
@testing.requires.index_reflection
|
|
2478
|
+
@_multi_combination
|
|
2479
|
+
def test_get_multi_indexes(
|
|
2480
|
+
self, get_multi_exp, schema, scope, kind, use_filter
|
|
2481
|
+
):
|
|
2482
|
+
insp, kws, exp = get_multi_exp(
|
|
2483
|
+
schema,
|
|
2484
|
+
scope,
|
|
2485
|
+
kind,
|
|
2486
|
+
use_filter,
|
|
2487
|
+
Inspector.get_indexes,
|
|
2488
|
+
self.exp_indexes,
|
|
2489
|
+
)
|
|
2490
|
+
for kw in kws:
|
|
2491
|
+
insp.clear_cache()
|
|
2492
|
+
result = insp.get_multi_indexes(**kw)
|
|
2493
|
+
self._check_table_dict(result, exp, self._required_index_keys)
|
|
2494
|
+
|
|
2495
|
+
@testing.requires.unique_constraint_reflection
|
|
2496
|
+
@_multi_combination
|
|
2497
|
+
def test_get_multi_unique_constraints(
|
|
2498
|
+
self, get_multi_exp, schema, scope, kind, use_filter
|
|
2499
|
+
):
|
|
2500
|
+
insp, kws, exp = get_multi_exp(
|
|
2501
|
+
schema,
|
|
2502
|
+
scope,
|
|
2503
|
+
kind,
|
|
2504
|
+
use_filter,
|
|
2505
|
+
Inspector.get_unique_constraints,
|
|
2506
|
+
self.exp_ucs,
|
|
2507
|
+
)
|
|
2508
|
+
for kw in kws:
|
|
2509
|
+
insp.clear_cache()
|
|
2510
|
+
result = insp.get_multi_unique_constraints(**kw)
|
|
2511
|
+
self._adjust_sort(result, exp, lambda d: tuple(d["column_names"]))
|
|
2512
|
+
self._check_table_dict(result, exp, self._required_unique_cst_keys)
|
|
2513
|
+
|
|
2514
|
+
@testing.requires.check_constraint_reflection
|
|
2515
|
+
@_multi_combination
|
|
2516
|
+
def test_get_multi_check_constraints(
|
|
2517
|
+
self, get_multi_exp, schema, scope, kind, use_filter
|
|
2518
|
+
):
|
|
2519
|
+
insp, kws, exp = get_multi_exp(
|
|
2520
|
+
schema,
|
|
2521
|
+
scope,
|
|
2522
|
+
kind,
|
|
2523
|
+
use_filter,
|
|
2524
|
+
Inspector.get_check_constraints,
|
|
2525
|
+
self.exp_ccs,
|
|
2526
|
+
)
|
|
2527
|
+
for kw in kws:
|
|
2528
|
+
insp.clear_cache()
|
|
2529
|
+
result = insp.get_multi_check_constraints(**kw)
|
|
2530
|
+
self._adjust_sort(result, exp, lambda d: tuple(d["sqltext"]))
|
|
2531
|
+
self._check_table_dict(result, exp, self._required_cc_keys)
|
|
2532
|
+
|
|
2533
|
+
@testing.combinations(
|
|
2534
|
+
("get_table_options", testing.requires.reflect_table_options),
|
|
2535
|
+
"get_columns",
|
|
2536
|
+
(
|
|
2537
|
+
"get_pk_constraint",
|
|
2538
|
+
testing.requires.primary_key_constraint_reflection,
|
|
2539
|
+
),
|
|
2540
|
+
(
|
|
2541
|
+
"get_foreign_keys",
|
|
2542
|
+
testing.requires.foreign_key_constraint_reflection,
|
|
2543
|
+
),
|
|
2544
|
+
("get_indexes", testing.requires.index_reflection),
|
|
2545
|
+
(
|
|
2546
|
+
"get_unique_constraints",
|
|
2547
|
+
testing.requires.unique_constraint_reflection,
|
|
2548
|
+
),
|
|
2549
|
+
(
|
|
2550
|
+
"get_check_constraints",
|
|
2551
|
+
testing.requires.check_constraint_reflection,
|
|
2552
|
+
),
|
|
2553
|
+
("get_table_comment", testing.requires.comment_reflection),
|
|
2554
|
+
argnames="method",
|
|
2555
|
+
)
|
|
2556
|
+
def test_not_existing_table(self, method, connection):
|
|
2557
|
+
insp = inspect(connection)
|
|
2558
|
+
meth = getattr(insp, method)
|
|
2559
|
+
with expect_raises(NoSuchTableError):
|
|
2560
|
+
meth("table_does_not_exists")
|
|
2561
|
+
|
|
2562
|
+
def test_unreflectable(self, connection):
|
|
2563
|
+
mc = Inspector.get_multi_columns
|
|
2564
|
+
|
|
2565
|
+
def patched(*a, **k):
|
|
2566
|
+
ur = k.setdefault("unreflectable", {})
|
|
2567
|
+
ur[(None, "some_table")] = UnreflectableTableError("err")
|
|
2568
|
+
return mc(*a, **k)
|
|
2569
|
+
|
|
2570
|
+
with mock.patch.object(Inspector, "get_multi_columns", patched):
|
|
2571
|
+
with expect_raises_message(UnreflectableTableError, "err"):
|
|
2572
|
+
inspect(connection).reflect_table(
|
|
2573
|
+
Table("some_table", MetaData()), None
|
|
2574
|
+
)
|
|
2575
|
+
|
|
2576
|
+
@testing.combinations(True, False, argnames="use_schema")
|
|
2577
|
+
@testing.combinations(
|
|
2578
|
+
(True, testing.requires.views), False, argnames="views"
|
|
2579
|
+
)
|
|
2580
|
+
def test_metadata(self, connection, use_schema, views):
|
|
2581
|
+
m = MetaData()
|
|
2582
|
+
schema = config.test_schema if use_schema else None
|
|
2583
|
+
m.reflect(connection, schema=schema, views=views, resolve_fks=False)
|
|
2584
|
+
|
|
2585
|
+
insp = inspect(connection)
|
|
2586
|
+
tables = insp.get_table_names(schema)
|
|
2587
|
+
if views:
|
|
2588
|
+
tables += insp.get_view_names(schema)
|
|
2589
|
+
try:
|
|
2590
|
+
tables += insp.get_materialized_view_names(schema)
|
|
2591
|
+
except NotImplementedError:
|
|
2592
|
+
pass
|
|
2593
|
+
if schema:
|
|
2594
|
+
tables = [f"{schema}.{t}" for t in tables]
|
|
2595
|
+
eq_(sorted(m.tables), sorted(tables))
|
|
2596
|
+
|
|
2597
|
+
@testing.requires.comment_reflection
|
|
2598
|
+
def test_comments_unicode(self, connection, metadata):
|
|
2599
|
+
Table(
|
|
2600
|
+
"unicode_comments",
|
|
2601
|
+
metadata,
|
|
2602
|
+
Column("unicode", Integer, comment="é試蛇ẟΩ"),
|
|
2603
|
+
Column("emoji", Integer, comment="☁️✨"),
|
|
2604
|
+
comment="試蛇ẟΩ✨",
|
|
2605
|
+
)
|
|
2606
|
+
|
|
2607
|
+
metadata.create_all(connection)
|
|
2608
|
+
|
|
2609
|
+
insp = inspect(connection)
|
|
2610
|
+
tc = insp.get_table_comment("unicode_comments")
|
|
2611
|
+
eq_(tc, {"text": "試蛇ẟΩ✨"})
|
|
2612
|
+
|
|
2613
|
+
cols = insp.get_columns("unicode_comments")
|
|
2614
|
+
value = {c["name"]: c["comment"] for c in cols}
|
|
2615
|
+
exp = {"unicode": "é試蛇ẟΩ", "emoji": "☁️✨"}
|
|
2616
|
+
eq_(value, exp)
|
|
2617
|
+
|
|
2618
|
+
@testing.requires.comment_reflection_full_unicode
|
|
2619
|
+
def test_comments_unicode_full(self, connection, metadata):
|
|
2620
|
+
Table(
|
|
2621
|
+
"unicode_comments",
|
|
2622
|
+
metadata,
|
|
2623
|
+
Column("emoji", Integer, comment="🐍🧙🝝🧙♂️🧙♀️"),
|
|
2624
|
+
comment="🎩🁰🝑🤷♀️🤷♂️",
|
|
2625
|
+
)
|
|
2626
|
+
|
|
2627
|
+
metadata.create_all(connection)
|
|
2628
|
+
|
|
2629
|
+
insp = inspect(connection)
|
|
2630
|
+
tc = insp.get_table_comment("unicode_comments")
|
|
2631
|
+
eq_(tc, {"text": "🎩🁰🝑🤷♀️🤷♂️"})
|
|
2632
|
+
c = insp.get_columns("unicode_comments")[0]
|
|
2633
|
+
eq_({c["name"]: c["comment"]}, {"emoji": "🐍🧙🝝🧙♂️🧙♀️"})
|
|
2634
|
+
|
|
2635
|
+
@testing.requires.column_collation_reflection
|
|
2636
|
+
@testing.requires.order_by_collation
|
|
2637
|
+
def test_column_collation_reflection(self, connection, metadata):
|
|
2638
|
+
collation = testing.requires.get_order_by_collation(config)
|
|
2639
|
+
Table(
|
|
2640
|
+
"t",
|
|
2641
|
+
metadata,
|
|
2642
|
+
Column("collated", sa.String(collation=collation)),
|
|
2643
|
+
Column("not_collated", sa.String()),
|
|
2644
|
+
)
|
|
2645
|
+
metadata.create_all(connection)
|
|
2646
|
+
|
|
2647
|
+
m2 = MetaData()
|
|
2648
|
+
t2 = Table("t", m2, autoload_with=connection)
|
|
2649
|
+
|
|
2650
|
+
eq_(t2.c.collated.type.collation, collation)
|
|
2651
|
+
is_none(t2.c.not_collated.type.collation)
|
|
2652
|
+
|
|
2653
|
+
insp = inspect(connection)
|
|
2654
|
+
collated, not_collated = insp.get_columns("t")
|
|
2655
|
+
eq_(collated["type"].collation, collation)
|
|
2656
|
+
is_none(not_collated["type"].collation)
|
|
2657
|
+
|
|
2658
|
+
|
|
2659
|
+
class TableNoColumnsTest(fixtures.TestBase):
|
|
2660
|
+
__requires__ = ("reflect_tables_no_columns",)
|
|
2661
|
+
__sparse_driver_backend__ = True
|
|
2662
|
+
|
|
2663
|
+
@testing.fixture
|
|
2664
|
+
def table_no_columns(self, connection, metadata):
|
|
2665
|
+
Table("empty", metadata)
|
|
2666
|
+
metadata.create_all(connection)
|
|
2667
|
+
|
|
2668
|
+
@testing.fixture
|
|
2669
|
+
def view_no_columns(self, connection, metadata):
|
|
2670
|
+
Table("empty", metadata)
|
|
2671
|
+
event.listen(
|
|
2672
|
+
metadata,
|
|
2673
|
+
"after_create",
|
|
2674
|
+
DDL("CREATE VIEW empty_v AS SELECT * FROM empty"),
|
|
2675
|
+
)
|
|
2676
|
+
|
|
2677
|
+
# for transactional DDL the transaction is rolled back before this
|
|
2678
|
+
# drop statement is invoked
|
|
2679
|
+
event.listen(
|
|
2680
|
+
metadata, "before_drop", DDL("DROP VIEW IF EXISTS empty_v")
|
|
2681
|
+
)
|
|
2682
|
+
metadata.create_all(connection)
|
|
2683
|
+
|
|
2684
|
+
def test_reflect_table_no_columns(self, connection, table_no_columns):
|
|
2685
|
+
t2 = Table("empty", MetaData(), autoload_with=connection)
|
|
2686
|
+
eq_(list(t2.c), [])
|
|
2687
|
+
|
|
2688
|
+
def test_get_columns_table_no_columns(self, connection, table_no_columns):
|
|
2689
|
+
insp = inspect(connection)
|
|
2690
|
+
eq_(insp.get_columns("empty"), [])
|
|
2691
|
+
multi = insp.get_multi_columns()
|
|
2692
|
+
eq_(multi, {(None, "empty"): []})
|
|
2693
|
+
|
|
2694
|
+
def test_reflect_incl_table_no_columns(self, connection, table_no_columns):
|
|
2695
|
+
m = MetaData()
|
|
2696
|
+
m.reflect(connection)
|
|
2697
|
+
assert set(m.tables).intersection(["empty"])
|
|
2698
|
+
|
|
2699
|
+
@testing.requires.views
|
|
2700
|
+
def test_reflect_view_no_columns(self, connection, view_no_columns):
|
|
2701
|
+
t2 = Table("empty_v", MetaData(), autoload_with=connection)
|
|
2702
|
+
eq_(list(t2.c), [])
|
|
2703
|
+
|
|
2704
|
+
@testing.requires.views
|
|
2705
|
+
def test_get_columns_view_no_columns(self, connection, view_no_columns):
|
|
2706
|
+
insp = inspect(connection)
|
|
2707
|
+
eq_(insp.get_columns("empty_v"), [])
|
|
2708
|
+
multi = insp.get_multi_columns(kind=ObjectKind.VIEW)
|
|
2709
|
+
eq_(multi, {(None, "empty_v"): []})
|
|
2710
|
+
|
|
2711
|
+
|
|
2712
|
+
class ComponentReflectionTestExtra(ComparesIndexes, fixtures.TestBase):
|
|
2713
|
+
__sparse_driver_backend__ = True
|
|
2714
|
+
|
|
2715
|
+
@testing.fixture(params=[True, False])
|
|
2716
|
+
def use_schema_fixture(self, request):
|
|
2717
|
+
if request.param:
|
|
2718
|
+
return config.test_schema
|
|
2719
|
+
else:
|
|
2720
|
+
return None
|
|
2721
|
+
|
|
2722
|
+
@testing.fixture()
|
|
2723
|
+
def inspect_for_table(self, metadata, connection, use_schema_fixture):
|
|
2724
|
+
@contextlib.contextmanager
|
|
2725
|
+
def go(tablename):
|
|
2726
|
+
yield use_schema_fixture, inspect(connection)
|
|
2727
|
+
|
|
2728
|
+
metadata.create_all(connection)
|
|
2729
|
+
|
|
2730
|
+
return go
|
|
2731
|
+
|
|
2732
|
+
def ck_eq(self, reflected, expected):
|
|
2733
|
+
# trying to minimize effect of quoting, parenthesis, etc.
|
|
2734
|
+
# may need to add more to this as new dialects get CHECK
|
|
2735
|
+
# constraint reflection support
|
|
2736
|
+
def normalize(sqltext):
|
|
2737
|
+
return " ".join(
|
|
2738
|
+
re.findall(r"and|\d|=|a|b|c|or|<|>", sqltext.lower(), re.I)
|
|
2739
|
+
)
|
|
2740
|
+
|
|
2741
|
+
reflected = sorted(
|
|
2742
|
+
[
|
|
2743
|
+
{"name": item["name"], "sqltext": normalize(item["sqltext"])}
|
|
2744
|
+
for item in reflected
|
|
2745
|
+
],
|
|
2746
|
+
key=lambda item: (item["sqltext"]),
|
|
2747
|
+
)
|
|
2748
|
+
|
|
2749
|
+
expected = sorted(
|
|
2750
|
+
expected,
|
|
2751
|
+
key=lambda item: (item["sqltext"]),
|
|
2752
|
+
)
|
|
2753
|
+
eq_(reflected, expected)
|
|
2754
|
+
|
|
2755
|
+
@testing.requires.check_constraint_reflection
|
|
2756
|
+
def test_check_constraint_no_constraint(self, metadata, inspect_for_table):
|
|
2757
|
+
with inspect_for_table("no_constraints") as (schema, inspector):
|
|
2758
|
+
Table(
|
|
2759
|
+
"no_constraints",
|
|
2760
|
+
metadata,
|
|
2761
|
+
Column("data", sa.String(20)),
|
|
2762
|
+
schema=schema,
|
|
2763
|
+
)
|
|
2764
|
+
|
|
2765
|
+
self.ck_eq(
|
|
2766
|
+
inspector.get_check_constraints("no_constraints", schema=schema),
|
|
2767
|
+
[],
|
|
2768
|
+
)
|
|
2769
|
+
|
|
2770
|
+
@testing.requires.inline_check_constraint_reflection
|
|
2771
|
+
@testing.combinations(
|
|
2772
|
+
"my_inline", "MyInline", None, argnames="constraint_name"
|
|
2773
|
+
)
|
|
2774
|
+
def test_check_constraint_inline(
|
|
2775
|
+
self, metadata, inspect_for_table, constraint_name
|
|
2776
|
+
):
|
|
2777
|
+
|
|
2778
|
+
with inspect_for_table("sa_cc") as (schema, inspector):
|
|
2779
|
+
Table(
|
|
2780
|
+
"sa_cc",
|
|
2781
|
+
metadata,
|
|
2782
|
+
Column("id", Integer(), primary_key=True),
|
|
2783
|
+
Column(
|
|
2784
|
+
"a",
|
|
2785
|
+
Integer(),
|
|
2786
|
+
sa.CheckConstraint(
|
|
2787
|
+
"a > 1 AND a < 5", name=constraint_name
|
|
2788
|
+
),
|
|
2789
|
+
),
|
|
2790
|
+
Column("data", String(50)),
|
|
2791
|
+
schema=schema,
|
|
2792
|
+
)
|
|
2793
|
+
|
|
2794
|
+
reflected = inspector.get_check_constraints("sa_cc", schema=schema)
|
|
2795
|
+
|
|
2796
|
+
self.ck_eq(
|
|
2797
|
+
reflected,
|
|
2798
|
+
[
|
|
2799
|
+
{
|
|
2800
|
+
"name": constraint_name or mock.ANY,
|
|
2801
|
+
"sqltext": "a > 1 and a < 5",
|
|
2802
|
+
},
|
|
2803
|
+
],
|
|
2804
|
+
)
|
|
2805
|
+
|
|
2806
|
+
@testing.requires.check_constraint_reflection
|
|
2807
|
+
@testing.combinations(
|
|
2808
|
+
"my_ck_const", "MyCkConst", None, argnames="constraint_name"
|
|
2809
|
+
)
|
|
2810
|
+
def test_check_constraint_standalone(
|
|
2811
|
+
self, metadata, inspect_for_table, constraint_name
|
|
2812
|
+
):
|
|
2813
|
+
with inspect_for_table("sa_cc") as (schema, inspector):
|
|
2814
|
+
Table(
|
|
2815
|
+
"sa_cc",
|
|
2816
|
+
metadata,
|
|
2817
|
+
Column("a", Integer()),
|
|
2818
|
+
sa.CheckConstraint(
|
|
2819
|
+
"a = 1 OR (a > 2 AND a < 5)", name=constraint_name
|
|
2820
|
+
),
|
|
2821
|
+
schema=schema,
|
|
2822
|
+
)
|
|
2823
|
+
|
|
2824
|
+
reflected = inspector.get_check_constraints("sa_cc", schema=schema)
|
|
2825
|
+
|
|
2826
|
+
self.ck_eq(
|
|
2827
|
+
reflected,
|
|
2828
|
+
[
|
|
2829
|
+
{
|
|
2830
|
+
"name": constraint_name or mock.ANY,
|
|
2831
|
+
"sqltext": "a = 1 or a > 2 and a < 5",
|
|
2832
|
+
},
|
|
2833
|
+
],
|
|
2834
|
+
)
|
|
2835
|
+
|
|
2836
|
+
@testing.requires.inline_check_constraint_reflection
|
|
2837
|
+
def test_check_constraint_mixed(self, metadata, inspect_for_table):
|
|
2838
|
+
with inspect_for_table("sa_cc") as (schema, inspector):
|
|
2839
|
+
Table(
|
|
2840
|
+
"sa_cc",
|
|
2841
|
+
metadata,
|
|
2842
|
+
Column("id", Integer(), primary_key=True),
|
|
2843
|
+
Column("a", Integer(), sa.CheckConstraint("a > 1 AND a < 5")),
|
|
2844
|
+
Column(
|
|
2845
|
+
"b",
|
|
2846
|
+
Integer(),
|
|
2847
|
+
sa.CheckConstraint("b > 1 AND b < 5", name="my_inline"),
|
|
2848
|
+
),
|
|
2849
|
+
Column("c", Integer()),
|
|
2850
|
+
Column("data", String(50)),
|
|
2851
|
+
sa.UniqueConstraint("data", name="some_uq"),
|
|
2852
|
+
sa.CheckConstraint("c > 1 AND c < 5", name="cc1"),
|
|
2853
|
+
sa.UniqueConstraint("c", name="some_c_uq"),
|
|
2854
|
+
schema=schema,
|
|
2855
|
+
)
|
|
2856
|
+
|
|
2857
|
+
reflected = inspector.get_check_constraints("sa_cc", schema=schema)
|
|
2858
|
+
|
|
2859
|
+
self.ck_eq(
|
|
2860
|
+
reflected,
|
|
2861
|
+
[
|
|
2862
|
+
{"name": "cc1", "sqltext": "c > 1 and c < 5"},
|
|
2863
|
+
{"name": "my_inline", "sqltext": "b > 1 and b < 5"},
|
|
2864
|
+
{"name": mock.ANY, "sqltext": "a > 1 and a < 5"},
|
|
2865
|
+
],
|
|
2866
|
+
)
|
|
2867
|
+
|
|
2868
|
+
@testing.requires.indexes_check_column_order
|
|
2869
|
+
def test_index_column_order(self, metadata, inspect_for_table):
|
|
2870
|
+
"""test for #12894"""
|
|
2871
|
+
with inspect_for_table("sa_multi_index") as (schema, inspector):
|
|
2872
|
+
test_table = Table(
|
|
2873
|
+
"sa_multi_index",
|
|
2874
|
+
metadata,
|
|
2875
|
+
Column("Column1", Integer, primary_key=True),
|
|
2876
|
+
Column("Column2", Integer),
|
|
2877
|
+
Column("Column3", Integer),
|
|
2878
|
+
)
|
|
2879
|
+
Index(
|
|
2880
|
+
"Index_Example",
|
|
2881
|
+
test_table.c.Column3,
|
|
2882
|
+
test_table.c.Column1,
|
|
2883
|
+
test_table.c.Column2,
|
|
2884
|
+
)
|
|
2885
|
+
indexes = inspector.get_indexes("sa_multi_index")
|
|
2886
|
+
eq_(indexes[0]["column_names"], ["Column3", "Column1", "Column2"])
|
|
2887
|
+
|
|
2888
|
+
@testing.requires.indexes_with_expressions
|
|
2889
|
+
def test_reflect_expression_based_indexes(self, metadata, connection):
|
|
2890
|
+
t = Table(
|
|
2891
|
+
"t",
|
|
2892
|
+
metadata,
|
|
2893
|
+
Column("x", String(30)),
|
|
2894
|
+
Column("y", String(30)),
|
|
2895
|
+
Column("z", String(30)),
|
|
2896
|
+
)
|
|
2897
|
+
|
|
2898
|
+
Index("t_idx", func.lower(t.c.x), t.c.z, func.lower(t.c.y))
|
|
2899
|
+
long_str = "long string " * 100
|
|
2900
|
+
Index("t_idx_long", func.coalesce(t.c.x, long_str))
|
|
2901
|
+
Index("t_idx_2", t.c.x)
|
|
2902
|
+
|
|
2903
|
+
metadata.create_all(connection)
|
|
2904
|
+
|
|
2905
|
+
insp = inspect(connection)
|
|
2906
|
+
|
|
2907
|
+
expected = [
|
|
2908
|
+
{
|
|
2909
|
+
"name": "t_idx_2",
|
|
2910
|
+
"column_names": ["x"],
|
|
2911
|
+
"unique": False,
|
|
2912
|
+
"dialect_options": {},
|
|
2913
|
+
}
|
|
2914
|
+
]
|
|
2915
|
+
|
|
2916
|
+
def completeIndex(entry):
|
|
2917
|
+
if testing.requires.index_reflects_included_columns.enabled:
|
|
2918
|
+
entry["include_columns"] = []
|
|
2919
|
+
entry["dialect_options"] = {
|
|
2920
|
+
f"{connection.engine.name}_include": []
|
|
2921
|
+
}
|
|
2922
|
+
else:
|
|
2923
|
+
entry.setdefault("dialect_options", {})
|
|
2924
|
+
|
|
2925
|
+
completeIndex(expected[0])
|
|
2926
|
+
|
|
2927
|
+
class lower_index_str(str):
|
|
2928
|
+
def __eq__(self, other):
|
|
2929
|
+
ol = other.lower()
|
|
2930
|
+
# test that lower and x or y are in the string
|
|
2931
|
+
return "lower" in ol and ("x" in ol or "y" in ol)
|
|
2932
|
+
|
|
2933
|
+
class coalesce_index_str(str):
|
|
2934
|
+
def __eq__(self, other):
|
|
2935
|
+
# test that coalesce and the string is in other
|
|
2936
|
+
return "coalesce" in other.lower() and long_str in other
|
|
2937
|
+
|
|
2938
|
+
if testing.requires.reflect_indexes_with_expressions.enabled:
|
|
2939
|
+
expr_index = {
|
|
2940
|
+
"name": "t_idx",
|
|
2941
|
+
"column_names": [None, "z", None],
|
|
2942
|
+
"expressions": [
|
|
2943
|
+
lower_index_str("lower(x)"),
|
|
2944
|
+
"z",
|
|
2945
|
+
lower_index_str("lower(y)"),
|
|
2946
|
+
],
|
|
2947
|
+
"unique": False,
|
|
2948
|
+
}
|
|
2949
|
+
completeIndex(expr_index)
|
|
2950
|
+
expected.insert(0, expr_index)
|
|
2951
|
+
|
|
2952
|
+
expr_index_long = {
|
|
2953
|
+
"name": "t_idx_long",
|
|
2954
|
+
"column_names": [None],
|
|
2955
|
+
"expressions": [
|
|
2956
|
+
coalesce_index_str(f"coalesce(x, '{long_str}')")
|
|
2957
|
+
],
|
|
2958
|
+
"unique": False,
|
|
2959
|
+
}
|
|
2960
|
+
completeIndex(expr_index_long)
|
|
2961
|
+
expected.append(expr_index_long)
|
|
2962
|
+
|
|
2963
|
+
eq_(insp.get_indexes("t"), expected)
|
|
2964
|
+
m2 = MetaData()
|
|
2965
|
+
t2 = Table("t", m2, autoload_with=connection)
|
|
2966
|
+
else:
|
|
2967
|
+
with expect_warnings(
|
|
2968
|
+
"Skipped unsupported reflection of expression-based "
|
|
2969
|
+
"index t_idx"
|
|
2970
|
+
):
|
|
2971
|
+
eq_(insp.get_indexes("t"), expected)
|
|
2972
|
+
m2 = MetaData()
|
|
2973
|
+
t2 = Table("t", m2, autoload_with=connection)
|
|
2974
|
+
|
|
2975
|
+
self.compare_table_index_with_expected(
|
|
2976
|
+
t2, expected, connection.engine.name
|
|
2977
|
+
)
|
|
2978
|
+
|
|
2979
|
+
@testing.requires.index_reflects_included_columns
|
|
2980
|
+
def test_reflect_covering_index(self, metadata, connection):
|
|
2981
|
+
t = Table(
|
|
2982
|
+
"t",
|
|
2983
|
+
metadata,
|
|
2984
|
+
Column("x", String(30)),
|
|
2985
|
+
Column("y", String(30)),
|
|
2986
|
+
)
|
|
2987
|
+
idx = Index("t_idx", t.c.x)
|
|
2988
|
+
idx.dialect_options[connection.engine.name]["include"] = ["y"]
|
|
2989
|
+
|
|
2990
|
+
metadata.create_all(connection)
|
|
2991
|
+
|
|
2992
|
+
insp = inspect(connection)
|
|
2993
|
+
|
|
2994
|
+
get_indexes = insp.get_indexes("t")
|
|
2995
|
+
eq_(
|
|
2996
|
+
get_indexes,
|
|
2997
|
+
[
|
|
2998
|
+
{
|
|
2999
|
+
"name": "t_idx",
|
|
3000
|
+
"column_names": ["x"],
|
|
3001
|
+
"include_columns": ["y"],
|
|
3002
|
+
"unique": False,
|
|
3003
|
+
"dialect_options": mock.ANY,
|
|
3004
|
+
}
|
|
3005
|
+
],
|
|
3006
|
+
)
|
|
3007
|
+
eq_(
|
|
3008
|
+
get_indexes[0]["dialect_options"][
|
|
3009
|
+
"%s_include" % connection.engine.name
|
|
3010
|
+
],
|
|
3011
|
+
["y"],
|
|
3012
|
+
)
|
|
3013
|
+
|
|
3014
|
+
t2 = Table("t", MetaData(), autoload_with=connection)
|
|
3015
|
+
eq_(
|
|
3016
|
+
list(t2.indexes)[0].dialect_options[connection.engine.name][
|
|
3017
|
+
"include"
|
|
3018
|
+
],
|
|
3019
|
+
["y"],
|
|
3020
|
+
)
|
|
3021
|
+
|
|
3022
|
+
def _type_round_trip(self, connection, metadata, *types):
|
|
3023
|
+
t = Table(
|
|
3024
|
+
"t",
|
|
3025
|
+
metadata,
|
|
3026
|
+
*[Column("t%d" % i, type_) for i, type_ in enumerate(types)],
|
|
3027
|
+
)
|
|
3028
|
+
t.create(connection)
|
|
3029
|
+
|
|
3030
|
+
return [c["type"] for c in inspect(connection).get_columns("t")]
|
|
3031
|
+
|
|
3032
|
+
@testing.requires.table_reflection
|
|
3033
|
+
def test_numeric_reflection(self, connection, metadata):
|
|
3034
|
+
for typ in self._type_round_trip(
|
|
3035
|
+
connection, metadata, sql_types.Numeric(18, 5)
|
|
3036
|
+
):
|
|
3037
|
+
assert isinstance(typ, sql_types.Numeric)
|
|
3038
|
+
eq_(typ.precision, 18)
|
|
3039
|
+
eq_(typ.scale, 5)
|
|
3040
|
+
|
|
3041
|
+
@testing.requires.table_reflection
|
|
3042
|
+
@testing.combinations(
|
|
3043
|
+
sql_types.String,
|
|
3044
|
+
sql_types.VARCHAR,
|
|
3045
|
+
sql_types.CHAR,
|
|
3046
|
+
(sql_types.NVARCHAR, testing.requires.nvarchar_types),
|
|
3047
|
+
(sql_types.NCHAR, testing.requires.nvarchar_types),
|
|
3048
|
+
argnames="type_",
|
|
3049
|
+
)
|
|
3050
|
+
def test_string_length_reflection(self, connection, metadata, type_):
|
|
3051
|
+
typ = self._type_round_trip(connection, metadata, type_(52))[0]
|
|
3052
|
+
if issubclass(type_, sql_types.VARCHAR):
|
|
3053
|
+
assert isinstance(typ, sql_types.VARCHAR)
|
|
3054
|
+
elif issubclass(type_, sql_types.CHAR):
|
|
3055
|
+
assert isinstance(typ, sql_types.CHAR)
|
|
3056
|
+
else:
|
|
3057
|
+
assert isinstance(typ, sql_types.String)
|
|
3058
|
+
|
|
3059
|
+
eq_(typ.length, 52)
|
|
3060
|
+
assert isinstance(typ.length, int)
|
|
3061
|
+
|
|
3062
|
+
@testing.requires.table_reflection
|
|
3063
|
+
def test_nullable_reflection(self, connection, metadata):
|
|
3064
|
+
t = Table(
|
|
3065
|
+
"t",
|
|
3066
|
+
metadata,
|
|
3067
|
+
Column("a", Integer, nullable=True),
|
|
3068
|
+
Column("b", Integer, nullable=False),
|
|
3069
|
+
)
|
|
3070
|
+
t.create(connection)
|
|
3071
|
+
eq_(
|
|
3072
|
+
{
|
|
3073
|
+
col["name"]: col["nullable"]
|
|
3074
|
+
for col in inspect(connection).get_columns("t")
|
|
3075
|
+
},
|
|
3076
|
+
{"a": True, "b": False},
|
|
3077
|
+
)
|
|
3078
|
+
|
|
3079
|
+
@testing.combinations(
|
|
3080
|
+
(
|
|
3081
|
+
None,
|
|
3082
|
+
"CASCADE",
|
|
3083
|
+
None,
|
|
3084
|
+
testing.requires.foreign_key_constraint_option_reflection_ondelete,
|
|
3085
|
+
),
|
|
3086
|
+
(
|
|
3087
|
+
None,
|
|
3088
|
+
None,
|
|
3089
|
+
"SET NULL",
|
|
3090
|
+
testing.requires.foreign_key_constraint_option_reflection_onupdate,
|
|
3091
|
+
),
|
|
3092
|
+
(
|
|
3093
|
+
{},
|
|
3094
|
+
None,
|
|
3095
|
+
"NO ACTION",
|
|
3096
|
+
testing.requires.foreign_key_constraint_option_reflection_onupdate,
|
|
3097
|
+
),
|
|
3098
|
+
(
|
|
3099
|
+
{},
|
|
3100
|
+
"NO ACTION",
|
|
3101
|
+
None,
|
|
3102
|
+
testing.requires.fk_constraint_option_reflection_ondelete_noaction,
|
|
3103
|
+
),
|
|
3104
|
+
(
|
|
3105
|
+
None,
|
|
3106
|
+
None,
|
|
3107
|
+
"RESTRICT",
|
|
3108
|
+
testing.requires.fk_constraint_option_reflection_onupdate_restrict,
|
|
3109
|
+
),
|
|
3110
|
+
(
|
|
3111
|
+
None,
|
|
3112
|
+
"RESTRICT",
|
|
3113
|
+
None,
|
|
3114
|
+
testing.requires.fk_constraint_option_reflection_ondelete_restrict,
|
|
3115
|
+
),
|
|
3116
|
+
argnames="expected,ondelete,onupdate",
|
|
3117
|
+
)
|
|
3118
|
+
def test_get_foreign_key_options(
|
|
3119
|
+
self, connection, metadata, expected, ondelete, onupdate
|
|
3120
|
+
):
|
|
3121
|
+
options = {}
|
|
3122
|
+
if ondelete:
|
|
3123
|
+
options["ondelete"] = ondelete
|
|
3124
|
+
if onupdate:
|
|
3125
|
+
options["onupdate"] = onupdate
|
|
3126
|
+
|
|
3127
|
+
if expected is None:
|
|
3128
|
+
expected = options
|
|
3129
|
+
|
|
3130
|
+
Table(
|
|
3131
|
+
"x",
|
|
3132
|
+
metadata,
|
|
3133
|
+
Column("id", Integer, primary_key=True),
|
|
3134
|
+
test_needs_fk=True,
|
|
3135
|
+
)
|
|
3136
|
+
|
|
3137
|
+
Table(
|
|
3138
|
+
"table",
|
|
3139
|
+
metadata,
|
|
3140
|
+
Column("id", Integer, primary_key=True),
|
|
3141
|
+
Column("x_id", Integer, ForeignKey("x.id", name="xid")),
|
|
3142
|
+
Column("test", String(10)),
|
|
3143
|
+
test_needs_fk=True,
|
|
3144
|
+
)
|
|
3145
|
+
|
|
3146
|
+
Table(
|
|
3147
|
+
"user",
|
|
3148
|
+
metadata,
|
|
3149
|
+
Column("id", Integer, primary_key=True),
|
|
3150
|
+
Column("name", String(50), nullable=False),
|
|
3151
|
+
Column("tid", Integer),
|
|
3152
|
+
sa.ForeignKeyConstraint(
|
|
3153
|
+
["tid"], ["table.id"], name="myfk", **options
|
|
3154
|
+
),
|
|
3155
|
+
test_needs_fk=True,
|
|
3156
|
+
)
|
|
3157
|
+
|
|
3158
|
+
metadata.create_all(connection)
|
|
3159
|
+
|
|
3160
|
+
insp = inspect(connection)
|
|
3161
|
+
|
|
3162
|
+
# test 'options' is always present for a backend
|
|
3163
|
+
# that can reflect these, since alembic looks for this
|
|
3164
|
+
opts = insp.get_foreign_keys("table")[0]["options"]
|
|
3165
|
+
|
|
3166
|
+
eq_({k: opts[k] for k in opts if opts[k]}, {})
|
|
3167
|
+
|
|
3168
|
+
opts = insp.get_foreign_keys("user")[0]["options"]
|
|
3169
|
+
eq_(opts, expected)
|
|
3170
|
+
# eq_(dict((k, opts[k]) for k in opts if opts[k]), expected)
|
|
3171
|
+
|
|
3172
|
+
@testing.combinations(
|
|
3173
|
+
(Integer, sa.text("10"), r"'?10'?"),
|
|
3174
|
+
(Integer, "10", r"'?10'?"),
|
|
3175
|
+
(Boolean, sa.true(), r"1|true"),
|
|
3176
|
+
(
|
|
3177
|
+
Integer,
|
|
3178
|
+
sa.text("3 + 5"),
|
|
3179
|
+
r"3\+5",
|
|
3180
|
+
testing.requires.expression_server_defaults,
|
|
3181
|
+
),
|
|
3182
|
+
(
|
|
3183
|
+
Integer,
|
|
3184
|
+
sa.text("(3 * 5)"),
|
|
3185
|
+
r"3\*5",
|
|
3186
|
+
testing.requires.expression_server_defaults,
|
|
3187
|
+
),
|
|
3188
|
+
(DateTime, func.now(), r"current_timestamp|now|getdate"),
|
|
3189
|
+
(
|
|
3190
|
+
Integer,
|
|
3191
|
+
sa.literal_column("3") + sa.literal_column("5"),
|
|
3192
|
+
r"3\+5",
|
|
3193
|
+
testing.requires.expression_server_defaults,
|
|
3194
|
+
),
|
|
3195
|
+
argnames="datatype, default, expected_reg",
|
|
3196
|
+
)
|
|
3197
|
+
@testing.requires.server_defaults
|
|
3198
|
+
def test_server_defaults(
|
|
3199
|
+
self, metadata, connection, datatype, default, expected_reg
|
|
3200
|
+
):
|
|
3201
|
+
t = Table(
|
|
3202
|
+
"t",
|
|
3203
|
+
metadata,
|
|
3204
|
+
Column("id", Integer, primary_key=True),
|
|
3205
|
+
Column("thecol", datatype, server_default=default),
|
|
3206
|
+
)
|
|
3207
|
+
t.create(connection)
|
|
3208
|
+
|
|
3209
|
+
reflected = inspect(connection).get_columns("t")[1]["default"]
|
|
3210
|
+
reflected_sanitized = re.sub(r"[\(\) \']", "", reflected)
|
|
3211
|
+
eq_regex(reflected_sanitized, expected_reg, flags=re.IGNORECASE)
|
|
3212
|
+
|
|
3213
|
+
|
|
3214
|
+
class NormalizedNameTest(fixtures.TablesTest):
|
|
3215
|
+
__requires__ = ("denormalized_names",)
|
|
3216
|
+
__sparse_driver_backend__ = True
|
|
3217
|
+
|
|
3218
|
+
@classmethod
|
|
3219
|
+
def define_tables(cls, metadata):
|
|
3220
|
+
Table(
|
|
3221
|
+
quoted_name("t1", quote=True),
|
|
3222
|
+
metadata,
|
|
3223
|
+
Column("id", Integer, primary_key=True),
|
|
3224
|
+
)
|
|
3225
|
+
Table(
|
|
3226
|
+
quoted_name("t2", quote=True),
|
|
3227
|
+
metadata,
|
|
3228
|
+
Column("id", Integer, primary_key=True),
|
|
3229
|
+
Column("t1id", ForeignKey("t1.id")),
|
|
3230
|
+
)
|
|
3231
|
+
|
|
3232
|
+
def test_reflect_lowercase_forced_tables(self):
|
|
3233
|
+
m2 = MetaData()
|
|
3234
|
+
t2_ref = Table(
|
|
3235
|
+
quoted_name("t2", quote=True), m2, autoload_with=config.db
|
|
3236
|
+
)
|
|
3237
|
+
t1_ref = m2.tables["t1"]
|
|
3238
|
+
assert t2_ref.c.t1id.references(t1_ref.c.id)
|
|
3239
|
+
|
|
3240
|
+
m3 = MetaData()
|
|
3241
|
+
m3.reflect(
|
|
3242
|
+
config.db, only=lambda name, m: name.lower() in ("t1", "t2")
|
|
3243
|
+
)
|
|
3244
|
+
assert m3.tables["t2"].c.t1id.references(m3.tables["t1"].c.id)
|
|
3245
|
+
|
|
3246
|
+
def test_get_table_names(self):
|
|
3247
|
+
tablenames = [
|
|
3248
|
+
t
|
|
3249
|
+
for t in inspect(config.db).get_table_names()
|
|
3250
|
+
if t.lower() in ("t1", "t2")
|
|
3251
|
+
]
|
|
3252
|
+
|
|
3253
|
+
eq_(tablenames[0].upper(), tablenames[0].lower())
|
|
3254
|
+
eq_(tablenames[1].upper(), tablenames[1].lower())
|
|
3255
|
+
|
|
3256
|
+
|
|
3257
|
+
class ComputedReflectionTest(fixtures.ComputedReflectionFixtureTest):
|
|
3258
|
+
def test_computed_col_default_not_set(self):
|
|
3259
|
+
insp = inspect(config.db)
|
|
3260
|
+
|
|
3261
|
+
cols = insp.get_columns("computed_default_table")
|
|
3262
|
+
col_data = {c["name"]: c for c in cols}
|
|
3263
|
+
is_true("42" in col_data["with_default"]["default"])
|
|
3264
|
+
is_(col_data["normal"]["default"], None)
|
|
3265
|
+
is_(col_data["computed_col"]["default"], None)
|
|
3266
|
+
|
|
3267
|
+
def test_get_column_returns_computed(self):
|
|
3268
|
+
insp = inspect(config.db)
|
|
3269
|
+
|
|
3270
|
+
cols = insp.get_columns("computed_default_table")
|
|
3271
|
+
data = {c["name"]: c for c in cols}
|
|
3272
|
+
for key in ("id", "normal", "with_default"):
|
|
3273
|
+
is_true("computed" not in data[key])
|
|
3274
|
+
compData = data["computed_col"]
|
|
3275
|
+
is_true("computed" in compData)
|
|
3276
|
+
is_true("sqltext" in compData["computed"])
|
|
3277
|
+
eq_(self.normalize(compData["computed"]["sqltext"]), "normal+42")
|
|
3278
|
+
eq_(
|
|
3279
|
+
"persisted" in compData["computed"],
|
|
3280
|
+
testing.requires.computed_columns_reflect_persisted.enabled,
|
|
3281
|
+
)
|
|
3282
|
+
if testing.requires.computed_columns_reflect_persisted.enabled:
|
|
3283
|
+
eq_(
|
|
3284
|
+
compData["computed"]["persisted"],
|
|
3285
|
+
testing.requires.computed_columns_default_persisted.enabled,
|
|
3286
|
+
)
|
|
3287
|
+
|
|
3288
|
+
def check_column(self, data, column, sqltext, persisted):
|
|
3289
|
+
is_true("computed" in data[column])
|
|
3290
|
+
compData = data[column]["computed"]
|
|
3291
|
+
eq_(self.normalize(compData["sqltext"]), sqltext)
|
|
3292
|
+
if testing.requires.computed_columns_reflect_persisted.enabled:
|
|
3293
|
+
is_true("persisted" in compData)
|
|
3294
|
+
is_(compData["persisted"], persisted)
|
|
3295
|
+
|
|
3296
|
+
def test_get_column_returns_persisted(self):
|
|
3297
|
+
insp = inspect(config.db)
|
|
3298
|
+
|
|
3299
|
+
cols = insp.get_columns("computed_column_table")
|
|
3300
|
+
data = {c["name"]: c for c in cols}
|
|
3301
|
+
|
|
3302
|
+
self.check_column(
|
|
3303
|
+
data,
|
|
3304
|
+
"computed_no_flag",
|
|
3305
|
+
"normal+42",
|
|
3306
|
+
testing.requires.computed_columns_default_persisted.enabled,
|
|
3307
|
+
)
|
|
3308
|
+
if testing.requires.computed_columns_virtual.enabled:
|
|
3309
|
+
self.check_column(
|
|
3310
|
+
data,
|
|
3311
|
+
"computed_virtual",
|
|
3312
|
+
"normal+2",
|
|
3313
|
+
False,
|
|
3314
|
+
)
|
|
3315
|
+
if testing.requires.computed_columns_stored.enabled:
|
|
3316
|
+
self.check_column(
|
|
3317
|
+
data,
|
|
3318
|
+
"computed_stored",
|
|
3319
|
+
"normal-42",
|
|
3320
|
+
True,
|
|
3321
|
+
)
|
|
3322
|
+
|
|
3323
|
+
@testing.requires.schemas
|
|
3324
|
+
def test_get_column_returns_persisted_with_schema(self):
|
|
3325
|
+
insp = inspect(config.db)
|
|
3326
|
+
|
|
3327
|
+
cols = insp.get_columns(
|
|
3328
|
+
"computed_column_table", schema=config.test_schema
|
|
3329
|
+
)
|
|
3330
|
+
data = {c["name"]: c for c in cols}
|
|
3331
|
+
|
|
3332
|
+
self.check_column(
|
|
3333
|
+
data,
|
|
3334
|
+
"computed_no_flag",
|
|
3335
|
+
"normal/42",
|
|
3336
|
+
testing.requires.computed_columns_default_persisted.enabled,
|
|
3337
|
+
)
|
|
3338
|
+
if testing.requires.computed_columns_virtual.enabled:
|
|
3339
|
+
self.check_column(
|
|
3340
|
+
data,
|
|
3341
|
+
"computed_virtual",
|
|
3342
|
+
"normal/2",
|
|
3343
|
+
False,
|
|
3344
|
+
)
|
|
3345
|
+
if testing.requires.computed_columns_stored.enabled:
|
|
3346
|
+
self.check_column(
|
|
3347
|
+
data,
|
|
3348
|
+
"computed_stored",
|
|
3349
|
+
"normal*42",
|
|
3350
|
+
True,
|
|
3351
|
+
)
|
|
3352
|
+
|
|
3353
|
+
|
|
3354
|
+
class IdentityReflectionTest(fixtures.TablesTest):
|
|
3355
|
+
run_inserts = run_deletes = None
|
|
3356
|
+
|
|
3357
|
+
__sparse_driver_backend__ = True
|
|
3358
|
+
__requires__ = ("identity_columns", "table_reflection")
|
|
3359
|
+
|
|
3360
|
+
@classmethod
|
|
3361
|
+
def define_tables(cls, metadata):
|
|
3362
|
+
Table(
|
|
3363
|
+
"t1",
|
|
3364
|
+
metadata,
|
|
3365
|
+
Column("normal", Integer),
|
|
3366
|
+
Column("id1", Integer, Identity()),
|
|
3367
|
+
)
|
|
3368
|
+
Table(
|
|
3369
|
+
"t2",
|
|
3370
|
+
metadata,
|
|
3371
|
+
Column(
|
|
3372
|
+
"id2",
|
|
3373
|
+
Integer,
|
|
3374
|
+
Identity(
|
|
3375
|
+
always=True,
|
|
3376
|
+
start=2,
|
|
3377
|
+
increment=3,
|
|
3378
|
+
minvalue=-2,
|
|
3379
|
+
maxvalue=42,
|
|
3380
|
+
cycle=True,
|
|
3381
|
+
cache=4,
|
|
3382
|
+
),
|
|
3383
|
+
),
|
|
3384
|
+
)
|
|
3385
|
+
if testing.requires.schemas.enabled:
|
|
3386
|
+
Table(
|
|
3387
|
+
"t1",
|
|
3388
|
+
metadata,
|
|
3389
|
+
Column("normal", Integer),
|
|
3390
|
+
Column("id1", Integer, Identity(always=True, start=20)),
|
|
3391
|
+
schema=config.test_schema,
|
|
3392
|
+
)
|
|
3393
|
+
|
|
3394
|
+
def check(self, value, exp, approx):
|
|
3395
|
+
if testing.requires.identity_columns_standard.enabled:
|
|
3396
|
+
common_keys = (
|
|
3397
|
+
"always",
|
|
3398
|
+
"start",
|
|
3399
|
+
"increment",
|
|
3400
|
+
"minvalue",
|
|
3401
|
+
"maxvalue",
|
|
3402
|
+
"cycle",
|
|
3403
|
+
"cache",
|
|
3404
|
+
)
|
|
3405
|
+
for k in list(value):
|
|
3406
|
+
if k not in common_keys:
|
|
3407
|
+
value.pop(k)
|
|
3408
|
+
if approx:
|
|
3409
|
+
eq_(len(value), len(exp))
|
|
3410
|
+
for k in value:
|
|
3411
|
+
if k == "minvalue":
|
|
3412
|
+
is_true(value[k] <= exp[k])
|
|
3413
|
+
elif k in {"maxvalue", "cache"}:
|
|
3414
|
+
is_true(value[k] >= exp[k])
|
|
3415
|
+
else:
|
|
3416
|
+
eq_(value[k], exp[k], k)
|
|
3417
|
+
else:
|
|
3418
|
+
eq_(value, exp)
|
|
3419
|
+
else:
|
|
3420
|
+
eq_(value["start"], exp["start"])
|
|
3421
|
+
eq_(value["increment"], exp["increment"])
|
|
3422
|
+
|
|
3423
|
+
def test_reflect_identity(self):
|
|
3424
|
+
insp = inspect(config.db)
|
|
3425
|
+
|
|
3426
|
+
cols = insp.get_columns("t1") + insp.get_columns("t2")
|
|
3427
|
+
for col in cols:
|
|
3428
|
+
if col["name"] == "normal":
|
|
3429
|
+
is_false("identity" in col)
|
|
3430
|
+
elif col["name"] == "id1":
|
|
3431
|
+
if "autoincrement" in col:
|
|
3432
|
+
is_true(col["autoincrement"])
|
|
3433
|
+
eq_(col["default"], None)
|
|
3434
|
+
is_true("identity" in col)
|
|
3435
|
+
self.check(
|
|
3436
|
+
col["identity"],
|
|
3437
|
+
dict(
|
|
3438
|
+
always=False,
|
|
3439
|
+
start=1,
|
|
3440
|
+
increment=1,
|
|
3441
|
+
minvalue=1,
|
|
3442
|
+
maxvalue=2147483647,
|
|
3443
|
+
cycle=False,
|
|
3444
|
+
cache=1,
|
|
3445
|
+
),
|
|
3446
|
+
approx=True,
|
|
3447
|
+
)
|
|
3448
|
+
elif col["name"] == "id2":
|
|
3449
|
+
if "autoincrement" in col:
|
|
3450
|
+
is_true(col["autoincrement"])
|
|
3451
|
+
eq_(col["default"], None)
|
|
3452
|
+
is_true("identity" in col)
|
|
3453
|
+
self.check(
|
|
3454
|
+
col["identity"],
|
|
3455
|
+
dict(
|
|
3456
|
+
always=True,
|
|
3457
|
+
start=2,
|
|
3458
|
+
increment=3,
|
|
3459
|
+
minvalue=-2,
|
|
3460
|
+
maxvalue=42,
|
|
3461
|
+
cycle=True,
|
|
3462
|
+
cache=4,
|
|
3463
|
+
),
|
|
3464
|
+
approx=False,
|
|
3465
|
+
)
|
|
3466
|
+
|
|
3467
|
+
@testing.requires.schemas
|
|
3468
|
+
def test_reflect_identity_schema(self):
|
|
3469
|
+
insp = inspect(config.db)
|
|
3470
|
+
|
|
3471
|
+
cols = insp.get_columns("t1", schema=config.test_schema)
|
|
3472
|
+
for col in cols:
|
|
3473
|
+
if col["name"] == "normal":
|
|
3474
|
+
is_false("identity" in col)
|
|
3475
|
+
elif col["name"] == "id1":
|
|
3476
|
+
if "autoincrement" in col:
|
|
3477
|
+
is_true(col["autoincrement"])
|
|
3478
|
+
eq_(col["default"], None)
|
|
3479
|
+
is_true("identity" in col)
|
|
3480
|
+
self.check(
|
|
3481
|
+
col["identity"],
|
|
3482
|
+
dict(
|
|
3483
|
+
always=True,
|
|
3484
|
+
start=20,
|
|
3485
|
+
increment=1,
|
|
3486
|
+
minvalue=1,
|
|
3487
|
+
maxvalue=2147483647,
|
|
3488
|
+
cycle=False,
|
|
3489
|
+
cache=1,
|
|
3490
|
+
),
|
|
3491
|
+
approx=True,
|
|
3492
|
+
)
|
|
3493
|
+
|
|
3494
|
+
|
|
3495
|
+
class CompositeKeyReflectionTest(fixtures.TablesTest):
|
|
3496
|
+
__sparse_driver_backend__ = True
|
|
3497
|
+
|
|
3498
|
+
@classmethod
|
|
3499
|
+
def define_tables(cls, metadata):
|
|
3500
|
+
tb1 = Table(
|
|
3501
|
+
"tb1",
|
|
3502
|
+
metadata,
|
|
3503
|
+
Column("id", Integer),
|
|
3504
|
+
Column("attr", Integer),
|
|
3505
|
+
Column("name", sql_types.VARCHAR(20)),
|
|
3506
|
+
sa.PrimaryKeyConstraint("name", "id", "attr", name="pk_tb1"),
|
|
3507
|
+
schema=None,
|
|
3508
|
+
test_needs_fk=True,
|
|
3509
|
+
)
|
|
3510
|
+
Table(
|
|
3511
|
+
"tb2",
|
|
3512
|
+
metadata,
|
|
3513
|
+
Column("id", Integer, primary_key=True),
|
|
3514
|
+
Column("pid", Integer),
|
|
3515
|
+
Column("pattr", Integer),
|
|
3516
|
+
Column("pname", sql_types.VARCHAR(20)),
|
|
3517
|
+
sa.ForeignKeyConstraint(
|
|
3518
|
+
["pname", "pid", "pattr"],
|
|
3519
|
+
[tb1.c.name, tb1.c.id, tb1.c.attr],
|
|
3520
|
+
name="fk_tb1_name_id_attr",
|
|
3521
|
+
),
|
|
3522
|
+
schema=None,
|
|
3523
|
+
test_needs_fk=True,
|
|
3524
|
+
)
|
|
3525
|
+
|
|
3526
|
+
@testing.requires.primary_key_constraint_reflection
|
|
3527
|
+
def test_pk_column_order(self, connection):
|
|
3528
|
+
# test for issue #5661
|
|
3529
|
+
insp = inspect(connection)
|
|
3530
|
+
primary_key = insp.get_pk_constraint(self.tables.tb1.name)
|
|
3531
|
+
eq_(primary_key.get("constrained_columns"), ["name", "id", "attr"])
|
|
3532
|
+
|
|
3533
|
+
@testing.requires.foreign_key_constraint_reflection
|
|
3534
|
+
def test_fk_column_order(self, connection):
|
|
3535
|
+
# test for issue #5661
|
|
3536
|
+
insp = inspect(connection)
|
|
3537
|
+
foreign_keys = insp.get_foreign_keys(self.tables.tb2.name)
|
|
3538
|
+
eq_(len(foreign_keys), 1)
|
|
3539
|
+
fkey1 = foreign_keys[0]
|
|
3540
|
+
eq_(fkey1.get("referred_columns"), ["name", "id", "attr"])
|
|
3541
|
+
eq_(fkey1.get("constrained_columns"), ["pname", "pid", "pattr"])
|
|
3542
|
+
|
|
3543
|
+
|
|
3544
|
+
__all__ = (
|
|
3545
|
+
"ComponentReflectionTest",
|
|
3546
|
+
"ComponentReflectionTestExtra",
|
|
3547
|
+
"TableNoColumnsTest",
|
|
3548
|
+
"QuotedNameArgumentTest",
|
|
3549
|
+
"BizarroCharacterTest",
|
|
3550
|
+
"HasTableTest",
|
|
3551
|
+
"HasIndexTest",
|
|
3552
|
+
"NormalizedNameTest",
|
|
3553
|
+
"ComputedReflectionTest",
|
|
3554
|
+
"IdentityReflectionTest",
|
|
3555
|
+
"CompositeKeyReflectionTest",
|
|
3556
|
+
"TempTableElementsTest",
|
|
3557
|
+
)
|