Refs #29722 -- Added introspection of materialized views for PostgreSQL.

This commit is contained in:
Nick Pope 2018-09-12 01:23:35 +01:00 committed by Tim Graham
parent 45ef3df7d0
commit bf8b625a3b
4 changed files with 50 additions and 12 deletions

View File

@ -44,11 +44,11 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
SELECT c.relname, c.relkind
FROM pg_catalog.pg_class c
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 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 [
TableInfo(row[0], mapping[row[1]])
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
interface.
"""
# As cursor.description does not return reliably the nullable property,
# we have to query the information_schema (#7783)
# Query the pg_catalog tables as cursor.description does not reliably
# return the nullable property and information_schema.columns does not
# contain details of materialized views.
cursor.execute("""
SELECT column_name, is_nullable, column_default
FROM information_schema.columns
WHERE table_name = %s""", [table_name])
SELECT
a.attname AS column_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()}
cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
return [
FieldInfo(*line[0:6], field_map[line.name][0] == 'YES', field_map[line.name][1])
for line in cursor.description
]
return [FieldInfo(*line[0:6], *field_map[line.name]) for line in cursor.description]
def get_sequences(self, cursor, table_name, table_fields=()):
cursor.execute("""

View File

@ -402,10 +402,12 @@ PostgreSQL
^^^^^^^^^^
* Models are created for foreign tables.
* Models are created for materialized views if
:option:`--include-views` is used.
.. versionchanged:: 2.2
Support for foreign tables was added.
Support for foreign tables and materialized views was added.
.. django-admin-option:: --database DATABASE

View File

@ -179,6 +179,9 @@ Management Commands
* :djadmin:`inspectdb` now creates models for foreign tables on PostgreSQL.
* :option:`inspectdb --include-views` now creates models for materialized views
on PostgreSQL.
Migrations
~~~~~~~~~~

View File

@ -310,6 +310,30 @@ class InspectDBTransactionalTests(TransactionTestCase):
with connection.cursor() as cursor:
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')
def test_foreign_data_wrapper(self):
with connection.cursor() as cursor: