Code

Ticket #2358: mssql_update6b.diff

File mssql_update6b.diff, 14.2 KB (added by wycharon@…, 7 years ago)

i am sorry. in mssql_update6a.diff. I forgot importing django.conf.settings

Line 
1Index: db/models/base.py
2===================================================================
3--- db/models/base.py   (revision 4459)
4+++ db/models/base.py   (working copy)
5@@ -170,7 +170,7 @@
6         record_exists = True
7         if pk_val is not None:
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: db/models/fields/__init__.py
15===================================================================
16--- db/models/fields/__init__.py        (revision 4459)
17+++ db/models/fields/__init__.py        (working copy)
18@@ -519,12 +519,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+        # MSSQL doesn't like microseconds.
30+        if settings.DATABASE_ENGINE == 'ado_mssql' and hasattr(value, 'microsecond'):
31+            value = value.replace(microsecond=0)
32         if lookup_type == 'range':
33             value = [str(v) for v in value]
34         else:
35@@ -803,7 +806,7 @@
36         if value is not None:
37             # MySQL will throw a warning if microseconds are given, because it
38             # doesn't support microseconds.
39-            if settings.DATABASE_ENGINE == 'mysql':
40+            if settings.DATABASE_ENGINE == 'mysql' or settings.DATABASE_ENGINE == 'ado_mssql':
41                 value = value.replace(microsecond=0)
42             value = str(value)
43         return Field.get_db_prep_save(self, value)
44Index: db/backends/ado_mssql/base.py
45===================================================================
46--- db/backends/ado_mssql/base.py       (revision 4459)
47+++ db/backends/ado_mssql/base.py       (working copy)
48@@ -3,10 +3,13 @@
49 
50 Requires adodbapi 2.0.1: http://adodbapi.sourceforge.net/
51 """
52+import pythoncom
53 
54+from django.conf import settings
55 from django.db.backends import util
56+
57 try:
58-    import adodbapi as Database
59+    import adodbapi.adodbapi as Database
60 except ImportError, e:
61     from django.core.exceptions import ImproperlyConfigured
62     raise ImproperlyConfigured, "Error loading adodbapi module: %s" % e
63@@ -25,7 +28,9 @@
64     def executeHelper(self, operation, isStoredProcedureCall, parameters=None):
65         if parameters is not None and "%s" in operation:
66             operation = operation.replace("%s", "?")
67+        pythoncom.CoInitialize()
68         Database.Cursor.executeHelper(self, operation, isStoredProcedureCall, parameters)
69+        pythoncom.CoUninitialize()
70 
71 class Connection(Database.Connection):
72     def cursor(self):
73@@ -44,8 +49,10 @@
74         return datetime.datetime(*tuple(tv))
75     if type(res) == float and str(res)[-2:] == ".0":
76         return int(res) # If float but int, then int.
77+    if type(res) == unicode:
78+        return res.encode(settings.DEFAULT_CHARSET)
79     return res
80-Database.convertVariantToPython = variantToPython
81+Database.convertVariantToPython = variantToPythn
82 
83 try:
84     # Only exists in Python 2.4+
85@@ -69,8 +76,11 @@
86                 settings.DATABASE_HOST = "127.0.0.1"
87             # TODO: Handle DATABASE_PORT.
88             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)
89+            pythoncom.CoInitialize()
90             self.connection = Database.connect(conn_string)
91-        cursor = self.connection.cursor()
92+            pythoncom.CoUninitialize()
93+       
94+        cursor = Cursor(self.connection)
95         if settings.DEBUG:
96             return util.CursorDebugWrapper(cursor, self)
97         return cursor
98@@ -117,10 +127,13 @@
99 
100 def get_limit_offset_sql(limit, offset=None):
101     # TODO: This is a guess. Make sure this is correct.
102-    sql = "LIMIT %s" % limit
103-    if offset and offset != 0:
104-        sql += " OFFSET %s" % offset
105-    return sql
106+    # should be "SELECT TOP %s" % limit
107+    # not LIMIT at the end
108+    return ""
109+    #sql = "LIMIT %s" % limit
110+    #if offset and offset != 0:
111+    #    sql += " OFFSET %s" % offset
112+    #return sql
113 
114 def get_random_function_sql():
115     return "RAND()"
116Index: db/backends/ado_mssql/introspection.py
117===================================================================
118--- db/backends/ado_mssql/introspection.py      (revision 4459)
119+++ db/backends/ado_mssql/introspection.py      (working copy)
120@@ -1,13 +1,147 @@
121+# Tested against MSDE and SQL Server 2000 using adodbapi 2.0.1
122+# Python 2.4.2 and 2.4.3 were used during testing.
123+from django.db.backends.ado_mssql.base import Cursor
124+
125 def get_table_list(cursor):
126-    raise NotImplementedError
127+    "Returns a list of table names in the current database."
128+    print "# Note: Any fields that are named 'id', are of type 'AutoField', and"
129+    print "# and are Primary Keys will NOT appear in the model output below."
130+    print "# By default Django assumes that the each model's Primary Key is an "
131+    print "# AutoField with a name of 'id', so there is no need to add it to the"
132+    print "# model description."
133+    print
134+    cursor.execute("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")
135+    return [row[2] for row in cursor.fetchall()]
136 
137-def get_table_description(cursor, table_name):
138-    raise NotImplementedError
139+def _is_auto_field(cursor, table_name, column_name):
140+    cursor.execute("SELECT COLUMNPROPERTY( OBJECT_ID('%s'),'%s','IsIdentity')" % (table_name, column_name))
141+    return cursor.fetchall()[0][0]
142 
143+def get_table_description(cursor, table_name, identity_check=True):
144+    """Returns a description of the table, with the DB-API cursor.description interface.
145+
146+    The 'auto_check' parameter has been added to the function argspec.
147+    If set to True, the function will check each of the table's fields for the
148+    IDENTITY property (the IDENTITY property is the MSSQL equivalent to an AutoField).
149+
150+    When a field is found with an IDENTITY property, it is given a custom field number
151+    of -777, which maps to the 'AutoField' value in the DATA_TYPES_REVERSE dict.
152+    """   
153+    cursor.execute("SELECT TOP 1 * FROM %s" % table_name)
154+    cursor.nextset()
155+    items = []
156+    if identity_check:
157+        for data in cursor.description:
158+            if _is_auto_field(cursor, table_name, data[0]):
159+                data = list(data)
160+                data[1] = -777
161+            items.append(list(data))
162+    else:
163+        items = cursor.description
164+    return items
165+
166+def _name_to_index(cursor, table_name):
167+    """
168+    Returns a dictionary of {field_name: field_index} for the given table.
169+    Indexes are 0-based.
170+    """
171+    return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name, identity_check=False))])
172+
173 def get_relations(cursor, table_name):
174-    raise NotImplementedError
175-
176+    """
177+    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
178+    representing all relationships to the given table. Indexes are 0-based.   
179+    """
180+    table_index = _name_to_index(cursor, table_name)
181+    sql = """SELECT e.COLUMN_NAME AS column_name,
182+                    c.TABLE_NAME AS referenced_table_name,
183+                    d.COLUMN_NAME AS referenced_column_name
184+                    FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a
185+                        INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS b
186+                              ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME
187+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE AS c
188+                              ON b.UNIQUE_CONSTRAINT_NAME = c.CONSTRAINT_NAME
189+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS d
190+                              ON c.CONSTRAINT_NAME = d.CONSTRAINT_NAME
191+                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS e
192+                              ON a.CONSTRAINT_NAME = e.CONSTRAINT_NAME
193+                    WHERE a.TABLE_NAME = ? AND
194+                          a.CONSTRAINT_TYPE = 'FOREIGN KEY'"""
195+    cursor = Cursor(cursor.db.connection)
196+    cursor.execute(sql, (table_name,))
197+    return dict([(table_index[item[0]], (_name_to_index(cursor, item[1])[item[2]], item[1]))
198+                  for item in cursor.fetchall()])
199+   
200 def get_indexes(cursor, table_name):
201-    raise NotImplementedError
202+    """
203+    Returns a dictionary of fieldname -> infodict for the given table,
204+    where each infodict is in the format:
205+        {'primary_key': boolean representing whether it's the primary key,
206+         'unique': boolean representing whether it's a unique index}
207+    """
208+    sql = """SELECT b.COLUMN_NAME, a.CONSTRAINT_TYPE
209+               FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a INNER JOIN
210+                    INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS b
211+                    ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME AND
212+                       a.TABLE_NAME = b.TABLE_NAME
213+               WHERE a.TABLE_NAME = ? AND
214+                     (CONSTRAINT_TYPE = 'PRIMARY KEY' OR
215+                      CONSTRAINT_TYPE = 'UNIQUE')"""
216+    field_names = [item[0] for item in get_table_description(cursor, table_name, identity_check=False)]
217+    cursor = Cursor(cursor.db.connection)
218+    cursor.execute(sql, (table_name,))
219+    indexes = {}
220+    results = {}
221+    data = cursor.fetchall()
222+    if data:
223+        results.update(data)
224+    for field in field_names:
225+        val = results.get(field, None)
226+        indexes[field] = dict(primary_key=(val=='PRIMARY KEY'), unique=(val=='UNIQUE'))
227+    return indexes
228 
229-DATA_TYPES_REVERSE = {}
230+# A reference for the values below:
231+# http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ado270/htm/mdcstdatatypeenum.asp
232+DATA_TYPES_REVERSE = {
233+# 8192 : Array ,
234+# 128 : Binary ,
235+# 9 : IDispatch ,
236+# 12 : Variant ,
237+# 13 : IUnknown ,
238+# 21  : UnsignedBigInt,
239+# 132 : UserDefined ,
240+# 0   : Empty ,
241+# 136 : Chapter ,
242+# 138 : PropVariant ,
243+# 204 : VarBinary ,
244+# 205 : LongVarBinary ,
245+-777: 'AutoField',                  # Custom number used to identify AutoFields
246+2   : 'SmallIntegerField',          # SmallInt
247+3   : 'IntegerField',               # Integer
248+4   : 'FloatField',                 # Single
249+5   : 'FloatField',                 # Decimal
250+6   : 'FloatField',                 # Currency
251+7   : 'DateField',                  # Date
252+8   : 'CharField',                  # BSTR
253+10  : 'IntegerField',               # Error
254+11  : 'BooleanField',               # Boolean
255+14  : 'FloatField',                 # Decimal
256+16  : 'SmallIntegerField',          # TinyInt
257+17  : 'PositiveSmallIntegerField',  # UnsignedTinyInt
258+18  : 'PositiveSmallIntegerField',  # UnsignedSmallInt
259+19  : 'PositiveIntegerField',       # UnsignedInt
260+20  : 'IntegerField',               # BigInt
261+64  : 'DateTimeField',              # FileTime
262+72  : 'CharField',                  # GUID
263+129 : 'CharField',                  # Char
264+130 : 'CharField',                  # WChar
265+131 : 'FloatField',                 # Numeric
266+133 : 'DateField',                  # DBDate
267+134 : 'TimeField',                  # DBTime
268+135 : 'DateTimeField',              # DBTimeStamp
269+139 : 'FloatField',                 # VarNumeric
270+200 : 'CharField',                  # VarChar
271+201 : 'TextField',                  # LongVarChar
272+202 : 'CharField',                  # VarWChar
273+203 : 'TextField',                  # LongVarWChar
274+}
275Index: db/backends/util.py
276===================================================================
277--- db/backends/util.py (revision 4459)
278+++ db/backends/util.py (working copy)
279@@ -1,3 +1,4 @@
280+from django.conf import settings
281 import datetime
282 from time import time
283 
284@@ -16,8 +17,24 @@
285             # formatting with '%' only works with tuples or dicts.
286             if not isinstance(params, (tuple, dict)):
287                 params = tuple(params)
288+            # ado_mssql uses '?' for parameter escaping, so all '?'
289+            # must be replaced with the standard '%s' if the parameter
290+            # substitution is going to work.
291+            if settings.DATABASE_ENGINE == 'ado_mssql':
292+                sql = sql.replace('?', '%s')
293+            # There are many situations that will cause the string
294+            # substituion below to fail (e.g. wildcard characters '%'
295+            # in LIKE queries).  Instead of attempting to figure out
296+            # the many variations that can cause an error, the string substition
297+            # will be attempted first; if it fails, then the sql
298+            # and its parameters will be combined into a string similar to
299+            # the one created in the executemany function below.
300+            try:
301+                sql = sql % tuple(params)
302+            except:
303+                sql = '%s SQL: %s' % (sql, str(tuple(params)))
304             self.db.queries.append({
305-                'sql': sql % params,
306+                'sql': sql,
307                 'time': "%.3f" % (stop - start),
308             })
309 
310Index: contrib/sessions/middleware.py
311===================================================================
312--- contrib/sessions/middleware.py      (revision 4459)
313+++ contrib/sessions/middleware.py      (working copy)
314@@ -53,8 +53,11 @@
315                 self._session_cache = {}
316             else:
317                 try:
318+                    datenow = datetime.datetime.now()
319+                    if hasattr(datenow, 'microsecond'):
320+                        datenow = datenow.replace(microsecond=0)
321                     s = Session.objects.get(session_key=self.session_key,
322-                        expire_date__gt=datetime.datetime.now())
323+                        expire_date__gt=datenow)
324                     self._session_cache = s.get_decoded()
325                 except (Session.DoesNotExist, SuspiciousOperation):
326                     self._session_cache = {}