11import json
2+ import warnings
23
34from django import forms
45from django .core import checks , exceptions
1112 PostgresOperatorLookup ,
1213 Transform ,
1314)
15+ from django .utils .deprecation import RemovedInDjango70Warning , django_file_prefixes
1416from django .utils .translation import gettext_lazy as _
1517
1618from . import Field
@@ -148,6 +150,27 @@ def formfield(self, **kwargs):
148150 )
149151
150152
153+ class JSONNull (expressions .Value ):
154+ """Represent JSON `null` primitive."""
155+
156+ def __init__ (self ):
157+ super ().__init__ (None , output_field = JSONField ())
158+
159+ def __repr__ (self ):
160+ return f"{ self .__class__ .__name__ } ()"
161+
162+ def as_sql (self , compiler , connection ):
163+ value = self .output_field .get_db_prep_value (self .value , connection )
164+ if value is None :
165+ value = "null"
166+ return "%s" , (value ,)
167+
168+ def as_mysql (self , compiler , connection ):
169+ sql , params = self .as_sql (compiler , connection )
170+ sql = "JSON_EXTRACT(%s, '$')"
171+ return sql , params
172+
173+
151174class DataContains (FieldGetDbPrepValueMixin , PostgresOperatorLookup ):
152175 lookup_name = "contains"
153176 postgres_operator = "@>"
@@ -311,14 +334,28 @@ def process_rhs(self, compiler, connection):
311334
312335
313336class JSONExact (lookups .Exact ):
337+ # RemovedInDjango70Warning: When the deprecation period is over, remove
338+ # the following line.
314339 can_use_none_as_rhs = True
315340
316341 def process_rhs (self , compiler , connection ):
342+ if self .rhs is None and not isinstance (self .lhs , KeyTransform ):
343+ warnings .warn (
344+ "Using None as the right-hand side of an exact lookup on JSONField to "
345+ "mean JSON scalar 'null' is deprecated. Use JSONNull() instead (or use "
346+ "the __isnull lookup if you meant SQL NULL)." ,
347+ RemovedInDjango70Warning ,
348+ skip_file_prefixes = django_file_prefixes (),
349+ )
350+
317351 rhs , rhs_params = super ().process_rhs (compiler , connection )
352+
353+ # RemovedInDjango70Warning: When the deprecation period is over, remove
354+ # The following if-block entirely.
318355 # Treat None lookup values as null.
319- if rhs == "%s" and rhs_params == [ None ] :
320- rhs_params = [ "null" ]
321- if connection .vendor == "mysql" :
356+ if rhs == "%s" and ( * rhs_params ,) == ( None ,) :
357+ rhs_params = ( "null" ,)
358+ if connection .vendor == "mysql" and not isinstance ( self . rhs , JSONNull ) :
322359 func = ["JSON_EXTRACT(%s, '$')" ] * len (rhs_params )
323360 rhs %= tuple (func )
324361 return rhs , rhs_params
@@ -526,6 +563,10 @@ def resolve_expression_parameter(self, compiler, connection, sql, param):
526563
527564
528565class KeyTransformExact (JSONExact ):
566+ # RemovedInDjango70Warning: When deprecation period ends, uncomment the
567+ # flag below.
568+ # can_use_none_as_rhs = True
569+
529570 def process_rhs (self , compiler , connection ):
530571 if isinstance (self .rhs , KeyTransform ):
531572 return super (lookups .Exact , self ).process_rhs (compiler , connection )
@@ -552,7 +593,7 @@ def process_rhs(self, compiler, connection):
552593
553594 def as_oracle (self , compiler , connection ):
554595 rhs , rhs_params = super ().process_rhs (compiler , connection )
555- if rhs_params == [ "null" ] :
596+ if rhs_params and ( * rhs_params ,) == ( "null" ,) :
556597 # Field has key and it's NULL.
557598 has_key_expr = HasKeyOrArrayIndex (self .lhs .lhs , self .lhs .key_name )
558599 has_key_sql , has_key_params = has_key_expr .as_oracle (compiler , connection )
0 commit comments