#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 [email protected].
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