#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.