Index: django/contrib/sessions/middleware.py
===================================================================
--- django/contrib/sessions/middleware.py	(revision 4754)
+++ django/contrib/sessions/middleware.py	(working copy)
@@ -55,8 +55,11 @@
                 self._session_cache = {}
             else:
                 try:
+                    datenow = datetime.datetime.now()
+                    if hasattr(datenow, 'microsecond'):
+                        datenow = datenow.replace(microsecond=0)
                     s = Session.objects.get(session_key=self.session_key,
-                        expire_date__gt=datetime.datetime.now())
+                        expire_date__gt=datenow)
                     self._session_cache = s.get_decoded()
                 except (Session.DoesNotExist, SuspiciousOperation):
                     self._session_cache = {}
Index: django/db/backends/ado_mssql/base.py
===================================================================
--- django/db/backends/ado_mssql/base.py	(revision 4754)
+++ django/db/backends/ado_mssql/base.py	(working copy)
@@ -3,10 +3,13 @@
 
 Requires adodbapi 2.0.1: http://adodbapi.sourceforge.net/
 """
+import pythoncom
 
+from django.conf import settings
 from django.db.backends import util
+
 try:
-    import adodbapi as Database
+    import adodbapi.adodbapi as Database
 except ImportError, e:
     from django.core.exceptions import ImproperlyConfigured
     raise ImproperlyConfigured, "Error loading adodbapi module: %s" % e
@@ -25,7 +28,9 @@
     def executeHelper(self, operation, isStoredProcedureCall, parameters=None):
         if parameters is not None and "%s" in operation:
             operation = operation.replace("%s", "?")
+        pythoncom.CoInitialize()
         Database.Cursor.executeHelper(self, operation, isStoredProcedureCall, parameters)
+        pythoncom.CoUninitialize()
 
 class Connection(Database.Connection):
     def cursor(self):
@@ -44,6 +49,8 @@
         return datetime.datetime(*tuple(tv))
     if type(res) == float and str(res)[-2:] == ".0":
         return int(res) # If float but int, then int.
+    if type(res) == unicode:
+        return res.encode(settings.DEFAULT_CHARSET)
     return res
 Database.convertVariantToPython = variantToPython
 
@@ -69,8 +76,14 @@
                 settings.DATABASE_HOST = "127.0.0.1"
             # TODO: Handle DATABASE_PORT.
             conn_string = "PROVIDER=SQLOLEDB;DATA SOURCE=%s;UID=%s;PWD=%s;DATABASE=%s" % (settings.DATABASE_HOST, settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
+            # The driver claims to be threadsafe, surely it should handle this?
+            # Either way, I'm not convinced this is the right place.
+            # It should be done per thread
+            #pythoncom.CoInitialize()
             self.connection = Database.connect(conn_string)
-        cursor = self.connection.cursor()
+            #pythoncom.CoUninitialize()
+        
+        cursor = Cursor(self.connection) 
         if settings.DEBUG:
             return util.CursorDebugWrapper(cursor, self)
         return cursor
@@ -100,7 +113,7 @@
 dictfetchall  = util.dictfetchall
 
 def get_last_insert_id(cursor, table_name, pk_name):
-    cursor.execute("SELECT %s FROM %s WHERE %s = @@IDENTITY" % (pk_name, table_name, pk_name))
+    cursor.execute("SELECT %s FROM %s WHERE %s = IDENT_CURRENT('%s')" % (pk_name, table_name, pk_name,table_name))
     return cursor.fetchone()[0]
 
 def get_date_extract_sql(lookup_type, table_name):
@@ -118,16 +131,25 @@
 
 def get_limit_offset_sql(limit, offset=None):
     # TODO: This is a guess. Make sure this is correct.
-    sql = "LIMIT %s" % limit
-    if offset and offset != 0:
-        sql += " OFFSET %s" % offset
-    return sql
+    # should be "SELECT TOP %s" % limit 
+    # not LIMIT at the end 
+    return "" 
+    #sql = "LIMIT %s" % limit 
+    #if offset and offset != 0: 
+    #    sql += " OFFSET %s" % offset 
+    #return sql 
 
 def get_random_function_sql():
     return "RAND()"
 
 def get_deferrable_sql():
-    return " DEFERRABLE INITIALLY DEFERRED"
+    return " ON DELETE CASCADE ON UPDATE CASCADE"
+    # DEFERRABLE and INITALLY DEFFERRED are not apparently supported on constraints
+    # Never actually specified, but implied by:
+    # http://msdn2.microsoft.com/en-us/library/ms178011.aspx
+    # and the fact that DEFERRABLE and INITALLY DEFERRED don't appear in the
+    # ALTER TABLE syntax
+    # HOWEVER, You can put a much needed ON DELETE CASCADE thing here
 
 def get_fulltext_search_sql(field_name):
     raise NotImplementedError
@@ -138,19 +160,33 @@
 def get_pk_default_value():
     return "DEFAULT"
 
-def get_sql_flush(sql_styler, full_table_list):
+def get_sql_flush(sql_styler, full_table_list, sequences):
     """Return a list of SQL statements required to remove all data from
     all tables in the database (without actually removing the tables
     themselves) and put the database in an empty 'initial' state
     """
-    # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
-    # TODO - SQL not actually tested against ADO MSSQL yet!
-    # TODO - autoincrement indices reset required? See other get_sql_flush() implementations
-    sql_list = ['%s %s;' % \
-                (sql_styler.SQL_KEYWORD('TRUNCATE'),
+    # Cannot use TRUNCATE on tables that are reference by a FOREIGN KEY
+    # So must use the much slower DELETE
+    
+    sql_list = ['%s %s %s;' % \
+                (sql_styler.SQL_KEYWORD('DELETE'),
+                 sql_styler.SQL_KEYWORD('FROM'),
                  sql_styler.SQL_FIELD(quote_name(table))
                  )  for table in full_table_list]
 
+    #The reset the counters on each table.
+    sql_list.extend(['%s %s %s %s %s %s %s;' % (
+                sql_styler.SQL_KEYWORD('DBCC'),
+                sql_styler.SQL_KEYWORD('CHECKIDENT'),
+                sql_styler.SQL_FIELD(quote_name(seq["table"])),
+                sql_styler.SQL_KEYWORD('RESEED'),
+                sql_styler.SQL_FIELD('1'),
+                sql_styler.SQL_KEYWORD('WITH'),
+                sql_styler.SQL_KEYWORD('NO_INFOMSGS'),
+                ) for seq in sequences])
+    return sql_list
+
+
 OPERATOR_MAPPING = {
     'exact': '= %s',
     'iexact': 'LIKE %s',
Index: django/db/backends/ado_mssql/introspection.py
===================================================================
--- django/db/backends/ado_mssql/introspection.py	(revision 4754)
+++ django/db/backends/ado_mssql/introspection.py	(working copy)
@@ -1,13 +1,147 @@
+# Tested against MSDE and SQL Server 2000 using adodbapi 2.0.1
+# Python 2.4.2 and 2.4.3 were used during testing.
+from django.db.backends.ado_mssql.base import Cursor
+
 def get_table_list(cursor):
-    raise NotImplementedError
+    "Returns a list of table names in the current database."
+    print "# Note: Any fields that are named 'id', are of type 'AutoField', and"
+    print "# and are Primary Keys will NOT appear in the model output below."
+    print "# By default Django assumes that the each model's Primary Key is an "
+    print "# AutoField with a name of 'id', so there is no need to add it to the" 
+    print "# model description."
+    print
+    cursor.execute("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")
+    return [row[2] for row in cursor.fetchall()]
 
-def get_table_description(cursor, table_name):
-    raise NotImplementedError
+def _is_auto_field(cursor, table_name, column_name):
+    cursor.execute("SELECT COLUMNPROPERTY( OBJECT_ID('%s'),'%s','IsIdentity')" % (table_name, column_name))
+    return cursor.fetchall()[0][0]
 
+def get_table_description(cursor, table_name, identity_check=True):
+    """Returns a description of the table, with the DB-API cursor.description interface.
+
+    The 'auto_check' parameter has been added to the function argspec.
+    If set to True, the function will check each of the table's fields for the
+    IDENTITY property (the IDENTITY property is the MSSQL equivalent to an AutoField).
+
+    When a field is found with an IDENTITY property, it is given a custom field number
+    of -777, which maps to the 'AutoField' value in the DATA_TYPES_REVERSE dict.
+    """    
+    cursor.execute("SELECT TOP 1 * FROM %s" % table_name)
+    cursor.nextset()
+    items = []
+    if identity_check:
+        for data in cursor.description:
+            if _is_auto_field(cursor, table_name, data[0]):
+                data = list(data)
+                data[1] = -777
+            items.append(list(data))
+    else:
+        items = cursor.description
+    return items
+
+def _name_to_index(cursor, table_name):
+    """
+    Returns a dictionary of {field_name: field_index} for the given table.
+    Indexes are 0-based.
+    """
+    return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name, identity_check=False))])
+
 def get_relations(cursor, table_name):
-    raise NotImplementedError
-
+    """
+    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
+    representing all relationships to the given table. Indexes are 0-based.    
+    """
+    table_index = _name_to_index(cursor, table_name)
+    sql = """SELECT e.COLUMN_NAME AS column_name,
+                    c.TABLE_NAME AS referenced_table_name,
+                    d.COLUMN_NAME AS referenced_column_name
+                    FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a
+                        INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS b
+                              ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME
+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE AS c
+                              ON b.UNIQUE_CONSTRAINT_NAME = c.CONSTRAINT_NAME
+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS d
+                              ON c.CONSTRAINT_NAME = d.CONSTRAINT_NAME
+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS e
+                              ON a.CONSTRAINT_NAME = e.CONSTRAINT_NAME
+                    WHERE a.TABLE_NAME = ? AND
+                          a.CONSTRAINT_TYPE = 'FOREIGN KEY'"""
+    cursor = Cursor(cursor.db.connection)
+    cursor.execute(sql, (table_name,))
+    return dict([(table_index[item[0]], (_name_to_index(cursor, item[1])[item[2]], item[1]))
+                  for item in cursor.fetchall()])
+    
 def get_indexes(cursor, table_name):
-    raise NotImplementedError
+    """
+    Returns a dictionary of fieldname -> infodict for the given table,
+    where each infodict is in the format:
+        {'primary_key': boolean representing whether it's the primary key,
+         'unique': boolean representing whether it's a unique index}
+    """
+    sql = """SELECT b.COLUMN_NAME, a.CONSTRAINT_TYPE
+               FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a INNER JOIN
+                    INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS b
+                    ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME AND
+                       a.TABLE_NAME = b.TABLE_NAME
+               WHERE a.TABLE_NAME = ? AND
+                     (CONSTRAINT_TYPE = 'PRIMARY KEY' OR
+                      CONSTRAINT_TYPE = 'UNIQUE')"""
+    field_names = [item[0] for item in get_table_description(cursor, table_name, identity_check=False)]
+    cursor = Cursor(cursor.db.connection)
+    cursor.execute(sql, (table_name,))
+    indexes = {}
+    results = {}
+    data = cursor.fetchall()
+    if data:
+        results.update(data)
+    for field in field_names:
+        val = results.get(field, None)
+        indexes[field] = dict(primary_key=(val=='PRIMARY KEY'), unique=(val=='UNIQUE'))
+    return indexes
 
-DATA_TYPES_REVERSE = {}
+# A reference for the values below:
+# http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ado270/htm/mdcstdatatypeenum.asp
+DATA_TYPES_REVERSE = {
+# 8192 : Array ,
+# 128 : Binary ,
+# 9 : IDispatch ,
+# 12 : Variant ,
+# 13 : IUnknown ,
+# 21  : UnsignedBigInt,
+# 132 : UserDefined ,
+# 0   : Empty ,
+# 136 : Chapter ,
+# 138 : PropVariant ,
+# 204 : VarBinary ,
+# 205 : LongVarBinary ,
+-777: 'AutoField',                  # Custom number used to identify AutoFields
+2   : 'SmallIntegerField',          # SmallInt
+3   : 'IntegerField',               # Integer
+4   : 'FloatField',                 # Single
+5   : 'FloatField',                 # Decimal
+6   : 'FloatField',                 # Currency
+7   : 'DateField',                  # Date
+8   : 'CharField',                  # BSTR
+10  : 'IntegerField',               # Error
+11  : 'BooleanField',               # Boolean
+14  : 'FloatField',                 # Decimal
+16  : 'SmallIntegerField',          # TinyInt
+17  : 'PositiveSmallIntegerField',  # UnsignedTinyInt
+18  : 'PositiveSmallIntegerField',  # UnsignedSmallInt
+19  : 'PositiveIntegerField',       # UnsignedInt
+20  : 'IntegerField',               # BigInt
+64  : 'DateTimeField',              # FileTime
+72  : 'CharField',                  # GUID
+129 : 'CharField',                  # Char
+130 : 'CharField',                  # WChar
+131 : 'FloatField',                 # Numeric
+133 : 'DateField',                  # DBDate
+134 : 'TimeField',                  # DBTime
+135 : 'DateTimeField',              # DBTimeStamp
+139 : 'FloatField',                 # VarNumeric
+200 : 'CharField',                  # VarChar
+201 : 'TextField',                  # LongVarChar
+202 : 'CharField',                  # VarWChar
+203 : 'TextField',                  # LongVarWChar
+}
Index: django/db/backends/util.py
===================================================================
--- django/db/backends/util.py	(revision 4754)
+++ django/db/backends/util.py	(working copy)
@@ -1,3 +1,4 @@
+from django.conf import settings
 import datetime
 from time import time
 
@@ -16,8 +17,24 @@
             # formatting with '%' only works with tuples or dicts.
             if not isinstance(params, (tuple, dict)):
                 params = tuple(params)
+            # ado_mssql uses '?' for parameter escaping, so all '?'
+            # must be replaced with the standard '%s' if the parameter
+            # substitution is going to work.
+            if settings.DATABASE_ENGINE == 'ado_mssql':
+                sql = sql.replace('?', '%s')
+            # There are many situations that will cause the string
+            # substituion below to fail (e.g. wildcard characters '%'
+            # in LIKE queries).  Instead of attempting to figure out
+            # the many variations that can cause an error, the string substition
+            # will be attempted first; if it fails, then the sql
+            # and its parameters will be combined into a string similar to
+            # the one created in the executemany function below.
+            try:
+                sql = sql % tuple(params)
+            except:
+                sql = '%s SQL: %s' % (sql, str(tuple(params)))
             self.db.queries.append({
-                'sql': sql % params,
+                'sql': sql,
                 'time': "%.3f" % (stop - start),
             })
 
Index: django/db/models/base.py
===================================================================
--- django/db/models/base.py	(revision 4754)
+++ django/db/models/base.py	(working copy)
@@ -205,7 +205,7 @@
         record_exists = True
         if pk_set:
             # Determine whether a record with the primary key already exists.
-            cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % \
+            cursor.execute("SELECT 1 FROM %s WHERE %s=%%s" % 
                 (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)), [pk_val])
             # If it does already exist, do an UPDATE.
             if cursor.fetchone():
@@ -233,9 +233,19 @@
                     (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.order_with_respect_to.column)))
                 db_values.append(getattr(self, self._meta.order_with_respect_to.attname))
             if db_values:
+                if pk_set and settings.DATABASE_ENGINE=="ado_mssql":
+                    # You can't insert an auto value into a column unless you do
+                    # this in MSSQL
+                    cursor.execute("SET IDENTITY_INSERT %s ON" % \
+                        backend.quote_name(self._meta.db_table))
+                    
                 cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \
                     (backend.quote_name(self._meta.db_table), ','.join(field_names),
                     ','.join(placeholders)), db_values)
+                    
+                if pk_set and settings.DATABASE_ENGINE=="ado_mssql":
+                    cursor.execute("SET IDENTITY_INSERT %s OFF" %\
+                        backend.quote_name(self._meta.db_table))
             else:
                 # Create a new record with defaults for everything.
                 cursor.execute("INSERT INTO %s (%s) VALUES (%s)" %
Index: django/db/models/fields/__init__.py
===================================================================
--- django/db/models/fields/__init__.py	(revision 4754)
+++ django/db/models/fields/__init__.py	(working copy)
@@ -530,12 +530,15 @@
         if value is not None:
             # MySQL will throw a warning if microseconds are given, because it
             # doesn't support microseconds.
-            if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
+            if (settings.DATABASE_ENGINE == 'mysql' or settings.DATABASE_ENGINE == 'ado_mssql') and hasattr(value, 'microsecond'):
                 value = value.replace(microsecond=0)
             value = str(value)
         return Field.get_db_prep_save(self, value)
 
     def get_db_prep_lookup(self, lookup_type, value):
+        # MSSQL doesn't like microseconds.
+        if settings.DATABASE_ENGINE == 'ado_mssql' and hasattr(value, 'microsecond'):
+            value = value.replace(microsecond=0)
         if lookup_type == 'range':
             value = [str(v) for v in value]
         else:
@@ -826,8 +829,9 @@
         # Casts dates into string format for entry into database.
         if value is not None:
             # MySQL will throw a warning if microseconds are given, because it
-            # doesn't support microseconds.
-            if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
+            # doesn't support microseconds. Ditto MSSQL
+            if settings.DATABASE_ENGINE in ('mysql', 'ado_mssql') \
+                            and hasattr(value, 'microsecond'):
                 value = value.replace(microsecond=0)
             value = str(value)
         return Field.get_db_prep_save(self, value)
