Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #27060: Extended inspectdb so indexes are added to Meta.indexes. #12712

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS --
people who have submitted patches, reported bugs, added translations, helped
answer newbie questions, and generally made Django that much better:

Aaron Cannon <cannona@fireantproductions.com>
Aaron Cannon<cannona@fireantproductions.com>
Aaron Swartz <http://www.aaronsw.com/>
Aaron T. Myers <atmyers@gmail.com>
Abeer Upadhyay <ab.esquarer@gmail.com>
Expand Down Expand Up @@ -288,6 +288,7 @@ answer newbie questions, and generally made Django that much better:
Doug Beck <doug@douglasbeck.com>
Doug Napoleone <doug@dougma.com>
dready <wil@mojipage.com>
Drew Winstel <https://github.com/drewbrew>
Durval Carvalho de Souza <dudurval2@gmail.com>
dusk@woofle.net
Dustyn Gibson <miigotu@gmail.com>
Expand Down Expand Up @@ -1049,7 +1050,7 @@ answer newbie questions, and generally made Django that much better:
Zeynel Özdemir <ozdemir.zynl@gmail.com>
Zlatko Mašek <zlatko.masek@gmail.com>
zriv <https://github.com/zriv>
<Please alphabetize new entries>
<Please alphabetize new entries>

A big THANK YOU goes to:

Expand Down
1 change: 1 addition & 0 deletions django/contrib/gis/db/backends/spatialite/introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,6 @@ def get_constraints(self, cursor, table_name):
"foreign_key": None,
"check": False,
"index": True,
"type": "rtree", # spatial enabled indexes are of type rtree
}
return constraints
42 changes: 41 additions & 1 deletion django/core/management/commands/inspectdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.core.management.base import BaseCommand, CommandError
from django.db import DEFAULT_DB_ALIAS, connections
from django.db.models.constants import LOOKUP_SEP
from django.db.models.indexes import Index


