Django

Code

Ticket #5246: mssql_pyodbc.patch

File mssql_pyodbc.patch, 41.8 kB (added by Filip Wasilewski <filip.wasilewski@gmail.com>, 1 year ago)

MS SQL Server backend patch

  • contrib/sessions/middleware.py

    old new  
    6161                self._session_cache = {} 
    6262            else: 
    6363                try: 
     64                    datenow = datetime.datetime.now() 
     65                    if hasattr(datenow, 'microsecond'): 
     66                        datenow = datenow.replace(microsecond=0) 
    6467                    s = Session.objects.get(session_key=self.session_key, 
    65                         expire_date__gt=datetime.datetime.now()
     68                        expire_date__gt=datenow
    6669                    self._session_cache = s.get_decoded() 
    6770                except (Session.DoesNotExist, SuspiciousOperation): 
    6871                    self._session_cache = {} 
  • mssql/__init__.py

    old new  
  • db/backends/mssql/base.py

    old new  
     1""" 
     2Alpha Multi-plataform MSSQL database backend for Django. 
     3 
     4Requires pyodbc http://pyodbc.sourceforge.net/ 
     5 
     6The configurable settings in the settings file are: 
     7DATABASE_NAME               - Database name. Required. 
     8DATABASE_HOST               - SQL Server instance in "server\instance" format. 
     9DATABASE_PORT               - SQL Server instance port. 
     10DATABASE_USER               - Database user name. If not given then the 
     11                              Integrated Security will be used. 
     12DATABASE_PASSWORD           - Database user password. 
     13DATABASE_ODBC_DSN           - A named DSN can be used instead of DATABASE_HOST. 
     14DATABASE_ODBC_DRIVER        - ODBC Driver. Defalut is "{Sql Server}". 
     15DATABASE_ODBC_EXTRA_PARAMS  - Additional parameters for the ODBC connection. 
     16                              The format is "param=value;param=value". 
     17""" 
     18 
     19from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, util 
     20from django.core.exceptions import ImproperlyConfigured 
     21from operations import DatabaseOperations 
     22 
     23try: 
     24    import pyodbc as Database 
     25except ImportError, e: 
     26    raise ImproperlyConfigured("Error loading pyodbc module: %s" % e) 
     27 
     28try: 
     29    # Only exists in Python 2.4+ 
     30    from threading import local 
     31except ImportError: 
     32    # Import copy of _thread_local.py from Python 2.4 
     33    from django.utils._threading_local import local 
     34 
     35DatabaseError = Database.DatabaseError 
     36IntegrityError = Database.IntegrityError 
     37 
     38class DatabaseFeatures(BaseDatabaseFeatures): 
     39    allows_group_by_ordinal = False 
     40    allows_unique_and_pk = True 
     41    autoindexes_primary_keys = True 
     42    needs_datetime_string_cast = True 
     43    needs_upper_for_iops = False 
     44    supports_constraints = True 
     45    supports_tablespaces = True 
     46    uses_case_insensitive_names = True 
     47    uses_custom_queryset = True 
     48 
     49class DatabaseWrapper(BaseDatabaseWrapper): 
     50    features = DatabaseFeatures()  
     51    ops = DatabaseOperations() 
     52 
     53    operators = { 
     54        'exact': '= %s', 
     55        'iexact': 'LIKE %s COLLATE SQL_Latin1_General_CP1_CI_AS', 
     56        'contains': 'LIKE %s COLLATE SQL_Latin1_General_CP1_CS_AS', 
     57        'icontains': 'LIKE %s COLLATE SQL_Latin1_General_CP1_CI_AS', 
     58        'gt': '> %s', 
     59        'gte': '>= %s', 
     60        'lt': '< %s', 
     61        'lte': '<= %s', 
     62        'startswith': 'LIKE %s COLLATE SQL_Latin1_General_CP1_CS_AS', 
     63        'endswith': 'LIKE %s COLLATE SQL_Latin1_General_CP1_CI_AS', 
     64        'istartswith': 'LIKE %s COLLATE SQL_Latin1_General_CP1_CS_AS', 
     65        'iendswith': 'LIKE %s COLLATE SQL_Latin1_General_CP1_CI_AS', 
     66    } 
     67    def __init__(self, autocommit=False, **kwargs): 
     68        super(DatabaseWrapper, self).__init__(autocommit=autocommit, **kwargs) 
     69        self.connection = None 
     70        self.queries = [] 
     71 
     72    def cursor(self): 
     73        from django.conf import settings 
     74        if self.connection is None: 
     75            if settings.DATABASE_NAME == '': 
     76                raise ImproperlyConfigured("You need to specify DATABASE_NAME in your Django settings file.") 
     77 
     78            if not settings.DATABASE_HOST and not hasattr(settings, "DATABASE_ODBC_DSN"): 
     79                raise ImproperlyConfigured("You need to specify DATABASE_HOST or DATABASE_ODBC_DSN  in your Django settings file.") 
     80 
     81            if settings.DATABASE_PORT: 
     82                host_str = '%s:%s' % ( settings.DATABASE_HOST ,settings.DATABASE_PORT) 
     83            else: 
     84                host_str = settings.DATABASE_HOST 
     85 
     86            if hasattr(settings, "DATABASE_ODBC_DRIVER"): 
     87                odbc_driver = settings.DATABASE_ODBC_DRIVER 
     88            else: 
     89                odbc_driver = "{Sql Server}" 
     90             
     91            odbc_string = "Driver=%s;" % (odbc_driver) 
     92 
     93            if hasattr(settings, "DATABASE_ODBC_DSN"): 
     94                odbc_string += "DSN=%s;" % settings.DATABASE_ODBC_DSN 
     95            else: 
     96                odbc_string += "Server=%s;" % host_str 
     97             
     98            if settings.DATABASE_USER: 
     99                odbc_string += "Uid=%s;Pwd=%s;" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD) 
     100            else: 
     101                odbc_string += "Integrated Security=SSPI;" 
     102             
     103            odbc_string += "Database=%s" % settings.DATABASE_NAME 
     104             
     105            if hasattr(settings, "DATABASE_ODBC_EXTRA_PARAMS"): 
     106                odbc_string +=  ";" + settings.DATABASE_ODBC_EXTRA_PARAMS 
     107                 
     108            self.connection = Database.connect(odbc_string, self.options["autocommit"]) 
     109         
     110        self.connection.cursor().execute("SET DATEFORMAT ymd") 
     111         
     112        cursor = CursorWrapper(self.connection.cursor()) 
     113        if settings.DEBUG: 
     114            return util.CursorDebugWrapper(cursor, self) 
     115        return cursor 
     116 
     117class CursorWrapper(object): 
     118    """ 
     119    A wrapper around the pyodbc cursor that: 
     120        1. Converts input strings to unicde. 
     121        2. Replaces '%s' parameter placeholder in sql queries to '?' (pyodbc specific). 
     122    """ 
     123    def __init__(self, cursor): 
     124        self.cursor = cursor 
     125         
     126    def format_params(self, params, encoding='utf-8', errors='strict'): 
     127        new_params = [] 
     128        for param in params: 
     129            if isinstance(param, str): 
     130                # Ensure that plain strings are converted to unicode. 
     131                # Assumed input encoding is 'utf-8' 
     132                # TODO: Verify this with upper layers 
     133                param = unicode(param, encoding, errors) 
     134            new_params.append(param) 
     135        return tuple(new_params) 
     136     
     137    def format_sql(self, sql): 
     138        # pyodbc uses '?' instead of '%s' as parameter placeholder. 
     139        if "%s" in sql: 
     140            sql = sql.replace('%s', '?') 
     141        return sql 
     142                     
     143    def execute(self, sql, params=()): 
     144        if params: 
     145            params = self.format_params(params) 
     146            sql = self.format_sql(sql) 
     147        return self.cursor.execute(sql, params) 
     148 
     149    def executemany(self, sql, param_list): 
     150        if param_list: 
     151            param_list = [self.format_params(params) for params in param_list] 
     152            sql = self.format_sql(sql) 
     153        return self.cursor.executemany(sql, param_list) 
     154 
     155    def fetchone(self): 
     156        row = self.cursor.fetchone() 
     157        if row is not None: 
     158            # Convert row to tuple (pyodbc Rows are not sliceable). 
     159            return tuple(row) 
     160        return row 
     161         
     162    def fetchmany(self, chunk): 
     163        return [tuple(row) for row in self.cursor.fetchmany(chunk)] 
     164 
     165    def fetchall(self): 
     166        return [tuple(row) for row in self.cursor.fetchall()] 
     167 
     168    def __getattr__(self, attr): 
     169        if attr in self.__dict__: 
     170            return self.__dict__[attr] 
     171        else: 
     172            return getattr(self.cursor, attr) 
  • db/backends/mssql/client.py

    old new  
     1from django.conf import settings 
     2import os 
     3import sys 
     4 
     5def runshell(): 
     6    if os.name=='nt': 
     7        args = [''] 
     8        db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME) 
     9        user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER) 
     10        passwd = settings.DATABASE_OPTIONS.get('passwd', settings.DATABASE_PASSWORD) 
     11        host = settings.DATABASE_OPTIONS.get('host', settings.DATABASE_HOST) 
     12        port = settings.DATABASE_OPTIONS.get('port', settings.DATABASE_PORT) 
     13        defaults_file = settings.DATABASE_OPTIONS.get('read_default_file') 
     14        # Seems to be no good way to set sql_mode with CLI 
     15         
     16        if defaults_file: 
     17            args += ["-i %s" % defaults_file] 
     18        if user: 
     19            args += ["-U %s" % user] 
     20        if passwd: 
     21            args += ["-P %s" % passwd] 
     22        if host: 
     23            args += ["-E %s" % host] 
     24        if db: 
     25            args += ["-d %s" % db] 
     26     
     27        cmd = "osql %s" % ' '.join(args) 
     28     
     29        rv = os.system(cmd) 
     30        if (rv): 
     31           print "Error al ejecutar %s " % rv 
     32           sys.exit(rv) 
     33    else: 
     34        raise NotImplementedError 
     35 
     36     
     37     
  • db/backends/mssql/introspection.py

    old new  
     1try: 
     2    import pyodbc as Database 
     3except ImportError, e: 
     4    raise ImproperlyConfigured, "Error loading pyodbc module: %s" % e 
     5 
     6SQL_AUTOFIELD = -777555 
     7 
     8def get_table_list(cursor): 
     9    "Returns a list of table names in the current database." 
     10    cursor.execute("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'") 
     11    return [row[2] for row in cursor.fetchall()] 
     12 
     13def _is_auto_field(cursor, table_name, column_name): 
     14    cursor.execute("SELECT COLUMNPROPERTY( OBJECT_ID('%s'),'%s','IsIdentity')" % (table_name, column_name)) 
     15    return cursor.fetchall()[0][0] 
     16 
     17def get_table_description(cursor, table_name, identity_check=True): 
     18    """Returns a description of the table, with the DB-API cursor.description interface. 
     19 
     20    The 'auto_check' parameter has been added to the function argspec. 
     21    If set to True, the function will check each of the table's fields for the 
     22    IDENTITY property (the IDENTITY property is the MSSQL equivalent to an AutoField). 
     23 
     24    When a field is found with an IDENTITY property, it is given a custom field number 
     25    of SQL_AUTOFIELD, which maps to the 'AutoField' value in the DATA_TYPES_REVERSE dict. 
     26    """     
     27    cursor.execute("SELECT TOP 1 * FROM %s" % table_name) 
     28    items = [] 
     29    if identity_check: 
     30        for data in cursor.description: 
     31            if _is_auto_field(cursor, table_name, data[0]): 
     32                data = list(data) 
     33                data[1] = SQL_AUTOFIELD 
     34            items.append(list(data)) 
     35    else: 
     36        items = cursor.description 
     37    return items 
     38 
     39def _name_to_index(cursor, table_name): 
     40    """ 
     41    Returns a dictionary of {field_name: field_index} for the given table. 
     42    Indexes are 0-based. 
     43    """ 
     44    return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name, identity_check=False))]) 
     45 
     46def get_relations(cursor, table_name): 
     47    """ 
     48    Returns a dictionary of {field_index: (field_index_other_table, other_table)} 
     49    representing all relationships to the given table. Indexes are 0-based.     
     50    """ 
     51    table_index = _name_to_index(cursor, table_name) 
     52    sql = """SELECT e.COLUMN_NAME AS column_name, 
     53                    c.TABLE_NAME AS referenced_table_name, 
     54                    d.COLUMN_NAME AS referenced_column_name 
     55                    FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a 
     56                        INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS b 
     57                              ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME 
     58                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE AS c 
     59                              ON b.UNIQUE_CONSTRAINT_NAME = c.CONSTRAINT_NAME 
     60                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS d 
     61                              ON c.CONSTRAINT_NAME = d.CONSTRAINT_NAME 
     62                        INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS e 
     63                              ON a.CONSTRAINT_NAME = e.CONSTRAINT_NAME 
     64                    WHERE a.TABLE_NAME = ? AND 
     65                          a.CONSTRAINT_TYPE = 'FOREIGN KEY'""" 
     66    cursor = Cursor(cursor.db.connection) 
     67    cursor.execute(sql, (table_name,)) 
     68    return dict([(table_index[item[0]], (_name_to_index(cursor, item[1])[item[2]], item[1])) 
     69                  for item in cursor.fetchall()]) 
     70     
     71def get_indexes(cursor, table_name): 
     72    """ 
     73    Returns a dictionary of fieldname -> infodict for the given table, 
     74    where each infodict is in the format: 
     75        {'primary_key': boolean representing whether it's the primary key, 
     76         'unique': boolean representing whether it's a unique index} 
     77    """ 
     78    sql = """SELECT b.COLUMN_NAME, a.CONSTRAINT_TYPE 
     79               FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a INNER JOIN 
     80                    INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS b 
     81                    ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME AND 
     82                       a.TABLE_NAME = b.TABLE_NAME 
     83               WHERE a.TABLE_NAME = ? AND 
     84                     (CONSTRAINT_TYPE = 'PRIMARY KEY' OR 
     85                      CONSTRAINT_TYPE = 'UNIQUE')""" 
     86    field_names = [item[0] for item in get_table_description(cursor, table_name, identity_check=False)] 
     87    cursor = Cursor(cursor.db.connection) 
     88    cursor.execute(sql, (table_name,)) 
     89    indexes = {} 
     90    results = {} 
     91    data = cursor.fetchall() 
     92    if data: 
     93        results.update(data) 
     94    for field in field_names: 
     95        val = results.get(field, None) 
     96        indexes[field] = dict(primary_key=(val=='PRIMARY KEY'), unique=(val=='UNIQUE')) 
     97    return indexes 
     98 
     99DATA_TYPES_REVERSE = { 
     100    SQL_AUTOFIELD:                  'AutoField', 
     101    Database.SQL_BIGINT:            'IntegerField', 
     102    #Database.SQL_BINARY:            , 
     103    Database.SQL_BIT:               'BooleanField', 
     104    Database.SQL_CHAR:              'CharField', 
     105    Database.SQL_DECIMAL:           'DecimalField', 
     106    Database.SQL_DOUBLE:            'FloatField', 
     107    Database.SQL_FLOAT:             'FloatField', 
     108    Database.SQL_GUID:              'TextField', 
     109    Database.SQL_INTEGER:           'IntegerField', 
     110    #Database.SQL_LONGVARBINARY:     , 
     111    #Database.SQL_LONGVARCHAR:       , 
     112    Database.SQL_NUMERIC:           'DecimalField', 
     113    Database.SQL_REAL:              'FloatField', 
     114    Database.SQL_SMALLINT:          'SmallIntegerField', 
     115    Database.SQL_TINYINT:           'SmallIntegerField', 
     116    Database.SQL_TYPE_DATE:         'DateField', 
     117    Database.SQL_TYPE_TIME:         'TimeField', 
     118    Database.SQL_TYPE_TIMESTAMP:    'DateTimeField', 
     119    #Database.SQL_VARBINARY:         , 
     120    Database.SQL_VARCHAR:           'TextField', 
     121    Database.SQL_WCHAR:             'TextField', 
     122    Database.SQL_WLONGVARCHAR:      'TextField', 
     123    Database.SQL_WVARCHAR:          'TextField', 
     124} 
  • db/backends/mssql/operations.py

    old new  
     1from django.db.backends import BaseDatabaseOperations, util 
     2from django.utils.datastructures import SortedDict 
     3 
     4SQL_SERVER_7_8_LIMIT_QUERY = \ 
     5""" 
     6SELECT * FROM ( 
     7  SELECT TOP %(limit)s * FROM ( 
     8    SELECT TOP %(end_row)s %(distinc)s%(fields)s 
     9        %(sql)s     
     10    ORDER BY %(orderby)s 
     11  ) AS %(table)s 
     12  ORDER BY %(orderby_reversed)s) AS %(table)s 
     13ORDER BY %(orderby)s 
     14""" 
     15 
     16SQL_SERVER_9_LIMIT_QUERY = \ 
     17""" 
     18SELECT * 
     19FROM ( 
     20    SELECT %(distinc)s TOP %(end_row)s 
     21        %(fields)s, ROW_NUMBER() 
     22        OVER( 
     23            ORDER BY  %(orderby)s 
     24        ) AS row 
     25    %(sql)s ORDER BY %(orderby)s 
     26    ) AS x 
     27    WHERE x.row BETWEEN %(start_row)s AND %(end_row)s 
     28""" 
     29 
     30ORDER_ASC = "ASC" 
     31ORDER_DESC = "DESC" 
     32 
     33SQL_SERVER_2005_VERSION = 9 
     34SQL_SERVER_VERSION = None 
     35 
     36def sql_server_version(): 
     37    """ 
     38    Returns the major version of the SQL Server: 
     39      7    -> 7 
     40      2000 -> 8 
     41      2005 -> 9 
     42    """ 
     43    global SQL_SERVER_VERSION 
     44    if SQL_SERVER_VERSION is not None: 
     45        return SQL_SERVER_VERSION 
     46    else: 
     47        from django.db import connection 
     48        cur = connection.cursor() 
     49        cur.execute("SELECT cast(SERVERPROPERTY('ProductVersion') as varchar)") 
     50        SQL_SERVER_VERSION = int(cur.fetchone()[0].split('.')[0]) 
     51        return SQL_SERVER_VERSION 
     52 
     53class DatabaseOperations(BaseDatabaseOperations): 
     54    def last_insert_id(self, cursor, table_name, pk_name): 
     55        #http://msdn2.microsoft.com/en-us/library/ms190315.aspx 
     56        cursor.execute("SELECT %s FROM %s WHERE %s = IDENT_CURRENT('%s')" % (pk_name, table_name, pk_name,table_name))  
     57        return cursor.fetchone()[0] 
     58 
     59    def query_set_class(self, DefaultQuerySet): 
     60        "Create a custom QuerySet class for SQL Server." 
     61 
     62        from django.db import connection 
     63        from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE, quote_only_if_word 
     64 
     65        class SqlServerQuerySet(DefaultQuerySet): 
     66 
     67            def iterator(self): 
     68                "Performs the SELECT database lookup of this QuerySet." 
     69 
     70                from django.db.models.query import get_cached_row 
     71 
     72                # self._select is a dictionary, and dictionaries' key order is 
     73                # undefined, so we convert it to a list of tuples. 
     74                extra_select = self._select.items() 
     75 
     76                full_query = None 
     77 
     78                try: 
     79                    try: 
     80                        select, sql, params, full_query = self._get_sql_clause(get_full_query=True) 
     81                    except TypeError: 
     82                        select, sql, params = self._get_sql_clause() 
     83                except EmptyResultSet: 
     84                    raise StopIteration 
     85                if not full_query: 
     86                    full_query = "SELECT %s%s\n%s" % ((self._distinct and "DISTINCT " or ""), ', '.join(select), sql) 
     87 
     88                cursor = connection.cursor() 
     89                cursor.execute(full_query, params) 
     90 
     91                fill_cache = self._select_related 
     92                fields = self.model._meta.fields 
     93                index_end = len(fields) 
     94 
     95                while 1: 
     96                    rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) 
     97                    if not rows: 
     98                        raise StopIteration 
     99                    for row in rows: 
     100                        row = self.resolve_columns(row, fields) 
     101                        if fill_cache: 
     102                            obj, index_end = get_cached_row(klass=self.model, row=row, 
     103                                                            index_start=0, max_depth=self._max_related_depth) 
     104                        else: 
     105                            obj = self.model(*row[:index_end]) 
     106                        for i, k in enumerate(extra_select): 
     107                            setattr(obj, k[0], row[index_end+i]) 
     108                        yield obj 
     109 
     110            def _get_sql_clause(self, get_full_query=False): 
     111                from django.db.models.query import fill_table_cache, \ 
     112                    handle_legacy_orderlist, orderfield2column 
     113 
     114                opts = self.model._meta 
     115                qn = connection.ops.quote_name 
     116 
     117                # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z. 
     118                select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields] 
     119                tables = [quote_only_if_word(t) for t in self._tables] 
     120                joins = SortedDict() 
     121                where = self._where[:] 
     122                params = self._params[:] 
     123 
     124                # Convert self._filters into SQL. 
     125                joins2, where2, params2 = self._filters.get_sql(opts) 
     126                joins.update(joins2) 
     127                where.extend(where2) 
     128                params.extend(params2) 
     129 
     130                # Add additional tables and WHERE clauses based on select_related. 
     131                if self._select_related: 
     132                    fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table]) 
     133 
     134                # Add any additional SELECTs. 
     135                if self._select: 
     136                    select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in self._select.items()]) 
     137 
     138                # Start composing the body of the SQL statement. 
     139                sql = [" FROM", qn(opts.db_table)] 
     140 
     141                # Compose the join dictionary into SQL describing the joins. 
     142                if joins: 
     143                    sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition) 
     144                                    for (alias, (table, join_type, condition)) in joins.items()])) 
     145 
     146                # Compose the tables clause into SQL. 
     147                if tables: 
     148                    sql.append(", " + ", ".join(tables)) 
     149 
     150                # Compose the where clause into SQL. 
     151                if where: 
     152                    sql.append(where and "WHERE " + " AND ".join(where)) 
     153 
     154                # Copy version suitable for LIMIT 
     155                sql2 = sql[:] 
     156 
     157                # ORDER BY clause 
     158                order_by = [] 
     159                if self._order_by is not None: 
     160                    ordering_to_use = self._order_by 
     161                else: 
     162                    ordering_to_use = opts.ordering 
     163                for f in handle_legacy_orderlist(ordering_to_use): 
     164                    if f == '?': # Special case. 
     165                        order_by.append(connection.ops.get_random_function_sql()) 
     166                    else: 
     167                        if f.startswith('-'): 
     168                            col_name = f[1:] 
     169                            order = ORDER_DESC 
     170                        else: 
     171                            col_name = f 
     172                            order = ORDER_ASC 
     173                        if "." in col_name: 
     174                            table_prefix, col_name = col_name.split('.', 1) 
     175                            table_prefix = qn(table_prefix) + '.' 
     176                        else: 
     177                            # Use the database table as a column prefix if it wasn't given, 
     178                            # and if the requested column isn't a custom SELECT. 
     179                            if "." not in col_name and col_name not in (self._select or ()): 
     180                                table_prefix = qn(opts.db_table) + '.' 
     181                            else: 
     182                                table_prefix = '' 
     183                        order_by.append('%s%s %s' % (table_prefix, qn(orderfield2column(col_name, opts)), order)) 
     184                if order_by: 
     185                    sql.append("ORDER BY " + ", ".join(order_by)) 
     186 
     187                # Look for column name collisions in the select elements 
     188                # and fix them with an AS alias.  This allows us to do a 
     189                # SELECT * later in the paging query. 
     190                cols = [clause.split('.')[-1] for clause in select] 
     191                for index, col in enumerate(cols): 
     192                    if cols.count(col) > 1: 
     193                        col = '%s%d' % (col.replace('[', '').replace(']',''), index) 
     194                        cols[index] = qn(col) 
     195                        select[index] = '%s AS %s' % (select[index], qn(col)) 
     196 
     197                # LIMIT and OFFSET clauses 
     198                # To support limits and offsets, SQL Server requires some funky rewriting of an otherwise normal looking query. Yay.. 
     199                select_clause = ",".join(select) 
     200                distinct = (self._distinct and "DISTINCT " or "") 
     201                full_query = None 
     202 
     203                if self._limit is None: 
     204                    assert self._offset is None, "'offset' is not allowed without 'limit'" # TODO: actually, why not? 
     205 
     206                if self._limit is not None: 
     207                    limit = int(self._limit) 
     208                else: 
     209                    limit = None 
     210 
     211                if self._offset is not None and limit > 0: 
     212                    offset = int(self._offset) 
     213                else: 
     214                    offset = 0 
     215 
     216                limit_and_offset_clause = '' 
     217 
     218                if limit is not None: 
     219                    limit_and_offset_clause = True 
     220                elif offset: 
     221                    limit_and_offset_clause = True 
     222 
     223                if limit_and_offset_clause: 
     224                    # Input: 
     225                    #   offset: start row 
     226                    #   limit: chunk size 
     227 
     228                    # For SQL Server 2005 this becomes indices: 
     229                    start_row = offset + 1 
     230                    end_row = start_row + limit - 1 
     231 
     232                    # And for SQL Server 2000 we use: 
     233                    #   end_row: the upper indice 
     234                    #   limit: chunk size 
     235 
     236                    # TOP and ROW_NUMBER in T-SQL requires an order. 
     237                    # If order is not specified the use id column. 
     238                    if len(order_by)==0: 
     239                        order_by.append('%s.%s %s' % (qn(opts.db_table), qn(opts.fields[0].db_column or opts.fields[0].column), ORDER_ASC)) 
     240 
     241                    order_by_clause = ", ".join(order_by) 
     242                    order_by_clause_reverse = "" 
     243 
     244                    if sql_server_version() >= SQL_SERVER_2005_VERSION: 
     245                        fmt = SQL_SERVER_9_LIMIT_QUERY 
     246                    else: 
     247                        # Compatibility mode for older versions 
     248                        order_by_clause_reverse = ", ".join(self.change_order_direction(order_by)) 
     249                        fmt = SQL_SERVER_7_8_LIMIT_QUERY 
     250 
     251                    full_query = fmt % {'distinc': distinct, 'fields': select_clause, 
     252                                        'sql': " ".join(sql2), 'orderby': order_by_clause, 
     253                                        'orderby_reversed': order_by_clause_reverse, 
     254                                        'table': qn(opts.db_table), 
     255                                        'limit': limit, #'offset': offset, 
     256                                        'start_row': start_row, 'end_row': end_row} 
     257                if get_full_query: 
     258                    return select, " ".join(sql), params, full_query 
     259                else: 
     260                    return select, " ".join(sql), params 
     261 
     262            def change_order_direction(self, order_by): 
     263                new_order = [] 
     264                for order in order_by: 
     265                    if order.endswith(ORDER_ASC): 
     266                        new_order.append(order[:-len(ORDER_ASC)] + ORDER_DESC) 
     267                    elif order.endswith(ORDER_DESC): 
     268                        new_order.append(order[:-len(ORDER_DESC)] + ORDER_ASC) 
     269                    else: 
     270                        # TODO: check special case '?' -- random order 
     271                        new_order.append(order) 
     272                return new_order 
     273 
     274            def resolve_columns(self, row, fields=()): 
     275                from django.db.models.fields import DateField, DateTimeField, \ 
     276                    TimeField, DecimalField 
     277                values = [] 
     278                for value, field in map(None, row, fields): 
     279                    if value is not None: 
     280                        # Convert floats to decimals. TODO: check if needed 
     281                        if isinstance(field, DecimalField): 
     282                            value = util.typecast_decimal(field.format_number(value)) 
     283                        elif isinstance(field, DateTimeField): 
     284                            pass # do nothing 
     285                        elif isinstance(field, DateField): 
     286                            value = value.date() # extract date 
     287                        elif isinstance(field, TimeField): 
     288                            value = value.time() # extract time 
     289                        elif isinstance(value, buffer): 
     290                            # Convert Binary Object to sting. 
     291                            # TODO: This does not work for inserting into Binary columns 
     292                            value = str(value)  
     293                    values.append(value) 
     294                return values 
     295 
     296        return SqlServerQuerySet 
     297 
     298    def date_extract_sql(self, lookup_type, field_name): 
     299        """ 
     300        Given a lookup_type of 'year', 'month' or 'day', returns the SQL that 
     301        extracts a value from the given date field field_name. 
     302        """ 
     303        return "DATEPART(%s, %s)" % (lookup_type, field_name) 
     304 
     305    def date_trunc_sql(self, lookup_type, field_name): 
     306        """ 
     307        Given a lookup_type of 'year', 'month' or 'day', returns the SQL that 
     308        truncates the given date field field_name to a DATE object with only 
     309        the given specificity. 
     310        """ 
     311        if lookup_type=='year': 
     312            return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/01/01')" % field_name 
     313        if lookup_type=='month': 
     314            return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/' + Convert(varchar, DATEPART(month, %s)) + '/01')" % (field_name, field_name) 
     315        if lookup_type=='day': 
     316            return "Convert(datetime, Convert(varchar(12), %s))" % field_name 
     317 
     318    def limit_offset_sql(self, limit, offset=None): 
     319        # Limits and offset are too complicated to be handled here. 
     320        # Look for a implementation similar to SQL Server backend 
     321        return "" 
     322 
     323    def quote_name(self, name): 
     324        """ 
     325        Returns a quoted version of the given table, index or column name. Does 
     326        not quote the given name if it's already been quoted. 
     327        """ 
     328        if name.startswith('[') and name.endswith(']'): 
     329            return name # Quoting once is enough. 
     330        return '[%s]' % name 
     331 
     332    def get_random_function_sql(self): 
     333        """ 
     334        Returns a SQL expression that returns a random value. 
     335        """ 
     336        return "RAND()" 
     337 
     338    def tablespace_sql(self, tablespace, inline=False): 
     339        """ 
     340        Returns the tablespace SQL, or None if the backend doesn't use 
     341        tablespaces. 
     342        """ 
     343        return "ON %s" % quote_name(tablespace) 
     344 
     345    def sql_flush(self, style, tables, sequences): 
     346        """ 
     347        Returns a list of SQL statements required to remove all data from 
     348        the given database tables (without actually removing the tables 
     349        themselves). 
     350 
     351        The `style` argument is a Style object as returned by either 
     352        color_style() or no_style() in django.core.management.color. 
     353        """ 
     354        # Cannot use TRUNCATE on tables that are referenced by a FOREIGN KEY 
     355        # So must use the much slower DELETE 
     356        from django.db import connection 
     357        cursor = connection.cursor() 
     358        cursor.execute("SELECT TABLE_NAME, CONSTRAINT_NAME FROM information_schema.table_constraints") 
     359        fks = cursor.fetchall() 
     360        sql_list = ['ALTER TABLE %s NOCHECK CONSTRAINT %s;' % \ 
     361                (self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks] 
     362        sql_list.extend(['%s %s %s;' % \ 
     363                    (style.SQL_KEYWORD('DELETE'),  
     364                    style.SQL_KEYWORD('FROM'), 
     365                    style.SQL_FIELD(self.quote_name(table)) 
     366                    )  for table in tables]) 
     367        # The reset the counters on each table. 
     368        sql_list.extend(['%s %s (%s, %s, %s) %s %s;' % ( 
     369            style.SQL_KEYWORD('DBCC'), 
     370            style.SQL_KEYWORD('CHECKIDENT'), 
     371            style.SQL_FIELD(self.quote_name(seq["table"])), 
     372            style.SQL_KEYWORD('RESEED'), 
     373            style.SQL_FIELD('1'), 
     374            style.SQL_KEYWORD('WITH'), 
     375            style.SQL_KEYWORD('NO_INFOMSGS'), 
     376            ) for seq in sequences]) 
     377        sql_list.extend(['ALTER TABLE %s CHECK CONSTRAINT %s;' % \ 
     378                (self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks]) 
     379        return sql_list  
     380 
     381    def start_transaction_sql(self): 
     382        """ 
     383        Returns the SQL statement required to start a transaction. 
     384        """ 
     385        return "BEGIN TRANSACTION" 
  • db/backends/mssql/creation.py

    old new  
     1DATA_TYPES = { 
     2    'AutoField':         'int IDENTITY (1, 1)', 
     3    'BooleanField':      'bit', 
     4    'CharField':         'nvarchar(%(max_length)s) %(collation)s', 
     5    'CommaSeparatedIntegerField': 'nvarchar(%(max_length)s) %(collation)s', 
     6    'DateField':         'datetime', 
     7    'DateTimeField':     'datetime', 
     8    'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)', 
     9    'FileField':         'nvarchar(254) %(collation)s', 
     10    'FilePathField':     'nvarchar(254) %(collation)s', 
     11    'FloatField':        'double precision', 
     12    'ImageField':        'nvarchar(254) %(collation)s', 
     13    'IntegerField':      'int', 
     14    'IPAddressField':    'char(15)', 
     15    'ManyToManyField':   None, 
     16    'NullBooleanField':  'bit', 
     17    'OneToOneField':     'int', 
     18    'PhoneNumberField':  'nvarchar(20) %(collation)s', 
     19    #The check must be unique in for the database. Put random so the regresion test not complain about duplicate names 
     20    'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(creation_counter)s_%(column)s] CHECK ([%(column)s] > 0)',     
     21    'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(creation_counter)s_%(column)s] CHECK ([%(column)s] > 0)', 
     22    'SlugField':         'nvarchar(%(max_length)s) %(collation)s', 
     23    'SmallIntegerField': 'smallint', 
     24    'TextField':         'ntext %(collation)s', 
     25    'TimeField':         'datetime', 
     26    'USStateField':      'nchar(2) %(collation)s', 
     27} 
     28 
     29from operations import sql_server_version, SQL_SERVER_2005_VERSION 
     30if sql_server_version() >= SQL_SERVER_2005_VERSION: 
     31    DATA_TYPES['TextField'] = 'nvarchar(max) %(collation)s' 
  • db/models/base.py

    old new  
    11import django.db.models.manipulators 
    22import django.db.models.manager 
     3import django.db 
    34from django.core import validators 
    45from django.core.exceptions import ObjectDoesNotExist 
    56from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist 
     
    246247                    (qn(self._meta.db_table), qn(self._meta.order_with_respect_to.column))) 
    247248                db_values.append(getattr(self, self._meta.order_with_respect_to.attname)) 
    248249            if db_values: 
    249                 cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \ 
    250                     (qn(self._meta.db_table), ','.join(field_names), 
    251                     ','.join(placeholders)), db_values) 
     250                try: 
     251                    if pk_set and settings.DATABASE_ENGINE.endswith("mssql"): 
     252                        # You can't insert an auto value into a column unless you do this in MSSQL 
     253                        if [None for f in self._meta.fields if isinstance(f, AutoField)]: 
     254                            cursor.execute("SET IDENTITY_INSERT %s ON" % qn(self._meta.db_table)) 
     255                    cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \ 
     256                        (qn(self._meta.db_table), ','.join(field_names), 
     257                        ','.join(placeholders)), db_values) 
     258                finally: 
     259                    if pk_set and settings.DATABASE_ENGINE.endswith("mssql"): 
     260                        try: 
     261                            if [None for f in self._meta.fields if isinstance(f, AutoField)]: 
     262                                cursor.execute("SET IDENTITY_INSERT %s OFF" % qn(self._meta.db_table)) 
     263                        except django.db.DatabaseError: 
     264                            pass 
    252265            else: 
    253266                # Create a new record with defaults for everything. 
    254267                cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % 
  • db/models/fields/__init__.py

    old new  
    2929BLANK_CHOICE_NONE = [("", "None")] 
    3030 
    3131# prepares a value for use in a LIKE query 
    32 prep_for_like_query = lambda x: smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_") 
     32# TODO: refactor database specific code 
     33if settings.DATABASE_ENGINE == 'mssql': 
     34    prep_for_like_query = lambda x: smart_unicode(x).replace("%", "[%]").replace("_", "[_]") 
     35else: 
     36    prep_for_like_query = lambda x: smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_") 
    3337 
    3438# returns the <ul> class for a given radio_admin value 
    3539get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '') 
     
    8084        core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True, 
    8185        prepopulate_from=None, unique_for_date=None, unique_for_month=None, 
    8286        unique_for_year=None, validator_list=None, choices=None, radio_admin=None, 
    83         help_text='', db_column=None, db_tablespace=None): 
     87        help_text='', db_column=None, db_tablespace=None, 
     88        collation=None): 
    8489        self.name = name 
    8590        self.verbose_name = verbose_name 
    8691        self.primary_key = primary_key 
     
    102107        self.help_text = help_text 
    103108        self.db_column = db_column 
    104109        self.db_tablespace = db_tablespace 
    105  
     110        # TODO: refactor database specific code 
     111        if settings.DATABASE_ENGINE == 'mssql': 
     112            self.collation = (collation and "COLLATE %s" % collation) or '' 
     113         
    106114        # Set db_index to True if the field has a relationship and doesn't explicitly set db_index. 
    107115        self.db_index = db_index 
    108116 
     
    223231                value = int(value) 
    224232            except ValueError: 
    225233                raise ValueError("The __year lookup type requires an integer argument") 
    226             return ['%s-01-01 00:00:00' % value, '%s-12-31 23:59:59.999999' % value] 
     234            return ['%s-01-01 00:00:00' % value, '%s-12-31 23:59:59.99' % value] 
    227235        raise TypeError("Field has invalid lookup: %s" % lookup_type) 
    228236 
    229237    def has_default(self): 
     
    574582        if value is not None: 
    575583            # MySQL will throw a warning if microseconds are given, because it 
    576584