django-postgresql-netfields icon indicating copy to clipboard operation
django-postgresql-netfields copied to clipboard

Invalid query when combining with ArrayField and NULL values

Open sevdog opened this issue 1 year ago • 1 comments

When combining a field from this library with ArrayField if any of the values in the array is None (or should be translated to NULL) an invalid query is generated causing an error like the following:

django.db.utils.DataError: invalid input syntax for type ...

In example, if using InetAddressField the generated SQL is something like

SELECT '{192.168.1.1,None}'::inet[]::inet[];

Using django built-in GenericIPAddressField instead produces the right query:

SELECT '{192.168.1.1,NULL}'::inet[]::inet[];
Here is a sample model with tests to ensure this behaviour:
# models.py
from django.contrib.postgres.fields import ArrayField
from django.db import models
from netfields import (CidrAddressField, InetAddressField, MACAddress8Field,
                       MACAddressField)


class ArrayModel(models.Model):
    ip_array = ArrayField(models.GenericIPAddressField(), null=True)
    inet_array = ArrayField(InetAddressField(), null=True)
    mac_array = ArrayField(MACAddressField(), null=True)
    mac8_array = ArrayField(MACAddress8Field(), null=True)
    cidr_array = ArrayField(CidrAddressField(), null=True)

# tests.py
from django.test import TestCase

from .models import ArrayModel


class NullsTestCase(TestCase):

    def test_ip(self):
        model = ArrayModel(ip_array=["192.168.1.1", None])
        model.save()

    def test_inet(self):
        model = ArrayModel(inet_array=["192.168.1.1", None])
        model.save()

    def test_mac(self):
        model = ArrayModel(mac_array=["FF:FF:FF:FF:FF:FF", None])
        model.save()

    def test_mac8(self):
        model = ArrayModel(mac8_array=["FF:FF:FF:FF:FF:FF", None])
        model.save()

    def test_cidr(self):
        model = ArrayModel(cidr_array=["192.168.1.0/24", None])
        model.save()

I have seen this issue using Django 4.2 and psycopg 3.1.17.

sevdog avatar Jan 17 '24 11:01 sevdog

Looking into django code I see that GenericIPAddressField.get_db_prep_value internally calls connection.ops.adapt_ipaddressfield_value which behaves different from netfields:

https://github.com/django/django/blob/c7e986fc9f4848bd757d4b9b70a40586d2cee9fb/django/db/backends/postgresql/operations.py#L350-L353

Because if the value is falsy it just returns None and not Inet(None) (which is the result in netfields).

sevdog avatar Jan 17 '24 11:01 sevdog