Skip to content

Commit 69349c9

Browse files
authored
Revert "Prepare for 1.1.3 release (#124)" (#145)
This reverts commit c0476cf.
1 parent c0476cf commit 69349c9

27 files changed

+124
-904
lines changed

.github/workflows/devskim.yml

Lines changed: 0 additions & 34 deletions
This file was deleted.

README.md

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ We hope you enjoy using the MSSQL-Django 3rd party backend.
1010

1111
## Features
1212

13-
- Supports Django 3.2 and 4.0
13+
- Supports Django 2.2, 3.0, 3.1, 3.2 and 4.0
1414
- Tested on Microsoft SQL Server 2016, 2017, 2019
1515
- Passes most of the tests of the Django test suite
1616
- Compatible with
@@ -67,13 +67,6 @@ in DATABASES control the behavior of the backend:
6767

6868
String. Database user password.
6969

70-
- TOKEN
71-
72-
String. Access token fetched as a user or service principal which
73-
has access to the database. E.g. when using `azure.identity`, the
74-
result of `DefaultAzureCredential().get_token('https://database.windows.net/.default')`
75-
can be passed.
76-
7770
- AUTOCOMMIT
7871

7972
Boolean. Set this to `False` if you want to disable

SUPPORT.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ This project uses GitHub Issues to track bugs and feature requests. Please searc
66
issues before filing new issues to avoid duplicates. For new issues, file your bug or
77
feature request as a new Issue.
88

9+
For help and questions about using this project, please utilize the Django Developers form at https://groups.google.com/g/django-developers. Please search for an existing discussion on your topic before adding a new conversation. For new conversations, include "MSSQL" in a descriptive subject.
10+
911
## Microsoft Support Policy
1012

1113
Support for this project is limited to the resources listed above.

mssql/base.py

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import os
88
import re
99
import time
10-
import struct
1110

1211
from django.core.exceptions import ImproperlyConfigured
1312

@@ -54,22 +53,7 @@ def encode_connection_string(fields):
5453
'%s=%s' % (k, encode_value(v))
5554
for k, v in fields.items()
5655
)
57-
def prepare_token_for_odbc(token):
58-
"""
59-
Will prepare token for passing it to the odbc driver, as it expects
60-
bytes and not a string
61-
:param token:
62-
:return: packed binary byte representation of token string
63-
"""
64-
if not isinstance(token, str):
65-
raise TypeError("Invalid token format provided.")
6656

67-
tokenstr = token.encode()
68-
exptoken = b""
69-
for i in tokenstr:
70-
exptoken += bytes({i})
71-
exptoken += bytes(1)
72-
return struct.pack("=i", len(exptoken)) + exptoken
7357

