Code

Ticket #2358: mssql_update3.diff

File mssql_update3.diff, 12.4 KB (added by sdelatorre+django@…, 8 years ago)

Added a patch for queries that use a datetime.datetime object. Because MSSQL can't parse the microseconds from a datetime object, the microseconds need to be set to 0 before being used in a query. Also re-ran the diff against the SVN trunk for all the changes made in this ticket.

Line 
1Index: django/db/models/base.py
2===================================================================
3--- django/db/models/base.py    (revision 3882)
4+++ django/db/models/base.py    (working copy)
5@@ -171,7 +171,7 @@
6         record_exists = True
7         if pk_set:
8             # Determine whether a record with the primary key already exists.
9-            cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % \
10+            cursor.execute("SELECT 1 FROM %s WHERE %s=%%s" %
11                 (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)), [pk_val])
12             # If it does already exist, do an UPDATE.
13             if cursor.fetchone():
14Index: django/db/models/fields/__init__.py
15===================================================================
16--- django/db/models/fields/__init__.py (revision 3882)
17+++ django/db/models/fields/__init__.py (working copy)
18@@ -490,12 +490,15 @@
19         if value is not None:
20             # MySQL will throw a warning if microseconds are given, because it
21             # doesn't support microseconds.
22-            if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
23+            if (settings.DATABASE_ENGINE == 'mysql' or settings.DATABASE_ENGINE == 'ado_mssql') and hasattr(value, 'microsecond'):
24                 value = value.replace(microsecond=0)
25             value = str(value)
26         return Field.get_db_prep_save(self, value)
27 
28-    def get_db_prep_lookup(self, lookup_type, value):
29+    def get_db_prep_lookup(self, lookup_type, value):
30+        # MSSQL doesn't like microseconds.
31+        if settings.DATABASE_ENGINE == 'ado_mssql' and hasattr(value, 'microsecond'):
32+            value = value.replace(microsecond=0)
33         if lookup_type == 'range':
34             value = [str(v) for v in value]
35         else:
36@@ -754,7 +757,7 @@
37         if value is not None:
38             # MySQL will throw a warning if microseconds are given, because it
39             # doesn't support microseconds.
40-            if settings.DATABASE_ENGINE == 'mysql':
41+            if settings.DATABASE_ENGINE == 'mysql' or settings.DATABASE_ENGINE == 'ado_mssql':
42                 value = value.replace(microsecond=0)
43             value = str(value)
44         return Field.get_db_prep_save(self, value)
45Index: django/db/backends/ado_mssql/base.py
46===================================================================
47--- django/db/backends/ado_mssql/base.py        (revision 3882)
48+++ django/db/backends/ado_mssql/base.py        (working copy)
49@@ -3,6 +3,7 @@
50 
51 Requires adodbapi 2.0.1: http://adodbapi.sourceforge.net/
52 """
53+import pythoncom
54 
55 from django.db.backends import util
56 try:
57@@ -69,8 +70,11 @@
58                 settings.DATABASE_HOST = "127.0.0.1"
59             # TODO: Handle DATABASE_PORT.
60             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)
61-            self.connection = Database.connect(conn_string)
62-        cursor = self.connection.cursor()
63+            pythoncom.CoInitialize()
64+            self.connection = Database.connect(conn_string)
65+            pythoncom.CoUninitialize()
66+       
67+        cursor = Cursor(self.connection)
68         if settings.DEBUG:
69             return util.CursorDebugWrapper(cursor, self)
70         return cursor
71@@ -115,12 +119,15 @@
72     if lookup_type=='day':
73         return "Convert(datetime, Convert(varchar(12), %s))" % field_name
74 
75-def get_limit_offset_sql(limit, offset=None):
76-    # TODO: This is a guess. Make sure this is correct.
77-    sql = "LIMIT %s" % limit
78-    if offset and offset != 0:
79-        sql += " OFFSET %s" % offset
80-    return sql
81+def get_limit_offset_sql(limit, offset=None):
82+    # TODO: This is a guess. Make sure this is correct.
83+    # should be "SELECT TOP %s" % limit
84+    # not LIMIT at the end
85+    return ""
86+    #sql = "LIMIT %s" % limit
87+    #if offset and offset != 0:
88+    #    sql += " OFFSET %s" % offset
89+    #return sql
90 
91 def get_random_function_sql():
92     return "RAND()"
93Index: django/db/backends/ado_mssql/introspection.py
94===================================================================
95--- django/db/backends/ado_mssql/introspection.py       (revision 3882)
96+++ django/db/backends/ado_mssql/introspection.py       (working copy)
97@@ -1,13 +1,147 @@
98-def get_table_list(cursor):
99-    raise NotImplementedError
100+# Tested against MSDE and SQL Server 2000 using adodbapi 2.0.1
101+# Python 2.4.2 and 2.4.3 were used during testing.
102+from django.db.backends.ado_mssql.base import Cursor
103+
104+def get_table_list(cursor):
105+    "Returns a list of table names in the current database."
106+    print "# Note: Any fields that are named 'id', are of type 'AutoField', and"
107+    print "# and are Primary Keys will NOT appear in the model output below."
108+    print "# By default Django assumes that the each model's Primary Key is an "
109+    print "# AutoField with a name of 'id', so there is no need to add it to the"
110+    print "# model description."
111+    print
112+    cursor.execute("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")
113+    return [row[2] for row in cursor.fetchall()]
114+
115+def _is_auto_field(cursor, table_name, column_name):
116+    cursor.execute("SELECT COLUMNPROPERTY( OBJECT_ID('%s'),'%s','IsIdentity')" % (table_name, column_name))
117+    return cursor.fetchall()[0][0]
118 
119-def get_table_description(cursor, table_name):
120-    raise NotImplementedError
121+def get_table_description(cursor, table_name, identity_check=True):
122+    """Returns a description of the table, with the DB-API cursor.description interface.
123+
124+    The 'auto_check' parameter has been added to the function argspec.
125+    If set to True, the function will check each of the table's fields for the
126+    IDENTITY property (the IDENTITY property is the MSSQL equivalent to an AutoField).
127+
128+    When a field is found with an IDENTITY property, it is given a custom field number
129+    of -777, which maps to the 'AutoField' value in the DATA_TYPES_REVERSE dict.
130+    """   
131+    cursor.execute("SELECT TOP 1 * FROM %s" % table_name)
132+    cursor.nextset()
133+    items = []
134+    if identity_check:
135+        for data in cursor.description:
136+            if _is_auto_field(cursor, table_name, data[0]):
137+                data = list(data)
138+                data[1] = -777
139+            items.append(list(data))
140+    else:
141+        items = cursor.description
142+    return items
143+
144+def _name_to_index(cursor, table_name):
145+    """
146+    Returns a dictionary of {field_name: field_index} for the given table.
147+    Indexes are 0-based.
148+    """
149+    return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name, identity_check=False))])
150 
151-def get_relations(cursor, table_name):
152-    raise NotImplementedError
153-
154-def get_indexes(cursor, table_name):
155-    raise NotImplementedError
156-
157-DATA_TYPES_REVERSE = {}
158+def get_relations(cursor, table_name):
159+    """
160+    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
161+    representing all relationships to the given table. Indexes are 0-based.   
162+    """
163+    table_index = _name_to_index(cursor, table_name)
164+    sql = """SELECT e.COLUMN_NAME AS column_name,
165+                    c.TABLE_NAME AS referenced_table_name,
166+                    d.COLUMN_NAME AS referenced_column_name
167+                    FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a
168+                        INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS b
169+                              ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME
170+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE AS c
171+                              ON b.UNIQUE_CONSTRAINT_NAME = c.CONSTRAINT_NAME
172+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS d
173+                              ON c.CONSTRAINT_NAME = d.CONSTRAINT_NAME
174+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS e
175+                              ON a.CONSTRAINT_NAME = e.CONSTRAINT_NAME
176+                    WHERE a.TABLE_NAME = ? AND
177+                          a.CONSTRAINT_TYPE = 'FOREIGN KEY'"""
178+    cursor = Cursor(cursor.db.connection)
179+    cursor.execute(sql, (table_name,))
180+    return dict([(table_index[item[0]], (_name_to_index(cursor, item[1])[item[2]], item[1]))
181+                  for item in cursor.fetchall()])
182+   
183+def get_indexes(cursor, table_name):
184+    """
185+    Returns a dictionary of fieldname -> infodict for the given table,
186+    where each infodict is in the format:
187+        {'primary_key': boolean representing whether it's the primary key,
188+         'unique': boolean representing whether it's a unique index}
189+    """
190+    sql = """SELECT b.COLUMN_NAME, a.CONSTRAINT_TYPE
191+               FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a INNER JOIN
192+                    INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS b
193+                    ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME AND
194+                       a.TABLE_NAME = b.TABLE_NAME
195+               WHERE a.TABLE_NAME = ? AND
196+                     (CONSTRAINT_TYPE = 'PRIMARY KEY' OR
197+                      CONSTRAINT_TYPE = 'UNIQUE')"""
198+    field_names = [item[0] for item in get_table_description(cursor, table_name, identity_check=False)]
199+    cursor = Cursor(cursor.db.connection)
200+    cursor.execute(sql, (table_name,))
201+    indexes = {}
202+    results = {}
203+    data = cursor.fetchall()
204+    if data:
205+        results.update(data)
206+    for field in field_names:
207+        val = results.get(field, None)
208+        indexes[field] = dict(primary_key=(val=='PRIMARY KEY'), unique=(val=='UNIQUE'))
209+    return indexes
210+
211+# A reference for the values below:
212+# http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ado270/htm/mdcstdatatypeenum.asp
213+DATA_TYPES_REVERSE = {
214+# 8192 : Array ,
215+# 128 : Binary ,
216+# 9 : IDispatch ,
217+# 12 : Variant ,
218+# 13 : IUnknown ,
219+# 21  : UnsignedBigInt,
220+# 132 : UserDefined ,
221+# 0   : Empty ,
222+# 136 : Chapter ,
223+# 138 : PropVariant ,
224+# 204 : VarBinary ,
225+# 205 : LongVarBinary ,
226+-777: 'AutoField',                  # Custom number used to identify AutoFields
227+2   : 'SmallIntegerField',          # SmallInt
228+3   : 'IntegerField',               # Integer
229+4   : 'FloatField',                 # Single
230+5   : 'FloatField',                 # Decimal
231+6   : 'FloatField',                 # Currency
232+7   : 'DateField',                  # Date
233+8   : 'CharField',                  # BSTR
234+10  : 'IntegerField',               # Error
235+11  : 'BooleanField',               # Boolean
236+14  : 'FloatField',                 # Decimal
237+16  : 'SmallIntegerField',          # TinyInt
238+17  : 'PositiveSmallIntegerField',  # UnsignedTinyInt
239+18  : 'PositiveSmallIntegerField',  # UnsignedSmallInt
240+19  : 'PositiveIntegerField',       # UnsignedInt
241+20  : 'IntegerField',               # BigInt
242+64  : 'DateTimeField',              # FileTime
243+72  : 'CharField',                  # GUID
244+129 : 'CharField',                  # Char
245+130 : 'CharField',                  # WChar
246+131 : 'FloatField',                 # Numeric
247+133 : 'DateField',                  # DBDate
248+134 : 'TimeField',                  # DBTime
249+135 : 'DateTimeField',              # DBTimeStamp
250+139 : 'FloatField',                 # VarNumeric
251+200 : 'CharField',                  # VarChar
252+201 : 'TextField',                  # LongVarChar
253+202 : 'CharField',                  # VarWChar
254+203 : 'TextField',                  # LongVarWChar
255+}
256Index: django/contrib/sessions/middleware.py
257===================================================================
258--- django/contrib/sessions/middleware.py       (revision 3882)
259+++ django/contrib/sessions/middleware.py       (working copy)
260@@ -51,9 +51,12 @@
261             if self.session_key is None:
262                 self._session_cache = {}
263             else:
264-                try:
265-                    s = Session.objects.get(session_key=self.session_key,
266-                        expire_date__gt=datetime.datetime.now())
267+                try:
268+                    datenow = datetime.datetime.now()
269+                    if hasattr(datenow, 'microsecond'):
270+                        datenow = datenow.replace(microsecond=0)
271+                    s = Session.objects.get(session_key=self.session_key,
272+                        expire_date__gt=datenow)
273                     self._session_cache = s.get_decoded()
274                 except Session.DoesNotExist:
275                     self._session_cache = {}