Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare for 1.3 release #269

Merged
merged 23 commits into from May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0a51811
Allow Django 4.2 (#227)
khanhmaibui Feb 16, 2023
81018de
Fix errors with raising FullResultSet exception and with alter_column…
khanhmaibui Feb 21, 2023
06e0788
fix last_executed_query() to properly replace placeholders with param…
khanhmaibui Mar 3, 2023
6875151
disable allows_group_by_select_index
mShan0 Mar 10, 2023
9ac7c2f
unskip old tests
mShan0 Mar 10, 2023
9816cdf
unskip some tests
mShan0 Mar 13, 2023
8e5c9cd
skip more tests
mShan0 Mar 13, 2023
895909c
Merge pull request #236 from microsoft/fix-aggregate-tests
mShan0 Mar 13, 2023
127838e
Merge branch 'dev' of github.com:microsoft/mssql-django into django42
khanhmaibui Mar 13, 2023
303859c
Use latest Django 4.2 beta for tox (#238)
mShan0 Mar 15, 2023
e196f5a
use 4.2 rc1 branch (#240)
mShan0 Mar 20, 2023
5a630ba
allow partial support for filtering against window functions (#239)
khanhmaibui Mar 23, 2023
adc1409
add subsecond support to Now() (#242)
khanhmaibui Mar 24, 2023
0c4d052
assign value to display_size (#244)
khanhmaibui Apr 4, 2023
f9a1b43
add latest django 4.2 branch to ci
mShan0 Apr 4, 2023
b311485
add latest django 4.2 branch to ci
mShan0 Apr 5, 2023
4a37e32
raise an error when batch_size is zero. (#259)
khanhmaibui Apr 24, 2023
3375f3a
replicate get or create test for mssql (#265)
mShan0 May 3, 2023
0a2664a
Add skipped tests to Django 4.2 (#268)
khanhmaibui May 30, 2023
f2d82e9
Merge branch 'dev' into django42
mShan0 May 30, 2023
a3e895b
syntax fix
mShan0 May 31, 2023
19e1539
ci fix
mShan0 May 31, 2023
2885672
bump version to 1.3
mShan0 May 31, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 26 additions & 0 deletions azure-pipelines.yml
Expand Up @@ -24,6 +24,19 @@ jobs:

strategy:
matrix:
Python3.11 - Django 4.2:
python.version: '3.11'
tox.env: 'py311-django42'
Python3.10 - Django 4.2:
python.version: '3.10'
tox.env: 'py310-django42'
Python 3.9 - Django 4.2:
python.version: '3.9'
tox.env: 'py39-django42'
Python 3.8 - Django 4.2:
python.version: '3.8'
tox.env: 'py38-django42'

Python3.11 - Django 4.1:
python.version: '3.11'
tox.env: 'py311-django41'
Expand Down Expand Up @@ -118,6 +131,19 @@ jobs:

strategy:
matrix:
Python3.11 - Django 4.2:
python.version: '3.11'
tox.env: 'py311-django42'
Python3.10 - Django 4.2:
python.version: '3.10'
tox.env: 'py310-django42'
Python 3.9 - Django 4.2:
python.version: '3.9'
tox.env: 'py39-django42'
Python 3.8 - Django 4.2:
python.version: '3.8'
tox.env: 'py38-django42'

Python3.11 - Django 4.1:
python.version: '3.11'
tox.env: 'py311-django41'
Expand Down
19 changes: 17 additions & 2 deletions mssql/compiler.py
Expand Up @@ -15,6 +15,8 @@
from django.db.utils import NotSupportedError
if django.VERSION >= (3, 1):
from django.db.models.fields.json import compile_json_path, KeyTransform as json_KeyTransform
if django.VERSION >= (4, 2):
from django.core.exceptions import FullResultSet

def _as_sql_agv(self, compiler, connection):
return self.as_sql(compiler, connection, template='%(function)s(CONVERT(float, %(field)s))')
Expand Down Expand Up @@ -228,13 +230,26 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
if not getattr(features, 'supports_select_{}'.format(combinator)):
raise NotSupportedError('{} is not supported on this database backend.'.format(combinator))
result, params = self.get_combinator_sql(combinator, self.query.combinator_all)
elif django.VERSION >= (4, 2) and self.qualify:
result, params = self.get_qualify_sql()
order_by = None
else:
distinct_fields, distinct_params = self.get_distinct()
# This must come after 'select', 'ordering', and 'distinct' -- see
# docstring of get_from_clause() for details.
from_, f_params = self.get_from_clause()
where, w_params = self.compile(self.where) if self.where is not None else ("", [])
having, h_params = self.compile(self.having) if self.having is not None else ("", [])
if django.VERSION >= (4, 2):
try:
where, w_params = self.compile(self.where) if self.where is not None else ("", [])
except FullResultSet:
where, w_params = "", []
try:
having, h_params = self.compile(self.having) if self.having is not None else ("", [])
except FullResultSet:
having, h_params = "", []
else:
where, w_params = self.compile(self.where) if self.where is not None else ("", [])
having, h_params = self.compile(self.having) if self.having is not None else ("", [])
params = []
result = ['SELECT']

Expand Down
3 changes: 2 additions & 1 deletion mssql/features.py
Expand Up @@ -6,6 +6,7 @@


class DatabaseFeatures(BaseDatabaseFeatures):
allows_group_by_select_index = False
allow_sliced_subqueries_with_in = False
can_introspect_autofield = True
can_introspect_json_field = False
Expand Down Expand Up @@ -71,4 +72,4 @@ def introspected_field_types(self):
return {
**super().introspected_field_types,
"DurationField": "BigIntegerField",
}
}
8 changes: 7 additions & 1 deletion mssql/functions.py
Expand Up @@ -10,6 +10,7 @@
from django.db.models.expressions import Case, Exists, Expression, OrderBy, When, Window
from django.db.models.fields import BinaryField, Field
from django.db.models.functions import Cast, NthValue, MD5, SHA1, SHA224, SHA256, SHA384, SHA512
from django.db.models.functions.datetime import Now
from django.db.models.functions.math import ATan2, Ln, Log, Mod, Round, Degrees, Radians, Power
from django.db.models.functions.text import Replace
from django.db.models.lookups import In, Lookup
Expand Down Expand Up @@ -123,6 +124,10 @@ def sqlserver_exists(self, compiler, connection, template=None, **extra_context)
sql = 'CASE WHEN {} THEN 1 ELSE 0 END'.format(sql)
return sql, params

def sqlserver_now(self, compiler, connection, **extra_context):
return self.as_sql(
compiler, connection, template="SYSDATETIME()", **extra_context
)

def sqlserver_lookup(self, compiler, connection):
# MSSQL doesn't allow EXISTS() to be compared to another expression
Expand Down Expand Up @@ -287,7 +292,7 @@ def bulk_update_with_default(self, objs, fields, batch_size=None, default=0):
SQL Server require that at least one of the result expressions in a CASE specification must be an expression other than the NULL constant.
Patched with a default value 0. The user can also pass a custom default value for CASE statement.
"""
if batch_size is not None and batch_size < 0:
if batch_size is not None and batch_size <= 0:
raise ValueError('Batch size must be a positive integer.')
if not fields:
raise ValueError('Field names must be given to bulk_update().')
Expand Down Expand Up @@ -456,6 +461,7 @@ def sqlserver_sha512(self, compiler, connection, **extra_context):
Round.as_microsoft = sqlserver_round
Window.as_microsoft = sqlserver_window
Replace.as_microsoft = sqlserver_replace
Now.as_microsoft = sqlserver_now
MD5.as_microsoft = sqlserver_md5
SHA1.as_microsoft = sqlserver_sha1
SHA224.as_microsoft = sqlserver_sha224
Expand Down
2 changes: 1 addition & 1 deletion mssql/introspection.py
Expand Up @@ -107,7 +107,7 @@ def get_table_description(self, cursor, table_name, identity_check=True):
"""

# map pyodbc's cursor.columns to db-api cursor description
columns = [[c[3], c[4], None, c[6], c[6], c[8], c[10], c[12]] for c in cursor.columns(table=table_name)]
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)]

if not columns:
raise DatabaseError(f"Table {table_name} does not exist.")
Expand Down
8 changes: 7 additions & 1 deletion mssql/operations.py
Expand Up @@ -418,7 +418,13 @@ def last_executed_query(self, cursor, sql, params):
exists for database backends to provide a better implementation
according to their own quoting schemes.
"""
return super().last_executed_query(cursor, cursor.last_sql, cursor.last_params)
if params:
if isinstance(params, list):
params = tuple(params)
return sql % params
# Just return sql when there are no parameters.
else:
return sql

def savepoint_create_sql(self, sid):
"""
Expand Down
37 changes: 28 additions & 9 deletions mssql/schema.py
Expand Up @@ -161,9 +161,14 @@ def _alter_column_null_sql(self, model, old_field, new_field):
[],
)

def _alter_column_type_sql(self, model, old_field, new_field, new_type):
new_type = self._set_field_new_type_null_status(old_field, new_type)
return super()._alter_column_type_sql(model, old_field, new_field, new_type)
if django_version >= (4, 2):
def _alter_column_type_sql(self, model, old_field, new_field, new_type, old_collation, new_collation):
new_type = self._set_field_new_type_null_status(old_field, new_type)
return super()._alter_column_type_sql(model, old_field, new_field, new_type, old_collation, new_collation)
else:
def _alter_column_type_sql(self, model, old_field, new_field, new_type):
new_type = self._set_field_new_type_null_status(old_field, new_type)
return super()._alter_column_type_sql(model, old_field, new_field, new_type)

def alter_unique_together(self, model, old_unique_together, new_unique_together):
"""
Expand Down Expand Up @@ -443,7 +448,12 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
post_actions = []
# Type change?
if old_type != new_type:
fragment, other_actions = self._alter_column_type_sql(model, old_field, new_field, new_type)
if django_version >= (4, 2):
fragment, other_actions = self._alter_column_type_sql(
model, old_field, new_field, new_type, old_collation=None, new_collation=None
)
else:
fragment, other_actions = self._alter_column_type_sql(model, old_field, new_field, new_type)
actions.append(fragment)
post_actions.extend(other_actions)
# Drop unique constraint, SQL Server requires explicit deletion
Expand Down Expand Up @@ -683,9 +693,14 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
for old_rel, new_rel in rels_to_update:
rel_db_params = new_rel.field.db_parameters(connection=self.connection)
rel_type = rel_db_params['type']
fragment, other_actions = self._alter_column_type_sql(
new_rel.related_model, old_rel.field, new_rel.field, rel_type
)
if django_version >= (4, 2):
fragment, other_actions = self._alter_column_type_sql(
new_rel.related_model, old_rel.field, new_rel.field, rel_type, old_collation=None, new_collation=None
)
else:
fragment, other_actions = self._alter_column_type_sql(
new_rel.related_model, old_rel.field, new_rel.field, rel_type
)
# Drop related_model indexes, so it can be altered
index_names = self._db_table_constraint_names(old_rel.related_model._meta.db_table, index=True)
for index_name in index_names:
Expand Down Expand Up @@ -1262,8 +1277,12 @@ def add_constraint(self, model, constraint):
(constraint.condition.connector, constraint.name))
super().add_constraint(model, constraint)

def _collate_sql(self, collation):
return ' COLLATE ' + collation
if django_version >= (4, 2):
def _collate_sql(self, collation, old_collation=None, table_name=None):
return ' COLLATE ' + collation if collation else ""
else:
def _collate_sql(self, collation):
return ' COLLATE ' + collation

def _create_index_name(self, table_name, column_names, suffix=""):
index_name = super()._create_index_name(table_name, column_names, suffix)
Expand Down
5 changes: 3 additions & 2 deletions setup.py
Expand Up @@ -18,6 +18,7 @@
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.0',
'Framework :: Django :: 4.1',
'Framework :: Django :: 4.2',
]

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

setup(
name='mssql-django',
version='1.2',
version='1.3',
description='Django backend for Microsoft SQL Server',
long_description=long_description,
long_description_content_type='text/markdown',
Expand All @@ -39,7 +40,7 @@
license='BSD',
packages=find_packages(),
install_requires=[
'django>=3.2,<4.2',
'django>=3.2,<4.3',
'pyodbc>=3.0',
'pytz',
],
Expand Down
58 changes: 58 additions & 0 deletions testapp/migrations/0024_publisher_book.py
@@ -0,0 +1,58 @@
# Generated by Django 4.2 on 2023-05-03 15:08

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("testapp", "0023_number"),
]

operations = [
migrations.CreateModel(
name="Publisher",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
],
),
migrations.CreateModel(
name="Book",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
("updated", models.DateTimeField(auto_now=True)),
(
"authors",
models.ManyToManyField(related_name="books", to="testapp.author"),
),
(
"publisher",
models.ForeignKey(
db_column="publisher_id_column",
on_delete=django.db.models.deletion.CASCADE,
related_name="books",
to="testapp.publisher",
),
),
],
),
]
20 changes: 18 additions & 2 deletions testapp/models.py
Expand Up @@ -9,7 +9,7 @@
from django.db.models import Q
from django.utils import timezone

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

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

def __str__(self):
return "%i, %.3f, %.17f" % (self.integer, self.float, self.decimal_value)
return "%i, %.3f, %.17f" % (self.integer, self.float, self.decimal_value)


class Publisher(models.Model):
name = models.CharField(max_length=100)


class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author, related_name="books")
publisher = models.ForeignKey(
Publisher,
models.CASCADE,
related_name="books",
db_column="publisher_id_column",
)
updated = models.DateTimeField(auto_now=True)
18 changes: 13 additions & 5 deletions testapp/settings.py
Expand Up @@ -99,8 +99,6 @@

TEST_RUNNER = "testapp.runners.ExcludedTestSuiteRunner"
EXCLUDED_TESTS = [
'aggregation.tests.AggregateTestCase.test_expression_on_aggregation',
'aggregation_regress.tests.AggregationTests.test_annotated_conditional_aggregate',
'aggregation_regress.tests.AggregationTests.test_annotation_with_value',
'aggregation.tests.AggregateTestCase.test_distinct_on_aggregate',
'annotations.tests.NonAggregateAnnotationTestCase.test_annotate_exists',
Expand Down Expand Up @@ -150,7 +148,6 @@
'schema.tests.SchemaTests.test_unique_and_reverse_m2m',
'schema.tests.SchemaTests.test_unique_no_unnecessary_fk_drops',
'select_for_update.tests.SelectForUpdateTests.test_for_update_after_from',
'backends.tests.LastExecutedQueryTest.test_last_executed_query',
'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_year_exact_lookup',
'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_year_greaterthan_lookup',
'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_year_lessthan_lookup',
Expand All @@ -172,9 +169,7 @@
'expressions.tests.FTimeDeltaTests.test_time_subquery_subtraction',
'migrations.test_operations.OperationTests.test_alter_field_reloads_state_on_fk_with_to_field_target_type_change',
'schema.tests.SchemaTests.test_alter_smallint_pk_to_smallautofield_pk',

'annotations.tests.NonAggregateAnnotationTestCase.test_combined_expression_annotation_with_aggregation',
'db_functions.comparison.test_cast.CastTests.test_cast_to_integer',
'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_func',
'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_iso_weekday_func',
'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_func',
Expand Down Expand Up @@ -291,6 +286,19 @@
'model_fields.test_jsonfield.TestQuerying.test_lookups_with_key_transform',
'model_fields.test_jsonfield.TestQuerying.test_ordering_grouping_by_count',
'model_fields.test_jsonfield.TestQuerying.test_has_key_number',

# Django 4.2
'get_or_create.tests.UpdateOrCreateTests.test_update_only_defaults_and_pre_save_fields_when_local_fields',
'aggregation.test_filter_argument.FilteredAggregateTests.test_filtered_aggregate_empty_condition',
'aggregation.test_filter_argument.FilteredAggregateTests.test_filtered_aggregate_ref_multiple_subquery_annotation',
'aggregation.test_filter_argument.FilteredAggregateTests.test_filtered_aggregate_ref_subquery_annotation',
"aggregation.tests.AggregateAnnotationPruningTests.test_referenced_group_by_annotation_kept",
'aggregation.tests.AggregateTestCase.test_group_by_nested_expression_with_params',
'expressions.tests.BasicExpressionsTests.test_aggregate_subquery_annotation',
'queries.test_qs_combinators.QuerySetSetOperationTests.test_union_order_with_null_first_last',
'queries.test_qs_combinators.QuerySetSetOperationTests.test_union_with_select_related_and_order',
'expressions_window.tests.WindowFunctionTests.test_limited_filter',
'schema.tests.SchemaTests.test_remove_ignored_unique_constraint_not_create_fk_index',
]

REGEX_TESTS = [
Expand Down