Skip to content

Commit 05da67d

Browse files
committed
More docs on upserts
1 parent 0e21d97 commit 05da67d

File tree

2 files changed

+109
-8
lines changed

2 files changed

+109
-8
lines changed

docs/manager.md

Lines changed: 108 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,58 @@
1+
# Using the manager
2+
`django-postgres-extra` provides the `psqlextra.manager.PostgresManager` which exposes a lot of functionality. Your model must use this manager in order to use most of this package's functionality.
3+
4+
There's three ways to do this:
5+
6+
* **Inherit your model from `psqlextra.models.PostgresModel`:**
7+
8+
from psqlextra.models import PostgresModel
9+
10+
class MyModel(PostgresModel):
11+
myfield = models.CharField(max_length=255)
12+
13+
14+
* **Override default manager with `psqlextra.manager.PostgresManager`:**
15+
16+
from django.db import models
17+
from psqlextra.manager import PostgresManager
18+
19+
class MyModel(models.Model):
20+
# override default django manager
21+
objects = PostgresManager()
22+
23+
myfield = models.CharField(max_length=255)
24+
25+
26+
* **Provide `psqlextra.manager.PostgresManager` as a custom manager:**
27+
28+
from django.db import models
29+
from psqlextra.manager import PostgresManager
30+
31+
class MyModel(models.Model):
32+
# custom mananger name
33+
beer = PostgresManager()
34+
35+
myfield = models.CharField(max_length=255)
36+
37+
# use like this:
38+
MyModel.beer.upsert(..)
39+
40+
# not like this:
41+
MyModel.objects.upsert(..) # error!
42+
43+
## Upserting
144
An "upsert" is an operation where a piece of data is inserted/created if it doesn't exist yet and updated (overwritten) when it already exists. Django has long provided this functionality through [`update_or_create`](https://docs.djangoproject.com/en/1.10/ref/models/querysets/#update-or-create). It does this by first checking whether the record exists and creating it not.
245

346
The major problem with this approach is possibility of race conditions. In between the `SELECT` and `INSERT`, another process could perform the `INSERT`. The last `INSERT` would most likely fail because it would be duplicating a `UNIQUE` constraint.
447

548
In order to combat this, PostgreSQL added native upserts. Also known as [`ON CONFLICT DO ...`](https://www.postgresql.org/docs/9.5/static/sql-insert.html#SQL-ON-CONFLICT). This allows a user to specify what to do when a conflict occurs.
649

50+
### `upsert`
51+
Attempts to insert a row with the specified data or updates (and overwrites) the duplicate row, and then returns the primary key of the row that was created/updated.
752

8-
## upsert
9-
The `upsert` method attempts to insert a row with the specified data or updates (and overwrites) the duplicate row, and then returns the primary key of the row that was created/updated.
53+
Upserts work by catching conflcits. PostgreSQL requires to know whichconflicts to react to. You have to specify the name of the column to which you want to react to. This is specified in the `conflict_target` parameter.
1054

11-
Upserts work by catching conflicts. PostgreSQL requires to know which conflicts to react to. You have to specify the name of the column which's constraint you want to react to. This is specified in the `conflict_target` field. If the constraint you're trying to react to consists of multiple columns, specify multiple columns.
55+
You can only specify a single "constraint" in this field. You **cannot** react to conflicts in multiple fields. This is a limitation by PostgreSQL. Note that this means **single constraint**, not necessarily a single column. A constraint can cover multiple columns.
1256

1357
from django.db import models
1458
from psqlextra.models import PostgresModel
@@ -34,17 +78,74 @@ Upserts work by catching conflicts. PostgreSQL requires to know which conflicts
3478

3579
Note that a single call to `upsert` results in a single `INSERT INTO ... ON CONFLICT DO UPDATE ...`. This fixes the problem outlined earlier about another process doing the `INSERT` in the mean time.
3680

37-
## upsert_and_get
38-
`upsert_and_get` does the same thing as `upsert`, but returns a model instance rather than the primary key of the row that was created/updated. This also happens in a single query using `RETURNING` clause on the `INSERT INTO` statement:
81+
#### unique_together
82+
As mentioned earlier, `conflict_target` expects a single column name, or multiple if the constraint you want to react to spans multiple columns. Django's [unique_together](https://docs.djangoproject.com/en/1.11/ref/models/options/#unique-together) has this. If you want to react to this constraint that covers multiple columns, specify those columns in the `conflict_target` parameter:
83+
84+
from django.db import models
85+
from psqlextra.models import PostgresModel
86+
87+
class MyModel(PostgresModel):
88+
class Meta:
89+
unique_together = ('myfield1', 'myfield2')
90+
91+
myfield1 = models.CharField(max_length=255)
92+
myfield1 = models.CharField(max_length=255)
93+
94+
MyModel.objects.upsert(
95+
conflict_target=['myfield1', 'myfield2'],
96+
fields=dict(
97+
myfield1='beer'
98+
myfield2='moar beer'
99+
)
100+
)
101+
102+
#### hstore
103+
You can specify HStore keys that have a unique constraint as a `conflict_target`:
104+
105+
from django.db import models
106+
from psqlextra.models import PostgresModel
107+
from psqlextra.fields import HStoreField
108+
109+
class MyModel(PostgresModel):
110+
# values in the key 'en' have to be unique
111+
myfield = HStoreField(uniqueness=['en'])
112+
113+
MyModel.objects.upsert(
114+
conflict_target=[('myfield', 'en')],
115+
fields=dict(
116+
myfield={'en': 'beer'}
117+
)
118+
)
119+
120+
It also supports specifying a "unique together" constraint on HStore keys:
121+
122+
from django.db import models
123+
from psqlextra.models import PostgresModel
124+
from psqlextra.fields import HStoreField
125+
126+
class MyModel(PostgresModel):
127+
# values in the key 'en' and 'ar' have to be
128+
# unique together
129+
myfield = HStoreField(uniqueness=[('en', 'ar')])
130+
131+
MyModel.objects.upsert(
132+
conflict_target=[('myfield', 'en'), ('myfield', 'ar')],
133+
fields=dict(
134+
myfield={'en': 'beer', 'ar': 'arabic beer'}
135+
)
136+
)
137+
138+
### `upsert_and_get`
139+
Does the same thing as `upsert`, but returns a model instance rather than the primary key of the row that was created/updated. This also happens in a single query using `RETURNING` clause on the `INSERT INTO` statement:
39140

40141
from django.db import models
41142
from psqlextra.models import PostgresModel
42143

43144
class MyModel(PostgresModel):
44145
myfield = models.CharField(max_length=255, unique=True)
45146

46-
obj1 = MyModel.objects.upsert_and_get(myfield='beer')
47-
obj2 = MyModel.objects.upsert_and_get(myfield='beer')
147+
obj1 = MyModel.objects.create(myfield='beer')
148+
obj2 = MyModel.objects.create(myfield='beer')
48149

49150
obj1 = MyModel.objects.upsert_and_get(
50151
conflict_target=['myfield'],

mkdocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
site_name: django-postgres-extra
22
pages:
33
- Home: index.md
4-
- Upsert: manager.md
4+
- Manager: manager.md
55
- HStore: hstore.md
66
- Signals: signals.md

0 commit comments

Comments
 (0)