Skip to content

Commit 071f2f6

Browse files
Merge pull request #647 from geoadmin/PB-1403-noraw2
Add tests for pgtriggers and clean up conditions.
2 parents e9fa4a3 + d7bbfea commit 071f2f6

4 files changed

Lines changed: 166 additions & 15 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Generated by Django 5.2.11 on 2026-06-03 06:08
2+
3+
import pgtrigger.compiler
4+
import pgtrigger.migrations
5+
6+
from django.db import migrations
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('stac_api', '0070_alter_asset_media_type_and_more'),
13+
]
14+
15+
operations = [
16+
pgtrigger.migrations.RemoveTrigger(
17+
model_name='asset',
18+
name='update_asset_item_file_size_trigger',
19+
),
20+
pgtrigger.migrations.AddTrigger(
21+
model_name='asset',
22+
trigger=pgtrigger.compiler.Trigger(
23+
name='update_asset_item_file_size_trigger',
24+
sql=pgtrigger.compiler.UpsertTriggerSql(
25+
condition='WHEN (OLD."file_size" IS DISTINCT FROM (NEW."file_size"))',
26+
declare=
27+
'DECLARE asset_instance stac_api_asset%ROWTYPE; updated_total_data_size NUMERIC;',
28+
func=
29+
"\n asset_instance = COALESCE(NEW, OLD);\n\n -- Update related item file_size variables\n UPDATE stac_api_item SET\n total_data_size = total_data_size + COALESCE(NEW.file_size, 0) - COALESCE(OLD.file_size, 0)\n WHERE id = asset_instance.item_id\n RETURNING total_data_size INTO updated_total_data_size;\n\n IF updated_total_data_size < 0\n THEN RAISE WARNING 'item.id=% total_data_size has negative value %',\n asset_instance.item_id, updated_total_data_size;\n END IF;\n\n RAISE INFO 'item.id=% total_data_size updated, due to asset.name=% updates.',\n asset_instance.item_id, asset_instance.name;\n\n RETURN asset_instance;\n ",
30+
hash='2e0243f743d6b4ba265e71d9a738d73551d84431',
31+
operation='UPDATE',
32+
pgid='pgtrigger_update_asset_item_file_size_trigger_316e8',
33+
table='stac_api_asset',
34+
when='AFTER'
35+
)
36+
),
37+
),
38+
]
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Generated by Django 5.2.11 on 2026-06-03 06:12
2+
3+
import pgtrigger.compiler
4+
import pgtrigger.migrations
5+
6+
from django.db import migrations
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('stac_api', '0071_remove_asset_update_asset_item_file_size_trigger_and_more'),
13+
]
14+
15+
operations = [
16+
pgtrigger.migrations.RemoveTrigger(
17+
model_name='collectionasset',
18+
name='upd_dec_col_asset_proj_epsg_trigger',
19+
),
20+
pgtrigger.migrations.RemoveTrigger(
21+
model_name='collectionasset',
22+
name='upd_inc_col_asset_proj_epsg_trigger',
23+
),
24+
pgtrigger.migrations.AddTrigger(
25+
model_name='collectionasset',
26+
trigger=pgtrigger.compiler.Trigger(
27+
name='upd_dec_col_asset_proj_epsg_trigger',
28+
sql=pgtrigger.compiler.UpsertTriggerSql(
29+
condition='WHEN (OLD."proj_epsg" IS DISTINCT FROM (NEW."proj_epsg"))',
30+
declare=
31+
'DECLARE asset_instance stac_api_collectionasset%ROWTYPE; related_collection_id INT;',
32+
func=
33+
"\n asset_instance = OLD;\n\n related_collection_id = asset_instance.collection_id;\n\n -- Remove entry when count will reach 0\n DELETE FROM stac_api_projepsgcount\n WHERE collection_id = related_collection_id\n AND value = asset_instance.proj_epsg\n AND count = 1;\n\n IF NOT FOUND THEN\n UPDATE stac_api_projepsgcount\n SET count = count-1\n WHERE collection_id = related_collection_id\n AND value = asset_instance.proj_epsg;\n\n RAISE INFO\n 'stac_api_projepsgcount (collection_id, value) (% %) count updated, due to asset.name=% update.',\n related_collection_id, asset_instance.proj_epsg, asset_instance.name;\n\n RETURN asset_instance;\n END IF;\n\n RAISE INFO\n 'stac_api_projepsgcount (collection_id, value) (% %) deleted, due to asset.name=% update.',\n related_collection_id, asset_instance.proj_epsg, asset_instance.name;\n\n RETURN asset_instance;\n ",
34+
hash='56e4e1fae63cd83c85410bb5e49f1672f644c015',
35+
operation='UPDATE',
36+
pgid='pgtrigger_upd_dec_col_asset_proj_epsg_trigger_07a9d',
37+
table='stac_api_collectionasset',
38+
when='AFTER'
39+
)
40+
),
41+
),
42+
pgtrigger.migrations.AddTrigger(
43+
model_name='collectionasset',
44+
trigger=pgtrigger.compiler.Trigger(
45+
name='upd_inc_col_asset_proj_epsg_trigger',
46+
sql=pgtrigger.compiler.UpsertTriggerSql(
47+
condition='WHEN (OLD."proj_epsg" IS DISTINCT FROM (NEW."proj_epsg"))',
48+
declare=
49+
'DECLARE asset_instance stac_api_collectionasset%ROWTYPE; related_collection_id INT;',
50+
func=
51+
"\n asset_instance = NEW;\n\n related_collection_id = asset_instance.collection_id;\n\n INSERT INTO stac_api_projepsgcount (collection_id, value, count)\n VALUES (related_collection_id, asset_instance.proj_epsg, 1)\n ON CONFLICT (collection_id, value)\n DO UPDATE SET count = stac_api_projepsgcount.count+1;\n\n RAISE INFO\n 'projepsgcount (collection_id, value) (% %) count updated, due to asset.name=% update.',\n related_collection_id, asset_instance.proj_epsg, asset_instance.name;\n\n RETURN asset_instance;\n ",
52+
hash='0626a8387cba7cffb40dd81594e3d5c5c16e9ea4',
53+
operation='UPDATE',
54+
pgid='pgtrigger_upd_inc_col_asset_proj_epsg_trigger_d717d',
55+
table='stac_api_collectionasset',
56+
when='AFTER'
57+
)
58+
),
59+
),
60+
]

