Skip to content

Commit ba3ba23

Browse files
Mikuláš Poulmikicz
authored andcommitted
Support indexes in mat. views in different schemas
1 parent 53c587a commit ba3ba23

File tree

3 files changed

+66
-6
lines changed

3 files changed

+66
-6
lines changed

django_pgviews/view.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from django.core import exceptions
1212
from django.db import connections, router, transaction
1313
from django.db import models
14+
from django.db.backends.postgresql.schema import DatabaseSchemaEditor
1415
from django.db.models.query import QuerySet
1516

1617
from django_pgviews.db import get_fields_by_name
@@ -96,7 +97,8 @@ def _drop_mat_view(cursor, view_name):
9697

9798

9899
def _concurrent_index_name(view_name, concurrent_index):
99-
return view_name + "_" + "_".join([s.strip() for s in concurrent_index.split(",")]) + "_index"
100+
# replace . with _ in view_name in case the table is in a schema
101+
return view_name.replace(".", "_") + "_" + "_".join([s.strip() for s in concurrent_index.split(",")]) + "_index"
100102

101103

102104
def _create_concurrent_index(cursor, view_name, concurrent_index):
@@ -109,6 +111,21 @@ def _create_concurrent_index(cursor, view_name, concurrent_index):
109111
)
110112

111113

