1+ from functools import partial
12from itertools import chain
23
34from django .db .models import JSONField
@@ -14,33 +15,75 @@ def as_sql(self):
1415 on_conflict = self .query .on_conflict ,
1516 )
1617 result = ["%s %s" % (insert_statement , qn (opts .db_table ))]
17- fields = self .query .fields or [opts .pk ]
18- result .append ("(%s)" % ", " .join (qn (f .column ) for f in fields ))
19-
18+ # This part is customized to wrap JSONField values with parse_json().
2019 select_columns = []
21- if self .query .fields :
22- value_rows = [
23- [
24- self .prepare_value (field , self .pre_save_val (field , obj ))
25- for field in fields
26- ]
27- for obj in self .query .objs
28- ]
20+ if fields := list (self .query .fields ):
21+ from django .db .models .expressions import DatabaseDefault
22+
23+ supports_default_keyword_in_bulk_insert = (
24+ self .connection .features .supports_default_keyword_in_bulk_insert
25+ )
26+ value_cols = []
2927 has_json_field = False
3028 for i , field in enumerate (fields , 1 ):
3129 if isinstance (field , JSONField ):
3230 has_json_field = True
3331 select_columns .append (f'parse_json(${ i } )' )
3432 else :
3533 select_columns .append (f'${ i } ' )
34+
35+ field_prepare = partial (self .prepare_value , field )
36+ field_pre_save = partial (self .pre_save_val , field )
37+ field_values = [
38+ field_prepare (field_pre_save (obj )) for obj in self .query .objs
39+ ]
40+ if not field .has_db_default ():
41+ value_cols .append (field_values )
42+ continue
43+
44+ # If all values are DEFAULT don't include the field and its
45+ # values in the query as they are redundant and could prevent
46+ # optimizations. This cannot be done if we're dealing with the
47+ # last field as INSERT statements require at least one.
48+ if len (fields ) > 1 and all (
49+ isinstance (value , DatabaseDefault ) for value in field_values
50+ ):
51+ fields .remove (field )
52+ continue
53+
54+ if supports_default_keyword_in_bulk_insert :
55+ value_cols .append (field_values )
56+ continue
57+
58+ # If the field cannot be excluded from the INSERT for the
59+ # reasons listed above and the backend doesn't support the
60+ # DEFAULT keyword each values must be expanded into their
61+ # underlying expressions.
62+ prepared_db_default = field_prepare (field .db_default )
63+ field_values = [
64+ (
65+ prepared_db_default
66+ if isinstance (value , DatabaseDefault )
67+ else value
68+ )
69+ for value in field_values
70+ ]
71+ value_cols .append (field_values )
72+ value_rows = list (zip (* value_cols ))
73+ result .append ("(%s)" % ", " .join (qn (f .column ) for f in fields ))
74+
3675 if not has_json_field :
3776 select_columns = []
3877 else :
39- # An empty object.
78+ # No fields were specified but an INSERT statement must include at
79+ # least one column. This can only happen when the model's primary
80+ # key is composed of a single auto-field so default to including it
81+ # as a placeholder to generate a valid INSERT statement.
4082 value_rows = [
4183 [self .connection .ops .pk_default_value ()] for _ in self .query .objs
4284 ]
4385 fields = [None ]
86+ result .append ("(%s)" % qn (opts .pk .column ))
4487
4588 # Currently the backends just accept values when generating bulk
4689 # queries and generate their own placeholders. Doing that isn't
0 commit comments