Opened 5 weeks ago
Last modified 4 weeks ago
#36078 assigned Cleanup/optimization
Document that Postgres normalizes a range field with no points normalizes to empty
Reported by: | Mapiarz | Owned by: | Clifford Gama |
---|---|---|---|
Component: | contrib.postgres | Version: | 4.2 |
Severity: | Normal | Keywords: | postgres |
Cc: | Triage Stage: | Accepted | |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description (last modified by )
This short test exemplifies the problem:
now = timezone.now() empty_range = psycopg2_range.DateTimeTZRange(empty=True) test_model = TestModel(range=psycopg2_range.DateTimeTZRange(now, now)) self.assertFalse(test_model.range.isempty) self.assertNotEqual(test_model.range, empty_range) test_model.save() test_model.refresh_from_db() self.assertTrue(test_model.range.isempty) self.assertEqual(test_model.range, empty_range)
I would expect the field value not to change after saving, and it should continue to have the lower, upper and boundary fields set.
Repro project: https://github.com/Mapiarz/django_postgres_range_repro/tree/master
The example is for DateTimeRange, but DateRange Is also affected. Other ranges possibly too, I haven't tested.
Change History (8)
comment:1 by , 5 weeks ago
Description: | modified (diff) |
---|
comment:2 by , 5 weeks ago
Resolution: | → invalid |
---|---|
Status: | new → closed |
comment:3 by , 5 weeks ago
Read also https://forum.djangoproject.com/t/baserangefield-and-equal-lower-upper-values/35160
I think this should be at least documented.
comment:4 by , 5 weeks ago
Resolution: | invalid |
---|---|
Status: | closed → new |
Summary: | Postgres date and time range fields change after saving object → Document that Postgres normalizes a range field with no points normalizes to empty |
Triage Stage: | Unreviewed → Accepted |
Type: | Bug → Cleanup/optimization |
Yeah, I think it was surprising and would help to be pointed out in the docs
comment:5 by , 4 weeks ago
I agree. There should be an explicit warning about this. It's really easy to be caught of guard by this and lose data.
I do not understand why psycopg opted out of normalizing the value to empty, just like postgres does. I have raised this issue here: https://github.com/psycopg/psycopg/discussions/993
comment:6 by , 4 weeks ago
I agree. There should be an explicit warning about this. It's really easy to be caught of guard by this and lose data.
You are already losing data, because if you are calling DateTimeTZRange('2025-01-01', '2025-01-31')
to mean the whole month of January you are losing a day, because the default bounds are [)
so the 31st is not in the range, the same way that 4 is not in the [4,4)
range. The data loss comes from using the wrong bounds, not from the normalization rules, which are the same whether implemented by Python or by Postgres.
In the above example, DateTimeTZRange(now, now, '[]')
might have been what you wanted.
I do not understand why psycopg opted out of normalizing the value to empty, just like postgres does. I have raised this issue here: https://github.com/psycopg/psycopg/discussions/993
I have left a response on the psycopg tracker. I am -0.5 to introduce normalization in Python, but, regardless from that, I don't think it would have resulted in any difference w.r.t. the issue explained here. If we had returned what the OP expected to receive it would have meant we have a bug as Psycopg normalization would disagree with Postgres one.
comment:7 by , 4 weeks ago
Owner: | set to |
---|---|
Status: | new → assigned |
Thank you for the careful report I was able to replicate and had a look into this
From what I can tell, this is Postgres behavior and so I don't think this is a bug in Django
See https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-IO