Skip to content

Commit 7bbb89f

Browse files
committed
Document views & materialized views support
1 parent a5cde0a commit 7bbb89f

File tree

5 files changed

+215
-0
lines changed

5 files changed

+215
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ With seamless we mean that any features we add will work truly seamlessly. You s
3939

4040
Adds support for PostgreSQL 11.x declarative table partitioning. Fully integrated into Django migrations. Supports all types of partitioning. Includes a command to automatically create time-based partitions.
4141

42+
* **Views & materialized views**
43+
44+
Adds support for creating views & materialized views as any other model. Fully integrated into Django migrations.
4245

4346
* **Locking models & tables**
4447

docs/source/index.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ Explore the documentation to learn about all features:
1919

2020
Adds support for PostgreSQL 11.x declarative table partitioning. Fully integrated into Django migrations. Supports all types of partitioning. Includes a command to automatically create time-based partitions.
2121

22+
* :ref:`Views & materialized views <views_page>`
23+
24+
Adds support for creating views & materialized views as any other model. Fully integrated into Django migrations.
25+
2226
* :ref:`Locking models & tables <locking_page>`
2327

2428
Support for explicit table-level locks.
@@ -63,6 +67,7 @@ For Django 2.2 and older:
6367
conflict_handling
6468
deletion
6569
table_partitioning
70+
views
6671
expressions
6772
annotations
6873
locking

docs/source/snippets/postgres_doc_links.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@
33
.. _hstore: https://www.postgresql.org/docs/11/hstore.html
44
.. _PostgreSQL Declarative Table Partitioning: https://www.postgresql.org/docs/current/ddl-partitioning.html#DDL-PARTITIONING-DECLARATIVE
55
.. _Explicit table-level locks: https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-TABLES
6+
.. _PostgreSQL Views: https://www.postgresql.org/docs/current/sql-createview.html
7+
.. _PostgreSQL Materialized Views: https://www.postgresql.org/docs/current/sql-creatematerializedview.html
8+
.. _PostgreSQL Refresh Materialized Views: https://www.postgresql.org/docs/current/sql-refreshmaterializedview.html
69
.. _PostgreSQL Table Partitioning Limitations: https://www.postgresql.org/docs/current/ddl-partitioning.html#DDL-PARTITIONING-DECLARATIVE-LIMITATIONS

docs/source/table_partitioning.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ Although table partitioning is available in PostgreSQL 10.x, it is highly recomm
4040

4141
PostgreSQL 10.x does not support creating foreign keys to/from partitioned tables and does not automatically create an index across all partitions.
4242

43+
Changing the partition key or partition method
44+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
45+
46+
There is **NO SUPPORT** whatsoever for changing the partitioning key or method on a partitioned model after the initial creation.
47+
48+
Such changes are not detected by ``python manage.py pgmakemigrations`` and there are no pre-built operations for modifying them.
49+
4350
Transforming existing models into partitioned models
4451
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4552

