#36612: KeyTextTransform uses ->> on CASTed TextField to JSONField in MySQL,
causing a syntax error
-------------------------------------+-------------------------------------
     Reporter:  jacobtavenerhulla    |                     Type:  Bug
       Status:  new                  |                Component:  Database
                                     |  layer (models, ORM)
      Version:  4.2                  |                 Severity:  Normal
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
 == Background / Context ==

 In Django 4.2, '''KeyTextTransform''' is used for JSON key lookups that
 return text, such as when performing __icontains on a JSON key.

 On MySQL, Django generates SQL using the ->> operator. While this works
 for '''native JSONField columns''', it '''fails when the JSON expression
 comes from a CAST of a TextField to a JSONField''', because MySQL does not
 allow ->> on expressions like CAST(text_column AS JSON). The ->> operator
 is only valid on actual JSON columns. Attempting it on a CASTed expression
 produces a '''syntax error'''.

 == Steps to Reproduce ==

 Create a model with a TextField storing JSON:

 {{{#!div style="font-size: 80%"
 {{{#!python
 from django.db import models

 class ExampleModel(models.Model):
 json_text = models.TextField()
 }}}
 }}}

 Store a sample JSON string in the TextField:

 {{{#!div style="font-size: 80%"
 {{{#!python
 ExampleModel.objects.create(json_text='{"key": "value"}')
 }}}
 }}}

 Perform a JSON key lookup using a cast and string lookup:

 {{{#!div style="font-size: 80%"
 {{{#!python
 from django.db.models import F, JSONField
 from django.db.models.functions import Cast

 ExampleModel.objects.annotate(
 json_data=Cast('json_text', JSONField())
 ).filter(json_data__key__icontains='val')
 }}}
 }}}

 == Expected Behavior ==

 Django should generate valid MySQL SQL that extracts the JSON key as text,
 compatible with CASTed TextField columns. Example:

 {{{#!div style="font-size: 80%"
 {{{#!sql
 JSON_UNQUOTE(JSON_EXTRACT(json_text, '$.key')) LIKE '%val%'
 }}}
 }}}

 This SQL should execute without errors.

 == Actual Behavior ==

 Django generates SQL using the ->> operator on the CASTed expression:

 {{{#!div style="font-size: 80%"
 {{{#!sql
 json_text->>'$.key' LIKE '%val%'
 }}}
 }}}

 This is invalid in MySQL when applied to a CASTed expression.

 Results in a syntax error at runtime:

 {{{#!div style="font-size: 80%"
 {{{#!text
 ProgrammingError: (1064, 'You have an error in your SQL syntax; check the
 manual that corresponds to your MySQL server version for the right syntax
 to use near '->> '$.\"key\"')) LIKE LOWER('%val%') LIMIT 21' at line 1')
 }}}
 }}}

 == Current Workaround ==

 A temporary workaround in projects using MySQL is to monkey patch
 KeyTextTransform.as_mysql to use JSON_UNQUOTE(JSON_EXTRACT(...)) instead
 of the ->> operator. This allows JSON key lookups on CASTed TextField →
 JSONField expressions to work correctly.

 Example monkey patch:

 {{{#!div style="font-size: 80%"
 {{{#!python
 from django.db.models.fields.json import KeyTextTransform,
 compile_json_path
 from django.db.models import JSONField
 from django.db.models.functions import Cast

 original_as_mysql = KeyTextTransform.as_mysql

 def safe_as_mysql(self, compiler, connection):
 lhs, params, key_transforms = self.preprocess_lhs(compiler, connection)
 json_path = compile_json_path(key_transforms)

 sql, params = "JSON_EXTRACT(%s, %%s)" % lhs, tuple(params) + (json_path,)
 return "JSON_UNQUOTE(%s)" % sql, params


 KeyTextTransform.as_mysql = safe_as_mysql
 }}}
 }}}

 Notes:

 This patch only affects MySQL connections.

 It applies globally and must be applied once at project startup (e.g., in
 an AppConfig.ready() method).

 This is a temporary workaround; a proper fix in Django core is preferred.

 == Notes / Additional Context ==

 This issue occurs only on MySQL.

 Native JSONField columns are not affected; ->> works fine on them.

 The problem arises specifically when a TextField containing JSON is cast
 to JSONField for ORM JSON lookups.
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36612>
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 visit 
https://groups.google.com/d/msgid/django-updates/0107019955b91ed1-0b452063-f254-4005-85af-772fd46686e5-000000%40eu-central-1.amazonses.com.

Reply via email to