#35336: Adding GeneratedField fails with ProgrammingError when using When on
CharField
-------------------------------------+-------------------------------------
               Reporter:  Adrian     |          Owner:  nobody
  Garcia                             |
                   Type:  Bug        |         Status:  new
              Component:             |        Version:  5.0
  Migrations                         |       Keywords:  postgres,
               Severity:  Normal     |  generatedfield, _split_query
           Triage Stage:             |      Has patch:  0
  Unreviewed                         |
    Needs documentation:  0          |    Needs tests:  0
Patch needs improvement:  0          |  Easy pickings:  0
                  UI/UX:  0          |
-------------------------------------+-------------------------------------
 Forgive me if I am doing something incorrectly, but I cannot for the life
 of me add a GeneratedField to one of my models. To replicate this error, I
 created this test model:
 {{{
 from django.db import models

 class TestModel(models.Model):
     description = models.TextField()
 }}}
 Running makemigrations/migrate, then modifying the model to add this
 `contains_heck` field:
 {{{
 from django.db import models

 class TestModel(models.Model):
     description = models.TextField()
     contains_heck = models.GeneratedField(
         expression=models.Case(
             models.When(description__icontains="HECK",
 then=models.Value(True)),
             default=models.Value(False),
             output_field=models.BooleanField(),
         ),
         output_field=models.BooleanField(),
         db_persist=True,
     )
 }}}
 Which generates this migration:
 {{{
 # Generated by Django 5.0.3 on 2024-03-27 20:34

 from django.db import migrations, models


 class Migration(migrations.Migration):
     dependencies = [
         ("core", "00001_initial"),
     ]

     operations = [
         migrations.AddField(
             model_name="testmodel",
             name="contains_heck",
             field=models.GeneratedField(
                 db_persist=True,
                 expression=models.Case(
                     models.When(description__icontains="HECK",
 then=models.Value(True)), default=models.Value(False),
 output_field=models.BooleanField()
                 ),
                 output_field=models.BooleanField(),
             ),
         ),
     ]
 }}}

 And after running `python manage.py migrate` I get the following error:
 {{{
 Operations to perform:
   Apply all migrations: admin, auth, consumer, contenttypes, core, db,
 sessions
 Running migrations:
   Applying core.0002_testmodel_contains_heck...Traceback (most recent call
 last):
   File "/opt/project/app/manage.py", line 24, in <module>
     main()
   File "/opt/project/app/manage.py", line 20, in main
     execute_from_command_line(sys.argv)
   File "/usr/local/lib/python3.11/site-
 packages/django/core/management/__init__.py", line 442, in
 execute_from_command_line
     utility.execute()
   File "/usr/local/lib/python3.11/site-
 packages/django/core/management/__init__.py", line 436, in execute
     self.fetch_command(subcommand).run_from_argv(self.argv)
   File "/usr/local/lib/python3.11/site-
 packages/django/core/management/base.py", line 413, in run_from_argv
     self.execute(*args, **cmd_options)
   File "/usr/local/lib/python3.11/site-
 packages/django/core/management/base.py", line 459, in execute
     output = self.handle(*args, **options)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-
 packages/django/core/management/base.py", line 107, in wrapper
     res = handle_func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-
 packages/django/core/management/commands/migrate.py", line 356, in handle
     post_migrate_state = executor.migrate(
                          ^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-
 packages/django/db/migrations/executor.py", line 135, in migrate
     state = self._migrate_all_forwards(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-
 packages/django/db/migrations/executor.py", line 167, in
 _migrate_all_forwards
     state = self.apply_migration(
             ^^^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-
 packages/django/db/migrations/executor.py", line 252, in apply_migration
     state = migration.apply(state, schema_editor)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-
 packages/django/db/migrations/migration.py", line 132, in apply
     operation.database_forwards(
   File "/usr/local/lib/python3.11/site-
 packages/django/db/migrations/operations/fields.py", line 108, in
 database_forwards
     schema_editor.add_field(
   File "/usr/local/lib/python3.11/site-
 packages/django/db/backends/base/schema.py", line 750, in add_field
     self.execute(sql, params)
   File "/usr/local/lib/python3.11/site-
 packages/django/db/backends/postgresql/schema.py", line 46, in execute
     sql = self.connection.ops.compose_sql(str(sql), params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-
 packages/django/db/backends/postgresql/operations.py", line 195, in
 compose_sql
     return mogrify(sql, params, self.connection)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-
 packages/django/db/backends/postgresql/psycopg_any.py", line 22, in
 mogrify
     return ClientCursor(cursor.connection).mogrify(sql, params)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-packages/psycopg/client_cursor.py",
 line 40, in mogrify
     pgq = self._convert_query(query, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-packages/psycopg/client_cursor.py",
 line 79, in _convert_query
     pgq.convert(query, params)
   File "/usr/local/lib/python3.11/site-packages/psycopg/_queries.py", line
 208, in convert
     (self.template, self._order, self._parts) = f(bquery, self._encoding)
                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-packages/psycopg/_queries.py", line
 242, in _query2pg_client_nocache
     parts = _split_query(query, encoding, collapse_double_percent=False)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/site-packages/psycopg/_queries.py", line
 388, in _split_query
     raise e.ProgrammingError(
 psycopg.ProgrammingError: only '%s', '%b', '%t' are allowed as
 placeholders, got '%H'
 }}}

 I took a look with the debugger and I think either Django is incorrectly
 passing the values to psycopg, or psycopg isn't splitting this SQL
 statement correctly. I say this because when `_split_query` tries to split
 the query, it ends up matching and consuming the first character of each
 Value:
 {{{
 [
     ('ALTER TABLE "core_testmodel" ADD COLUMN "contains_heck" boolean
 GENERATED ALWAYS AS (CASE WHEN UPPER("description"::text) LIKE UPPER(\'',
 "<re.Match object; span=(140, 142), match=b'%H'>"),
     ("ECK", '<re.Match object; span=(145, 147), match=b"%\'">'),
     (") THEN true ELSE false END) STORED", "None"),
 ]
 }}}

 Switching to `models.When(description__icontains=models.Value("HECK"),
 ...) yields a similar error except it's complaining about `%'` instead of
 `%H`.

 If the `contains_heck` field is created at the same time as the rest of
 the model, everything works in either the `description__icontains="HECK"`
 or `description__icontains=models.Value("HECK")` configuration.


 **Versions:**
 * Postgres 14.9
 * Python 3.11.2
 * Django 5.0.3
 * psycopg[binary] 3.1.18
-- 
Ticket URL: <https://code.djangoproject.com/ticket/35336>
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 django-updates+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/0107018e81da681e-237bcbdb-228b-4bfe-b035-1700f54bd190-000000%40eu-central-1.amazonses.com.

Reply via email to