Skip to content

Commit 53fe410

Browse files
committed
Add support for creating materialized view without data
Fixes #216
1 parent 939ca84 commit 53fe410

File tree

3 files changed

+56
-5
lines changed

3 files changed

+56
-5
lines changed

psqlextra/backend/migrations/operations/create_materialized_view_model.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ def __init__(
2323
view_options={},
2424
bases=None,
2525
managers=None,
26+
*,
27+
with_data: bool = True,
2628
):
2729
super().__init__(name, fields, options, bases, managers)
2830

2931
self.view_options = view_options or {}
32+
self.with_data = with_data
3033

3134
def state_forwards(self, app_label, state):
3235
state.add_model(
@@ -46,7 +49,9 @@ def database_forwards(self, app_label, schema_editor, from_state, to_state):
4649

4750
model = to_state.apps.get_model(app_label, self.name)
4851
if self.allow_migrate_model(schema_editor.connection.alias, model):
49-
schema_editor.create_materialized_view_model(model)
52+
schema_editor.create_materialized_view_model(
53+
model, with_data=self.with_data
54+
)
5055

5156
def database_backwards(
5257
self, app_label, schema_editor, from_state, to_state
@@ -63,6 +68,9 @@ def deconstruct(self):
6368
if self.view_options:
6469
kwargs["view_options"] = self.view_options
6570

71+
if self.with_data is not False:
72+
kwargs["with_data"] = self.with_data
73+
6674
return name, args, kwargs
6775

6876
def describe(self):

psqlextra/backend/schema.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,12 @@ class PostgresSchemaEditor(SchemaEditor):
6161
sql_create_view = "CREATE VIEW %s AS (%s)"
6262
sql_replace_view = "CREATE OR REPLACE VIEW %s AS (%s)"
6363
sql_drop_view = "DROP VIEW IF EXISTS %s"
64-
sql_create_materialized_view = (
64+
sql_create_materialized_view_with_data = (
6565
"CREATE MATERIALIZED VIEW %s AS (%s) WITH DATA"
6666
)
67+
sql_create_materialized_view_without_data = (
68+
"CREATE MATERIALIZED VIEW %s AS (%s) WITH NO DATA"
69+
)
6770
sql_drop_materialized_view = "DROP MATERIALIZED VIEW %s"
6871
sql_refresh_materialized_view = "REFRESH MATERIALIZED VIEW %s"
6972
sql_refresh_materialized_view_concurrently = (
@@ -548,10 +551,19 @@ def delete_view_model(self, model: Type[Model]) -> None:
548551
sql = self.sql_drop_view % self.quote_name(model._meta.db_table)
549552
self.execute(sql)
550553

551-
def create_materialized_view_model(self, model: Type[Model]) -> None:
554+
def create_materialized_view_model(
555+
self, model: Type[Model], *, with_data: bool = True
556+
) -> None:
552557
"""Creates a new materialized view model."""
553558

554-
self._create_view_model(self.sql_create_materialized_view, model)
559+
if with_data:
560+
self._create_view_model(
561+
self.sql_create_materialized_view_with_data, model
562+
)
563+
else:
564+
self._create_view_model(
565+
self.sql_create_materialized_view_without_data, model
566+
)
555567

556568
def replace_materialized_view_model(self, model: Type[Model]) -> None:
557569
"""Replaces a materialized view with a newer version.

tests/test_schema_editor_view.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
from django.db import connection, models
1+
import pytest
2+
3+
from django.db import OperationalError, connection, models
24

35
from psqlextra.backend.schema import PostgresSchemaEditor
6+
from psqlextra.error import extract_postgres_error_code
47

58
from . import db_introspection
69
from .fake_model import (
@@ -103,6 +106,34 @@ def test_schema_editor_create_delete_materialized_view():
103106
assert model._meta.db_table not in db_introspection.table_names(True)
104107

105108

109+
@pytest.mark.django_db(transaction=True)
110+
def test_schema_editor_create_materialized_view_without_data():
111+
underlying_model = get_fake_model({"name": models.TextField()})
112+
113+
model = define_fake_materialized_view_model(
114+
{"name": models.TextField()},
115+
{"query": underlying_model.objects.filter(name="test1")},
116+
)
117+
118+
underlying_model.objects.create(name="test1")
119+
underlying_model.objects.create(name="test2")
120+
121+
schema_editor = PostgresSchemaEditor(connection)
122+
schema_editor.create_materialized_view_model(model, with_data=False)
123+
124+
with pytest.raises(OperationalError) as exc_info:
125+
list(model.objects.all())
126+
127+
pg_error = extract_postgres_error_code(exc_info.value)
128+
assert pg_error == "55000" # OBJECT_NOT_IN_PREREQUISITE_STATE
129+
130+
model.refresh()
131+
132+
objs = list(model.objects.all())
133+
assert len(objs) == 1
134+
assert objs[0].name == "test1"
135+
136+
106137
def test_schema_editor_replace_materialized_view():
107138
"""Tests whether creating a materialized view and then replacing it with
108139
another one (thus changing the backing query) works as expected."""

0 commit comments

Comments
 (0)