Commits

Michael Manfre  committed 0b87f67

Fixed #17 - Added support for can_return_id_from_insert

This is enabled by default and disabled if the SQL version is
detected as SQL Server 2000. Implementation for Django < 1.5 will
inject a comment containing the quoted table name and column
name. This comment is not present in Django 1.5, compliments of
Django ticket #19096.

  • Participants
  • Parent commits eaf3d9a

Comments (0)

Files changed (5)

File docs/changelog.txt

 v1.3 (in development)
 ---------------------
 
+- Backend now supports returning the ID from an insert without needing an additional query. This is disabled
+  for SQL Server 2000 (assuming that version still works with this backend). :issue:`17`
 
 v1.2
 ----

File sqlserver_ado/base.py

 DatabaseError = Database.DatabaseError
 IntegrityError = Database.IntegrityError
 
-
 class DatabaseFeatures(BaseDatabaseFeatures):
     uses_custom_query_class = True
     has_bulk_insert = False
     supports_timezones = False
     supports_sequence_reset = False
     
-    query_needed_to_fetch_return_id = True
+    can_return_id_from_insert = True
 
 # IP Address recognizer taken from:
 # http://mail.python.org/pipermail/python-list/2006-March/375505.html
         except ValueError:
             self.cast_avg_to_float = False
         
+        self.ops.is_sql2000 = self.is_sql2000
         self.ops.is_sql2005 = self.is_sql2005
         self.ops.is_sql2008 = self.is_sql2008
 
             self.command_timeout,
             use_transactions=self.use_transactions,
         )
+        
+        if self.connection.is_sql2000:
+            # SQL 2000 doesn't support the OUTPUT clause
+            self.features.can_return_id_from_insert = False
+        
         connection_created.send(sender=self.__class__, connection=self)
         return self.connection
 
+    def is_sql2000(self):
+        """
+        Returns True if the current connection is SQL2000. Establishes a
+        connection if needed.
+        """
+        if not self.connection:
+            self.__connect()
+        return self.connection.is_sql2000
+
     def is_sql2005(self):
         """
         Returns True if the current connection is SQL2005. Establishes a

File sqlserver_ado/compiler.py

                     sql=sql,
                 )
 
+        # mangle SQL to return ID from insert
+        # http://msdn.microsoft.com/en-us/library/ms177564.aspx
+        if self.return_id and self.connection.features.can_return_id_from_insert:
+            sql = 'SET NOCOUNT ON; {sql}'.format(sql=sql)
+            sql = sql.replace(' VALUES', ' OUTPUT INSERTED.{0} VALUES'.format(
+                self.connection.ops.quote_name(meta.pk.db_column or meta.pk.get_attname()),
+            ))
+
         return sql, params
 
 class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler):

File sqlserver_ado/dbapi.py

     if p.Size == 0:
         p.Size = -1
 
+VERSION_SQL2000 = 8
 VERSION_SQL2005 = 9
 VERSION_SQL2008 = 10
 
             self.adoConn.BeginTrans() # Disables autocommit per DBPAI
 
     @property
+    def is_sql2000(self):
+        v = self.adoConnProperties.get('DBMS Version', '')
+        return v.startswith(unicode(VERSION_SQL2000))
+
+    @property
     def is_sql2005(self):
         v = self.adoConnProperties.get('DBMS Version', '')
         return v.startswith(unicode(VERSION_SQL2005))

File sqlserver_ado/operations.py

 import datetime
 import time
+import django
 from django.conf import settings
 from django.db.backends import BaseDatabaseOperations
 
         return "DATEADD(%s, DATEDIFF(%s, 0, %s), 0)" % (lookup_type, lookup_type, field_name)
 
     def last_insert_id(self, cursor, table_name, pk_name):
+        """
+        Fetch the last inserted ID by executing another query.
+        """
+        # IDENT_CURRENT   returns the last identity value generated for a 
+        #                 specific table in any session and any scope.
+        # http://msdn.microsoft.com/en-us/library/ms175098.aspx
         cursor.execute("SELECT CAST(IDENT_CURRENT(%s) as bigint)", [self.quote_name(table_name)])
         return cursor.fetchone()[0]
 
+    def return_insert_id(self):
+        """
+        MSSQL implements the RETURNING SQL standard extension differently from
+        the core database backends and this function is essentially a no-op. 
+        The SQL is altered in the SQLInsertCompiler to add the necessary OUTPUT
+        clause.
+        """
+        if django.VERSION[0] == 1 and django.VERSION[1] < 5:
+            # This gets around inflexibility of SQLInsertCompiler's need to 
+            # append an SQL fragment at the end of the insert query, which also must
+            # expect the full quoted table and column name.
+            return ('/* %s */', '')
+        
+        # Django #19096 - As of Django 1.5, can return None, None to bypass the 
+        # core's SQL mangling.
+        return (None, None)
+
     def no_limit_value(self):
         return None