Skip to content

Commit c507348

Browse files
committed
Update to Django 6.0
1 parent 3680d66 commit c507348

File tree

9 files changed

+86
-24
lines changed

9 files changed

+86
-24
lines changed

.github/workflows/linters.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
- name: Set up Python
1717
uses: actions/setup-python@v5
1818
with:
19-
python-version: '3.10'
19+
python-version: '3.12'
2020
- run: python -m pip install flake8
2121
- name: flake8
2222
uses: liskin/gh-problem-matcher-wrap@v3
@@ -32,7 +32,7 @@ jobs:
3232
- name: Set up Python
3333
uses: actions/setup-python@v5
3434
with:
35-
python-version: '3.10'
35+
python-version: '3.12'
3636
- run: python -m pip install isort
3737
- name: isort
3838
uses: liskin/gh-problem-matcher-wrap@v3

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
uses: actions/checkout@v4
1919
with:
2020
repository: 'timgraham/django'
21-
ref: 'snowflake-5.2.x'
21+
ref: 'snowflake-6.0.x'
2222
path: 'django_repo'
2323
- name: Install system packages for Django's Python test dependencies
2424
run: |

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
## Install and usage
44

55
Use the version of django-snowflake that corresponds to your version of
6-
Django. For example, to get the latest compatible release for Django 5.2.x:
6+
Django. For example, to get the latest compatible release for Django 6.0.x:
77

8-
`pip install django-snowflake==5.2.*`
8+
`pip install django-snowflake==6.0.*`
99

1010
The minor release number of Django doesn't correspond to the minor release
1111
number of django-snowflake. Use the latest minor release of each.

django_snowflake/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
__version__ = '5.2'
1+
__version__ = '6.0a0'
22

33
# Check Django compatibility before other imports which may fail if the
44
# wrong version of Django is installed.
55
from .utils import check_django_compatability
66

77
check_django_compatability()
88

9+
from .aggregates import register_aggregates # noqa
910
from .expressions import register_expressions # noqa
1011
from .functions import register_functions # noqa
1112
from .lookups import register_lookups # noqa
1213

14+
register_aggregates()
1315
register_expressions()
1416
register_functions()
1517
register_lookups()

django_snowflake/aggregates.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.db.models.aggregates import StringAgg
2+
3+
4+
def string_agg(self, compiler, connection, **extra_context):
5+
return self.as_sql(compiler, connection, function="LISTAGG", **extra_context)
6+
7+
8+
def register_aggregates():
9+
StringAgg.as_snowflake = string_agg

django_snowflake/compiler.py

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from functools import partial
12
from itertools import chain
23

34
from 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

django_snowflake/features.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
4545
supports_json_field_contains = False
4646
# This feature is specific to the Django fork used for testing.
4747
supports_limit_in_exists = False
48+
supports_json_negative_indexing = False
4849
supports_over_clause = True
4950
supports_partial_indexes = False
5051
# https://docs.snowflake.com/en/sql-reference/functions-regexp.html#backreferences
@@ -173,6 +174,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
173174
'model_fields.test_jsonfield.TestSaveLoad.test_bulk_update_custom_get_prep_value',
174175
# AssertionError: possibly a server bug that returns the array as a string?
175176
'db_functions.json.test_json_array.JSONArrayTests.test_expressions',
177+
# LISTAGG returns empty string rather than NULL
178+
'aggregation.tests.AggregateTestCase.test_stringagg_default_value',
176179
}
177180

178181
django_test_skips = {
@@ -230,6 +233,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
230233
'aggregation.tests.AggregateAnnotationPruningTests.test_referenced_subquery_requires_wrapping',
231234
'aggregation.tests.AggregateTestCase.test_aggregation_subquery_annotation',
232235
'aggregation.tests.AggregateTestCase.test_aggregation_subquery_annotation_values',
236+
'aggregation.tests.AggregateTestCase.test_string_agg_filter_in_subquery',
233237
'annotations.tests.NonAggregateAnnotationTestCase.test_annotation_filter_with_subquery',
234238
'annotations.tests.NonAggregateAnnotationTestCase.test_annotation_subquery_outerref_transform',
235239
'composite_pk.test_filter.CompositePKFilterTests.test_outer_ref_pk',

django_snowflake/lookups.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from django.db import NotSupportedError
12
from django.db.models.fields.json import (
23
HasKeyLookup, KeyTextTransform, KeyTransform,
34
)
@@ -14,6 +15,11 @@ def compile_json_path(key_transforms):
1415
# Escape quotes to protect against SQL injection.
1516
transform = transform.replace('"', '\\"')
1617
json_path += f'{separator}"{transform}"'
18+
if idx < 0:
19+
raise NotSupportedError(
20+
"Using negative JSON array indices is not supported on this "
21+
"database backend."
22+
)
1723
else:
1824
# An integer lookup is an array index.
1925
json_path += f'[{idx}]'
@@ -51,7 +57,7 @@ def has_key_lookup(self, compiler, connection):
5157
rhs_key_transforms = [key]
5258
*rhs_key_transforms, final_key = rhs_key_transforms
5359
rhs_json_path = compile_json_path(rhs_key_transforms)
54-
final_key = self.compile_json_path_final_key(final_key)
60+
final_key = self.compile_json_path_final_key(connection, final_key)
5561
# If this is the only key, the separator must be a colon.
5662
if rhs_json_path == '':
5763
final_key = final_key.replace('.', ':', 1)

setup.cfg

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,21 @@ long_description_content_type = text/markdown
1111
classifiers =
1212
Development Status :: 5 - Production/Stable
1313
Framework :: Django
14-
Framework :: Django :: 5.2
14+
Framework :: Django :: 6.0
1515
License :: OSI Approved :: MIT License
1616
Operating System :: OS Independent
1717
Programming Language :: Python
1818
Programming Language :: Python :: 3
19-
Programming Language :: Python :: 3.10
20-
Programming Language :: Python :: 3.11
2119
Programming Language :: Python :: 3.12
2220
project_urls =
2321
Source = https://github.com/Snowflake-Labs/django-snowflake
2422
Tracker = https://github.com/Snowflake-Labs/django-snowflake/issues
2523

2624
[options]
27-
python_requires = >=3.10
25+
python_requires = >=3.12
2826
packages = find:
2927
install_requires =
30-
django >= 5.2, < 6.0
28+
# django >= 5.2, < 6.0
3129
snowflake-connector-python >= 3.6.0
3230

3331
[flake8]

0 commit comments

Comments
 (0)