Code

Ticket #5062: mssql.r5986-2.diff

File mssql.r5986-2.diff, 32.9 KB (added by gregoire@…, 7 years ago)

fixes in db/models/base.py for quote_name() and working implementation of sql_flush

Line 
1Index: db/models/base.py
2===================================================================
3--- db/models/base.py   (r‚vision 5986)
4+++ db/models/base.py   (copie de travail)
5@@ -246,9 +246,22 @@
6                     (qn(self._meta.db_table), qn(self._meta.order_with_respect_to.column)))
7                 db_values.append(getattr(self, self._meta.order_with_respect_to.attname))
8             if db_values:
9+                if pk_set and (settings.DATABASE_ENGINE=="ado_mssql" or settings.DATABASE_ENGINE=="mssql"):
10+                    # You can't insert an auto value into a column unless you do
11+                    # this in MSSQL
12+                    # TODO: Only works for auto-id's... how chek it properly?
13+                    if self._meta.pk.column == 'id':
14+                        cursor.execute("SET IDENTITY_INSERT %s ON" % \
15+                            qn(self._meta.db_table))
16+                   
17                 cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \
18                     (qn(self._meta.db_table), ','.join(field_names),
19                     ','.join(placeholders)), db_values)
20+                   
21+                if pk_set and (settings.DATABASE_ENGINE=="ado_mssql" or settings.DATABASE_ENGINE=="mssql"):
22+                    if self._meta.pk.column == 'id':
23+                        cursor.execute("SET IDENTITY_INSERT %s OFF" %\
24+                            qn(self._meta.db_table))
25             else:
26                 # Create a new record with defaults for everything.
27                 cursor.execute("INSERT INTO %s (%s) VALUES (%s)" %
28Index: db/models/fields/__init__.py
29===================================================================
30--- db/models/fields/__init__.py        (r‚vision 5986)
31+++ db/models/fields/__init__.py        (copie de travail)
32@@ -223,7 +223,7 @@
33                 value = int(value)
34             except ValueError:
35                 raise ValueError("The __year lookup type requires an integer argument")
36-            return ['%s-01-01 00:00:00' % value, '%s-12-31 23:59:59.999999' % value]
37+            return ['%s-01-01 00:00:00' % value, '%s-12-31 23:59:59.99' % value]
38         raise TypeError("Field has invalid lookup: %s" % lookup_type)
39 
40     def has_default(self):
41@@ -574,12 +574,15 @@
42         if value is not None:
43             # MySQL will throw a warning if microseconds are given, because it
44             # doesn't support microseconds.
45-            if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
46+            if settings.DATABASE_ENGINE in ('mysql', 'ado_mssql','mssql') and hasattr(value, 'microsecond'):
47                 value = value.replace(microsecond=0)
48             value = smart_unicode(value)
49         return Field.get_db_prep_save(self, value)
50 
51     def get_db_prep_lookup(self, lookup_type, value):
52+        # MSSQL doesn't like microseconds.
53+        if settings.DATABASE_ENGINE in ('mysql', 'ado_mssql','mssql') and hasattr(value, 'microsecond'):
54+            value = value.replace(microsecond=0)       
55         if lookup_type == 'range':
56             value = [smart_unicode(v) for v in value]
57         else:
58@@ -966,8 +969,9 @@
59         # Casts dates into string format for entry into database.
60         if value is not None:
61             # MySQL will throw a warning if microseconds are given, because it
62-            # doesn't support microseconds.
63-            if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
64+            # doesn't support microseconds. Ditto MSSQL
65+            if settings.DATABASE_ENGINE in ('mysql', 'ado_mssql','mssql') \
66+                            and hasattr(value, 'microsecond'):
67                 value = value.replace(microsecond=0)
68             if settings.DATABASE_ENGINE == 'oracle':
69                 # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field.
70Index: db/backends/mssql/base.py
71===================================================================
72--- db/backends/mssql/base.py   (r‚vision 0)
73+++ db/backends/mssql/base.py   (r‚vision 0)
74@@ -0,0 +1,464 @@
75+"""
76+Alpha Multi-plataform MSSQL database backend for Django.
77+
78+Requires pymssql >= v0.8.0: http://pymssql.sourceforge.net/
79+"""
80+
81+import datetime
82+from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
83+from django.core.exceptions import ImproperlyConfigured
84+from django.utils.datastructures import SortedDict
85+
86+try:
87+    import pymssql as Database
88+except ImportError, e:
89+    raise ImproperlyConfigured("Error loading pymssql module: %s" % e)
90+
91+try:
92+    import mx
93+except ImportError:
94+    mx = None
95+
96+try:
97+    # Only exists in Python 2.4+
98+    from threading import local
99+except ImportError:
100+    # Import copy of _thread_local.py from Python 2.4
101+    from django.utils._threading_local import local
102+
103+DatabaseError = Database.DatabaseError
104+IntegrityError = Database.IntegrityError
105+
106+class DatabaseFeatures(BaseDatabaseFeatures):
107+       pass
108+
109+class DatabaseOperations(BaseDatabaseOperations):
110+    def last_insert_id(self, cursor, table_name, pk_name):
111+           cursor.execute("SELECT %s FROM %s WHERE %s = IDENT_CURRENT('%s')" % (pk_name, table_name, pk_name,table_name))
112+           return cursor.fetchone()[0]
113+         
114+    def query_set_class(self, DefaultQuerySet):
115+           "Create a custom QuerySet class for SqlServer."
116+       
117+           from django.db import connection
118+           from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE, quote_only_if_word
119+       
120+           class SqlServerQuerySet(DefaultQuerySet):
121+       
122+               def iterator(self):
123+                   "Performs the SELECT database lookup of this QuerySet."
124+       
125+                   from django.db.models.query import get_cached_row
126+       
127+                   # self._select is a dictionary, and dictionaries' key order is
128+                   # undefined, so we convert it to a list of tuples.
129+                   extra_select = self._select.items()
130+       
131+                   full_query = None
132+       
133+                   try:
134+                       try:
135+                           select, sql, params, full_query = self._get_sql_clause(get_full_query=True)
136+                       except TypeError:
137+                           select, sql, params = self._get_sql_clause()
138+                   except EmptyResultSet:
139+                       raise StopIteration
140+                   if not full_query:
141+                       full_query = "SELECT %s%s\n%s" % \
142+                                    ((self._distinct and "DISTINCT " or ""),
143+                                     ', '.join(select), sql)
144+       
145+                   cursor = connection.cursor()
146+                   cursor.execute(full_query, params)
147+       
148+                   fill_cache = self._select_related
149+                   fields = self.model._meta.fields
150+                   index_end = len(fields)
151+       
152+                   # so here's the logic;
153+                   # 1. retrieve each row in turn
154+                   # 2. convert NCLOBs
155+       
156+                   while 1:
157+                       rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
158+                       if not rows:
159+                           raise StopIteration
160+                       for row in rows:
161+                           row = self.resolve_columns(row, fields)
162+                           if fill_cache:
163+                               obj, index_end = get_cached_row(klass=self.model, row=row,
164+                                                               index_start=0, max_depth=self._max_related_depth)
165+                           else:
166+                               obj = self.model(*row[:index_end])
167+                           for i, k in enumerate(extra_select):
168+                               setattr(obj, k[0], row[index_end+i])
169+                           yield obj
170+       
171+       
172+               def _get_sql_clause(self, get_full_query=False):
173+                   from django.db.models.query import fill_table_cache, \
174+                       handle_legacy_orderlist, orderfield2column
175+       
176+                   opts = self.model._meta
177+                   qn = connection.ops.quote_name
178+       
179+                   # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
180+                   select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields]
181+                   tables = [quote_only_if_word(t) for t in self._tables]
182+                   joins = SortedDict()
183+                   where = self._where[:]
184+                   params = self._params[:]
185+       
186+                   # Convert self._filters into SQL.
187+                   joins2, where2, params2 = self._filters.get_sql(opts)
188+                   joins.update(joins2)
189+                   where.extend(where2)
190+                   params.extend(params2)
191+       
192+                   # Add additional tables and WHERE clauses based on select_related.
193+                   if self._select_related:
194+                       fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])
195+       
196+                   # Add any additional SELECTs.
197+                   if self._select:
198+                       select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in self._select.items()])
199+       
200+                   # Start composing the body of the SQL statement.
201+                   sql = [" FROM", qn(opts.db_table)]
202+       
203+                   # Compose the join dictionary into SQL describing the joins.
204+                   if joins:
205+                       sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition)
206+                                       for (alias, (table, join_type, condition)) in joins.items()]))
207+       
208+                   # Compose the tables clause into SQL.
209+                   if tables:
210+                       sql.append(", " + ", ".join(tables))
211+       
212+                   # Compose the where clause into SQL.
213+                   if where:
214+                       sql.append(where and "WHERE " + " AND ".join(where))
215+                   
216+                   #copy a version suitable for LIMIT
217+                   sql2=[]
218+                   [sql2.append(x) for x in sql]           
219+                   # ORDER BY clause
220+                   order_by = []
221+                   if self._order_by is not None:
222+                       ordering_to_use = self._order_by
223+                   else:
224+                       ordering_to_use = opts.ordering
225+                   for f in handle_legacy_orderlist(ordering_to_use):
226+                       if f == '?': # Special case.
227+                           order_by.append(connection.ops.get_random_function_sql())
228+                       else:
229+                           if f.startswith('-'):
230+                               col_name = f[1:]
231+                               order = "DESC"
232+                           else:
233+                               col_name = f
234+                               order = "ASC"
235+                           if "." in col_name:
236+                               table_prefix, col_name = col_name.split('.', 1)
237+                               table_prefix = qn(table_prefix) + '.'
238+                           else:
239+                               # Use the database table as a column prefix if it wasn't given,
240+                               # and if the requested column isn't a custom SELECT.
241+                               if "." not in col_name and col_name not in (self._select or ()):
242+                                   table_prefix = qn(opts.db_table) + '.'
243+                               else:
244+                                   table_prefix = ''
245+                           order_by.append('%s%s %s' % (table_prefix, qn(orderfield2column(col_name, opts)), order))
246+                   if order_by:
247+                       sql.append("ORDER BY " + ", ".join(order_by))
248+       
249+                   # Look for column name collisions in the select elements
250+                   # and fix them with an AS alias.  This allows us to do a
251+                   # SELECT * later in the paging query.
252+                   cols = [clause.split('.')[-1] for clause in select]
253+                   for index, col in enumerate(cols):
254+                       if cols.count(col) > 1:
255+                           col = '%s%d' % (col.replace('[', '').replace(']',''), index)
256+                           cols[index] = qn(col)
257+                           select[index] = '%s AS %s' % (select[index], qn(col))
258+       
259+                   # LIMIT and OFFSET clauses
260+                   # To support limits and offsets, SqlServer requires some funky rewriting of an otherwise normal looking query.
261+                   select_clause = ",".join(select)
262+                   distinct = (self._distinct and "DISTINCT " or "")           
263+                   full_query = None
264+                   
265+                   if self._limit is None:
266+                       assert self._offset is None, "'offset' is not allowed without 'limit'"
267+       
268+                   if self._offset is not None:
269+                       offset = int(self._offset)
270+                   else:
271+                       offset = 0
272+       
273+                   if self._limit is not None:
274+                       limit = int(self._limit)
275+                   else:
276+                       limit = None
277+                   
278+                   limit_and_offset_clause = ''
279+       
280+                   if limit is not None:
281+                       limit_and_offset_clause = True
282+                   elif offset:
283+                       limit_and_offset_clause = True
284+                   
285+                   if limit_and_offset_clause:
286+                       #Django give:
287+                       # Offset : Start row
288+                       # Limit : How much aditional rows fetch
289+                       
290+                       # This must be transformed to Sql2005 to:
291+                       # Offset : First Row
292+                       # Limit : EndRow
293+                       StartRow = offset + 1
294+                       EndRow = StartRow + limit - 1
295+                       # and for Sql2000
296+                       # Offset : Top rows
297+                       # Limit: From where
298+                       limit = limit + offset
299+                       if offset==0:
300+                           offset = limit
301+                       else:
302+                           offset = offset + 1
303+                       #Must use a order. If not specified, use Id.
304+                       if len(order_by)==0:
305+                           order_by.append('%s.%s ASC' %
306+                               (qn(opts.db_table),
307+                               qn(opts.fields[0].db_column or opts.fields[0].column)
308+                               )
309+                           )
310+       
311+                       order_by_clause = ", ".join(order_by)
312+                       order_by_clauseReverse = ""
313+       
314+                       #For Sql2005+ use native implementation...
315+                       if version()>8:
316+                           fmt = \
317+"""
318+SELECT *
319+FROM (
320+    SELECT %(distinc)s TOP %(EndRow)s
321+        %(fields)s, ROW_NUMBER()
322+        OVER(
323+            ORDER BY  %(orderby)s
324+        ) AS row
325+    %(sql)s ORDER BY %(orderby)s
326+    ) AS x
327+    WHERE x.row BETWEEN %(StartRow)s AND %(EndRow)s
328+"""
329+                       else:   
330+                           #Is necesary reverse all the second order by for the trick to work...
331+                           order_by_clauseReverse= ", ".join(self.change_order_direction(order_by))
332+                           
333+                           fmt = \
334+"""
335+SELECT * FROM (
336+  SELECT TOP %(offset)s * FROM (
337+    SELECT TOP %(limit)s %(distinc)s%(fields)s
338+        %(sql)s   
339+    ORDER BY %(orderby)s
340+  ) AS %(table)s
341+  ORDER BY %(orderbyReverse)s) AS %(table)s
342+ORDER BY %(orderby)s
343+"""
344+       
345+                       full_query = fmt % {'distinc':distinct, 'fields':select_clause,
346+                                               'sql':" ".join(sql2),'orderby':order_by_clause,
347+                                               'orderbyReverse':order_by_clauseReverse,
348+                                               'table':qn(opts.db_table),
349+                                               'offset':offset,'limit':limit,
350+                                               'StartRow':StartRow,'EndRow':EndRow}
351+                   
352+                   print full_query
353+                   if get_full_query:
354+                       return select, " ".join(sql), params, full_query
355+                   else:
356+                       return select, " ".join(sql), params
357+       
358+       
359+               def change_order_direction(self,order_by):
360+                   newOrder=[]
361+                   
362+                   for order in order_by:
363+                       if order.find(' ASC'):
364+                           newOrder.append(order.replace(' ASC',' DESC'))
365+                       else:
366+                           newOrder.append(order.replace(' DESC',' ASC'))
367+                   
368+                   return newOrder
369+       
370+               def resolve_columns(self, row, fields=()):
371+                   from django.db.models.fields import DateField, DateTimeField, \
372+                       TimeField, BooleanField, NullBooleanField, DecimalField, Field
373+                   values = []
374+                   for value, field in map(None, row, fields):
375+                       # Convert 1 or 0 to True or False
376+                       if value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)):
377+                           value = bool(value)
378+                       # Convert floats to decimals
379+                       elif value is not None and isinstance(field, DecimalField):
380+                           value = util.typecast_decimal(field.format_number(value))
381+                       values.append(value)
382+                   return values
383+       
384+           return SqlServerQuerySet
385+   
386+    def date_extract_sql(self, lookup_type, field_name):
387+               # lookup_type is 'year', 'month', 'day'
388+               return "DATEPART(%s, %s)" % (lookup_type, table_name)
389+
390+    def date_trunc_sql(self, lookup_type, field_name):
391+               # lookup_type is 'year', 'month', 'day'
392+               if lookup_type=='year':
393+                       return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/01/01')" % field_name
394+               if lookup_type=='month':
395+                       return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/' + Convert(varchar, DATEPART(month, %s)) + '/01')" % (field_name, field_name)
396+               if lookup_type=='day':
397+                       return "Convert(datetime, Convert(varchar(12), %s))" % field_name
398+
399+    def limit_offset_sql(self, limit, offset=None):
400+               # Limits and offset are too complicated to be handled here.
401+               # Look for a implementation similar to SqlServer backend
402+               return ""
403+
404+    def quote_name(self, name):
405+        if name.startswith('[') and name.endswith(']'):
406+               return name # Quoting once is enough.
407+        return '[%s]' % name
408+
409+    def random_function_sql(self):
410+        return "RAND()"
411+       
412+    def tablespace_sql(self, tablespace, inline=False):
413+     return "ON %s" % quote_name(tablespace)
414+
415+    def sql_flush(self, style, tables, sequences):
416+               """Return a list of SQL statements required to remove all data from
417+               all tables in the database (without actually removing the tables
418+               themselves) and put the database in an empty 'initial' state
419+               """
420+               # Cannot use TRUNCATE on tables that are referenced by a FOREIGN KEY
421+               # So must use the much slower DELETE
422+               from django.db import connection
423+               cursor = connection.cursor()
424+               cursor.execute("SELECT TABLE_NAME, CONSTRAINT_NAME FROM information_schema.table_constraints")
425+               fks = cursor.fetchall()
426+               sql_list = ['ALTER TABLE %s NOCHECK CONSTRAINT %s;' % \
427+                               (self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks]
428+               sql_list.extend(['%s %s %s;' % \
429+                                       (style.SQL_KEYWORD('DELETE'),
430+                                       style.SQL_KEYWORD('FROM'),
431+                                       style.SQL_FIELD(self.quote_name(table))
432+                                       )  for table in tables])
433+               #The reset the counters on each table.
434+               sql_list.extend(['%s %s (%s, %s, %s) %s %s;' % (
435+                       style.SQL_KEYWORD('DBCC'),
436+                       style.SQL_KEYWORD('CHECKIDENT'),
437+                       style.SQL_FIELD(self.quote_name(seq["table"])),
438+                       style.SQL_KEYWORD('RESEED'),
439+                       style.SQL_FIELD('1'),
440+                       style.SQL_KEYWORD('WITH'),
441+                       style.SQL_KEYWORD('NO_INFOMSGS'),
442+                       ) for seq in sequences])
443+               sql_list.extend(['ALTER TABLE %s CHECK CONSTRAINT %s;' % \
444+                               (self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks])
445+               return sql_list
446+
447+
448+
449+def complain(*args, **kwargs):
450+    raise ImproperlyConfigured("You haven't set the DATABASE_ENGINE setting yet.")
451+
452+def ignore(*args, **kwargs):
453+    pass
454+
455+class DatabaseError(Exception):
456+    pass
457+
458+class IntegrityError(DatabaseError):
459+    pass
460+
461+class DatabaseWrapper(BaseDatabaseWrapper):
462+    features = DatabaseFeatures()
463+    ops = DatabaseOperations()
464+    operators = {
465+           'exact': '= %s',
466+           'iexact': 'LIKE %s',
467+           'contains': 'LIKE %s',
468+           'icontains': 'LIKE %s',
469+           'gt': '> %s',
470+           'gte': '>= %s',
471+           'lt': '< %s',
472+           'lte': '<= %s',
473+           'startswith': 'LIKE %s',
474+           'endswith': 'LIKE %s',
475+           'istartswith': 'LIKE %s',
476+           'iendswith': 'LIKE %s',
477+       }
478+    def __init__(self, **kwargs):
479+        self.connection = None
480+        self.queries = []
481+
482+    def cursor(self):
483+        from django.conf import settings
484+        if self.connection is None:
485+            if settings.DATABASE_NAME == '' or settings.DATABASE_USER == '':
486+                raise ImproperlyConfigured("You need to specify both DATABASE_NAME and DATABASE_USER in your Django settings file.")
487+            if not settings.DATABASE_HOST:
488+                settings.DATABASE_HOST = "127.0.0.1"
489+
490+            if settings.DATABASE_PORT:
491+                hostStr = '%s:%s' % ( settings.DATABASE_HOST ,settings.DATABASE_PORT)
492+            else:
493+                hostStr = settings.DATABASE_HOST
494+                                                       
495+            self.connection = Database.connect(host=hostStr,user=settings.DATABASE_USER,password=settings.DATABASE_PASSWORD,database=settings.DATABASE_NAME)
496+       
497+        self.connection.cursor().execute("SET DATEFORMAT ymd\nGO")
498+       
499+        cursor = self.connection.cursor()
500+        if settings.DEBUG:
501+            return util.CursorDebugWrapper(cursor, self)
502+        return cursor
503+
504+    def _commit(self):
505+        if self.connection is not None:
506+            return self.connection.commit()
507+
508+    def _rollback(self):
509+        if self.connection is not None:
510+            return self.connection.rollback()
511+
512+    def close(self):
513+        if self.connection is not None:
514+            self.connection.close()
515+            self.connection = None
516+
517+'''
518+    Return the major version of the server. 7=Sql 7,8=Sql2000,9=Sql2005
519+'''
520+def version():
521+    cur = DatabaseWrapper().cursor()
522+    cur.execute("SELECT SERVERPROPERTY('ProductVersion')")
523+   
524+    return int(cur.fetchone()[0].split('.')[0])
525+
526+
527+if __name__ == '__main__':
528+    from mysite.polls.models import Poll, Choice
529+    from datetime import datetime
530+   
531+    #Poll.objects.all().delete()
532+    #i =0
533+    #for i in range(i,150):
534+    #    p = Poll(question="%s" % i, pub_date=datetime.now())
535+    #    p.save()
536+
537+    for poll in Poll.objects.all()[:10]:
538+        print poll
539\ Pas de fin de ligne … la fin du fichier
540Index: db/backends/mssql/client.py
541===================================================================
542--- db/backends/mssql/client.py (r‚vision 0)
543+++ db/backends/mssql/client.py (r‚vision 0)
544@@ -0,0 +1,37 @@
545+from django.conf import settings
546+import os
547+import sys
548+
549+def runshell():
550+    if os.name=='nt':
551+        args = ['']
552+        db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME)
553+        user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER)
554+        passwd = settings.DATABASE_OPTIONS.get('passwd', settings.DATABASE_PASSWORD)
555+        host = settings.DATABASE_OPTIONS.get('host', settings.DATABASE_HOST)
556+        port = settings.DATABASE_OPTIONS.get('port', settings.DATABASE_PORT)
557+        defaults_file = settings.DATABASE_OPTIONS.get('read_default_file')
558+        # Seems to be no good way to set sql_mode with CLI
559+       
560+        if defaults_file:
561+            args += ["-i %s" % defaults_file]
562+        if user:
563+            args += ["-U %s" % user]
564+        if passwd:
565+            args += ["-P %s" % passwd]
566+        if host:
567+            args += ["-E %s" % host]
568+        if db:
569+            args += ["-d %s" % db]
570+   
571+        cmd = "osql %s" % ' '.join(args)
572+   
573+        rv = os.system(cmd)
574+        if (rv):
575+           print "Error al ejecutar %s " % rv
576+           sys.exit(rv)
577+    else:
578+        raise NotImplementedError
579+
580+   
581+   
582Index: db/backends/mssql/__init__.py
583===================================================================
584--- db/backends/mssql/__init__.py       (r‚vision 0)
585+++ db/backends/mssql/__init__.py       (r‚vision 0)
586@@ -0,0 +1 @@
587+# placeholder
588\ Pas de fin de ligne … la fin du fichier
589Index: db/backends/mssql/introspection.py
590===================================================================
591--- db/backends/mssql/introspection.py  (r‚vision 0)
592+++ db/backends/mssql/introspection.py  (r‚vision 0)
593@@ -0,0 +1,137 @@
594+def get_table_list(cursor):
595+    "Returns a list of table names in the current database."
596+    cursor.execute("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")
597+    return [row[2] for row in cursor.fetchall()]
598+
599+def _is_auto_field(cursor, table_name, column_name):
600+    cursor.execute("SELECT COLUMNPROPERTY( OBJECT_ID('%s'),'%s','IsIdentity')" % (table_name, column_name))
601+    return cursor.fetchall()[0][0]
602+
603+def get_table_description(cursor, table_name, identity_check=True):
604+    """Returns a description of the table, with the DB-API cursor.description interface.
605+
606+    The 'auto_check' parameter has been added to the function argspec.
607+    If set to True, the function will check each of the table's fields for the
608+    IDENTITY property (the IDENTITY property is the MSSQL equivalent to an AutoField).
609+
610+    When a field is found with an IDENTITY property, it is given a custom field number
611+    of -777, which maps to the 'AutoField' value in the DATA_TYPES_REVERSE dict.
612+    """   
613+    cursor.execute("SELECT TOP 1 * FROM %s" % table_name)
614+    cursor.nextset()
615+    items = []
616+    if identity_check:
617+        for data in cursor.description:
618+            if _is_auto_field(cursor, table_name, data[0]):
619+                data = list(data)
620+                data[1] = -777
621+            items.append(list(data))
622+    else:
623+        items = cursor.description
624+    return items
625+
626+def _name_to_index(cursor, table_name):
627+    """
628+    Returns a dictionary of {field_name: field_index} for the given table.
629+    Indexes are 0-based.
630+    """
631+    return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name, identity_check=False))])
632+
633+def get_relations(cursor, table_name):
634+    """
635+    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
636+    representing all relationships to the given table. Indexes are 0-based.   
637+    """
638+    table_index = _name_to_index(cursor, table_name)
639+    sql = """SELECT e.COLUMN_NAME AS column_name,
640+                    c.TABLE_NAME AS referenced_table_name,
641+                    d.COLUMN_NAME AS referenced_column_name
642+                    FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a
643+                        INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS b
644+                              ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME
645+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE AS c
646+                              ON b.UNIQUE_CONSTRAINT_NAME = c.CONSTRAINT_NAME
647+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS d
648+                              ON c.CONSTRAINT_NAME = d.CONSTRAINT_NAME
649+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS e
650+                              ON a.CONSTRAINT_NAME = e.CONSTRAINT_NAME
651+                    WHERE a.TABLE_NAME = ? AND
652+                          a.CONSTRAINT_TYPE = 'FOREIGN KEY'"""
653+    cursor = Cursor(cursor.db.connection)
654+    cursor.execute(sql, (table_name,))
655+    return dict([(table_index[item[0]], (_name_to_index(cursor, item[1])[item[2]], item[1]))
656+                  for item in cursor.fetchall()])
657+   
658+def get_indexes(cursor, table_name):
659+    """
660+    Returns a dictionary of fieldname -> infodict for the given table,
661+    where each infodict is in the format:
662+        {'primary_key': boolean representing whether it's the primary key,
663+         'unique': boolean representing whether it's a unique index}
664+    """
665+    sql = """SELECT b.COLUMN_NAME, a.CONSTRAINT_TYPE
666+               FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a INNER JOIN
667+                    INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS b
668+                    ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME AND
669+                       a.TABLE_NAME = b.TABLE_NAME
670+               WHERE a.TABLE_NAME = ? AND
671+                     (CONSTRAINT_TYPE = 'PRIMARY KEY' OR
672+                      CONSTRAINT_TYPE = 'UNIQUE')"""
673+    field_names = [item[0] for item in get_table_description(cursor, table_name, identity_check=False)]
674+    cursor = Cursor(cursor.db.connection)
675+    cursor.execute(sql, (table_name,))
676+    indexes = {}
677+    results = {}
678+    data = cursor.fetchall()
679+    if data:
680+        results.update(data)
681+    for field in field_names:
682+        val = results.get(field, None)
683+        indexes[field] = dict(primary_key=(val=='PRIMARY KEY'), unique=(val=='UNIQUE'))
684+    return indexes
685+
686+# A reference for the values below:
687+# http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ado270/htm/mdcstdatatypeenum.asp
688+DATA_TYPES_REVERSE = {
689+# 8192 : Array ,
690+# 128 : Binary ,
691+# 9 : IDispatch ,
692+# 12 : Variant ,
693+# 13 : IUnknown ,
694+# 21  : UnsignedBigInt,
695+# 132 : UserDefined ,
696+# 0   : Empty ,
697+# 136 : Chapter ,
698+# 138 : PropVariant ,
699+# 204 : VarBinary ,
700+# 205 : LongVarBinary ,
701+-777: 'AutoField',                  # Custom number used to identify AutoFields
702+2   : 'SmallIntegerField',          # SmallInt
703+3   : 'IntegerField',               # Integer
704+4   : 'FloatField',                 # Single
705+5   : 'FloatField',                 # Decimal
706+6   : 'FloatField',                 # Currency
707+7   : 'DateField',                  # Date
708+8   : 'CharField',                  # BSTR
709+10  : 'IntegerField',               # Error
710+11  : 'BooleanField',               # Boolean
711+14  : 'FloatField',                 # Decimal
712+16  : 'SmallIntegerField',          # TinyInt
713+17  : 'PositiveSmallIntegerField',  # UnsignedTinyInt
714+18  : 'PositiveSmallIntegerField',  # UnsignedSmallInt
715+19  : 'PositiveIntegerField',       # UnsignedInt
716+20  : 'IntegerField',               # BigInt
717+64  : 'DateTimeField',              # FileTime
718+72  : 'CharField',                  # GUID
719+129 : 'CharField',                  # Char
720+130 : 'CharField',                  # WChar
721+131 : 'FloatField',                 # Numeric
722+133 : 'DateField',                  # DBDate
723+134 : 'TimeField',                  # DBTime
724+135 : 'DateTimeField',              # DBTimeStamp
725+139 : 'FloatField',                 # VarNumeric
726+200 : 'CharField',                  # VarChar
727+201 : 'TextField',                  # LongVarChar
728+202 : 'CharField',                  # VarWChar
729+203 : 'TextField',                  # LongVarWChar
730+}
731\ Pas de fin de ligne … la fin du fichier
732Index: db/backends/mssql/creation.py
733===================================================================
734--- db/backends/mssql/creation.py       (r‚vision 0)
735+++ db/backends/mssql/creation.py       (r‚vision 0)
736@@ -0,0 +1,27 @@
737+DATA_TYPES = {
738+    'AutoField':         'int IDENTITY (1, 1)',
739+    'BooleanField':      'bit',
740+    'CharField':         'varchar(%(max_length)s)',
741+    'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
742+    'DateField':         'datetime',
743+    'DateTimeField':     'datetime',
744+    'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)',
745+    'FileField':         'varchar(254)',
746+    'FilePathField':     'varchar(254)',
747+    'FloatField':        'double precision',
748+    'ImageField':        'varchar(254)',
749+    'IntegerField':      'int',
750+    'IPAddressField':    'char(15)',
751+    'ManyToManyField':   None,
752+    'NullBooleanField':  'bit',
753+    'OneToOneField':     'int',
754+    'PhoneNumberField':  'varchar(20)',
755+    #The check must be unique in for the database. Put random so the regresion test not complain about duplicate names
756+    'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(creation_counter)s_%(column)s] CHECK ([%(column)s] > 0)',   
757+    'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(creation_counter)s_%(column)s] CHECK ([%(column)s] > 0)',
758+    'SlugField':         'varchar(%(max_length)s)',
759+    'SmallIntegerField': 'smallint',
760+    'TextField':         'text',
761+    'TimeField':         'datetime',
762+    'USStateField':      'varchar(2)',
763+}
764Index: db/backends/util.py
765===================================================================
766--- db/backends/util.py (r‚vision 5986)
767+++ db/backends/util.py (copie de travail)
768@@ -1,3 +1,4 @@
769+from django.conf import settings
770 import datetime
771 import md5
772 from time import time
773Index: contrib/sessions/middleware.py
774===================================================================
775--- contrib/sessions/middleware.py      (r‚vision 5986)
776+++ contrib/sessions/middleware.py      (copie de travail)
777@@ -61,8 +61,11 @@
778                 self._session_cache = {}
779             else:
780                 try:
781+                    datenow = datetime.datetime.now()
782+                    if hasattr(datenow, 'microsecond'):
783+                        datenow = datenow.replace(microsecond=0)
784                     s = Session.objects.get(session_key=self.session_key,
785-                        expire_date__gt=datetime.datetime.now())
786+                        expire_date__gt=datenow)
787                     self._session_cache = s.get_decoded()
788                 except (Session.DoesNotExist, SuspiciousOperation):
789                     self._session_cache = {}