app/stac_api/pgtriggers.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def auto_variables_triggers(name, *fields):
3131
]
3232

3333

34-
def child_triggers(parent_name, child_name):
34+
def child_triggers(parent_name, child_name, *fields):
3535
'''Triggers used by various tables to update the `updated` and `etag` fields
3636
of the parent table when a child gets inserted, updated or deleted.
3737
@@ -63,7 +63,7 @@ def child_triggers(parent_name, child_name):
6363
name=f"update_{parent_name}_child_trigger",
6464
operation=pgtrigger.Update,
6565
when=pgtrigger.After,
66-
condition=pgtrigger.Condition('OLD.* IS DISTINCT FROM NEW.*'),
66+
condition=pgtrigger.AnyChange(*fields),
6767
func=child_update_func.format(
6868
parent_name=parent_name, child_obj="NEW", child_name=child_name
6969
)
@@ -225,7 +225,7 @@ class ItemFileSizeTrigger(pgtrigger.Trigger):
225225
ItemFileSizeTrigger(
226226
name='update_asset_item_file_size_trigger',
227227
operation=pgtrigger.Update,
228-
condition=pgtrigger.Condition('OLD.file_size IS DISTINCT FROM NEW.file_size'),
228+
condition=pgtrigger.AnyChange('file_size'),
229229
)
230230
]
231231

@@ -327,7 +327,7 @@ class IncreaseCounterTrigger(pgtrigger.Trigger):
327327
DecreaseCounterTrigger(
328328
name='upd_dec_col_asset_proj_epsg_trigger',
329329
operation=pgtrigger.Update,
330-
condition=pgtrigger.Condition('''OLD.proj_epsg IS DISTINCT FROM NEW.proj_epsg''')
330+
condition=pgtrigger.AnyChange('proj_epsg')
331331
),
332332
DecreaseCounterTrigger(
333333
name='del_col_asset_proj_epsg_trigger',
@@ -336,7 +336,7 @@ class IncreaseCounterTrigger(pgtrigger.Trigger):
336336
IncreaseCounterTrigger(
337337
name='upd_inc_col_asset_proj_epsg_trigger',
338338
operation=pgtrigger.Update,
339-
condition=pgtrigger.Condition('''OLD.proj_epsg IS DISTINCT FROM NEW.proj_epsg''')
339+
condition=pgtrigger.AnyChange('proj_epsg')
340340
),
341341
IncreaseCounterTrigger(
342342
name='add_col_asset_proj_epsg_trigger',
@@ -349,7 +349,7 @@ class IncreaseCounterTrigger(pgtrigger.Trigger):
349349
CollectionFileSizeTrigger(
350350
name='update_col_asset_col_file_size_trigger',
351351
operation=pgtrigger.Update,
352-
condition=pgtrigger.Condition('OLD.* IS DISTINCT FROM NEW.*'),
352+
condition=pgtrigger.AnyChange(),
353353
)
354354
]
355355

@@ -480,7 +480,7 @@ def generates_asset_upload_triggers():
480480
pgtrigger.Trigger(
481481
name="update_asset_upload_trigger",
482482
operation=pgtrigger.Update,
483-
condition=pgtrigger.Condition('OLD.* IS DISTINCT FROM NEW.*'),
483+
condition=pgtrigger.AnyChange(),
484484
when=pgtrigger.Before,
485485
func=etag_func
486486
),

app/tests/tests_10/test_pgtriggers.py

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import logging
22

3+
from parameterized import parameterized
4+
5+
from stac_api.models.item import AssetUpload
6+
37
from tests.tests_10.base_test import StacBaseTransactionTestCase
48
from tests.tests_10.data_factory import Factory
59
from tests.tests_10.sample_data.asset_samples import FILE_CONTENT_1
@@ -73,17 +77,66 @@ class PgTriggersUpdated(MockS3PerTestMixin, StacBaseTransactionTestCase):
7377
def setUp(self):
7478
super().setUp()
7579
self.factory = Factory()
76-
self.collection = self.factory.create_collection_sample().model
77-
self.item = self.factory.create_item_sample(collection=self.collection).model
80+
self.collection = self.factory.create_collection_sample(db_create=True).model
81+
self.collection_asset = self.factory.create_collection_asset_sample(
82+
collection=self.collection, db_create=True
83+
).model
84+
self.item = self.factory.create_item_sample(
85+
collection=self.collection, db_create=True
86+
).model
7887
self.asset = self.factory.create_asset_sample(
7988
self.item, sample='asset-1', db_create=True
8089
).model
8190

82-
def test_checksum_update_changes_timestamps(self):
83-
prev_mtime = self.asset.updated
91+
@parameterized.expand([
92+
('asset', 'asset', 'checksum_multihash'),
93+
('asset', 'item', 'checksum_multihash'),
94+
('item', 'collection', 'name'),
95+
('collection_asset', 'collection', 'checksum_multihash'),
96+
])
97+
def test_timestamp_updated(self, source_name, destination_name, field_name):
98+
destination = getattr(self, destination_name)
99+
source = getattr(self, source_name)
100+
101+
prev_etag = destination.etag
102+
prev_mtime = destination.updated
103+
setattr(source, field_name, 'new value')
104+
source.save()
105+
destination.refresh_from_db()
106+
107+
self.assertGreater(destination.updated, prev_mtime)
108+
self.assertNotEqual(destination.etag, prev_etag)
109+
84110

85-
self.asset.checksum_multihash = 'new hash'
86-
self.asset.save()
87-
self.asset.refresh_from_db()
111+
class PgTriggerAssetUploads(MockS3PerTestMixin, StacBaseTransactionTestCase):
88112

89-
self.assertGreater(self.asset.updated, prev_mtime)
113+
def setUp(self):
114+
super().setUp()
115+
self.factory = Factory()
116+
self.collection = self.factory.create_collection_sample(db_create=True).model
117+
self.collection_asset = self.factory.create_collection_asset_sample(
118+
collection=self.collection, db_create=True
119+
).model
120+
self.item = self.factory.create_item_sample(
121+
collection=self.collection, db_create=True
122+
).model
123+
self.asset = self.factory.create_asset_sample(
124+
self.item, sample='asset-1', db_create=True
125+
).model
126+
self.asset_upload = AssetUpload(
127+
asset=self.asset,
128+
upload_id=42,
129+
checksum_multihash=b'some hash',
130+
md5_parts=['totally a valid md5'],
131+
number_parts=1,
132+
)
133+
self.asset_upload.full_clean()
134+
self.asset_upload.save()
135+
self.asset_upload.refresh_from_db()
136+
137+
def test_etag_updated(self):
138+
prev_etag = self.asset_upload.etag
139+
self.asset_upload.status = AssetUpload.Status.ABORTED
140+
self.asset_upload.save()
141+
self.asset_upload.refresh_from_db()
142+
self.assertNotEqual(self.asset_upload.etag, prev_etag)

0 commit comments

Comments
 (0)