1
0
mirror of https://github.com/django/django.git synced 2025-03-31 19:46:42 +00:00

Refs #27098 -- Added introspection for expression-based index on PostgreSQL

Also test it on PostGIS raster fields.
This commit is contained in:
Claude Paroz 2016-08-31 21:05:39 +02:00
parent 0c6fbea59b
commit a1ad896422
2 changed files with 26 additions and 11 deletions

View File

@ -152,7 +152,9 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
def get_constraints(self, cursor, table_name): def get_constraints(self, cursor, table_name):
""" """
Retrieves any constraints or keys (unique, pk, fk, check, index) across one or more columns. Retrieve any constraints or keys (unique, pk, fk, check, index) across
one or more columns. Also retrieve the definition of expression-based
indexes.
""" """
constraints = {} constraints = {}
# Loop over the key table, collecting things as constraints. The column # Loop over the key table, collecting things as constraints. The column
@ -191,15 +193,20 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
"foreign_key": tuple(used_cols.split(".", 1)) if kind == "f" else None, "foreign_key": tuple(used_cols.split(".", 1)) if kind == "f" else None,
"check": kind == "c", "check": kind == "c",
"index": False, "index": False,
"definition": None,
} }
# Now get indexes # Now get indexes
cursor.execute(""" cursor.execute("""
SELECT SELECT
indexname, array_agg(attname), indisunique, indisprimary, indexname, array_agg(attname), indisunique, indisprimary,
array_agg(ordering), amname array_agg(ordering), amname, exprdef
FROM ( FROM (
SELECT SELECT
c2.relname as indexname, idx.*, attr.attname, am.amname, c2.relname as indexname, idx.*, attr.attname, am.amname,
CASE
WHEN idx.indexprs IS NOT NULL THEN
pg_get_indexdef(idx.indexrelid)
END AS exprdef,
CASE CASE
WHEN am.amcanorder THEN WHEN am.amcanorder THEN
CASE (option & 1) CASE (option & 1)
@ -217,18 +224,19 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
LEFT JOIN pg_attribute attr ON attr.attrelid = c.oid AND attr.attnum = idx.key LEFT JOIN pg_attribute attr ON attr.attrelid = c.oid AND attr.attnum = idx.key
WHERE c.relname = %s WHERE c.relname = %s
) s2 ) s2
GROUP BY indexname, indisunique, indisprimary, amname; GROUP BY indexname, indisunique, indisprimary, amname, exprdef;
""", [table_name]) """, [table_name])
for index, columns, unique, primary, orders, type_ in cursor.fetchall(): for index, columns, unique, primary, orders, type_, definition in cursor.fetchall():
if index not in constraints: if index not in constraints:
constraints[index] = { constraints[index] = {
"columns": columns, "columns": columns if columns != [None] else [],
"orders": orders, "orders": orders if orders != [None] else [],
"primary_key": primary, "primary_key": primary,
"unique": unique, "unique": unique,
"foreign_key": None, "foreign_key": None,
"check": False, "check": False,
"index": True, "index": True,
"type": type_, "type": type_,
"definition": definition,
} }
return constraints return constraints

View File

@ -67,9 +67,16 @@ class OperationTests(TransactionTestCase):
expected_count expected_count
) )
def assertSpatialIndexExists(self, table, column): def assertSpatialIndexExists(self, table, column, raster=False):
with connection.cursor() as cursor: with connection.cursor() as cursor:
constraints = connection.introspection.get_constraints(cursor, table) constraints = connection.introspection.get_constraints(cursor, table)
if raster:
self.assertTrue(any(
'st_convexhull(%s)' % column in c['definition']
for c in constraints.values()
if c['definition'] is not None
))
else:
self.assertIn([column], [c['columns'] for c in constraints.values()]) self.assertIn([column], [c['columns'] for c in constraints.values()])
def alter_gis_model(self, migration_class, model_name, field_name, def alter_gis_model(self, migration_class, model_name, field_name,
@ -111,7 +118,7 @@ class OperationTests(TransactionTestCase):
# Test spatial indices when available # Test spatial indices when available
if self.has_spatial_indexes: if self.has_spatial_indexes:
self.assertSpatialIndexExists('gis_neighborhood', 'heatmap') self.assertSpatialIndexExists('gis_neighborhood', 'heatmap', raster=True)
@skipIfDBFeature('supports_raster') @skipIfDBFeature('supports_raster')
def test_create_raster_model_on_db_without_raster_support(self): def test_create_raster_model_on_db_without_raster_support(self):
@ -159,7 +166,7 @@ class OperationTests(TransactionTestCase):
# Test spatial indices when available # Test spatial indices when available
if self.has_spatial_indexes: if self.has_spatial_indexes:
self.assertSpatialIndexExists('gis_neighborhood', 'heatmap') self.assertSpatialIndexExists('gis_neighborhood', 'heatmap', raster=True)
def test_remove_geom_field(self): def test_remove_geom_field(self):
""" """
@ -189,7 +196,7 @@ class OperationTests(TransactionTestCase):
self.assertSpatialIndexExists('gis_neighborhood', 'geom') self.assertSpatialIndexExists('gis_neighborhood', 'geom')
if connection.features.supports_raster: if connection.features.supports_raster:
self.assertSpatialIndexExists('gis_neighborhood', 'rast') self.assertSpatialIndexExists('gis_neighborhood', 'rast', raster=True)
@property @property
def has_spatial_indexes(self): def has_spatial_indexes(self):