114+
class CustomSchemaEditor(DatabaseSchemaEditor):
115+
def _create_index_sql(self, *args, **kwargs):
116+
"""
117+
Override to handle indexes in custom schemas, when the schema is explicitly set.
118+
"""
119+
statement = super()._create_index_sql(*args, **kwargs)
120+
121+
model = args[0]
122+
123+
if "." in model._meta.db_table: # by default the table it's quoted, but we need it non-quoted
124+
statement.parts["table"] = model._meta.db_table
125+
126+
return statement
127+
128+
112129
def _ensure_indexes(connection, cursor, view_cls, schema_name_log):
113130
"""
114131
This function gets called when a materialized view is deemed not needing a re-create. That is however only a part
@@ -133,17 +150,19 @@ def _ensure_indexes(connection, cursor, view_cls, schema_name_log):
133150
concurrent_index_name = None
134151

135152
for index_name in existing_indexes - required_indexes:
136-
cursor.execute(f"DROP INDEX {index_name}")
153+
cursor.execute(f"DROP INDEX {vschema}.{index_name}")
137154
logger.info("pgview dropped index %s on view %s (%s)", index_name, view_name, schema_name_log)
138155

156+
schema_editor: DatabaseSchemaEditor = CustomSchemaEditor(connection)
157+
139158
for index_name in required_indexes - existing_indexes:
140159
if index_name == concurrent_index_name:
141160
_create_concurrent_index(cursor, view_name, concurrent_index)
142161
logger.info("pgview created concurrent index on view %s (%s)", view_name, schema_name_log)
143162
else:
144163
for index in indexes:
145164
if index.name == index_name:
146-
connection.schema_editor().add_index(view_cls, index)
165+
schema_editor.add_index(view_cls, index)
147166
logger.info("pgview created index %s on view %s (%s)", index.name, view_name, schema_name_log)
148167
break
149168

@@ -218,7 +237,7 @@ def create_materialized_view(connection, view_cls, check_sql_changed=False):
218237
logger.info("pgview created concurrent index on view %s (%s)", view_name, schema_name_log)
219238

220239
if view_cls._meta.indexes:
221-
schema_editor = connection.schema_editor()
240+
schema_editor = CustomSchemaEditor(connection)
222241

223242
for index in view_cls._meta.indexes:
224243
schema_editor.add_index(view_cls, index)

tests/test_project/test_project/viewtest/models.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@ class Meta:
6868
indexes = [models.Index(fields=["model"])]
6969

7070

71+
class CustomSchemaMaterializedRelatedViewWithIndex(view.ReadOnlyMaterializedView):
72+
concurrent_index = "id"
73+
sql = """SELECT id AS model_id, id FROM viewtest_testmodel"""
74+
model = models.ForeignKey(TestModel, on_delete=models.DO_NOTHING)
75+
76+
class Meta:
77+
db_table = "test_schema.my_custom_view_with_index"
78+
managed = False
79+
indexes = [models.Index(fields=["model"])]
80+
81+
7182
class CustomSchemaView(view.ReadOnlyView):
7283
sql = """SELECT id AS model_id, id FROM viewtest_testmodel"""
7384
model = models.ForeignKey(TestModel, on_delete=models.DO_NOTHING)

tests/test_project/test_project/viewtest/tests.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from django.utils import timezone
1616

1717
from django_pgviews.signals import view_synced, all_views_synced
18+
from django_pgviews.view import _schema_and_name
1819

1920
from . import models
2021
from .models import LatestSuperusers
@@ -28,7 +29,9 @@ def create_test_schema(sender, app_config, using, **kwargs):
2829

2930

3031
def get_list_of_indexes(cursor, cls):
31-
cursor.execute("SELECT indexname FROM pg_indexes WHERE tablename = %s", [cls._meta.db_table])
32+
schema, table = _schema_and_name(cursor.connection, cls._meta.db_table)
33+
34+
cursor.execute("SELECT indexname FROM pg_indexes WHERE tablename = %s AND schemaname = %s", [table, schema])
3235
return set(x[0] for x in cursor.fetchall())
3336

3437

@@ -144,6 +147,7 @@ def test_materialized_view_indexes(self):
144147
self.assertIn("viewtest_materializedrelatedviewwithindex_id_index", orig_indexes)
145148
self.assertEqual(len(orig_indexes), 2)
146149

150+
# drop current indexes, add some random ones which will get deleted
147151
for index_name in orig_indexes:
148152
cursor.execute(f"DROP INDEX {index_name}")
149153

@@ -163,6 +167,32 @@ def test_materialized_view_indexes(self):
163167

164168
self.assertEqual(new_indexes, orig_indexes)
165169

170+
def test_materialized_view_schema_indexes(self):
171+
with connection.cursor() as cursor:
172+
orig_indexes = get_list_of_indexes(cursor, models.CustomSchemaMaterializedRelatedViewWithIndex)
173+
174+
self.assertEqual(len(orig_indexes), 2)
175+
self.assertIn("test_schema_my_custom_view_with_index_id_index", orig_indexes)
176+
177+
# drop current indexes, add some random ones which will get deleted
178+
for index_name in orig_indexes:
179+
cursor.execute(f"DROP INDEX test_schema.{index_name}")
180+
181+
cursor.execute(
182+
"CREATE UNIQUE INDEX my_custom_view_with_index_concurrent_idx "
183+
"ON test_schema.my_custom_view_with_index (id)"
184+
)
185+
cursor.execute(
186+
"CREATE INDEX my_custom_view_with_index_some_idx ON test_schema.my_custom_view_with_index (model_id)"
187+
)
188+
189+
call_command("sync_pgviews", materialized_views_check_sql_changed=True)
190+
191+
with connection.cursor() as cursor:
192+
new_indexes = get_list_of_indexes(cursor, models.CustomSchemaMaterializedRelatedViewWithIndex)
193+
194+
self.assertEqual(new_indexes, orig_indexes)
195+
166196
def test_materialized_view_with_no_data(self):
167197
"""
168198
Test a materialized view with no data works correctly
@@ -203,7 +233,7 @@ def on_all_views_synced(sender, **kwargs):
203233
call_command("sync_pgviews", update=False)
204234

205235
# All views went through syncing
206-
self.assertEqual(len(synced_views), 11)
236+
self.assertEqual(len(synced_views), 12)
207237
self.assertEqual(all_views_were_synced[0], True)
208238
self.assertFalse(expected)
209239

0 commit comments

Comments
 (0)