Code

Ticket #5062: NewMSSQL.diff

File NewMSSQL.diff, 38.0 KB (added by mamcx, 7 years ago)

Integration older patch + enabling of this new backend

Line 
1Index: contrib/sessions/middleware.py
2===================================================================
3--- contrib/sessions/middleware.py      (revision 5596)
4+++ contrib/sessions/middleware.py      (working copy)
5@@ -59,8 +59,11 @@
6                 self._session_cache = {}
7             else:
8                 try:
9+                    datenow = datetime.datetime.now()
10+                    if hasattr(datenow, 'microsecond'):
11+                        datenow = datenow.replace(microsecond=0)
12                     s = Session.objects.get(session_key=self.session_key,
13-                        expire_date__gt=datetime.datetime.now())
14+                        expire_date__gt=datenow)
15                     self._session_cache = s.get_decoded()
16                 except (Session.DoesNotExist, SuspiciousOperation):
17                     self._session_cache = {}
18Index: db/backends/mssql/base.py
19===================================================================
20--- db/backends/mssql/base.py   (revision 0)
21+++ db/backends/mssql/base.py   (revision 0)
22@@ -0,0 +1,232 @@
23+"""
24+Alpha Multi-plataform MSSQL database backend for Django.
25+
26+Requires pymssql >= v0.8.0: http://pymssql.sourceforge.net/
27+"""
28+if __name__ == '__main__':
29+    import sys
30+    import os
31+   
32+    SETTINGS_MODULE = 'settings'
33+
34+    project_dir = r'E:\Proyectos\Python\mysite'
35+    sys.path.append(os.path.join(project_dir, '..'))
36+    sys.path.append("..")
37+    sys.path.pop()
38+    os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
39+   
40+
41+import datetime
42+from django.db.backends import util
43+from django.core.exceptions import ImproperlyConfigured
44+
45+try:
46+    import pymssql as Database
47+except ImportError, e:
48+    raise ImproperlyConfigured, "Error loading pymssql module: %s" % e
49+
50+try:
51+    import mx
52+except ImportError:
53+    mx = None
54+
55+try:
56+    # Only exists in Python 2.4+
57+    from threading import local
58+except ImportError:
59+    # Import copy of _thread_local.py from Python 2.4
60+    from django.utils._threading_local import local
61+
62+DatabaseError = Database.DatabaseError
63+IntegrityError = Database.IntegrityError
64+
65+#Configure support options:
66+allows_group_by_ordinal = True
67+allows_unique_and_pk = True
68+autoindexes_primary_keys = True
69+needs_datetime_string_cast = True
70+needs_upper_for_iops = False
71+supports_constraints = True
72+supports_tablespaces = False
73+uses_case_insensitive_names = False
74+
75+def complain(*args, **kwargs):
76+    raise ImproperlyConfigured, "You haven't set the DATABASE_ENGINE setting yet."
77+
78+def ignore(*args, **kwargs):
79+    pass
80+
81+class DatabaseError(Exception):
82+    pass
83+
84+class IntegrityError(DatabaseError):
85+    pass
86+
87+class DatabaseWrapper(local):
88+    def __init__(self, **kwargs):
89+        self.connection = None
90+        self.queries = []
91+
92+    def cursor(self):
93+        from django.conf import settings
94+        if self.connection is None:
95+            if settings.DATABASE_NAME == '' or settings.DATABASE_USER == '':
96+                raise ImproperlyConfigured, "You need to specify both DATABASE_NAME and DATABASE_USER in your Django settings file."
97+            if not settings.DATABASE_HOST:
98+                settings.DATABASE_HOST = "127.0.0.1"
99+            # TODO: Handle DATABASE_PORT.
100+            self.connection = Database.connect(host=settings.DATABASE_HOST,user=settings.DATABASE_USER,password=settings.DATABASE_PASSWORD,database=settings.DATABASE_NAME)
101+       
102+        self.connection.cursor().execute("SET DATEFORMAT ymd\nGO")
103+       
104+        cursor = self.connection.cursor()
105+        if settings.DEBUG:
106+            return util.CursorDebugWrapper(cursor, self)
107+        return cursor
108+
109+    def _commit(self):
110+        if self.connection is not None:
111+            return self.connection.commit()
112+
113+    def _rollback(self):
114+        if self.connection is not None:
115+            return self.connection.rollback()
116+
117+    def close(self):
118+        if self.connection is not None:
119+            self.connection.close()
120+            self.connection = None
121+
122+'''
123+    Return the major version of the server. 7=Sql 7,8=Sql2000,9=Sql2005
124+'''
125+def version():
126+    cur = DatabaseWrapper().cursor()
127+    cur.execute("SELECT SERVERPROPERTY('ProductVersion')")
128+   
129+    return int(cur.fetchone()[0].split('.')[0])
130+
131+def quote_name(name):
132+    if name.startswith('[') and name.endswith(']'):
133+        return name # Quoting once is enough.
134+    return '[%s]' % name
135+
136+dictfetchone = util.dictfetchone
137+dictfetchmany = util.dictfetchmany
138+dictfetchall  = util.dictfetchall
139+
140+def get_last_insert_id(cursor, table_name, pk_name):
141+    cursor.execute("SELECT %s FROM %s WHERE %s = IDENT_CURRENT('%s')" % (pk_name, table_name, pk_name,table_name))
142+    return cursor.fetchone()[0]
143+
144+def get_date_extract_sql(lookup_type, table_name):
145+    # lookup_type is 'year', 'month', 'day'
146+    return "DATEPART(%s, %s)" % (lookup_type, table_name)
147+
148+def get_date_trunc_sql(lookup_type, field_name):
149+    # lookup_type is 'year', 'month', 'day'
150+    if lookup_type=='year':
151+        return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/01/01')" % field_name
152+    if lookup_type=='month':
153+        return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/' + Convert(varchar, DATEPART(month, %s)) + '/01')" % (field_name, field_name)
154+    if lookup_type=='day':
155+        return "Convert(datetime, Convert(varchar(12), %s))" % field_name
156+
157+def get_datetime_cast_sql():
158+    return None
159+
160+def get_limit_offset_sql(limit, offset=None):
161+    # Limits and offset are too complicated to be handled here.
162+    # Look for a implementation similar to oracle backend
163+    return ""
164+
165+def get_random_function_sql():
166+    return "RAND()"
167+
168+def get_deferrable_sql():
169+    # TODO: Workaround cicle paths...
170+    # DEFERRABLE and INITALLY DEFFERRED are not apparently supported on constraints
171+    # This cause SQL Server message 1750, severity 16, state 0, for example in the multiple joins path of comments.
172+    # So, this left Sql Server as if have not relations :(
173+    #return " ON DELETE CASCADE ON UPDATE CASCADE"
174+    return ""
175+
176+def get_fulltext_search_sql(field_name):
177+    raise NotImplementedError
178+
179+def get_drop_foreignkey_sql():
180+    return "DROP CONSTRAINT"
181+
182+def get_pk_default_value():
183+    return "DEFAULT"
184+
185+def get_max_name_length():
186+    return None
187+
188+def get_start_transaction_sql():
189+    return "BEGIN;"
190+
191+def get_tablespace_sql(tablespace, inline=False):
192+    return "ON %s" % quote_name(tablespace)
193+
194+def get_autoinc_sql(table):
195+    return None
196+
197+def get_sql_flush(sql_styler, full_table_list, sequences):
198+    """Return a list of SQL statements required to remove all data from
199+    all tables in the database (without actually removing the tables
200+    themselves) and put the database in an empty 'initial' state
201+    """
202+    # Cannot use TRUNCATE on tables that are reference by a FOREIGN KEY
203+    # So must use the much slower DELETE
204+    sql_list = ['%s %s %s;' % \
205+                (sql_styler.SQL_KEYWORD('DELETE'),
206+                sql_styler.SQL_KEYWORD('FROM'),
207+                sql_styler.SQL_FIELD(quote_name(table))
208+                )  for table in full_table_list]
209+    #The reset the counters on each table.
210+    sql_list.extend(['%s %s %s %s %s %s %s;' % (
211+        sql_styler.SQL_KEYWORD('DBCC'),
212+        sql_styler.SQL_KEYWORD('CHECKIDENT'),
213+        sql_styler.SQL_FIELD(quote_name(seq["table"])),
214+        sql_styler.SQL_KEYWORD('RESEED'),
215+        sql_styler.SQL_FIELD('1'),
216+        sql_styler.SQL_KEYWORD('WITH'),
217+        sql_styler.SQL_KEYWORD('NO_INFOMSGS'),
218+        ) for seq in sequences])
219+   
220+    return sql_list
221+
222+def get_sql_sequence_reset(style, model_list):
223+    "Returns a list of the SQL statements to reset sequences for the given models."
224+    # No sequence reset required
225+    return []
226+
227+OPERATOR_MAPPING = {
228+    'exact': '= %s',
229+    'iexact': 'LIKE %s',
230+    'contains': 'LIKE %s',
231+    'icontains': 'LIKE %s',
232+    'gt': '> %s',
233+    'gte': '>= %s',
234+    'lt': '< %s',
235+    'lte': '<= %s',
236+    'startswith': 'LIKE %s',
237+    'endswith': 'LIKE %s',
238+    'istartswith': 'LIKE %s',
239+    'iendswith': 'LIKE %s',
240+}
241+
242+if __name__ == '__main__':
243+    from mysite.polls.models import Poll, Choice
244+    from django.contrib.auth.models import User
245+    from datetime import datetime
246+    Poll.objects.all()
247+    p = Poll(question="What's up?", pub_date=datetime.now())
248+    p.save()
249+    print p.id
250+   
251+    db=DatabaseWrapper()
252+    print version()
253+
254+
255Index: db/backends/mssql/client.py
256===================================================================
257--- db/backends/mssql/client.py (revision 0)
258+++ db/backends/mssql/client.py (revision 0)
259@@ -0,0 +1,2 @@
260+def runshell():
261+    raise NotImplementedError
262Index: db/backends/mssql/__init__.py
263===================================================================
264Index: db/backends/mssql/introspection.py
265===================================================================
266--- db/backends/mssql/introspection.py  (revision 0)
267+++ db/backends/mssql/introspection.py  (revision 0)
268@@ -0,0 +1,137 @@
269+def get_table_list(cursor):
270+    "Returns a list of table names in the current database."
271+    cursor.execute("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")
272+    return [row[2] for row in cursor.fetchall()]
273+
274+def _is_auto_field(cursor, table_name, column_name):
275+    cursor.execute("SELECT COLUMNPROPERTY( OBJECT_ID('%s'),'%s','IsIdentity')" % (table_name, column_name))
276+    return cursor.fetchall()[0][0]
277+
278+def get_table_description(cursor, table_name, identity_check=True):
279+    """Returns a description of the table, with the DB-API cursor.description interface.
280+
281+    The 'auto_check' parameter has been added to the function argspec.
282+    If set to True, the function will check each of the table's fields for the
283+    IDENTITY property (the IDENTITY property is the MSSQL equivalent to an AutoField).
284+
285+    When a field is found with an IDENTITY property, it is given a custom field number
286+    of -777, which maps to the 'AutoField' value in the DATA_TYPES_REVERSE dict.
287+    """   
288+    cursor.execute("SELECT TOP 1 * FROM %s" % table_name)
289+    cursor.nextset()
290+    items = []
291+    if identity_check:
292+        for data in cursor.description:
293+            if _is_auto_field(cursor, table_name, data[0]):
294+                data = list(data)
295+                data[1] = -777
296+            items.append(list(data))
297+    else:
298+        items = cursor.description
299+    return items
300+
301+def _name_to_index(cursor, table_name):
302+    """
303+    Returns a dictionary of {field_name: field_index} for the given table.
304+    Indexes are 0-based.
305+    """
306+    return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name, identity_check=False))])
307+
308+def get_relations(cursor, table_name):
309+    """
310+    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
311+    representing all relationships to the given table. Indexes are 0-based.   
312+    """
313+    table_index = _name_to_index(cursor, table_name)
314+    sql = """SELECT e.COLUMN_NAME AS column_name,
315+                    c.TABLE_NAME AS referenced_table_name,
316+                    d.COLUMN_NAME AS referenced_column_name
317+                    FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a
318+                        INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS b
319+                              ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME
320+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE AS c
321+                              ON b.UNIQUE_CONSTRAINT_NAME = c.CONSTRAINT_NAME
322+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS d
323+                              ON c.CONSTRAINT_NAME = d.CONSTRAINT_NAME
324+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS e
325+                              ON a.CONSTRAINT_NAME = e.CONSTRAINT_NAME
326+                    WHERE a.TABLE_NAME = ? AND
327+                          a.CONSTRAINT_TYPE = 'FOREIGN KEY'"""
328+    cursor = Cursor(cursor.db.connection)
329+    cursor.execute(sql, (table_name,))
330+    return dict([(table_index[item[0]], (_name_to_index(cursor, item[1])[item[2]], item[1]))
331+                  for item in cursor.fetchall()])
332+   
333+def get_indexes(cursor, table_name):
334+    """
335+    Returns a dictionary of fieldname -> infodict for the given table,
336+    where each infodict is in the format:
337+        {'primary_key': boolean representing whether it's the primary key,
338+         'unique': boolean representing whether it's a unique index}
339+    """
340+    sql = """SELECT b.COLUMN_NAME, a.CONSTRAINT_TYPE
341+               FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a INNER JOIN
342+                    INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS b
343+                    ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME AND
344+                       a.TABLE_NAME = b.TABLE_NAME
345+               WHERE a.TABLE_NAME = ? AND
346+                     (CONSTRAINT_TYPE = 'PRIMARY KEY' OR
347+                      CONSTRAINT_TYPE = 'UNIQUE')"""
348+    field_names = [item[0] for item in get_table_description(cursor, table_name, identity_check=False)]
349+    cursor = Cursor(cursor.db.connection)
350+    cursor.execute(sql, (table_name,))
351+    indexes = {}
352+    results = {}
353+    data = cursor.fetchall()
354+    if data:
355+        results.update(data)
356+    for field in field_names:
357+        val = results.get(field, None)
358+        indexes[field] = dict(primary_key=(val=='PRIMARY KEY'), unique=(val=='UNIQUE'))
359+    return indexes
360+
361+# A reference for the values below:
362+# http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ado270/htm/mdcstdatatypeenum.asp
363+DATA_TYPES_REVERSE = {
364+# 8192 : Array ,
365+# 128 : Binary ,
366+# 9 : IDispatch ,
367+# 12 : Variant ,
368+# 13 : IUnknown ,
369+# 21  : UnsignedBigInt,
370+# 132 : UserDefined ,
371+# 0   : Empty ,
372+# 136 : Chapter ,
373+# 138 : PropVariant ,
374+# 204 : VarBinary ,
375+# 205 : LongVarBinary ,
376+-777: 'AutoField',                  # Custom number used to identify AutoFields
377+2   : 'SmallIntegerField',          # SmallInt
378+3   : 'IntegerField',               # Integer
379+4   : 'FloatField',                 # Single
380+5   : 'FloatField',                 # Decimal
381+6   : 'FloatField',                 # Currency
382+7   : 'DateField',                  # Date
383+8   : 'CharField',                  # BSTR
384+10  : 'IntegerField',               # Error
385+11  : 'BooleanField',               # Boolean
386+14  : 'FloatField',                 # Decimal
387+16  : 'SmallIntegerField',          # TinyInt
388+17  : 'PositiveSmallIntegerField',  # UnsignedTinyInt
389+18  : 'PositiveSmallIntegerField',  # UnsignedSmallInt
390+19  : 'PositiveIntegerField',       # UnsignedInt
391+20  : 'IntegerField',               # BigInt
392+64  : 'DateTimeField',              # FileTime
393+72  : 'CharField',                  # GUID
394+129 : 'CharField',                  # Char
395+130 : 'CharField',                  # WChar
396+131 : 'FloatField',                 # Numeric
397+133 : 'DateField',                  # DBDate
398+134 : 'TimeField',                  # DBTime
399+135 : 'DateTimeField',              # DBTimeStamp
400+139 : 'FloatField',                 # VarNumeric
401+200 : 'CharField',                  # VarChar
402+201 : 'TextField',                  # LongVarChar
403+202 : 'CharField',                  # VarWChar
404+203 : 'TextField',                  # LongVarWChar
405+}
406\ No newline at end of file
407Index: db/backends/mssql/creation.py
408===================================================================
409--- db/backends/mssql/creation.py       (revision 0)
410+++ db/backends/mssql/creation.py       (revision 0)
411@@ -0,0 +1,27 @@
412+DATA_TYPES = {
413+    'AutoField':         'int IDENTITY (1, 1)',
414+    'BooleanField':      'bit',
415+    'CharField':         'varchar(%(maxlength)s)',
416+    'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
417+    'DateField':         'datetime',
418+    'DateTimeField':     'datetime',
419+    'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)',
420+    'FileField':         'varchar(254)',
421+    'FilePathField':     'varchar(254)',
422+    'FloatField':        'double precision',
423+    'ImageField':        'varchar(254)',
424+    'IntegerField':      'int',
425+    'IPAddressField':    'char(15)',
426+    'ManyToManyField':   None,
427+    'NullBooleanField':  'bit',
428+    'OneToOneField':     'int',
429+    'PhoneNumberField':  'varchar(20)',
430+    #The check must be unique in for the database. Put random so the regresion test not complain about duplicate names
431+    'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(creation_counter)s_%(column)s] CHECK ([%(column)s] > 0)',   
432+    'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(creation_counter)s_%(column)s] CHECK ([%(column)s] > 0)',
433+    'SlugField':         'varchar(%(maxlength)s)',
434+    'SmallIntegerField': 'smallint',
435+    'TextField':         'text',
436+    'TimeField':         'datetime',
437+    'USStateField':      'varchar(2)',
438+}
439Index: db/backends/mssql/__init__.py
440===================================================================
441Index: db/backends/mssql/base.py
442===================================================================
443--- db/backends/mssql/base.py   (revision 0)
444+++ db/backends/mssql/base.py   (revision 0)
445@@ -0,0 +1,232 @@
446+"""
447+Alpha Multi-plataform MSSQL database backend for Django.
448+
449+Requires pymssql >= v0.8.0: http://pymssql.sourceforge.net/
450+"""
451+if __name__ == '__main__':
452+    import sys
453+    import os
454+   
455+    SETTINGS_MODULE = 'settings'
456+
457+    project_dir = r'E:\Proyectos\Python\mysite'
458+    sys.path.append(os.path.join(project_dir, '..'))
459+    sys.path.append("..")
460+    sys.path.pop()
461+    os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
462+   
463+
464+import datetime
465+from django.db.backends import util
466+from django.core.exceptions import ImproperlyConfigured
467+
468+try:
469+    import pymssql as Database
470+except ImportError, e:
471+    raise ImproperlyConfigured, "Error loading pymssql module: %s" % e
472+
473+try:
474+    import mx
475+except ImportError:
476+    mx = None
477+
478+try:
479+    # Only exists in Python 2.4+
480+    from threading import local
481+except ImportError:
482+    # Import copy of _thread_local.py from Python 2.4
483+    from django.utils._threading_local import local
484+
485+DatabaseError = Database.DatabaseError
486+IntegrityError = Database.IntegrityError
487+
488+#Configure support options:
489+allows_group_by_ordinal = True
490+allows_unique_and_pk = True
491+autoindexes_primary_keys = True
492+needs_datetime_string_cast = True
493+needs_upper_for_iops = False
494+supports_constraints = True
495+supports_tablespaces = False
496+uses_case_insensitive_names = False
497+
498+def complain(*args, **kwargs):
499+    raise ImproperlyConfigured, "You haven't set the DATABASE_ENGINE setting yet."
500+
501+def ignore(*args, **kwargs):
502+    pass
503+
504+class DatabaseError(Exception):
505+    pass
506+
507+class IntegrityError(DatabaseError):
508+    pass
509+
510+class DatabaseWrapper(local):
511+    def __init__(self, **kwargs):
512+        self.connection = None
513+        self.queries = []
514+
515+    def cursor(self):
516+        from django.conf import settings
517+        if self.connection is None:
518+            if settings.DATABASE_NAME == '' or settings.DATABASE_USER == '':
519+                raise ImproperlyConfigured, "You need to specify both DATABASE_NAME and DATABASE_USER in your Django settings file."
520+            if not settings.DATABASE_HOST:
521+                settings.DATABASE_HOST = "127.0.0.1"
522+            # TODO: Handle DATABASE_PORT.
523+            self.connection = Database.connect(host=settings.DATABASE_HOST,user=settings.DATABASE_USER,password=settings.DATABASE_PASSWORD,database=settings.DATABASE_NAME)
524+       
525+        self.connection.cursor().execute("SET DATEFORMAT ymd\nGO")
526+       
527+        cursor = self.connection.cursor()
528+        if settings.DEBUG:
529+            return util.CursorDebugWrapper(cursor, self)
530+        return cursor
531+
532+    def _commit(self):
533+        if self.connection is not None:
534+            return self.connection.commit()
535+
536+    def _rollback(self):
537+        if self.connection is not None:
538+            return self.connection.rollback()
539+
540+    def close(self):
541+        if self.connection is not None:
542+            self.connection.close()
543+            self.connection = None
544+
545+'''
546+    Return the major version of the server. 7=Sql 7,8=Sql2000,9=Sql2005
547+'''
548+def version():
549+    cur = DatabaseWrapper().cursor()
550+    cur.execute("SELECT SERVERPROPERTY('ProductVersion')")
551+   
552+    return int(cur.fetchone()[0].split('.')[0])
553+
554+def quote_name(name):
555+    if name.startswith('[') and name.endswith(']'):
556+        return name # Quoting once is enough.
557+    return '[%s]' % name
558+
559+dictfetchone = util.dictfetchone
560+dictfetchmany = util.dictfetchmany
561+dictfetchall  = util.dictfetchall
562+
563+def get_last_insert_id(cursor, table_name, pk_name):
564+    cursor.execute("SELECT %s FROM %s WHERE %s = IDENT_CURRENT('%s')" % (pk_name, table_name, pk_name,table_name))
565+    return cursor.fetchone()[0]
566+
567+def get_date_extract_sql(lookup_type, table_name):
568+    # lookup_type is 'year', 'month', 'day'
569+    return "DATEPART(%s, %s)" % (lookup_type, table_name)
570+
571+def get_date_trunc_sql(lookup_type, field_name):
572+    # lookup_type is 'year', 'month', 'day'
573+    if lookup_type=='year':
574+        return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/01/01')" % field_name
575+    if lookup_type=='month':
576+        return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/' + Convert(varchar, DATEPART(month, %s)) + '/01')" % (field_name, field_name)
577+    if lookup_type=='day':
578+        return "Convert(datetime, Convert(varchar(12), %s))" % field_name
579+
580+def get_datetime_cast_sql():
581+    return None
582+
583+def get_limit_offset_sql(limit, offset=None):
584+    # Limits and offset are too complicated to be handled here.
585+    # Look for a implementation similar to oracle backend
586+    return ""
587+
588+def get_random_function_sql():
589+    return "RAND()"
590+
591+def get_deferrable_sql():
592+    # TODO: Workaround cicle paths...
593+    # DEFERRABLE and INITALLY DEFFERRED are not apparently supported on constraints
594+    # This cause SQL Server message 1750, severity 16, state 0, for example in the multiple joins path of comments.
595+    # So, this left Sql Server as if have not relations :(
596+    #return " ON DELETE CASCADE ON UPDATE CASCADE"
597+    return ""
598+
599+def get_fulltext_search_sql(field_name):
600+    raise NotImplementedError
601+
602+def get_drop_foreignkey_sql():
603+    return "DROP CONSTRAINT"
604+
605+def get_pk_default_value():
606+    return "DEFAULT"
607+
608+def get_max_name_length():
609+    return None
610+
611+def get_start_transaction_sql():
612+    return "BEGIN;"
613+
614+def get_tablespace_sql(tablespace, inline=False):
615+    return "ON %s" % quote_name(tablespace)
616+
617+def get_autoinc_sql(table):
618+    return None
619+
620+def get_sql_flush(sql_styler, full_table_list, sequences):
621+    """Return a list of SQL statements required to remove all data from
622+    all tables in the database (without actually removing the tables
623+    themselves) and put the database in an empty 'initial' state
624+    """
625+    # Cannot use TRUNCATE on tables that are reference by a FOREIGN KEY
626+    # So must use the much slower DELETE
627+    sql_list = ['%s %s %s;' % \
628+                (sql_styler.SQL_KEYWORD('DELETE'),
629+                sql_styler.SQL_KEYWORD('FROM'),
630+                sql_styler.SQL_FIELD(quote_name(table))
631+                )  for table in full_table_list]
632+    #The reset the counters on each table.
633+    sql_list.extend(['%s %s %s %s %s %s %s;' % (
634+        sql_styler.SQL_KEYWORD('DBCC'),
635+        sql_styler.SQL_KEYWORD('CHECKIDENT'),
636+        sql_styler.SQL_FIELD(quote_name(seq["table"])),
637+        sql_styler.SQL_KEYWORD('RESEED'),
638+        sql_styler.SQL_FIELD('1'),
639+        sql_styler.SQL_KEYWORD('WITH'),
640+        sql_styler.SQL_KEYWORD('NO_INFOMSGS'),
641+        ) for seq in sequences])
642+   
643+    return sql_list
644+
645+def get_sql_sequence_reset(style, model_list):
646+    "Returns a list of the SQL statements to reset sequences for the given models."
647+    # No sequence reset required
648+    return []
649+
650+OPERATOR_MAPPING = {
651+    'exact': '= %s',
652+    'iexact': 'LIKE %s',
653+    'contains': 'LIKE %s',
654+    'icontains': 'LIKE %s',
655+    'gt': '> %s',
656+    'gte': '>= %s',
657+    'lt': '< %s',
658+    'lte': '<= %s',
659+    'startswith': 'LIKE %s',
660+    'endswith': 'LIKE %s',
661+    'istartswith': 'LIKE %s',
662+    'iendswith': 'LIKE %s',
663+}
664+
665+if __name__ == '__main__':
666+    from mysite.polls.models import Poll, Choice
667+    from django.contrib.auth.models import User
668+    from datetime import datetime
669+    Poll.objects.all()
670+    p = Poll(question="What's up?", pub_date=datetime.now())
671+    p.save()
672+    print p.id
673+   
674+    db=DatabaseWrapper()
675+    print version()
676+
677+
678Index: db/backends/mssql/client.py
679===================================================================
680--- db/backends/mssql/client.py (revision 0)
681+++ db/backends/mssql/client.py (revision 0)
682@@ -0,0 +1,2 @@
683+def runshell():
684+    raise NotImplementedError
685Index: db/backends/mssql/creation.py
686===================================================================
687--- db/backends/mssql/creation.py       (revision 0)
688+++ db/backends/mssql/creation.py       (revision 0)
689@@ -0,0 +1,27 @@
690+DATA_TYPES = {
691+    'AutoField':         'int IDENTITY (1, 1)',
692+    'BooleanField':      'bit',
693+    'CharField':         'varchar(%(maxlength)s)',
694+    'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
695+    'DateField':         'datetime',
696+    'DateTimeField':     'datetime',
697+    'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)',
698+    'FileField':         'varchar(254)',
699+    'FilePathField':     'varchar(254)',
700+    'FloatField':        'double precision',
701+    'ImageField':        'varchar(254)',
702+    'IntegerField':      'int',
703+    'IPAddressField':    'char(15)',
704+    'ManyToManyField':   None,
705+    'NullBooleanField':  'bit',
706+    'OneToOneField':     'int',
707+    'PhoneNumberField':  'varchar(20)',
708+    #The check must be unique in for the database. Put random so the regresion test not complain about duplicate names
709+    'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(creation_counter)s_%(column)s] CHECK ([%(column)s] > 0)',   
710+    'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(creation_counter)s_%(column)s] CHECK ([%(column)s] > 0)',
711+    'SlugField':         'varchar(%(maxlength)s)',
712+    'SmallIntegerField': 'smallint',
713+    'TextField':         'text',
714+    'TimeField':         'datetime',
715+    'USStateField':      'varchar(2)',
716+}
717Index: db/backends/mssql/introspection.py
718===================================================================
719--- db/backends/mssql/introspection.py  (revision 0)
720+++ db/backends/mssql/introspection.py  (revision 0)
721@@ -0,0 +1,137 @@
722+def get_table_list(cursor):
723+    "Returns a list of table names in the current database."
724+    cursor.execute("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")
725+    return [row[2] for row in cursor.fetchall()]
726+
727+def _is_auto_field(cursor, table_name, column_name):
728+    cursor.execute("SELECT COLUMNPROPERTY( OBJECT_ID('%s'),'%s','IsIdentity')" % (table_name, column_name))
729+    return cursor.fetchall()[0][0]
730+
731+def get_table_description(cursor, table_name, identity_check=True):
732+    """Returns a description of the table, with the DB-API cursor.description interface.
733+
734+    The 'auto_check' parameter has been added to the function argspec.
735+    If set to True, the function will check each of the table's fields for the
736+    IDENTITY property (the IDENTITY property is the MSSQL equivalent to an AutoField).
737+
738+    When a field is found with an IDENTITY property, it is given a custom field number
739+    of -777, which maps to the 'AutoField' value in the DATA_TYPES_REVERSE dict.
740+    """   
741+    cursor.execute("SELECT TOP 1 * FROM %s" % table_name)
742+    cursor.nextset()
743+    items = []
744+    if identity_check:
745+        for data in cursor.description:
746+            if _is_auto_field(cursor, table_name, data[0]):
747+                data = list(data)
748+                data[1] = -777
749+            items.append(list(data))
750+    else:
751+        items = cursor.description
752+    return items
753+
754+def _name_to_index(cursor, table_name):
755+    """
756+    Returns a dictionary of {field_name: field_index} for the given table.
757+    Indexes are 0-based.
758+    """
759+    return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name, identity_check=False))])
760+
761+def get_relations(cursor, table_name):
762+    """
763+    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
764+    representing all relationships to the given table. Indexes are 0-based.   
765+    """
766+    table_index = _name_to_index(cursor, table_name)
767+    sql = """SELECT e.COLUMN_NAME AS column_name,
768+                    c.TABLE_NAME AS referenced_table_name,
769+                    d.COLUMN_NAME AS referenced_column_name
770+                    FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a
771+                        INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS b
772+                              ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME
773+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE AS c
774+                              ON b.UNIQUE_CONSTRAINT_NAME = c.CONSTRAINT_NAME
775+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS d
776+                              ON c.CONSTRAINT_NAME = d.CONSTRAINT_NAME
777+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS e
778+                              ON a.CONSTRAINT_NAME = e.CONSTRAINT_NAME
779+                    WHERE a.TABLE_NAME = ? AND
780+                          a.CONSTRAINT_TYPE = 'FOREIGN KEY'"""
781+    cursor = Cursor(cursor.db.connection)
782+    cursor.execute(sql, (table_name,))
783+    return dict([(table_index[item[0]], (_name_to_index(cursor, item[1])[item[2]], item[1]))
784+                  for item in cursor.fetchall()])
785+   
786+def get_indexes(cursor, table_name):
787+    """
788+    Returns a dictionary of fieldname -> infodict for the given table,
789+    where each infodict is in the format:
790+        {'primary_key': boolean representing whether it's the primary key,
791+         'unique': boolean representing whether it's a unique index}
792+    """
793+    sql = """SELECT b.COLUMN_NAME, a.CONSTRAINT_TYPE
794+               FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a INNER JOIN
795+                    INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS b
796+                    ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME AND
797+                       a.TABLE_NAME = b.TABLE_NAME
798+               WHERE a.TABLE_NAME = ? AND
799+                     (CONSTRAINT_TYPE = 'PRIMARY KEY' OR
800+                      CONSTRAINT_TYPE = 'UNIQUE')"""
801+    field_names = [item[0] for item in get_table_description(cursor, table_name, identity_check=False)]
802+    cursor = Cursor(cursor.db.connection)
803+    cursor.execute(sql, (table_name,))
804+    indexes = {}
805+    results = {}
806+    data = cursor.fetchall()
807+    if data:
808+        results.update(data)
809+    for field in field_names:
810+        val = results.get(field, None)
811+        indexes[field] = dict(primary_key=(val=='PRIMARY KEY'), unique=(val=='UNIQUE'))
812+    return indexes
813+
814+# A reference for the values below:
815+# http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ado270/htm/mdcstdatatypeenum.asp
816+DATA_TYPES_REVERSE = {
817+# 8192 : Array ,
818+# 128 : Binary ,
819+# 9 : IDispatch ,
820+# 12 : Variant ,
821+# 13 : IUnknown ,
822+# 21  : UnsignedBigInt,
823+# 132 : UserDefined ,
824+# 0   : Empty ,
825+# 136 : Chapter ,
826+# 138 : PropVariant ,
827+# 204 : VarBinary ,
828+# 205 : LongVarBinary ,
829+-777: 'AutoField',                  # Custom number used to identify AutoFields
830+2   : 'SmallIntegerField',          # SmallInt
831+3   : 'IntegerField',               # Integer
832+4   : 'FloatField',                 # Single
833+5   : 'FloatField',                 # Decimal
834+6   : 'FloatField',                 # Currency
835+7   : 'DateField',                  # Date
836+8   : 'CharField',                  # BSTR
837+10  : 'IntegerField',               # Error
838+11  : 'BooleanField',               # Boolean
839+14  : 'FloatField',                 # Decimal
840+16  : 'SmallIntegerField',          # TinyInt
841+17  : 'PositiveSmallIntegerField',  # UnsignedTinyInt
842+18  : 'PositiveSmallIntegerField',  # UnsignedSmallInt
843+19  : 'PositiveIntegerField',       # UnsignedInt
844+20  : 'IntegerField',               # BigInt
845+64  : 'DateTimeField',              # FileTime
846+72  : 'CharField',                  # GUID
847+129 : 'CharField',                  # Char
848+130 : 'CharField',                  # WChar
849+131 : 'FloatField',                 # Numeric
850+133 : 'DateField',                  # DBDate
851+134 : 'TimeField',                  # DBTime
852+135 : 'DateTimeField',              # DBTimeStamp
853+139 : 'FloatField',                 # VarNumeric
854+200 : 'CharField',                  # VarChar
855+201 : 'TextField',                  # LongVarChar
856+202 : 'CharField',                  # VarWChar
857+203 : 'TextField',                  # LongVarWChar
858+}
859\ No newline at end of file
860Index: db/backends/util.py
861===================================================================
862--- db/backends/util.py (revision 5596)
863+++ db/backends/util.py (working copy)
864@@ -1,3 +1,4 @@
865+from django.conf import settings
866 import datetime
867 import md5
868 from time import time
869@@ -22,8 +23,24 @@
870             # formatting with '%' only works with tuples or dicts.
871             if not isinstance(params, (tuple, dict)):
872                 params = tuple(params)
873+            # ado_mssql uses '?' for parameter escaping, so all '?'
874+            # must be replaced with the standard '%s' if the parameter
875+            # substitution is going to work.
876+            if settings.DATABASE_ENGINE == 'ado_mssql':
877+                sql = sql.replace('?', '%s')
878+            # There are many situations that will cause the string
879+            # substituion below to fail (e.g. wildcard characters '%'
880+            # in LIKE queries).  Instead of attempting to figure out
881+            # the many variations that can cause an error, the string substition
882+            # will be attempted first; if it fails, then the sql
883+            # and its parameters will be combined into a string similar to
884+            # the one created in the executemany function below.
885+            try:
886+                sql = sql % tuple(params)
887+            except:
888+                sql = '%s SQL: %s' % (sql, str(tuple(params)))
889             self.db.queries.append({
890-                'sql': sql % params,
891+                'sql': sql,
892                 'time': "%.3f" % (stop - start),
893             })
894 
895Index: db/models/base.py
896===================================================================
897--- db/models/base.py   (revision 5596)
898+++ db/models/base.py   (working copy)
899@@ -239,9 +239,22 @@
900                     (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.order_with_respect_to.column)))
901                 db_values.append(getattr(self, self._meta.order_with_respect_to.attname))
902             if db_values:
903+                if pk_set and (settings.DATABASE_ENGINE=="ado_mssql" or settings.DATABASE_ENGINE=="mssql"):
904+                    # You can't insert an auto value into a column unless you do
905+                    # this in MSSQL
906+                    # TODO: Only works for auto-id's... how chek it properly?
907+                    if self._meta.pk.column == 'id':
908+                        cursor.execute("SET IDENTITY_INSERT %s ON" % \
909+                            backend.quote_name(self._meta.db_table))
910+                   
911                 cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \
912                     (backend.quote_name(self._meta.db_table), ','.join(field_names),
913                     ','.join(placeholders)), db_values)
914+                   
915+                if pk_set and (settings.DATABASE_ENGINE=="ado_mssql" or settings.DATABASE_ENGINE=="mssql"):
916+                    if self._meta.pk.column == 'id':
917+                        cursor.execute("SET IDENTITY_INSERT %s OFF" %\
918+                            backend.quote_name(self._meta.db_table))
919             else:
920                 # Create a new record with defaults for everything.
921                 cursor.execute("INSERT INTO %s (%s) VALUES (%s)" %
922Index: db/models/fields/__init__.py
923===================================================================
924--- db/models/fields/__init__.py        (revision 5596)
925+++ db/models/fields/__init__.py        (working copy)
926@@ -541,12 +541,15 @@
927         if value is not None:
928             # MySQL will throw a warning if microseconds are given, because it
929             # doesn't support microseconds.
930-            if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
931+            if settings.DATABASE_ENGINE in ('mysql', 'ado_mssql','mssql') and hasattr(value, 'microsecond'):
932                 value = value.replace(microsecond=0)
933             value = str(value)
934         return Field.get_db_prep_save(self, value)
935 
936     def get_db_prep_lookup(self, lookup_type, value):
937+        # MSSQL doesn't like microseconds.
938+        if settings.DATABASE_ENGINE in ('mysql', 'ado_mssql','mssql') and hasattr(value, 'microsecond'):
939+            value = value.replace(microsecond=0)
940         if lookup_type == 'range':
941             value = [str(v) for v in value]
942         else:
943@@ -908,8 +911,9 @@
944         # Casts dates into string format for entry into database.
945         if value is not None:
946             # MySQL will throw a warning if microseconds are given, because it
947-            # doesn't support microseconds.
948-            if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
949+            # doesn't support microseconds. Ditto MSSQL
950+            if settings.DATABASE_ENGINE in ('mysql', 'ado_mssql','mssql') \
951+                            and hasattr(value, 'microsecond'):
952                 value = value.replace(microsecond=0)
953             if settings.DATABASE_ENGINE == 'oracle':
954                 # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field.