Ticket #5246: mssql_pyodbc.patch

File mssql_pyodbc.patch, 41.8 KB (added by Filip Wasilewski <filip.wasilewski@…>, 8 years ago)

MS SQL Server backend patch

  • contrib/sessions/middleware.py

     
    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

     
     1# Placeholder
  • db/backends/mssql/base.py

     
     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

     
     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

     
     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

     
     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

     
     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

     
    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

     
    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            # doesn't support microseconds.
    577             if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
     585            if settings.DATABASE_ENGINE in ('mysql', 'ado_mssql','mssql') and hasattr(value, 'microsecond'):
    578586                value = value.replace(microsecond=0)
    579587            value = smart_unicode(value)
    580588        return Field.get_db_prep_save(self, value)
    581589
    582590    def get_db_prep_lookup(self, lookup_type, value):
     591        # MSSQL doesn't like microseconds.
     592        if settings.DATABASE_ENGINE in ('mysql', 'ado_mssql','mssql') and hasattr(value, 'microsecond'):
     593            value = value.replace(microsecond=0)       
    583594        if lookup_type == 'range':
    584595            value = [smart_unicode(v) for v in value]
    585596        else:
     
    940951        Field.__init__(self, verbose_name, name, **kwargs)
    941952
    942953    def get_db_prep_lookup(self, lookup_type, value):
    943         if settings.DATABASE_ENGINE == 'oracle':
     954        if settings.DATABASE_ENGINE in ('oracle', 'mssql'):
    944955            # Oracle requires a date in order to parse.
    945956            def prep(value):
    946957                if isinstance(value, datetime.time):
     
    966977        # Casts dates into string format for entry into database.
    967978        if value is not None:
    968979            # MySQL will throw a warning if microseconds are given, because it
    969             # doesn't support microseconds.
    970             if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
     980            # doesn't support microseconds. Ditto MSSQL
     981            if settings.DATABASE_ENGINE in ('mysql', 'ado_mssql','mssql') \
     982                            and hasattr(value, 'microsecond'):
    971983                value = value.replace(microsecond=0)
    972             if settings.DATABASE_ENGINE == 'oracle':
     984            if settings.DATABASE_ENGINE in ('oracle', 'mssql'):
    973985                # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field.
     986                # SQL Server does not have TIME type.
    974987                if isinstance(value, datetime.time):
    975988                    value = datetime.datetime(1900, 1, 1, value.hour, value.minute,
    976989                                              value.second, value.microsecond)
  • test/utils.py

     
    7070
    7171def _set_autocommit(connection):
    7272    "Make sure a connection is in autocommit mode."
    73     if hasattr(connection.connection, "autocommit"):
     73    if hasattr(connection.connection, "autocommit") and callable(connection.connection.autocommit):
    7474        connection.connection.autocommit(True)
    7575    elif hasattr(connection.connection, "set_isolation_level"):
    7676        connection.connection.set_isolation_level(0)
Back to Top