Skip to content

Commit 422cc3d

Browse files
mShan0maikhanhbui
andauthored
Prepare for 1.3 release (#269)
* Allow Django 4.2 (#227) * Allow Django 4.2 * allow Django 4.2 * Fix errors with raising FullResultSet exception and with alter_column_type_sql() and collate_sql() functions (#229) * fix error with raising fullresultset * add django4.2 condition * fix alter_column_type_sql and collate_sql to take 2 additional arguments * delete argument 'old_rel_collation' * fix arguments names * fix last_executed_query() to properly replace placeholders with params (#234) * disable allows_group_by_select_index * unskip old tests * unskip some tests * skip more tests * Use latest Django 4.2 beta for tox (#238) * use 4.2 rc1 branch (#240) * allow partial support for filtering against window functions (#239) * add subsecond support to Now() (#242) * assign value to display_size (#244) * add latest django 4.2 branch to ci * raise an error when batch_size is zero. (#259) * replicate get or create test for mssql (#265) * Add skipped tests to Django 4.2 (#268) * skip django 4.2 failing tests * skip schema test * skip aggregate annotation pruning test --------- Co-authored-by: mShan0 <mark.shan19@gmail.com> * syntax fix * ci fix * bump version to 1.3 --------- Co-authored-by: Khanh Bui <85855766+khanhmaibui@users.noreply.github.com> Co-authored-by: Khanh Bui <khanhmb815@gmail.com>
1 parent d074172 commit 422cc3d

File tree

13 files changed

+225
-26
lines changed

13 files changed

+225
-26
lines changed

azure-pipelines.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ jobs:
2424

2525
strategy:
2626
matrix:
27+
Python3.11 - Django 4.2:
28+
python.version: '3.11'
29+
tox.env: 'py311-django42'
30+
Python3.10 - Django 4.2:
31+
python.version: '3.10'
32+
tox.env: 'py310-django42'
33+
Python 3.9 - Django 4.2:
34+
python.version: '3.9'
35+
tox.env: 'py39-django42'
36+
Python 3.8 - Django 4.2:
37+
python.version: '3.8'
38+
tox.env: 'py38-django42'
39+
2740
Python3.11 - Django 4.1:
2841
python.version: '3.11'
2942
tox.env: 'py311-django41'
@@ -118,6 +131,19 @@ jobs:
118131

119132
strategy:
120133
matrix:
134+
Python3.11 - Django 4.2:
135+
python.version: '3.11'
136+
tox.env: 'py311-django42'
137+
Python3.10 - Django 4.2:
138+
python.version: '3.10'
139+
tox.env: 'py310-django42'
140+
Python 3.9 - Django 4.2:
141+
python.version: '3.9'
142+
tox.env: 'py39-django42'
143+
Python 3.8 - Django 4.2:
144+
python.version: '3.8'
145+
tox.env: 'py38-django42'
146+
121147
Python3.11 - Django 4.1:
122148
python.version: '3.11'
123149
tox.env: 'py311-django41'

mssql/compiler.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from django.db.utils import NotSupportedError
1616
if django.VERSION >= (3, 1):
1717
from django.db.models.fields.json import compile_json_path, KeyTransform as json_KeyTransform
18+
if django.VERSION >= (4, 2):
19+
from django.core.exceptions import FullResultSet
1820

1921
def _as_sql_agv(self, compiler, connection):
2022
return self.as_sql(compiler, connection, template='%(function)s(CONVERT(float, %(field)s))')
@@ -228,13 +230,26 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
228230
if not getattr(features, 'supports_select_{}'.format(combinator)):
229231
raise NotSupportedError('{} is not supported on this database backend.'.format(combinator))
230232
result, params = self.get_combinator_sql(combinator, self.query.combinator_all)
233+
elif django.VERSION >= (4, 2) and self.qualify:
234+
result, params = self.get_qualify_sql()
235+
order_by = None
231236
else:
232237
distinct_fields, distinct_params = self.get_distinct()
233238
# This must come after 'select', 'ordering', and 'distinct' -- see
234239
# docstring of get_from_clause() for details.
235240
from_, f_params = self.get_from_clause()
236-
where, w_params = self.compile(self.where) if self.where is not None else ("", [])
237-
having, h_params = self.compile(self.having) if self.having is not None else ("", [])
241+
if django.VERSION >= (4, 2):
242+
try:
243+
where, w_params = self.compile(self.where) if self.where is not None else ("", [])
244+
except FullResultSet:
245+
where, w_params = "", []
246+
try:
247+
having, h_params = self.compile(self.having) if self.having is not None else ("", [])
248+
except FullResultSet:
249+
having, h_params = "", []
250+
else:
251+
where, w_params = self.compile(self.where) if self.where is not None else ("", [])
252+
having, h_params = self.compile(self.having) if self.having is not None else ("", [])
238253
params = []
239254
result = ['SELECT']
240255

mssql/features.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77

88
class DatabaseFeatures(BaseDatabaseFeatures):
9+
allows_group_by_select_index = False
910
allow_sliced_subqueries_with_in = False
1011
can_introspect_autofield = True
1112
can_introspect_json_field = False
@@ -71,4 +72,4 @@ def introspected_field_types(self):
7172
return {
7273
**super().introspected_field_types,
7374
"DurationField": "BigIntegerField",
74-
}
75+
}

mssql/functions.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from django.db.models.expressions import Case, Exists, Expression, OrderBy, When, Window
1111
from django.db.models.fields import BinaryField, Field
1212
from django.db.models.functions import Cast, NthValue, MD5, SHA1, SHA224, SHA256, SHA384, SHA512
13+
from django.db.models.functions.datetime import Now
1314
from django.db.models.functions.math import ATan2, Ln, Log, Mod, Round, Degrees, Radians, Power
1415
from django.db.models.functions.text import Replace
1516
from django.db.models.lookups import In, Lookup
@@ -123,6 +124,10 @@ def sqlserver_exists(self, compiler, connection, template=None, **extra_context)
123124
sql = 'CASE WHEN {} THEN 1 ELSE 0 END'.format(sql)
124125
return sql, params
125126

127+
def sqlserver_now(self, compiler, connection, **extra_context):
128+
return self.as_sql(
129+
compiler, connection, template="SYSDATETIME()", **extra_context
130+
)
126131

127132
def sqlserver_lookup(self, compiler, connection):
128133
# MSSQL doesn't allow EXISTS() to be compared to another expression
@@ -287,7 +292,7 @@ def bulk_update_with_default(self, objs, fields, batch_size=None, default=0):
287292
SQL Server require that at least one of the result expressions in a CASE specification must be an expression other than the NULL constant.
288293
Patched with a default value 0. The user can also pass a custom default value for CASE statement.
289294
"""
290-
if batch_size is not None and batch_size < 0:
295+
if batch_size is not None and batch_size <= 0:
291296
raise ValueError('Batch size must be a positive integer.')
292297
if not fields:
293298
raise ValueError('Field names must be given to bulk_update().')
@@ -456,6 +461,7 @@ def sqlserver_sha512(self, compiler, connection, **extra_context):
456461
Round.as_microsoft = sqlserver_round
457462
Window.as_microsoft = sqlserver_window
458463
Replace.as_microsoft = sqlserver_replace
464+
Now.as_microsoft = sqlserver_now
459465
MD5.as_microsoft = sqlserver_md5
460466
SHA1.as_microsoft = sqlserver_sha1
461467
SHA224.as_microsoft = sqlserver_sha224

mssql/introspection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def get_table_description(self, cursor, table_name, identity_check=True):
107107
"""
108108

109109
# map pyodbc's cursor.columns to db-api cursor description
110-
columns = [[c[3], c[4], None, c[6], c[6], c[8], c[10], c[12]] for c in cursor.columns(table=table_name)]
110+
columns = [[c[3], c[4], c[6], c[6], c[6], c[8], c[10], c[12]] for c in cursor.columns(table=table_name)]
111111

112112
if not columns:
113113
raise DatabaseError(f"Table {table_name} does not exist.")

mssql/operations.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,13 @@ def last_executed_query(self, cursor, sql, params):
418418
exists for database backends to provide a better implementation
419419
according to their own quoting schemes.
420420
"""
421-
return super().last_executed_query(cursor, cursor.last_sql, cursor.last_params)
421+
if params:
422+
if isinstance(params, list):
423+
params = tuple(params)
424+
return sql % params
425+
# Just return sql when there are no parameters.
426+
else:
427+
return sql
422428

423429
def savepoint_create_sql(self, sid):
424430
"""

mssql/schema.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,14 @@ def _alter_column_null_sql(self, model, old_field, new_field):
161161
[],
162162
)
163163

