pyoq-sql 1.0.2__py3-none-any.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.
- pyoq/__init__.py +10 -0
- pyoq/__main__.py +5 -0
- pyoq/_native.pyi +5 -0
- pyoq/cli/__init__.py +5 -0
- pyoq/cli/commands.py +270 -0
- pyoq/cli/defaults.py +98 -0
- pyoq/cli/services.py +97 -0
- pyoq/config/__init__.py +31 -0
- pyoq/config/connection.py +161 -0
- pyoq/config/loader.py +289 -0
- pyoq/config/models.py +245 -0
- pyoq/config/values.py +142 -0
- pyoq/descriptors.py +165 -0
- pyoq/diagnostics/__init__.py +68 -0
- pyoq/diagnostics/budget.py +136 -0
- pyoq/diagnostics/events.py +137 -0
- pyoq/diagnostics/fingerprint.py +267 -0
- pyoq/diagnostics/instrumented.py +237 -0
- pyoq/diagnostics/metrics.py +61 -0
- pyoq/diagnostics/observation.py +227 -0
- pyoq/diagnostics/scoped.py +103 -0
- pyoq/django/__init__.py +15 -0
- pyoq/django/apps.py +17 -0
- pyoq/django/execution.py +317 -0
- pyoq/django/generation.py +59 -0
- pyoq/django/management/__init__.py +0 -0
- pyoq/django/management/commands/__init__.py +0 -0
- pyoq/django/management/commands/makemigrations.py +53 -0
- pyoq/django/management/commands/pyoq_codegen.py +75 -0
- pyoq/django/parameters.py +101 -0
- pyoq/django/schema.py +379 -0
- pyoq/django/settings.py +87 -0
- pyoq/django/timeouts.py +105 -0
- pyoq/dsl/__init__.py +64 -0
- pyoq/dsl/aio/__init__.py +31 -0
- pyoq/dsl/aio/context.py +295 -0
- pyoq/dsl/aio/queries.py +335 -0
- pyoq/dsl/aio/writes.py +368 -0
- pyoq/dsl/context.py +326 -0
- pyoq/dsl/entry.py +37 -0
- pyoq/dsl/labels.py +36 -0
- pyoq/dsl/queries.py +339 -0
- pyoq/dsl/result.py +164 -0
- pyoq/dsl/writes.py +360 -0
- pyoq/errors.py +317 -0
- pyoq/fastapi/__init__.py +32 -0
- pyoq/fastapi/dependencies.py +167 -0
- pyoq/fastapi/lifespan.py +119 -0
- pyoq/fetching/__init__.py +55 -0
- pyoq/fetching/collections.py +136 -0
- pyoq/fetching/execution.py +587 -0
- pyoq/fetching/joined.py +79 -0
- pyoq/fetching/nesting.py +183 -0
- pyoq/fetching/plans.py +541 -0
- pyoq/fetching/select_in.py +149 -0
- pyoq/fetching/tables.py +110 -0
- pyoq/generation/__init__.py +54 -0
- pyoq/generation/cleanup.py +44 -0
- pyoq/generation/contracts.py +248 -0
- pyoq/generation/drift.py +169 -0
- pyoq/generation/lock.py +33 -0
- pyoq/generation/manifest.py +114 -0
- pyoq/generation/model.py +1001 -0
- pyoq/generation/pipeline.py +119 -0
- pyoq/generation/rendering/__init__.py +5 -0
- pyoq/generation/rendering/domains.py +51 -0
- pyoq/generation/rendering/enums.py +29 -0
- pyoq/generation/rendering/exports.py +70 -0
- pyoq/generation/rendering/imports.py +63 -0
- pyoq/generation/rendering/package.py +56 -0
- pyoq/generation/rendering/relations.py +133 -0
- pyoq/generation/rendering/routines.py +396 -0
- pyoq/generation/rendering/rows.py +79 -0
- pyoq/generation/rendering/source.py +121 -0
- pyoq/generation/rendering/tables.py +300 -0
- pyoq/generation/rendering/writes.py +514 -0
- pyoq/generation/validation.py +27 -0
- pyoq/generation/writer.py +184 -0
- pyoq/hydration/__init__.py +24 -0
- pyoq/hydration/engine.py +155 -0
- pyoq/hydration/identity.py +194 -0
- pyoq/hydration/plan.py +116 -0
- pyoq/migrations/__init__.py +9 -0
- pyoq/migrations/alembic.py +106 -0
- pyoq/migrations/hooks.py +75 -0
- pyoq/naming.py +261 -0
- pyoq/policies/__init__.py +47 -0
- pyoq/policies/bypass.py +122 -0
- pyoq/policies/governed.py +430 -0
- pyoq/policies/model.py +242 -0
- pyoq/policies/rewriting.py +263 -0
- pyoq/py.typed +1 -0
- pyoq/query/__init__.py +312 -0
- pyoq/query/aggregates.py +172 -0
- pyoq/query/arrays.py +65 -0
- pyoq/query/binding.py +52 -0
- pyoq/query/capabilities.py +317 -0
- pyoq/query/casts.py +73 -0
- pyoq/query/choices.py +185 -0
- pyoq/query/decoding.py +360 -0
- pyoq/query/documents.py +56 -0
- pyoq/query/execution/__init__.py +63 -0
- pyoq/query/execution/aio/__init__.py +31 -0
- pyoq/query/execution/aio/operations.py +228 -0
- pyoq/query/execution/aio/pooling.py +233 -0
- pyoq/query/execution/aio/streaming.py +161 -0
- pyoq/query/execution/aio/transactions.py +105 -0
- pyoq/query/execution/batch.py +96 -0
- pyoq/query/execution/binding_style.py +30 -0
- pyoq/query/execution/compilation.py +48 -0
- pyoq/query/execution/context.py +61 -0
- pyoq/query/execution/control.py +50 -0
- pyoq/query/execution/operations.py +224 -0
- pyoq/query/execution/planning.py +107 -0
- pyoq/query/execution/pooling.py +279 -0
- pyoq/query/execution/results.py +36 -0
- pyoq/query/execution/streaming.py +178 -0
- pyoq/query/execution/transactions.py +95 -0
- pyoq/query/expressions.py +1200 -0
- pyoq/query/fields.py +60 -0
- pyoq/query/mysql/__init__.py +59 -0
- pyoq/query/mysql/aio/__init__.py +38 -0
- pyoq/query/mysql/aio/commands.py +389 -0
- pyoq/query/mysql/aio/driver.py +196 -0
- pyoq/query/mysql/aio/executor.py +123 -0
- pyoq/query/mysql/aio/factory.py +26 -0
- pyoq/query/mysql/aio/operations.py +38 -0
- pyoq/query/mysql/aio/pool.py +53 -0
- pyoq/query/mysql/aio/transactions.py +313 -0
- pyoq/query/mysql/commands.py +354 -0
- pyoq/query/mysql/compiler.py +134 -0
- pyoq/query/mysql/context.py +20 -0
- pyoq/query/mysql/executor.py +126 -0
- pyoq/query/mysql/expressions.py +244 -0
- pyoq/query/mysql/factory.py +46 -0
- pyoq/query/mysql/health.py +66 -0
- pyoq/query/mysql/identifiers.py +9 -0
- pyoq/query/mysql/model.py +79 -0
- pyoq/query/mysql/operations.py +43 -0
- pyoq/query/mysql/parameters.py +69 -0
- pyoq/query/mysql/planning.py +20 -0
- pyoq/query/mysql/pool.py +67 -0
- pyoq/query/mysql/transactions.py +331 -0
- pyoq/query/mysql/writes.py +73 -0
- pyoq/query/nodes.py +750 -0
- pyoq/query/postgres/__init__.py +48 -0
- pyoq/query/postgres/aio/__init__.py +25 -0
- pyoq/query/postgres/aio/bulk.py +56 -0
- pyoq/query/postgres/aio/commands.py +264 -0
- pyoq/query/postgres/aio/executor.py +152 -0
- pyoq/query/postgres/aio/factory.py +26 -0
- pyoq/query/postgres/aio/operations.py +26 -0
- pyoq/query/postgres/aio/pool.py +40 -0
- pyoq/query/postgres/aio/transactions.py +295 -0
- pyoq/query/postgres/bulk.py +62 -0
- pyoq/query/postgres/commands.py +238 -0
- pyoq/query/postgres/compiler.py +114 -0
- pyoq/query/postgres/context.py +20 -0
- pyoq/query/postgres/executor.py +147 -0
- pyoq/query/postgres/expressions.py +311 -0
- pyoq/query/postgres/factory.py +24 -0
- pyoq/query/postgres/health.py +24 -0
- pyoq/query/postgres/identifiers.py +9 -0
- pyoq/query/postgres/model.py +81 -0
- pyoq/query/postgres/operations.py +25 -0
- pyoq/query/postgres/parameters.py +71 -0
- pyoq/query/postgres/planning.py +20 -0
- pyoq/query/postgres/pool.py +52 -0
- pyoq/query/postgres/transactions.py +295 -0
- pyoq/query/postgres/writes.py +37 -0
- pyoq/query/projections.py +105 -0
- pyoq/query/raw.py +90 -0
- pyoq/query/recursion.py +265 -0
- pyoq/query/rendering/__init__.py +1 -0
- pyoq/query/rendering/expressions.py +913 -0
- pyoq/query/rendering/identifiers.py +40 -0
- pyoq/query/rendering/projections.py +63 -0
- pyoq/query/rendering/queries.py +334 -0
- pyoq/query/rendering/sources.py +66 -0
- pyoq/query/rendering/writes.py +176 -0
- pyoq/query/results.py +459 -0
- pyoq/query/routines.py +196 -0
- pyoq/query/rows.py +156 -0
- pyoq/query/select.py +793 -0
- pyoq/query/select_nodes.py +277 -0
- pyoq/query/sources.py +236 -0
- pyoq/query/sqlite/__init__.py +43 -0
- pyoq/query/sqlite/commands.py +201 -0
- pyoq/query/sqlite/compiler.py +139 -0
- pyoq/query/sqlite/context.py +20 -0
- pyoq/query/sqlite/executor.py +119 -0
- pyoq/query/sqlite/expressions.py +224 -0
- pyoq/query/sqlite/factory.py +32 -0
- pyoq/query/sqlite/health.py +28 -0
- pyoq/query/sqlite/identifiers.py +9 -0
- pyoq/query/sqlite/model.py +73 -0
- pyoq/query/sqlite/operations.py +36 -0
- pyoq/query/sqlite/parameters.py +50 -0
- pyoq/query/sqlite/planning.py +20 -0
- pyoq/query/sqlite/pool.py +50 -0
- pyoq/query/sqlite/streaming.py +13 -0
- pyoq/query/sqlite/transactions.py +274 -0
- pyoq/query/sqlite/writes.py +35 -0
- pyoq/query/statements.py +27 -0
- pyoq/query/values.py +23 -0
- pyoq/query/vendor.py +162 -0
- pyoq/query/windows.py +424 -0
- pyoq/query/write_nodes.py +174 -0
- pyoq/query/writes.py +628 -0
- pyoq/relations/__init__.py +66 -0
- pyoq/relations/batching.py +219 -0
- pyoq/relations/derivation.py +111 -0
- pyoq/relations/fetching.py +355 -0
- pyoq/relations/graph.py +245 -0
- pyoq/relations/loading.py +74 -0
- pyoq/relations/model.py +75 -0
- pyoq/relations/planning.py +206 -0
- pyoq/runtime/__init__.py +9 -0
- pyoq/runtime/kernels.py +25 -0
- pyoq/runtime/python.py +43 -0
- pyoq/runtime/selection.py +73 -0
- pyoq/sanic/__init__.py +32 -0
- pyoq/sanic/scope.py +197 -0
- pyoq/sanic/workers.py +129 -0
- pyoq/schema/__init__.py +108 -0
- pyoq/schema/codec.py +711 -0
- pyoq/schema/models.py +604 -0
- pyoq/schema/mysql/__init__.py +16 -0
- pyoq/schema/mysql/connection.py +73 -0
- pyoq/schema/mysql/dsn.py +72 -0
- pyoq/schema/mysql/records.py +354 -0
- pyoq/schema/mysql/reflection.py +309 -0
- pyoq/schema/mysql/source.py +30 -0
- pyoq/schema/mysql/sql.py +128 -0
- pyoq/schema/mysql/types.py +105 -0
- pyoq/schema/postgres/__init__.py +13 -0
- pyoq/schema/postgres/connection.py +63 -0
- pyoq/schema/postgres/records.py +384 -0
- pyoq/schema/postgres/reflection.py +466 -0
- pyoq/schema/postgres/source.py +30 -0
- pyoq/schema/postgres/sql.py +246 -0
- pyoq/schema/postgres/types.py +98 -0
- pyoq/schema/registry.py +45 -0
- pyoq/schema/source.py +15 -0
- pyoq/schema/sqlite/__init__.py +6 -0
- pyoq/schema/sqlite/connection.py +54 -0
- pyoq/schema/sqlite/records.py +167 -0
- pyoq/schema/sqlite/reflection.py +393 -0
- pyoq/schema/sqlite/source.py +30 -0
- pyoq/schema/sqlite/sql.py +254 -0
- pyoq/schema/sqlite/types.py +74 -0
- pyoq/serving/__init__.py +23 -0
- pyoq/serving/databases.py +107 -0
- pyoq/serving/opening.py +331 -0
- pyoq/snapshots/__init__.py +20 -0
- pyoq/snapshots/drift.py +312 -0
- pyoq/snapshots/files.py +96 -0
- pyoq/snapshots/routing.py +40 -0
- pyoq/snapshots/source.py +33 -0
- pyoq/tracing/__init__.py +5 -0
- pyoq/tracing/spans.py +89 -0
- pyoq/unset.py +14 -0
- pyoq_sql-1.0.2.dist-info/METADATA +3050 -0
- pyoq_sql-1.0.2.dist-info/RECORD +267 -0
- pyoq_sql-1.0.2.dist-info/WHEEL +4 -0
- pyoq_sql-1.0.2.dist-info/entry_points.txt +3 -0
- pyoq_sql-1.0.2.dist-info/licenses/LICENSE +373 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""PostgreSQL system catalog queries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
SCHEMA_OBJECTS = """
|
|
6
|
+
SELECT
|
|
7
|
+
n.nspname,
|
|
8
|
+
c.relname,
|
|
9
|
+
c.relkind,
|
|
10
|
+
obj_description(c.oid, 'pg_class'),
|
|
11
|
+
CASE
|
|
12
|
+
WHEN c.relkind IN ('v', 'm') THEN pg_get_viewdef(c.oid, true)
|
|
13
|
+
END
|
|
14
|
+
FROM pg_class AS c
|
|
15
|
+
JOIN pg_namespace AS n ON n.oid = c.relnamespace
|
|
16
|
+
WHERE c.relkind IN ('r', 'p', 'v', 'm')
|
|
17
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
18
|
+
AND n.nspname NOT LIKE 'pg\\_toast%'
|
|
19
|
+
AND n.nspname NOT LIKE 'pg\\_temp%'
|
|
20
|
+
ORDER BY n.nspname, c.relname
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
COLUMNS = """
|
|
24
|
+
SELECT
|
|
25
|
+
n.nspname,
|
|
26
|
+
c.relname,
|
|
27
|
+
a.attname,
|
|
28
|
+
format_type(a.atttypid, a.atttypmod),
|
|
29
|
+
NOT a.attnotnull AND NOT t.typnotnull,
|
|
30
|
+
t.typtype,
|
|
31
|
+
tn.nspname,
|
|
32
|
+
t.typname,
|
|
33
|
+
CASE
|
|
34
|
+
WHEN t.typtype = 'd'
|
|
35
|
+
THEN format_type(t.typbasetype, t.typtypmod)
|
|
36
|
+
END,
|
|
37
|
+
pg_get_expr(d.adbin, d.adrelid),
|
|
38
|
+
a.attidentity,
|
|
39
|
+
a.attgenerated,
|
|
40
|
+
col_description(c.oid, a.attnum)
|
|
41
|
+
FROM pg_attribute AS a
|
|
42
|
+
JOIN pg_class AS c ON c.oid = a.attrelid
|
|
43
|
+
JOIN pg_namespace AS n ON n.oid = c.relnamespace
|
|
44
|
+
JOIN pg_type AS t ON t.oid = a.atttypid
|
|
45
|
+
JOIN pg_namespace AS tn ON tn.oid = t.typnamespace
|
|
46
|
+
LEFT JOIN pg_attrdef AS d ON d.adrelid = a.attrelid AND d.adnum = a.attnum
|
|
47
|
+
WHERE a.attnum > 0
|
|
48
|
+
AND NOT a.attisdropped
|
|
49
|
+
AND c.relkind IN ('r', 'p', 'v', 'm')
|
|
50
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
51
|
+
AND n.nspname NOT LIKE 'pg\\_toast%'
|
|
52
|
+
AND n.nspname NOT LIKE 'pg\\_temp%'
|
|
53
|
+
ORDER BY n.nspname, c.relname, a.attnum
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
CONSTRAINTS = """
|
|
57
|
+
SELECT
|
|
58
|
+
n.nspname,
|
|
59
|
+
c.relname,
|
|
60
|
+
con.conname,
|
|
61
|
+
con.contype,
|
|
62
|
+
pg_get_constraintdef(con.oid, true),
|
|
63
|
+
ARRAY(
|
|
64
|
+
SELECT a.attname
|
|
65
|
+
FROM unnest(con.conkey) WITH ORDINALITY AS k(attnum, position)
|
|
66
|
+
JOIN pg_attribute AS a
|
|
67
|
+
ON a.attrelid = con.conrelid AND a.attnum = k.attnum
|
|
68
|
+
ORDER BY k.position
|
|
69
|
+
),
|
|
70
|
+
tn.nspname,
|
|
71
|
+
tc.relname,
|
|
72
|
+
ARRAY(
|
|
73
|
+
SELECT a.attname
|
|
74
|
+
FROM unnest(con.confkey) WITH ORDINALITY AS k(attnum, position)
|
|
75
|
+
JOIN pg_attribute AS a
|
|
76
|
+
ON a.attrelid = con.confrelid AND a.attnum = k.attnum
|
|
77
|
+
ORDER BY k.position
|
|
78
|
+
),
|
|
79
|
+
con.confupdtype,
|
|
80
|
+
con.confdeltype
|
|
81
|
+
FROM pg_constraint AS con
|
|
82
|
+
JOIN pg_class AS c ON c.oid = con.conrelid
|
|
83
|
+
JOIN pg_namespace AS n ON n.oid = c.relnamespace
|
|
84
|
+
LEFT JOIN pg_class AS tc ON tc.oid = con.confrelid
|
|
85
|
+
LEFT JOIN pg_namespace AS tn ON tn.oid = tc.relnamespace
|
|
86
|
+
WHERE con.contype IN ('p', 'u', 'f', 'c')
|
|
87
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
88
|
+
AND n.nspname NOT LIKE 'pg\\_toast%'
|
|
89
|
+
AND n.nspname NOT LIKE 'pg\\_temp%'
|
|
90
|
+
ORDER BY n.nspname, c.relname, con.contype, con.conname
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
INDEXES = """
|
|
94
|
+
SELECT
|
|
95
|
+
n.nspname,
|
|
96
|
+
c.relname,
|
|
97
|
+
ic.relname,
|
|
98
|
+
i.indisunique,
|
|
99
|
+
am.amname,
|
|
100
|
+
pg_get_expr(i.indpred, i.indrelid, true),
|
|
101
|
+
ARRAY(
|
|
102
|
+
SELECT k.attnum
|
|
103
|
+
FROM unnest(i.indkey) WITH ORDINALITY AS k(attnum, position)
|
|
104
|
+
ORDER BY k.position
|
|
105
|
+
),
|
|
106
|
+
ARRAY(
|
|
107
|
+
SELECT a.attname
|
|
108
|
+
FROM unnest(i.indkey) WITH ORDINALITY AS k(attnum, position)
|
|
109
|
+
LEFT JOIN pg_attribute AS a
|
|
110
|
+
ON a.attrelid = i.indrelid AND a.attnum = k.attnum
|
|
111
|
+
ORDER BY k.position
|
|
112
|
+
),
|
|
113
|
+
ARRAY(
|
|
114
|
+
SELECT pg_get_indexdef(i.indexrelid, k.position::int, true)
|
|
115
|
+
FROM unnest(i.indkey) WITH ORDINALITY AS k(attnum, position)
|
|
116
|
+
ORDER BY k.position
|
|
117
|
+
),
|
|
118
|
+
ARRAY(
|
|
119
|
+
SELECT o.option
|
|
120
|
+
FROM unnest(i.indoption) WITH ORDINALITY AS o(option, position)
|
|
121
|
+
ORDER BY o.position
|
|
122
|
+
)
|
|
123
|
+
FROM pg_index AS i
|
|
124
|
+
JOIN pg_class AS c ON c.oid = i.indrelid
|
|
125
|
+
JOIN pg_class AS ic ON ic.oid = i.indexrelid
|
|
126
|
+
JOIN pg_namespace AS n ON n.oid = c.relnamespace
|
|
127
|
+
JOIN pg_am AS am ON am.oid = ic.relam
|
|
128
|
+
WHERE NOT i.indisprimary
|
|
129
|
+
AND c.relkind IN ('r', 'p')
|
|
130
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
131
|
+
AND n.nspname NOT LIKE 'pg\\_toast%'
|
|
132
|
+
AND n.nspname NOT LIKE 'pg\\_temp%'
|
|
133
|
+
ORDER BY n.nspname, c.relname, ic.relname
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
ENUMS = """
|
|
137
|
+
SELECT
|
|
138
|
+
n.nspname,
|
|
139
|
+
t.typname,
|
|
140
|
+
e.enumlabel,
|
|
141
|
+
obj_description(t.oid)
|
|
142
|
+
FROM pg_type AS t
|
|
143
|
+
JOIN pg_namespace AS n ON n.oid = t.typnamespace
|
|
144
|
+
JOIN pg_enum AS e ON e.enumtypid = t.oid
|
|
145
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
146
|
+
AND n.nspname NOT LIKE 'pg\\_toast%'
|
|
147
|
+
AND n.nspname NOT LIKE 'pg\\_temp%'
|
|
148
|
+
ORDER BY n.nspname, t.typname, e.enumsortorder
|
|
149
|
+
"""
|
|
150
|
+
"""Every enum type and its labels, in the order the database keeps them.
|
|
151
|
+
|
|
152
|
+
The order is the type's own, not alphabetical, because an enum compares and
|
|
153
|
+
sorts by it and a schema that reordered them would describe a different type.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
DOMAINS = """
|
|
158
|
+
SELECT
|
|
159
|
+
n.nspname,
|
|
160
|
+
t.typname,
|
|
161
|
+
format_type(t.typbasetype, t.typtypmod),
|
|
162
|
+
t.typnotnull,
|
|
163
|
+
t.typdefault,
|
|
164
|
+
obj_description(t.oid)
|
|
165
|
+
FROM pg_type AS t
|
|
166
|
+
JOIN pg_namespace AS n ON n.oid = t.typnamespace
|
|
167
|
+
WHERE t.typtype = 'd'
|
|
168
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
169
|
+
AND n.nspname NOT LIKE 'pg\\_toast%'
|
|
170
|
+
AND n.nspname NOT LIKE 'pg\\_temp%'
|
|
171
|
+
ORDER BY n.nspname, t.typname
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
DOMAIN_CHECKS = """
|
|
175
|
+
SELECT
|
|
176
|
+
n.nspname,
|
|
177
|
+
t.typname,
|
|
178
|
+
c.conname,
|
|
179
|
+
pg_get_constraintdef(c.oid)
|
|
180
|
+
FROM pg_constraint AS c
|
|
181
|
+
JOIN pg_type AS t ON t.oid = c.contypid
|
|
182
|
+
JOIN pg_namespace AS n ON n.oid = t.typnamespace
|
|
183
|
+
WHERE c.contype = 'c'
|
|
184
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
185
|
+
ORDER BY n.nspname, t.typname, c.conname
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
ROUTINES = """
|
|
189
|
+
SELECT
|
|
190
|
+
n.nspname,
|
|
191
|
+
p.proname,
|
|
192
|
+
p.prokind,
|
|
193
|
+
p.oid,
|
|
194
|
+
CASE WHEN p.prokind = 'f' THEN format_type(p.prorettype, NULL) END,
|
|
195
|
+
a.ordinality,
|
|
196
|
+
p.proargnames[a.ordinality],
|
|
197
|
+
COALESCE(p.proargmodes[a.ordinality], 'i'),
|
|
198
|
+
format_type(a.argument_type, NULL),
|
|
199
|
+
rn.nspname,
|
|
200
|
+
rt.typname,
|
|
201
|
+
rt.typtype,
|
|
202
|
+
format_type(rt.typbasetype, NULL),
|
|
203
|
+
COALESCE(rd.typelem <> 0, FALSE),
|
|
204
|
+
an.nspname,
|
|
205
|
+
at.typname,
|
|
206
|
+
at.typtype,
|
|
207
|
+
format_type(at.typbasetype, NULL),
|
|
208
|
+
COALESCE(ad.typelem <> 0, FALSE),
|
|
209
|
+
COALESCE(at.typnotnull, FALSE),
|
|
210
|
+
p.pronargdefaults
|
|
211
|
+
FROM pg_proc AS p
|
|
212
|
+
JOIN pg_namespace AS n ON n.oid = p.pronamespace
|
|
213
|
+
LEFT JOIN pg_type AS rd ON rd.oid = p.prorettype
|
|
214
|
+
LEFT JOIN pg_type AS rt ON rt.oid = COALESCE(NULLIF(rd.typelem, 0), rd.oid)
|
|
215
|
+
LEFT JOIN pg_namespace AS rn ON rn.oid = rt.typnamespace
|
|
216
|
+
LEFT JOIN LATERAL unnest(
|
|
217
|
+
COALESCE(p.proallargtypes, p.proargtypes::oid[])
|
|
218
|
+
) WITH ORDINALITY AS a(argument_type, ordinality) ON TRUE
|
|
219
|
+
LEFT JOIN pg_type AS ad ON ad.oid = a.argument_type
|
|
220
|
+
LEFT JOIN pg_type AS at ON at.oid = COALESCE(NULLIF(ad.typelem, 0), ad.oid)
|
|
221
|
+
LEFT JOIN pg_namespace AS an ON an.oid = at.typnamespace
|
|
222
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
223
|
+
AND p.prokind IN ('f', 'p')
|
|
224
|
+
ORDER BY n.nspname, p.proname, p.oid, a.ordinality
|
|
225
|
+
"""
|
|
226
|
+
"""One row per parameter, so a routine of no parameters is still one row.
|
|
227
|
+
|
|
228
|
+
The routine's own identifier separates two that share a name, which is what
|
|
229
|
+
keeps an overload from being mistaken for the other.
|
|
230
|
+
|
|
231
|
+
PostgreSQL states no nullability for a parameter itself. A parameter typed as
|
|
232
|
+
a domain that forbids null cannot be given one, and ``typnotnull`` is where
|
|
233
|
+
the catalog says so, so that is what is read here.
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
__all__ = (
|
|
238
|
+
"COLUMNS",
|
|
239
|
+
"CONSTRAINTS",
|
|
240
|
+
"DOMAINS",
|
|
241
|
+
"DOMAIN_CHECKS",
|
|
242
|
+
"ENUMS",
|
|
243
|
+
"INDEXES",
|
|
244
|
+
"ROUTINES",
|
|
245
|
+
"SCHEMA_OBJECTS",
|
|
246
|
+
)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""PostgreSQL declared-type normalization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from pyoq.schema.models import SqlType, TypeKind
|
|
8
|
+
|
|
9
|
+
_TYPE_ARGUMENTS = re.compile(r"^\s*([^()\[\]]*?)\s*(?:\(([^()]*)\))?\s*((?:\[\])*)\s*$")
|
|
10
|
+
|
|
11
|
+
_EXACT_KINDS = {
|
|
12
|
+
"BOOL": TypeKind.BOOLEAN,
|
|
13
|
+
"BOOLEAN": TypeKind.BOOLEAN,
|
|
14
|
+
"SMALLINT": TypeKind.SMALL_INTEGER,
|
|
15
|
+
"INT2": TypeKind.SMALL_INTEGER,
|
|
16
|
+
"SMALLSERIAL": TypeKind.SMALL_INTEGER,
|
|
17
|
+
"SERIAL2": TypeKind.SMALL_INTEGER,
|
|
18
|
+
"INTEGER": TypeKind.INTEGER,
|
|
19
|
+
"INT": TypeKind.INTEGER,
|
|
20
|
+
"INT4": TypeKind.INTEGER,
|
|
21
|
+
"SERIAL": TypeKind.INTEGER,
|
|
22
|
+
"SERIAL4": TypeKind.INTEGER,
|
|
23
|
+
"BIGINT": TypeKind.BIG_INTEGER,
|
|
24
|
+
"INT8": TypeKind.BIG_INTEGER,
|
|
25
|
+
"BIGSERIAL": TypeKind.BIG_INTEGER,
|
|
26
|
+
"SERIAL8": TypeKind.BIG_INTEGER,
|
|
27
|
+
"NUMERIC": TypeKind.DECIMAL,
|
|
28
|
+
"DECIMAL": TypeKind.DECIMAL,
|
|
29
|
+
"MONEY": TypeKind.DECIMAL,
|
|
30
|
+
"REAL": TypeKind.REAL,
|
|
31
|
+
"FLOAT4": TypeKind.REAL,
|
|
32
|
+
"DOUBLE PRECISION": TypeKind.DOUBLE,
|
|
33
|
+
"FLOAT8": TypeKind.DOUBLE,
|
|
34
|
+
"TEXT": TypeKind.STRING,
|
|
35
|
+
"VARCHAR": TypeKind.STRING,
|
|
36
|
+
"CHARACTER VARYING": TypeKind.STRING,
|
|
37
|
+
"CHAR": TypeKind.STRING,
|
|
38
|
+
"CHARACTER": TypeKind.STRING,
|
|
39
|
+
"BPCHAR": TypeKind.STRING,
|
|
40
|
+
"NAME": TypeKind.STRING,
|
|
41
|
+
"CITEXT": TypeKind.STRING,
|
|
42
|
+
"BYTEA": TypeKind.BINARY,
|
|
43
|
+
"DATE": TypeKind.DATE,
|
|
44
|
+
"TIME": TypeKind.TIME,
|
|
45
|
+
"TIME WITHOUT TIME ZONE": TypeKind.TIME,
|
|
46
|
+
"TIME WITH TIME ZONE": TypeKind.TIME,
|
|
47
|
+
"TIMETZ": TypeKind.TIME,
|
|
48
|
+
"TIMESTAMP": TypeKind.DATETIME,
|
|
49
|
+
"TIMESTAMP WITHOUT TIME ZONE": TypeKind.DATETIME,
|
|
50
|
+
"TIMESTAMP WITH TIME ZONE": TypeKind.DATETIME,
|
|
51
|
+
"TIMESTAMPTZ": TypeKind.DATETIME,
|
|
52
|
+
"INTERVAL": TypeKind.INTERVAL,
|
|
53
|
+
"UUID": TypeKind.UUID,
|
|
54
|
+
"JSON": TypeKind.JSON,
|
|
55
|
+
"JSONB": TypeKind.JSON,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_SIZED_KINDS = frozenset({TypeKind.STRING})
|
|
59
|
+
_PRECISION_KINDS = frozenset({TypeKind.DECIMAL})
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def postgres_type(declared_type: str) -> SqlType:
|
|
63
|
+
source_name = declared_type.strip() or "text"
|
|
64
|
+
match = _TYPE_ARGUMENTS.fullmatch(source_name)
|
|
65
|
+
if match is None:
|
|
66
|
+
return SqlType(TypeKind.OTHER, source_name)
|
|
67
|
+
base_name, argument_text, array_suffix = match.groups()
|
|
68
|
+
if array_suffix:
|
|
69
|
+
element_source = source_name[: source_name.rindex("[]")].rstrip()
|
|
70
|
+
return SqlType(
|
|
71
|
+
TypeKind.ARRAY,
|
|
72
|
+
source_name,
|
|
73
|
+
element_type=postgres_type(element_source),
|
|
74
|
+
)
|
|
75
|
+
kind = _EXACT_KINDS.get(_normalized(base_name), TypeKind.OTHER)
|
|
76
|
+
arguments = _numeric_arguments(argument_text)
|
|
77
|
+
if kind in _SIZED_KINDS and len(arguments) == 1:
|
|
78
|
+
return SqlType(kind, source_name, length=arguments[0])
|
|
79
|
+
if kind in _PRECISION_KINDS and arguments:
|
|
80
|
+
scale = arguments[1] if len(arguments) == 2 else None
|
|
81
|
+
return SqlType(kind, source_name, precision=arguments[0], scale=scale)
|
|
82
|
+
return SqlType(kind, source_name)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _normalized(value: str) -> str:
|
|
86
|
+
return " ".join(value.upper().split())
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _numeric_arguments(value: str | None) -> tuple[int, ...]:
|
|
90
|
+
if value is None:
|
|
91
|
+
return ()
|
|
92
|
+
parts = tuple(part.strip() for part in value.split(","))
|
|
93
|
+
if not 1 <= len(parts) <= 2 or any(not part.isdecimal() for part in parts):
|
|
94
|
+
return ()
|
|
95
|
+
return tuple(int(part) for part in parts)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
__all__ = ("postgres_type",)
|
pyoq/schema/registry.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Dialect to schema source resolution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import TypeAlias
|
|
8
|
+
|
|
9
|
+
from pyoq.config import Configuration, DatabaseDialect
|
|
10
|
+
from pyoq.schema.models import SchemaSnapshot
|
|
11
|
+
from pyoq.schema.mysql.source import MySQLSchemaSource
|
|
12
|
+
from pyoq.schema.postgres.source import PostgresSchemaSource
|
|
13
|
+
from pyoq.schema.source import SchemaSource
|
|
14
|
+
from pyoq.schema.sqlite.source import SQLiteSchemaSource
|
|
15
|
+
|
|
16
|
+
SchemaSourceFactory: TypeAlias = Callable[[], SchemaSource]
|
|
17
|
+
|
|
18
|
+
_SCHEMA_SOURCES: Mapping[DatabaseDialect, SchemaSourceFactory] = {
|
|
19
|
+
DatabaseDialect.MYSQL: MySQLSchemaSource,
|
|
20
|
+
DatabaseDialect.POSTGRES: PostgresSchemaSource,
|
|
21
|
+
DatabaseDialect.SQLITE: SQLiteSchemaSource,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def supported_dialects() -> tuple[DatabaseDialect, ...]:
|
|
26
|
+
return tuple(_SCHEMA_SOURCES)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def schema_source_for(dialect: DatabaseDialect, /) -> SchemaSource:
|
|
30
|
+
return _SCHEMA_SOURCES[dialect]()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True, slots=True)
|
|
34
|
+
class DialectRoutingSchemaSource:
|
|
35
|
+
def load(self, configuration: Configuration) -> SchemaSnapshot:
|
|
36
|
+
dialect = configuration.active_profile.dialect
|
|
37
|
+
return schema_source_for(dialect).load(configuration)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__all__ = (
|
|
41
|
+
"DialectRoutingSchemaSource",
|
|
42
|
+
"SchemaSourceFactory",
|
|
43
|
+
"schema_source_for",
|
|
44
|
+
"supported_dialects",
|
|
45
|
+
)
|
pyoq/schema/source.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Dialect-neutral schema source contract."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Protocol
|
|
6
|
+
|
|
7
|
+
from pyoq.config import Configuration
|
|
8
|
+
from pyoq.schema.models import SchemaSnapshot
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SchemaSource(Protocol):
|
|
12
|
+
def load(self, configuration: Configuration) -> SchemaSnapshot: ...
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = ("SchemaSource",)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Read-only SQLite connection ownership."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sqlite3
|
|
6
|
+
from contextlib import AbstractContextManager, closing
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from pyoq.config import Configuration
|
|
11
|
+
from pyoq.errors import ConfigurationValidationError, SchemaInspectionError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True, slots=True)
|
|
15
|
+
class SQLiteConnectionProvider:
|
|
16
|
+
def open(
|
|
17
|
+
self,
|
|
18
|
+
configuration: Configuration,
|
|
19
|
+
) -> AbstractContextManager[sqlite3.Connection]:
|
|
20
|
+
path = _database_path(configuration)
|
|
21
|
+
_require_regular_database(path)
|
|
22
|
+
try:
|
|
23
|
+
connection = sqlite3.connect(
|
|
24
|
+
f"{path.as_uri()}?mode=ro",
|
|
25
|
+
uri=True,
|
|
26
|
+
check_same_thread=True,
|
|
27
|
+
)
|
|
28
|
+
except sqlite3.Error as error:
|
|
29
|
+
message = f"unable to open SQLite database: {error}"
|
|
30
|
+
raise SchemaInspectionError(message) from error
|
|
31
|
+
return closing(connection)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _database_path(configuration: Configuration) -> Path:
|
|
35
|
+
configured = configuration.active_database_path
|
|
36
|
+
if configured is not None:
|
|
37
|
+
return configured
|
|
38
|
+
resolved = configuration.active_profile.resolve_dsn()
|
|
39
|
+
if resolved is None:
|
|
40
|
+
message = "SQLite profile requires database-path or a dsn setting"
|
|
41
|
+
raise ConfigurationValidationError(message)
|
|
42
|
+
return Path(resolved.reveal()).expanduser().resolve()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _require_regular_database(path: Path) -> None:
|
|
46
|
+
if not path.exists():
|
|
47
|
+
message = f"SQLite database does not exist: {path}"
|
|
48
|
+
raise SchemaInspectionError(message)
|
|
49
|
+
if not path.is_file():
|
|
50
|
+
message = f"SQLite database is not a regular file: {path}"
|
|
51
|
+
raise SchemaInspectionError(message)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
__all__ = ("SQLiteConnectionProvider",)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Typed SQLite metadata records."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import TypeAlias, cast
|
|
7
|
+
|
|
8
|
+
from pyoq.errors import SchemaInspectionError
|
|
9
|
+
|
|
10
|
+
DatabaseRow: TypeAlias = tuple[object, ...]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True, slots=True)
|
|
14
|
+
class SchemaObjectRecord:
|
|
15
|
+
kind: str
|
|
16
|
+
name: str
|
|
17
|
+
sql: str | None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class ColumnRecord:
|
|
22
|
+
position: int
|
|
23
|
+
name: str
|
|
24
|
+
declared_type: str
|
|
25
|
+
not_null: bool
|
|
26
|
+
default: str | None
|
|
27
|
+
primary_ordinal: int
|
|
28
|
+
hidden: int
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True, slots=True)
|
|
32
|
+
class IndexRecord:
|
|
33
|
+
position: int
|
|
34
|
+
name: str
|
|
35
|
+
unique: bool
|
|
36
|
+
origin: str
|
|
37
|
+
partial: bool
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True, slots=True)
|
|
41
|
+
class IndexTermRecord:
|
|
42
|
+
position: int
|
|
43
|
+
column_position: int
|
|
44
|
+
column_name: str | None
|
|
45
|
+
descending: bool
|
|
46
|
+
key_term: bool
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True, slots=True)
|
|
50
|
+
class ForeignKeyRecord:
|
|
51
|
+
relation_id: int
|
|
52
|
+
position: int
|
|
53
|
+
target_table: str
|
|
54
|
+
source_column: str
|
|
55
|
+
target_column: str | None
|
|
56
|
+
on_update: str
|
|
57
|
+
on_delete: str
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def schema_object(row: object) -> SchemaObjectRecord:
|
|
61
|
+
values = _row(row, 3, "schema object")
|
|
62
|
+
return SchemaObjectRecord(
|
|
63
|
+
_text(values[0], "schema object kind"),
|
|
64
|
+
_text(values[1], "schema object name"),
|
|
65
|
+
_optional_text(values[2], "schema object SQL"),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def column(row: object) -> ColumnRecord:
|
|
70
|
+
values = _row(row, 7, "column")
|
|
71
|
+
return ColumnRecord(
|
|
72
|
+
_integer(values[0], "column position"),
|
|
73
|
+
_text(values[1], "column name"),
|
|
74
|
+
_optional_text(values[2], "column type") or "ANY",
|
|
75
|
+
_flag(values[3], "column not-null flag"),
|
|
76
|
+
_optional_text(values[4], "column default"),
|
|
77
|
+
_integer(values[5], "column primary-key position"),
|
|
78
|
+
_integer(values[6], "column hidden flag"),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def index(row: object) -> IndexRecord:
|
|
83
|
+
values = _row(row, 5, "index")
|
|
84
|
+
return IndexRecord(
|
|
85
|
+
_integer(values[0], "index position"),
|
|
86
|
+
_text(values[1], "index name"),
|
|
87
|
+
_flag(values[2], "index unique flag"),
|
|
88
|
+
_text(values[3], "index origin"),
|
|
89
|
+
_flag(values[4], "index partial flag"),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def index_term(row: object) -> IndexTermRecord:
|
|
94
|
+
values = _row(row, 6, "index term")
|
|
95
|
+
return IndexTermRecord(
|
|
96
|
+
_integer(values[0], "index term position"),
|
|
97
|
+
_integer(values[1], "index term column position"),
|
|
98
|
+
_optional_text(values[2], "index term column name"),
|
|
99
|
+
_flag(values[3], "index descending flag"),
|
|
100
|
+
_flag(values[5], "index key-term flag"),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def foreign_key(row: object) -> ForeignKeyRecord:
|
|
105
|
+
values = _row(row, 8, "foreign key")
|
|
106
|
+
return ForeignKeyRecord(
|
|
107
|
+
_integer(values[0], "foreign-key id"),
|
|
108
|
+
_integer(values[1], "foreign-key position"),
|
|
109
|
+
_text(values[2], "foreign-key target table"),
|
|
110
|
+
_text(values[3], "foreign-key source column"),
|
|
111
|
+
_optional_text(values[4], "foreign-key target column"),
|
|
112
|
+
_text(values[5], "foreign-key update action"),
|
|
113
|
+
_text(values[6], "foreign-key delete action"),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _row(value: object, size: int, label: str) -> DatabaseRow:
|
|
118
|
+
if not isinstance(value, tuple):
|
|
119
|
+
message = f"invalid SQLite {label} metadata"
|
|
120
|
+
raise SchemaInspectionError(message)
|
|
121
|
+
values = cast(DatabaseRow, value)
|
|
122
|
+
if len(values) != size:
|
|
123
|
+
message = f"invalid SQLite {label} metadata"
|
|
124
|
+
raise SchemaInspectionError(message)
|
|
125
|
+
return values
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _text(value: object, label: str) -> str:
|
|
129
|
+
if not isinstance(value, str) or not value:
|
|
130
|
+
message = f"invalid SQLite {label}"
|
|
131
|
+
raise SchemaInspectionError(message)
|
|
132
|
+
return value
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _optional_text(value: object, label: str) -> str | None:
|
|
136
|
+
if value is None:
|
|
137
|
+
return None
|
|
138
|
+
return _text(value, label)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _integer(value: object, label: str) -> int:
|
|
142
|
+
if not isinstance(value, int) or isinstance(value, bool):
|
|
143
|
+
message = f"invalid SQLite {label}"
|
|
144
|
+
raise SchemaInspectionError(message)
|
|
145
|
+
return value
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _flag(value: object, label: str) -> bool:
|
|
149
|
+
flag = _integer(value, label)
|
|
150
|
+
if flag not in {0, 1}:
|
|
151
|
+
message = f"invalid SQLite {label}"
|
|
152
|
+
raise SchemaInspectionError(message)
|
|
153
|
+
return bool(flag)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
__all__ = (
|
|
157
|
+
"ColumnRecord",
|
|
158
|
+
"ForeignKeyRecord",
|
|
159
|
+
"IndexRecord",
|
|
160
|
+
"IndexTermRecord",
|
|
161
|
+
"SchemaObjectRecord",
|
|
162
|
+
"column",
|
|
163
|
+
"foreign_key",
|
|
164
|
+
"index",
|
|
165
|
+
"index_term",
|
|
166
|
+
"schema_object",
|
|
167
|
+
)
|