Refs #29722 -- Added introspection of materialized views for PostgreSQL.
This commit is contained in:
parent
45ef3df7d0
commit
bf8b625a3b
|
@ -44,11 +44,11 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||||
SELECT c.relname, c.relkind
|
SELECT c.relname, c.relkind
|
||||||
FROM pg_catalog.pg_class c
|
FROM pg_catalog.pg_class c
|
||||||
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||||
WHERE c.relkind IN ('f', 'r', 'v')
|
WHERE c.relkind IN ('f', 'm', 'r', 'v')
|
||||||
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
||||||
AND pg_catalog.pg_table_is_visible(c.oid)
|
AND pg_catalog.pg_table_is_visible(c.oid)
|
||||||
""")
|
""")
|
||||||
mapping = {'f': 't', 'r': 't', 'v': 'v'}
|
mapping = {'f': 't', 'm': 'v', 'r': 't', 'v': 'v'}
|
||||||
return [
|
return [
|
||||||
TableInfo(row[0], mapping[row[1]])
|
TableInfo(row[0], mapping[row[1]])
|
||||||
for row in cursor.fetchall() if row[0] not in self.ignored_tables
|
for row in cursor.fetchall() if row[0] not in self.ignored_tables
|
||||||
|
@ -59,18 +59,27 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||||
Return a description of the table with the DB-API cursor.description
|
Return a description of the table with the DB-API cursor.description
|
||||||
interface.
|
interface.
|
||||||
"""
|
"""
|
||||||
# As cursor.description does not return reliably the nullable property,
|
# Query the pg_catalog tables as cursor.description does not reliably
|
||||||
# we have to query the information_schema (#7783)
|
# return the nullable property and information_schema.columns does not
|
||||||
|
# contain details of materialized views.
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT column_name, is_nullable, column_default
|
SELECT
|
||||||
FROM information_schema.columns
|
a.attname AS column_name,
|
||||||
WHERE table_name = %s""", [table_name])
|
NOT (a.attnotnull OR (t.typtype = 'd' AND t.typnotnull)) AS is_nullable,
|
||||||
|
pg_get_expr(ad.adbin, ad.adrelid) AS column_default
|
||||||
|
FROM pg_attribute a
|
||||||
|
LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
|
||||||
|
JOIN pg_type t ON a.atttypid = t.oid
|
||||||
|
JOIN pg_class c ON a.attrelid = c.oid
|
||||||
|
JOIN pg_namespace n ON c.relnamespace = n.oid
|
||||||
|
WHERE c.relkind IN ('f', 'm', 'r', 'v')
|
||||||
|
AND c.relname = %s
|
||||||
|
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
||||||
|
AND pg_catalog.pg_table_is_visible(c.oid)
|
||||||
|
""", [table_name])
|
||||||
field_map = {line[0]: line[1:] for line in cursor.fetchall()}
|
field_map = {line[0]: line[1:] for line in cursor.fetchall()}
|
||||||
cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
|
cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
|
||||||
return [
|
return [FieldInfo(*line[0:6], *field_map[line.name]) for line in cursor.description]
|
||||||
FieldInfo(*line[0:6], field_map[line.name][0] == 'YES', field_map[line.name][1])
|
|
||||||
for line in cursor.description
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_sequences(self, cursor, table_name, table_fields=()):
|
def get_sequences(self, cursor, table_name, table_fields=()):
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
|
|
|
@ -402,10 +402,12 @@ PostgreSQL
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
|
||||||
* Models are created for foreign tables.
|
* Models are created for foreign tables.
|
||||||
|
* Models are created for materialized views if
|
||||||
|
:option:`--include-views` is used.
|
||||||
|
|
||||||
.. versionchanged:: 2.2
|
.. versionchanged:: 2.2
|
||||||
|
|
||||||
Support for foreign tables was added.
|
Support for foreign tables and materialized views was added.
|
||||||
|
|
||||||
.. django-admin-option:: --database DATABASE
|
.. django-admin-option:: --database DATABASE
|
||||||
|
|
||||||
|
|
|
@ -179,6 +179,9 @@ Management Commands
|
||||||
|
|
||||||
* :djadmin:`inspectdb` now creates models for foreign tables on PostgreSQL.
|
* :djadmin:`inspectdb` now creates models for foreign tables on PostgreSQL.
|
||||||
|
|
||||||
|
* :option:`inspectdb --include-views` now creates models for materialized views
|
||||||
|
on PostgreSQL.
|
||||||
|
|
||||||
Migrations
|
Migrations
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -310,6 +310,30 @@ class InspectDBTransactionalTests(TransactionTestCase):
|
||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
cursor.execute('DROP VIEW inspectdb_people_view')
|
cursor.execute('DROP VIEW inspectdb_people_view')
|
||||||
|
|
||||||
|
@skipUnless(connection.vendor == 'postgresql', 'PostgreSQL specific SQL')
|
||||||
|
def test_include_materialized_views(self):
|
||||||
|
"""inspectdb --include-views creates models for database materialized views."""
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(
|
||||||
|
'CREATE MATERIALIZED VIEW inspectdb_people_materialized_view AS '
|
||||||
|
'SELECT id, name FROM inspectdb_people'
|
||||||
|
)
|
||||||
|
out = StringIO()
|
||||||
|
view_model = 'class InspectdbPeopleMaterializedView(models.Model):'
|
||||||
|
view_managed = 'managed = False # Created from a view.'
|
||||||
|
try:
|
||||||
|
call_command('inspectdb', table_name_filter=inspectdb_tables_only, stdout=out)
|
||||||
|
no_views_output = out.getvalue()
|
||||||
|
self.assertNotIn(view_model, no_views_output)
|
||||||
|
self.assertNotIn(view_managed, no_views_output)
|
||||||
|
call_command('inspectdb', table_name_filter=inspectdb_tables_only, include_views=True, stdout=out)
|
||||||
|
with_views_output = out.getvalue()
|
||||||
|
self.assertIn(view_model, with_views_output)
|
||||||
|
self.assertIn(view_managed, with_views_output)
|
||||||
|
finally:
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute('DROP MATERIALIZED VIEW IF EXISTS inspectdb_people_materialized_view')
|
||||||
|
|
||||||
@skipUnless(connection.vendor == 'postgresql', 'PostgreSQL specific SQL')
|
@skipUnless(connection.vendor == 'postgresql', 'PostgreSQL specific SQL')
|
||||||
def test_foreign_data_wrapper(self):
|
def test_foreign_data_wrapper(self):
|
||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
|
|
Loading…
Reference in New Issue