164-
def _alter_column_type_sql(self, model, old_field, new_field, new_type):
165-
new_type = self._set_field_new_type_null_status(old_field, new_type)
166-
return super()._alter_column_type_sql(model, old_field, new_field, new_type)
164+
if django_version >= (4, 2):
165+
def _alter_column_type_sql(self, model, old_field, new_field, new_type, old_collation, new_collation):
166+
new_type = self._set_field_new_type_null_status(old_field, new_type)
167+
return super()._alter_column_type_sql(model, old_field, new_field, new_type, old_collation, new_collation)
168+
else:
169+
def _alter_column_type_sql(self, model, old_field, new_field, new_type):
170+
new_type = self._set_field_new_type_null_status(old_field, new_type)
171+
return super()._alter_column_type_sql(model, old_field, new_field, new_type)
167172

168173
def alter_unique_together(self, model, old_unique_together, new_unique_together):
169174
"""
@@ -443,7 +448,12 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
443448
post_actions = []
444449
# Type change?
445450
if old_type != new_type:
446-
fragment, other_actions = self._alter_column_type_sql(model, old_field, new_field, new_type)
451+
if django_version >= (4, 2):
452+
fragment, other_actions = self._alter_column_type_sql(
453+
model, old_field, new_field, new_type, old_collation=None, new_collation=None
454+
)
455+
else:
456+
fragment, other_actions = self._alter_column_type_sql(model, old_field, new_field, new_type)
447457
actions.append(fragment)
448458
post_actions.extend(other_actions)
449459
# Drop unique constraint, SQL Server requires explicit deletion
@@ -683,9 +693,14 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
683693
for old_rel, new_rel in rels_to_update:
684694
rel_db_params = new_rel.field.db_parameters(connection=self.connection)
685695
rel_type = rel_db_params['type']
686-
fragment, other_actions = self._alter_column_type_sql(
687-
new_rel.related_model, old_rel.field, new_rel.field, rel_type
688-
)
696+
if django_version >= (4, 2):
697+
fragment, other_actions = self._alter_column_type_sql(
698+
new_rel.related_model, old_rel.field, new_rel.field, rel_type, old_collation=None, new_collation=None
699+
)
700+
else:
701+
fragment, other_actions = self._alter_column_type_sql(
702+
new_rel.related_model, old_rel.field, new_rel.field, rel_type
703+
)
689704
# Drop related_model indexes, so it can be altered
690705
index_names = self._db_table_constraint_names(old_rel.related_model._meta.db_table, index=True)
691706
for index_name in index_names:
@@ -1262,8 +1277,12 @@ def add_constraint(self, model, constraint):
12621277
(constraint.condition.connector, constraint.name))
12631278
super().add_constraint(model, constraint)
12641279

1265-
def _collate_sql(self, collation):
1266-
return ' COLLATE ' + collation
1280+
if django_version >= (4, 2):
1281+
def _collate_sql(self, collation, old_collation=None, table_name=None):
1282+
return ' COLLATE ' + collation if collation else ""
1283+
else:
1284+
def _collate_sql(self, collation):
1285+
return ' COLLATE ' + collation
12671286

12681287
def _create_index_name(self, table_name, column_names, suffix=""):
12691288
index_name = super()._create_index_name(table_name, column_names, suffix)

setup.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
'Framework :: Django :: 3.2',
1919
'Framework :: Django :: 4.0',
2020
'Framework :: Django :: 4.1',
21+
'Framework :: Django :: 4.2',
2122
]
2223

2324
this_directory = path.abspath(path.dirname(__file__))
@@ -26,7 +27,7 @@
2627

2728
setup(
2829
name='mssql-django',
29-
version='1.2',
30+
version='1.3',
3031
description='Django backend for Microsoft SQL Server',
3132
long_description=long_description,
3233
long_description_content_type='text/markdown',
@@ -39,7 +40,7 @@
3940
license='BSD',
4041
packages=find_packages(),
4142
install_requires=[
42-
'django>=3.2,<4.2',
43+
'django>=3.2,<4.3',
4344
'pyodbc>=3.0',
4445
'pytz',
4546
],
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Generated by Django 4.2 on 2023-05-03 15:08
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("testapp", "0023_number"),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name="Publisher",
16+
fields=[
17+
(
18+
"id",
19+
models.AutoField(
20+
auto_created=True,
21+
primary_key=True,
22+
serialize=False,
23+
verbose_name="ID",
24+
),
25+
),
26+
("name", models.CharField(max_length=100)),
27+
],
28+
),
29+
migrations.CreateModel(
30+
name="Book",
31+
fields=[
32+
(
33+
"id",
34+
models.AutoField(
35+
auto_created=True,
36+
primary_key=True,
37+
serialize=False,
38+
verbose_name="ID",
39+
),
40+
),
41+
("name", models.CharField(max_length=100)),
42+
("updated", models.DateTimeField(auto_now=True)),
43+
(
44+
"authors",
45+
models.ManyToManyField(related_name="books", to="testapp.author"),
46+
),
47+
(
48+
"publisher",
49+
models.ForeignKey(
50+
db_column="publisher_id_column",
51+
on_delete=django.db.models.deletion.CASCADE,
52+
related_name="books",
53+
to="testapp.publisher",
54+
),
55+
),
56+
],
57+
),
58+
]

testapp/models.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from django.db.models import Q
1010
from django.utils import timezone
1111

12-
# We are using this Mixin to test casting of BigAuto and Auto fields
12+
# We are using this Mixin to test casting of BigAuto and Auto fields
1313
class BigAutoFieldMixin(models.Model):
1414
id = models.BigAutoField(primary_key=True)
1515

@@ -229,4 +229,20 @@ class Number(models.Model):
229229
decimal_value = models.DecimalField(max_digits=20, decimal_places=17, null=True)
230230

231231
def __str__(self):
232-
return "%i, %.3f, %.17f" % (self.integer, self.float, self.decimal_value)
232+
return "%i, %.3f, %.17f" % (self.integer, self.float, self.decimal_value)
233+
234+
235+
class Publisher(models.Model):
236+
name = models.CharField(max_length=100)
237+
238+
239+
class Book(models.Model):
240+
name = models.CharField(max_length=100)
241+
authors = models.ManyToManyField(Author, related_name="books")
242+
publisher = models.ForeignKey(
243+
Publisher,
244+
models.CASCADE,
245+
related_name="books",
246+
db_column="publisher_id_column",
247+
)
248+
updated = models.DateTimeField(auto_now=True)

testapp/settings.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,6 @@
9999

100100
TEST_RUNNER = "testapp.runners.ExcludedTestSuiteRunner"
101101
EXCLUDED_TESTS = [
102-
'aggregation.tests.AggregateTestCase.test_expression_on_aggregation',
103-
'aggregation_regress.tests.AggregationTests.test_annotated_conditional_aggregate',
104102
'aggregation_regress.tests.AggregationTests.test_annotation_with_value',
105103
'aggregation.tests.AggregateTestCase.test_distinct_on_aggregate',
106104
'annotations.tests.NonAggregateAnnotationTestCase.test_annotate_exists',
@@ -150,7 +148,6 @@
150148
'schema.tests.SchemaTests.test_unique_and_reverse_m2m',
151149
'schema.tests.SchemaTests.test_unique_no_unnecessary_fk_drops',
152150
'select_for_update.tests.SelectForUpdateTests.test_for_update_after_from',
153-
'backends.tests.LastExecutedQueryTest.test_last_executed_query',
154151
'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_year_exact_lookup',
155152
'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_year_greaterthan_lookup',
156153
'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_year_lessthan_lookup',
@@ -172,9 +169,7 @@
172169
'expressions.tests.FTimeDeltaTests.test_time_subquery_subtraction',
173170
'migrations.test_operations.OperationTests.test_alter_field_reloads_state_on_fk_with_to_field_target_type_change',
174171
'schema.tests.SchemaTests.test_alter_smallint_pk_to_smallautofield_pk',
175-
176172
'annotations.tests.NonAggregateAnnotationTestCase.test_combined_expression_annotation_with_aggregation',
177-
'db_functions.comparison.test_cast.CastTests.test_cast_to_integer',
178173
'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_func',
179174
'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_iso_weekday_func',
180175
'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_func',
@@ -291,6 +286,19 @@
291286
'model_fields.test_jsonfield.TestQuerying.test_lookups_with_key_transform',
292287
'model_fields.test_jsonfield.TestQuerying.test_ordering_grouping_by_count',
293288
'model_fields.test_jsonfield.TestQuerying.test_has_key_number',
289+
290+
# Django 4.2
291+
'get_or_create.tests.UpdateOrCreateTests.test_update_only_defaults_and_pre_save_fields_when_local_fields',
292+
'aggregation.test_filter_argument.FilteredAggregateTests.test_filtered_aggregate_empty_condition',
293+
'aggregation.test_filter_argument.FilteredAggregateTests.test_filtered_aggregate_ref_multiple_subquery_annotation',
294+
'aggregation.test_filter_argument.FilteredAggregateTests.test_filtered_aggregate_ref_subquery_annotation',
295+
"aggregation.tests.AggregateAnnotationPruningTests.test_referenced_group_by_annotation_kept",
296+
'aggregation.tests.AggregateTestCase.test_group_by_nested_expression_with_params',
297+
'expressions.tests.BasicExpressionsTests.test_aggregate_subquery_annotation',
298+
'queries.test_qs_combinators.QuerySetSetOperationTests.test_union_order_with_null_first_last',
299+
'queries.test_qs_combinators.QuerySetSetOperationTests.test_union_with_select_related_and_order',
300+
'expressions_window.tests.WindowFunctionTests.test_limited_filter',
301+
'schema.tests.SchemaTests.test_remove_ignored_unique_constraint_not_create_fk_index',
294302
]
295303

296304
REGEX_TESTS = [

0 commit comments

Comments
 (0)