class Command(BaseCommand):
Expand Down Expand Up @@ -378,8 +379,10 @@ def get_meta(
to the given database table name.
"""
unique_together = []
indexes = []
index_comments = []
has_unsupported_constraint = False
for params in constraints.values():
for name, params in constraints.items():
if params["unique"]:
columns = params["columns"]
if None in columns:
Expand All @@ -391,6 +394,32 @@ def get_meta(
unique_together.append(
str(tuple(column_to_field_name[c] for c in columns))
)
elif params["index"] and not params["primary_key"]:
columns = params["columns"]
orders = params.get("orders", [""] * len(columns))

field_names = [
(
f'{"-" if order.lower() == "desc" else ""}'
f"{column_to_field_name[column]}"
)
for column, order in zip(columns, orders)
]
index_str = f"models.Index(fields={field_names!r}, name={name!r})"
comment = ""
if params["type"] not in ["btree", Index.suffix]:
comment = f'Index type is {params["type"]}'
if params["type"] in ["rtree", "spatial"] and len(columns) == 1:
# assume this is a default spatial index, so let's make a note
# of it and continue
index_comments.append(
"Skipped default spatial index for "
f"{column_to_field_name[columns[0]]}"
)
indexes.append("")
continue
indexes.append(index_str)
index_comments.append(comment)
if is_view:
managed_comment = " # Created from a view. Don't remove."
elif is_partition:
Expand All @@ -410,4 +439,15 @@ def get_meta(
meta += [" unique_together = %s" % tup]
if comment:
meta += [f" db_table_comment = {comment!r}"]
if indexes:
meta.append(" indexes = (")
for line, comment in zip(indexes, index_comments):
if line:
if comment:
meta.append(f" {line}, # {comment}")
else:
meta.append(f" {line},")
else:
meta.append(f" # {comment}")
meta.append(" )")
return meta
10 changes: 10 additions & 0 deletions tests/gis_tests/inspectapp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,13 @@ class Fields3D(models.Model):

class Meta:
required_db_features = {"supports_3d_storage"}


class Indexes(models.Model):
point = models.PointField()
name = models.CharField(max_length=5)
other = models.IntegerField()

class Meta:
required_db_features = {"supports_3d_storage"}
indexes = [models.Index(fields=["name", "other"], name="name_plus_other")]
23 changes: 23 additions & 0 deletions tests/gis_tests/inspectapp/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,29 @@ def test_3d_columns(self):
self.assertIn("line = models.GeometryField(", output)
self.assertIn("poly = models.GeometryField(", output)

def test_indexes(self):
out = StringIO()
call_command(
"inspectdb",
table_name_filter=lambda tn: tn.startswith("inspectapp_indexes"),
stdout=out,
)
output = out.getvalue()
self.assertIn(
" indexes = ",
output,
msg="inspectdb should generate indexes.",
)
# there should not be an index on a single-column geo field
# (assumed to be default)
index = "models.Index(fields=['point'], name="
self.assertNotIn(index, output)
# but our manually-created one should
self.assertIn(
"models.Index(fields=['name', 'other'], name='name_plus_other')",
output,
)


@modify_settings(
INSTALLED_APPS={"append": "django.contrib.gis"},
Expand Down
13 changes: 13 additions & 0 deletions tests/inspectdb/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,16 @@ class DbComment(models.Model):
class Meta:
db_table_comment = "Custom table comment"
required_db_features = {"supports_comments"}


class Indexes(models.Model):
field1 = models.IntegerField()
field2 = models.CharField(max_length=5)
from_field = models.IntegerField(unique=True, db_column="from")

class Meta:
indexes = [
models.Index(fields=["field1", "field2"], name="my_index"),
models.Index(fields=["field2", "-from_field"], name="my_other_index"),
models.Index(fields=["from_field"]),
]
43 changes: 42 additions & 1 deletion tests/inspectdb/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
from django.core.management import call_command
from django.db import connection
from django.db.backends.base.introspection import TableInfo
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
from django.test import (
TestCase,
TransactionTestCase,
skipIfDBFeature,
skipUnlessDBFeature,
)

from .models import PeopleMoreData, test_collation

Expand Down Expand Up @@ -392,6 +397,42 @@ def test_unique_together_meta(self):
# are given an integer suffix.
self.assertIn("('non_unique_column', 'non_unique_column_0')", fields)

@skipUnlessDBFeature("supports_index_column_ordering")
def test_indexes(self):
out = StringIO()
call_command("inspectdb", "inspectdb_indexes", stdout=out)
output = out.getvalue()
self.assertIn(" indexes = (", output)
# our two named indexes are there
self.assertIn("my_index", output)
self.assertIn("my_other_index", output)
# we have a total of three indexes
matches = re.findall(r"models.Index\(", output)
self.assertEqual(len(matches), 3)
# and our three column defs are in place
self.assertIn("['from_field']", output)
self.assertIn("['field1', 'field2']", output)
# including descending sort
self.assertIn("['field2', '-from_field']", output)

@skipIfDBFeature("supports_index_column_ordering")
def test_indexes_no_order(self):
out = StringIO()
call_command("inspectdb", "inspectdb_indexes", stdout=out)
output = out.getvalue()
self.assertIn(" indexes = (", output)
# our two named indexes are there
self.assertIn("my_index", output)
self.assertIn("my_other_index", output)
# we have a total of three indexes
matches = re.findall(r"models.Index\(", output)
self.assertEqual(len(matches), 3)
# and our three column defs are in place
self.assertIn("['from_field']", output)
self.assertIn("['field1', 'field2']", output)
# ignoring the descending sort
self.assertIn("['field2', 'from_field']", output)

@skipUnless(connection.vendor == "postgresql", "PostgreSQL specific SQL")
def test_unsupported_unique_together(self):
"""Unsupported index types (COALESCE here) are skipped."""
Expand Down