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 Mapiarz)

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 Mapiarz, 5 weeks ago

Description: modified (diff)

comment:2 by Sarah Boyce, 5 weeks ago

Resolution: invalid
Status: newclosed

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

 -- includes no points (and will be normalized to 'empty')
 SELECT '[4,4)'::int4range;

comment:3 by Claude Paroz, 5 weeks ago

comment:4 by Sarah Boyce, 5 weeks ago

Resolution: invalid
Status: closednew
Summary: Postgres date and time range fields change after saving objectDocument that Postgres normalizes a range field with no points normalizes to empty
Triage Stage: UnreviewedAccepted
Type: BugCleanup/optimization

Yeah, I think it was surprising and would help to be pointed out in the docs

comment:5 by Mapiarz, 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 Daniele Varrazzo, 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 Clifford Gama, 4 weeks ago

Owner: set to Clifford Gama
Status: newassigned

comment:8 by Clifford Gama, 4 weeks ago

Has patch: set
Note: See TracTickets for help on using tickets.
Back to Top