docs/source/views.rst

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
.. include:: ./snippets/postgres_doc_links.rst
2+
3+
.. _views_page:
4+
5+
6+
Views & materialized views
7+
==========================
8+
9+
:class:`~psqlextra.models.PostgresViewModel` and :class:`~psqlextra.models.PostgresMaterializedViewModel` add support for `PostgreSQL Views`_ and `PostgreSQL Materialized Views`_.
10+
11+
.. note::
12+
13+
You can create indices and constraints on (materialized) views just like you would on normal PostgreSQL tables. This is fully supported.
14+
15+
16+
Known limitations
17+
-----------------
18+
19+
Changing view query
20+
*******************
21+
22+
THere is **NO SUPPORT** whatsoever for changing the backing query of a view after the initial creation.
23+
24+
Such changes are not detected by ``python manage.py pgmakemigrations`` and there are no pre-built operations for modifying them.
25+
26+
27+
Creating a (materialized) view
28+
------------------------------
29+
30+
Views are declared like regular Django models with a special base class and an extra option to specify the query backing the view. Once declared, they behave like regular Django models with the exception that you cannot write to them.
31+
32+
Declaring the model
33+
*******************
34+
35+
.. warning::
36+
37+
All fields returned by the backing query must be declared as Django fields. Fields that are returned by the query that aren't declared as Django fields become
38+
part of the view, but will not be visible from Django.
39+
40+
With a queryset
41+
~~~~~~~~~~~~~~~
42+
43+
.. code-block:: python
44+
45+
from django.db import models
46+
47+
from psqlextra.models import PostgresViewModel
48+
49+
50+
class MyView(PostgresViewModel):
51+
name = models.TextField()
52+
somefk__name = models.TextField()
53+
54+
class Meta:
55+
indexes = [models.Index(fields=["name"])]
56+
57+
class ViewMeta:
58+
query = SomeOtherModel.objects.values('id', 'name', 'somefk__name')
59+
60+
class MyMaterializedView(PostgresMaterializedViewModel):
61+
name = models.TextField()
62+
somefk__name = models.TextField()
63+
64+
class Meta:
65+
indexes = [models.Index(fields=["name"])]
66+
67+
class ViewMeta:
68+
query = SomeOtherModel.objects.values('id', 'name', 'somefk__name')
69+
70+
With raw SQL
71+
~~~~~~~~~~~~
72+
73+
Any raw SQL can be used as the backing query for a view. Specify a tuple to pass the values for placeholders.
74+
75+
.. code-block:: python
76+
77+
from django.db import models
78+
79+
from psqlextra.models import PostgresViewModel
80+
81+
82+
class MyView(PostgresViewModel):
83+
name = models.TextField()
84+
somefk__name = models.TextField()
85+
86+
class Meta:
87+
indexes = [models.Index(fields=["name"])]
88+
89+
class ViewMeta:
90+
query = "SELECT id, somefk.name AS somefk__name FROM mytable INNER JOIN somefk ON somefk.id = mytable.somefk_id"
91+
92+
class MyMaterializedView(PostgresMaterializedViewModel):
93+
name = models.TextField()
94+
somefk__name = models.TextField()
95+
96+
class Meta:
97+
indexes = [models.Index(fields=["name"])]
98+
99+
class ViewMeta:
100+
query = ("SELECT id, somefk.name AS somefk__name FROM mytable INNER JOIN somefk ON somefk.id = mytable.somefk_id WHERE id > %s", 1)
101+
102+
103+
With a callable
104+
~~~~~~~~~~~~~~~
105+
106+
A callable can be used when your query depends on settings or other variables that aren't available at evaluation time. The callable can return raw SQL, raw SQL with params or a queryset.
107+
108+
.. code-block:: python
109+
110+
from django.db import models
111+
112+
from psqlextra.models import PostgresViewModel
113+
114+
def _generate_query():
115+
return ("SELECT * FROM sometable WHERE app_name = %s", settings.APP_NAME)
116+
117+
def _build_query():
118+
return SomeTable.objects.filter(app_name=settings.APP_NAME)
119+
120+
121+
class MyView(PostgresViewModel):
122+
name = models.TextField()
123+
somefk__name = models.TextField()
124+
125+
class ViewMeta:
126+
query = _generate_query
127+
128+
class MyMaterializedView(PostgresMaterializedViewModel):
129+
name = models.TextField()
130+
somefk__name = models.TextField()
131+
132+
class ViewMeta:
133+
query = _generate_query
134+
135+
136+
Generating a migration
137+
**********************
138+
Run the following command to automatically generate a migration:
139+
140+
.. code-block:: bash
141+
142+
python manage.py pgmakemigrations
143+
144+
This will generate a migration that creates the view with the specified query as the base.
145+
146+
.. warning::
147+
148+
Always use ``python manage.py pgmakemigrations`` for view models.
149+
150+
The model must be created by the :class:`~psqlextra.backend.migrations.operations.PostgresCreateViewModel` or :class:`~psqlextra.backend.migrations.operations.PostgresCreateMaterializedViewModel` operation.
151+
152+
Do not use the standard ``python manage.py makemigrations`` command for view models. Django will issue a standard :class:`~django:django.db.migrations.operations.CreateModel` operation. Doing this will not create a view and all subsequent operations will fail.
153+
154+
155+
Refreshing a materialized view
156+
------------------------------
157+
158+
Make sure to read the PostgreSQL documentation on refreshing materialized views for caveats: `PostgreSQL Refresh Materialized Views`_.
159+
160+
.. code-block:: python
161+
162+
# Takes an AccessExclusive lock and blocks till table is re-filled
163+
MyViewModel.refresh()
164+
165+
# Allows concurrent read, does block till table is re-filled.
166+
# Warning: Only works if the view was refreshed at least once before.
167+
MyViewModel.refresh(concurrently=True)
168+
169+
170+
Creating a materialized view without data
171+
-----------------------------------------
172+
173+
.. warning::
174+
175+
You cannot query your materialized view until it has been refreshed at least once. After creating the materialized view without data, you must execute a refresh at some point. The first refresh cannot be ``CONCURRENTLY`` (PostgreSQL restriction).
176+
177+
By default, the migration creates the materialized view and executes the first refresh. If you want to avoid this, pass the ``with_data=False`` flag in the :class:`~psqlextra.backend.migrations.operations.PostgresCreateMaterializedViewModel` operation in your generated migration.
178+
179+
.. code-block:: python
180+
181+
from django.db import migrations, models
182+
183+
from psqlextra.backend.migrations.operations import PostgresCreateMaterializedViewModel
184+
185+
class Migration(migrations.Migration):
186+
operations = [
187+
PostgresCreateMaterializedViewModel(
188+
name="myview",
189+
fields=[...],
190+
options={...},
191+
view_options={
192+
"query": ...
193+
},
194+
# Not the default, creates materialized with `WITH NO DATA`
195+
with_data=False,
196+
)
197+
]

0 commit comments

Comments
 (0)