#27213: PostgreSQL-9.4 ArrayField with null throws ProgrammingError but not
ValidationError on Linux but not Windows
-------------------------------------+-------------------------------------
     Reporter:  mikofski             |                    Owner:  nobody
         Type:  Bug                  |                   Status:  closed
    Component:  contrib.postgres     |                  Version:  1.9
     Severity:  Normal               |               Resolution:
                                     |  worksforme
     Keywords:  postgres,            |             Triage Stage:
  arrayfield, programmingerror       |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Changes (by timgraham):

 * status:  new => closed
 * needs_better_patch:   => 0
 * component:  Database layer (models, ORM) => contrib.postgres
 * needs_tests:   => 0
 * needs_docs:   => 0
 * resolution:   => worksforme


Old description:

> Python-2.7.10
> PostgreSQL-9.4
> psycopg2-2.5.1 (linux) and psycopg2-2.6.1 (windows) (*)
> Django-1.9
> OS: Oracle7 vs. Windows 7
>
> given a model:
> {{{#!python
> from django.contrib.postgres.fields import ArrayField
> from django.db import models
>

> class MyModel(models.Model):
>     my_array_field = ArrayField(base_field=models.FloatField(null=True))
> }}}
>
> If you try to save an array of `None` Django will validate it, but on
> Linux PostgreSQL will not insert the row, but on windows it does.
>
> {{{#!python
> from my_app.models import MyModel
>
> test_model = MyModel(my_array_field=[None])  # make a test instance of
> model
>
> test_model.full_clean()  # check for ValidationError
> # everything is okay!
>
> test_model_full.save()  # insert model instance into PostgreSQL database
> # Windows: Success!
> # Linux:   Failure!
> }}}
>
> here is the stacktrace from Linux:
> {{{
> In [59]: rtest.save()
> ---------------------------------------------------------------------------
> ProgrammingError                          Traceback (most recent call
> last)
> <ipython-input-59-34c0fed69116> in <module>()
> ----> 1 rtest.save()
>
> ~/.local/lib/python2.7/site-packages/django/db/models/base.pyc in
> save(self, force_insert, force_update, using, update_fields)
>     706
>     707         self.save_base(using=using, force_insert=force_insert,
> --> 708                        force_update=force_update,
> update_fields=update_fields)
>     709     save.alters_data = True
>     710
>
> ~/.local/lib/python2.7/site-packages/django/db/models/base.pyc in
> save_base(self, raw, force_insert, force_update, using, update_fields)
>     734             if not raw:
>     735                 self._save_parents(cls, using, update_fields)
> --> 736             updated = self._save_table(raw, cls, force_insert,
> force_update, using, update_fields)
>     737         # Store the database on which the object was saved
>     738         self._state.db = using
>
> ~/.local/lib/python2.7/site-packages/django/db/models/base.pyc in
> _save_table(self, raw, cls, force_insert, force_update, using,
> update_fields)
>     818
>     819             update_pk = bool(meta.has_auto_field and not pk_set)
> --> 820             result = self._do_insert(cls._base_manager, using,
> fields, update_pk, raw)
>     821             if update_pk:
>     822                 setattr(self, meta.pk.attname, result)
>
> ~/.local/lib/python2.7/site-packages/django/db/models/base.pyc in
> _do_insert(self, manager, using, fields, update_pk, raw)
>     857         """
>     858         return manager._insert([self], fields=fields,
> return_id=update_pk,
> --> 859                                using=using, raw=raw)
>     860
>     861     def delete(self, using=None, keep_parents=False):
>
> ~/.local/lib/python2.7/site-packages/django/db/models/manager.pyc in
> manager_method(self, *args, **kwargs)
>     120         def create_method(name, method):
>     121             def manager_method(self, *args, **kwargs):
> --> 122                 return getattr(self.get_queryset(), name)(*args,
> **kwargs)
>     123             manager_method.__name__ = method.__name__
>     124             manager_method.__doc__ = method.__doc__
>
> ~/.local/lib/python2.7/site-packages/django/db/models/query.pyc in
> _insert(self, objs, fields, return_id, raw, using)
>    1037         query = sql.InsertQuery(self.model)
>    1038         query.insert_values(fields, objs, raw=raw)
> -> 1039         return
> query.get_compiler(using=using).execute_sql(return_id)
>    1040     _insert.alters_data = True
>    1041     _insert.queryset_only = False
>
> ~/.local/lib/python2.7/site-packages/django/db/models/sql/compiler.pyc in
> execute_sql(self, return_id)
>    1058         with self.connection.cursor() as cursor:
>    1059             for sql, params in self.as_sql():
> -> 1060                 cursor.execute(sql, params)
>    1061             if not (return_id and cursor):
>    1062                 return
>
> ~/.local/lib/python2.7/site-packages/django/db/backends/utils.pyc in
> execute(self, sql, params)
>      77         start = time()
>      78         try:
> ---> 79             return super(CursorDebugWrapper, self).execute(sql,
> params)
>      80         finally:
>      81             stop = time()
>
> ~/.local/lib/python2.7/site-packages/django/db/backends/utils.pyc in
> execute(self, sql, params)
>      62                 return self.cursor.execute(sql)
>      63             else:
> ---> 64                 return self.cursor.execute(sql, params)
>      65
>      66     def executemany(self, sql, param_list):
>
> ~/.local/lib/python2.7/site-packages/django/db/utils.pyc in
> __exit__(self, exc_type, exc_value, traceback)
>      93                 if dj_exc_type not in (DataError,
> IntegrityError):
>      94                     self.wrapper.errors_occurred = True
> ---> 95                 six.reraise(dj_exc_type, dj_exc_value, traceback)
>      96
>      97     def __call__(self, func):
>
> ~/.local/lib/python2.7/site-packages/django/db/backends/utils.pyc in
> execute(self, sql, params)
>      62                 return self.cursor.execute(sql)
>      63             else:
> ---> 64                 return self.cursor.execute(sql, params)
>      65
>      66     def executemany(self, sql, param_list):
>
> ProgrammingError: column "my_array_field" is of type double precision[]
> but expression is of type text[]
> LINE 1: ...ARRAY[NULL...
>            ^
> HINT:  You will need to rewrite or cast the expression.
> }}}
>
> I wonder if it has anything to do with this SO question:
> http://stackoverflow.com/questions/14713106/insert-unnested-array-of-
> null-values-into-a-double-precision-column-postgresql
>
> (*) if the issue is psycopg2 version discrepancy that's a bummer, because
> I don't think I can build psycopg2 on a share without the postrgre dev
> libs. :(

New description:

 Python-2.7.10
 PostgreSQL-9.4
 psycopg2-2.5.1 (linux) and psycopg2-2.6.1 (windows) (*)
 Django-1.9
 OS: Oracle7 vs. Windows 7

 given a model:
 {{{#!python
 from django.contrib.postgres.fields import ArrayField
 from django.db import models


 class MyModel(models.Model):
     my_array_field = ArrayField(base_field=models.FloatField(null=True))
 }}}

 If you try to save an array of `None` Django will validate it, but on
 Linux PostgreSQL will not insert the row, but on windows it does.

 {{{#!python
 from my_app.models import MyModel

 test_model = MyModel(my_array_field=[None])  # make a test instance of
 model

 test_model.full_clean()  # check for ValidationError
 # everything is okay!

 test_model.save()  # insert model instance into PostgreSQL database
 # Windows: Success!
 # Linux:   Failure!
 }}}

 here is the stacktrace from Linux:
 {{{
 In [59]: rtest.save()
 ---------------------------------------------------------------------------
 ProgrammingError                          Traceback (most recent call
 last)
 <ipython-input-59-34c0fed69116> in <module>()
 ----> 1 rtest.save()

 ~/.local/lib/python2.7/site-packages/django/db/models/base.pyc in
 save(self, force_insert, force_update, using, update_fields)
     706
     707         self.save_base(using=using, force_insert=force_insert,
 --> 708                        force_update=force_update,
 update_fields=update_fields)
     709     save.alters_data = True
     710

 ~/.local/lib/python2.7/site-packages/django/db/models/base.pyc in
 save_base(self, raw, force_insert, force_update, using, update_fields)
     734             if not raw:
     735                 self._save_parents(cls, using, update_fields)
 --> 736             updated = self._save_table(raw, cls, force_insert,
 force_update, using, update_fields)
     737         # Store the database on which the object was saved
     738         self._state.db = using

 ~/.local/lib/python2.7/site-packages/django/db/models/base.pyc in
 _save_table(self, raw, cls, force_insert, force_update, using,
 update_fields)
     818
     819             update_pk = bool(meta.has_auto_field and not pk_set)
 --> 820             result = self._do_insert(cls._base_manager, using,
 fields, update_pk, raw)
     821             if update_pk:
     822                 setattr(self, meta.pk.attname, result)

 ~/.local/lib/python2.7/site-packages/django/db/models/base.pyc in
 _do_insert(self, manager, using, fields, update_pk, raw)
     857         """
     858         return manager._insert([self], fields=fields,
 return_id=update_pk,
 --> 859                                using=using, raw=raw)
     860
     861     def delete(self, using=None, keep_parents=False):

 ~/.local/lib/python2.7/site-packages/django/db/models/manager.pyc in
 manager_method(self, *args, **kwargs)
     120         def create_method(name, method):
     121             def manager_method(self, *args, **kwargs):
 --> 122                 return getattr(self.get_queryset(), name)(*args,
 **kwargs)
     123             manager_method.__name__ = method.__name__
     124             manager_method.__doc__ = method.__doc__

 ~/.local/lib/python2.7/site-packages/django/db/models/query.pyc in
 _insert(self, objs, fields, return_id, raw, using)
    1037         query = sql.InsertQuery(self.model)
    1038         query.insert_values(fields, objs, raw=raw)
 -> 1039         return
 query.get_compiler(using=using).execute_sql(return_id)
    1040     _insert.alters_data = True
    1041     _insert.queryset_only = False

 ~/.local/lib/python2.7/site-packages/django/db/models/sql/compiler.pyc in
 execute_sql(self, return_id)
    1058         with self.connection.cursor() as cursor:
    1059             for sql, params in self.as_sql():
 -> 1060                 cursor.execute(sql, params)
    1061             if not (return_id and cursor):
    1062                 return

 ~/.local/lib/python2.7/site-packages/django/db/backends/utils.pyc in
 execute(self, sql, params)
      77         start = time()
      78         try:
 ---> 79             return super(CursorDebugWrapper, self).execute(sql,
 params)
      80         finally:
      81             stop = time()

 ~/.local/lib/python2.7/site-packages/django/db/backends/utils.pyc in
 execute(self, sql, params)
      62                 return self.cursor.execute(sql)
      63             else:
 ---> 64                 return self.cursor.execute(sql, params)
      65
      66     def executemany(self, sql, param_list):

 ~/.local/lib/python2.7/site-packages/django/db/utils.pyc in __exit__(self,
 exc_type, exc_value, traceback)
      93                 if dj_exc_type not in (DataError, IntegrityError):
      94                     self.wrapper.errors_occurred = True
 ---> 95                 six.reraise(dj_exc_type, dj_exc_value, traceback)
      96
      97     def __call__(self, func):

 ~/.local/lib/python2.7/site-packages/django/db/backends/utils.pyc in
 execute(self, sql, params)
      62                 return self.cursor.execute(sql)
      63             else:
 ---> 64                 return self.cursor.execute(sql, params)
      65
      66     def executemany(self, sql, param_list):

 ProgrammingError: column "my_array_field" is of type double precision[]
 but expression is of type text[]
 LINE 1: ...ARRAY[NULL...
            ^
 HINT:  You will need to rewrite or cast the expression.
 }}}

 I wonder if it has anything to do with this SO question:
 http://stackoverflow.com/questions/14713106/insert-unnested-array-of-null-
 values-into-a-double-precision-column-postgresql

 (*) if the issue is psycopg2 version discrepancy that's a bummer, because
 I don't think I can build psycopg2 on a share without the postrgre dev
 libs. :(

--

Comment:

 I'm not able to reproduce this. I see `ValidationError: {'my_array_field':
 ['Item 0 in the array did not validate: This field cannot be blank.']}` at
 the `full_clean()` step.

--
Ticket URL: <https://code.djangoproject.com/ticket/27213#comment:1>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/066.7457b633f1e1d74a8948bd432a5bb25c%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to