7458
def encode_value(v):
7559
"""If the value contains a semicolon, or starts with a left curly brace,
@@ -310,7 +294,7 @@ def get_new_connection(self, conn_params):
310294
cstr_parts['UID'] = user
311295
if 'Authentication=ActiveDirectoryInteractive' not in options_extra_params:
312296
cstr_parts['PWD'] = password
313-
elif 'TOKEN' not in conn_params:
297+
else:
314298
if ms_drivers.match(driver) and 'Authentication=ActiveDirectoryMsi' not in options_extra_params:
315299
cstr_parts['Trusted_Connection'] = trusted_connection
316300
else:
@@ -340,17 +324,11 @@ def get_new_connection(self, conn_params):
340324
conn = None
341325
retry_count = 0
342326
need_to_retry = False
343-
args = {
344-
'unicode_results': unicode_results,
345-
'timeout': timeout,
346-
}
347-
if 'TOKEN' in conn_params:
348-
args['attrs_before'] = {
349-
1256: prepare_token_for_odbc(conn_params['TOKEN'])
350-
}
351327
while conn is None:
352328
try:
353-
conn = Database.connect(connstr, **args)
329+
conn = Database.connect(connstr,
330+
unicode_results=unicode_results,
331+
timeout=timeout)
354332
except Exception as e:
355333
for error_number in self._transient_error_numbers:
356334
if error_number in e.args[1]:

mssql/compiler.py

Lines changed: 7 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,7 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
319319
# For subqueres with an ORDER BY clause, SQL Server also
320320
# requires a TOP or OFFSET clause which is not generated for
321321
# Django 2.x. See https://github.com/microsoft/mssql-django/issues/12
322-
# Add OFFSET for all Django versions.
323-
# https://github.com/microsoft/mssql-django/issues/109
324-
if not (do_offset or do_limit):
322+
if django.VERSION < (3, 0, 0) and not (do_offset or do_limit):
325323
result.append("OFFSET 0 ROWS")
326324

327325
# SQL Server requires the backend-specific emulation (2008 or earlier)
@@ -428,16 +426,6 @@ def get_returned_fields(self):
428426
return self.returning_fields
429427
return self.return_id
430428

431-
def can_return_columns_from_insert(self):
432-
if django.VERSION >= (3, 0, 0):
433-
return self.connection.features.can_return_columns_from_insert
434-
return self.connection.features.can_return_id_from_insert
435-
436-
def can_return_rows_from_bulk_insert(self):
437-
if django.VERSION >= (3, 0, 0):
438-
return self.connection.features.can_return_rows_from_bulk_insert
439-
return self.connection.features.can_return_ids_from_bulk_insert
440-
441429
def fix_auto(self, sql, opts, fields, qn):
442430
if opts.auto_field is not None:
443431
# db_column is None if not explicitly specified by model field
@@ -453,39 +441,15 @@ def fix_auto(self, sql, opts, fields, qn):
453441

454442
return sql
455443

456-
def bulk_insert_default_values_sql(self, table):
457-
seed_rows_number = 8
458-
cross_join_power = 4 # 8^4 = 4096 > maximum allowed batch size for the backend = 1000
459-
460-
def generate_seed_rows(n):
461-
return " UNION ALL ".join("SELECT 1 AS x" for _ in range(n))
462-
463-
def cross_join(p):
464-
return ", ".join("SEED_ROWS AS _%s" % i for i in range(p))
465-
466-
return """
467-
WITH SEED_ROWS AS (%s)
468-
MERGE INTO %s
469-
USING (
470-
SELECT TOP %s * FROM (SELECT 1 as x FROM %s) FAKE_ROWS
471-
) FAKE_DATA
472-
ON 1 = 0
473-
WHEN NOT MATCHED THEN
474-
INSERT DEFAULT VALUES
475-
""" % (generate_seed_rows(seed_rows_number),
476-
table,
477-
len(self.query.objs),
478-
cross_join(cross_join_power))
479-
480444
def as_sql(self):
481445
# We don't need quote_name_unless_alias() here, since these are all
482446
# going to be column names (so we can avoid the extra overhead).
483447
qn = self.connection.ops.quote_name
484448
opts = self.query.get_meta()
485449
result = ['INSERT INTO %s' % qn(opts.db_table)]
450+
fields = self.query.fields or [opts.pk]
486451

487452
if self.query.fields:
488-
fields = self.query.fields
489453
result.append('(%s)' % ', '.join(qn(f.column) for f in fields))
490454
values_format = 'VALUES (%s)'
491455
value_rows = [
@@ -506,31 +470,11 @@ def as_sql(self):
506470

507471
placeholder_rows, param_rows = self.assemble_as_sql(fields, value_rows)
508472

509-
if self.get_returned_fields() and self.can_return_columns_from_insert():
510-
if self.can_return_rows_from_bulk_insert():
511-
if not(self.query.fields):
512-
# There isn't really a single statement to bulk multiple DEFAULT VALUES insertions,
513-
# so we have to use a workaround:
514-
# https://dba.stackexchange.com/questions/254771/insert-multiple-rows-into-a-table-with-only-an-identity-column
515-
result = [self.bulk_insert_default_values_sql(qn(opts.db_table))]
516-
r_sql, self.returning_params = self.connection.ops.return_insert_columns(self.get_returned_fields())
517-
if r_sql:
518-
result.append(r_sql)
519-
sql = " ".join(result) + ";"
520-
return [(sql, None)]
521-
# Regular bulk insert
522-
params = []
523-
r_sql, self.returning_params = self.connection.ops.return_insert_columns(self.get_returned_fields())
524-
if r_sql:
525-
result.append(r_sql)
526-
params += [self.returning_params]
527-
params += param_rows
528-
result.append(self.connection.ops.bulk_insert_sql(fields, placeholder_rows))
529-
else:
530-
result.insert(0, 'SET NOCOUNT ON')
531-
result.append((values_format + ';') % ', '.join(placeholder_rows[0]))
532-
params = [param_rows[0]]
533-
result.append('SELECT CAST(SCOPE_IDENTITY() AS bigint)')
473+
if self.get_returned_fields() and self.connection.features.can_return_id_from_insert:
474+
result.insert(0, 'SET NOCOUNT ON')
475+
result.append((values_format + ';') % ', '.join(placeholder_rows[0]))
476+
params = [param_rows[0]]
477+
result.append('SELECT CAST(SCOPE_IDENTITY() AS bigint)')
534478
sql = [(" ".join(result), tuple(chain.from_iterable(params)))]
535479
else:
536480
if can_bulk:

mssql/features.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
1212
can_introspect_small_integer_field = True
1313
can_return_columns_from_insert = True
1414
can_return_id_from_insert = True
15-
can_return_rows_from_bulk_insert = True
1615
can_rollback_ddl = True
1716
can_use_chunked_reads = False
1817
for_update_after_from = True

mssql/functions.py

Lines changed: 19 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44
import json
55

66
from django import VERSION
7-
from django.core import validators
7+
88
from django.db import NotSupportedError, connections, transaction
9-
from django.db.models import BooleanField, CheckConstraint, Value
10-
from django.db.models.expressions import Case, Exists, Expression, OrderBy, When, Window
11-
from django.db.models.fields import BinaryField, Field
9+
from django.db.models import BooleanField, Value
1210
from django.db.models.functions import Cast, NthValue
13-
from django.db.models.functions.math import ATan2, Ln, Log, Mod, Round
14-
from django.db.models.lookups import In, Lookup
15-
from django.db.models.query import QuerySet
11+
from django.db.models.functions.math import ATan2, Log, Ln, Mod, Round
12+
from django.db.models.expressions import Case, Exists, OrderBy, When, Window, Expression
13+
from django.db.models.lookups import Lookup, In
14+
from django.db.models import lookups, CheckConstraint
15+
from django.db.models.fields import BinaryField, Field
1616
from django.db.models.sql.query import Query
17+
from django.db.models.query import QuerySet
18+
from django.core import validators
1719

1820
if VERSION >= (3, 1):
1921
from django.db.models.fields.json import (
@@ -65,11 +67,9 @@ def sqlserver_nth_value(self, compiler, connection, **extra_content):
6567
def sqlserver_round(self, compiler, connection, **extra_context):
6668
return self.as_sql(compiler, connection, template='%(function)s(%(expressions)s, 0)', **extra_context)
6769

68-
6970
def sqlserver_random(self, compiler, connection, **extra_context):
7071
return self.as_sql(compiler, connection, function='RAND', **extra_context)
7172

72-
7373
def sqlserver_window(self, compiler, connection, template=None):
7474
# MSSQL window functions require an OVER clause with ORDER BY
7575
if self.order_by is None:
@@ -125,13 +125,6 @@ def sqlserver_orderby(self, compiler, connection):
125125

126126

127127
def split_parameter_list_as_sql(self, compiler, connection):
128-
if connection.vendor == 'microsoft':
129-
return mssql_split_parameter_list_as_sql(self, compiler, connection)
130-
else:
131-
return in_split_parameter_list_as_sql(self, compiler, connection)
132-
133-
134-
def mssql_split_parameter_list_as_sql(self, compiler, connection):
135128
# Insert In clause parameters 1000 at a time into a temp table.
136129
lhs, _ = self.process_lhs(compiler, connection)
137130
_, rhs_params = self.batch_process_rhs(compiler, connection)
@@ -150,29 +143,26 @@ def mssql_split_parameter_list_as_sql(self, compiler, connection):
150143

151144
return in_clause, ()
152145

153-
154146
def unquote_json_rhs(rhs_params):
155147
for value in rhs_params:
156148
value = json.loads(value)
157149
if not isinstance(value, (list, dict)):
158150
rhs_params = [param.replace('"', '') for param in rhs_params]
159151
return rhs_params
160152

161-
162153
def json_KeyTransformExact_process_rhs(self, compiler, connection):
163-
rhs, rhs_params = key_transform_exact_process_rhs(self, compiler, connection)
164-
if connection.vendor == 'microsoft':
165-
rhs_params = unquote_json_rhs(rhs_params)
166-
return rhs, rhs_params
154+
if isinstance(self.rhs, KeyTransform):
155+
return super(lookups.Exact, self).process_rhs(compiler, connection)
156+
rhs, rhs_params = super(KeyTransformExact, self).process_rhs(compiler, connection)
167157

158+
return rhs, unquote_json_rhs(rhs_params)
168159

169160
def json_KeyTransformIn(self, compiler, connection):
170161
lhs, _ = super(KeyTransformIn, self).process_lhs(compiler, connection)
171162
rhs, rhs_params = super(KeyTransformIn, self).process_rhs(compiler, connection)
172163

173164
return (lhs + ' IN ' + rhs, unquote_json_rhs(rhs_params))
174165

175-
176166
def json_HasKeyLookup(self, compiler, connection):
177167
# Process JSON path from the left-hand side.
178168
if isinstance(self.lhs, KeyTransform):
@@ -203,7 +193,6 @@ def json_HasKeyLookup(self, compiler, connection):
203193

204194
return sql % tuple(rhs_params), []
205195

206-
207196
def BinaryField_init(self, *args, **kwargs):
208197
# Add max_length option for BinaryField, default to max
209198
kwargs.setdefault('editable', False)
@@ -213,7 +202,6 @@ def BinaryField_init(self, *args, **kwargs):
213202
else:
214203
self.max_length = 'max'
215204

216-
217205
def _get_check_sql(self, model, schema_editor):
218206
if VERSION >= (3, 1):
219207
query = Query(model=model, alias_cols=False)
@@ -222,16 +210,13 @@ def _get_check_sql(self, model, schema_editor):
222210
where = query.build_where(self.check)
223211
compiler = query.get_compiler(connection=schema_editor.connection)
224212
sql, params = where.as_sql(compiler, schema_editor.connection)
225-
if schema_editor.connection.vendor == 'microsoft':
226-
try:
227-
for p in params:
228-
str(p).encode('ascii')
229-
except UnicodeEncodeError:
230-
sql = sql.replace('%s', 'N%s')
213+
try:
214+
for p in params: str(p).encode('ascii')
215+
except UnicodeEncodeError:
216+
sql = sql.replace('%s', 'N%s')
231217

232218
return sql % tuple(schema_editor.quote_value(p) for p in params)
233219

234-
235220
def bulk_update_with_default(self, objs, fields, batch_size=None, default=0):
236221
"""
237222
Update the given fields in each of the given objects in the database.
@@ -270,10 +255,10 @@ def bulk_update_with_default(self, objs, fields, batch_size=None, default=0):
270255
attr = getattr(obj, field.attname)
271256
if not isinstance(attr, Expression):
272257
if attr is None:
273-
value_none_counter += 1
258+
value_none_counter+=1
274259
attr = Value(attr, output_field=field)
275260
when_statements.append(When(pk=obj.pk, then=attr))
276-
if connections[self.db].vendor == 'microsoft' and value_none_counter == len(when_statements):
261+
if(value_none_counter == len(when_statements)):
277262
case_statement = Case(*when_statements, output_field=field, default=Value(default))
278263
else:
279264
case_statement = Case(*when_statements, output_field=field)
@@ -287,15 +272,10 @@ def bulk_update_with_default(self, objs, fields, batch_size=None, default=0):
287272
rows_updated += self.filter(pk__in=pks).update(**update_kwargs)
288273
return rows_updated
289274

290-
291275
ATan2.as_microsoft = sqlserver_atan2
292-
# Need copy of old In.split_parameter_list_as_sql for other backends to call
293-
in_split_parameter_list_as_sql = In.split_parameter_list_as_sql
294276
In.split_parameter_list_as_sql = split_parameter_list_as_sql
295277
if VERSION >= (3, 1):
296278
KeyTransformIn.as_microsoft = json_KeyTransformIn
297-
# Need copy of old KeyTransformExact.process_rhs to call later
298-
key_transform_exact_process_rhs = KeyTransformExact.process_rhs
299279
KeyTransformExact.process_rhs = json_KeyTransformExact_process_rhs
300280
HasKeyLookup.as_microsoft = json_HasKeyLookup
301281
Ln.as_microsoft = sqlserver_ln

0 commit comments

Comments
 (0)