Ticket #5246: mssql_pyodbc.2.patch

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

MS SQL Server backend patch, version 2.

  • test/utils.py

     
    7171def _set_autocommit(connection):
    7272    "Make sure a connection is in autocommit mode."
    7373    if hasattr(connection.connection, "autocommit"):
    74         connection.connection.autocommit(True)
     74        if callable(connection.connection.autocommit):
     75            connection.connection.autocommit(True)
     76        else:
     77            connection.connection.autocommit = True
    7578    elif hasattr(connection.connection, "set_isolation_level"):
    7679        connection.connection.set_isolation_level(0)
    7780
  • 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 == "mssql":
     252                        # You can't insert an auto value into an identity column in MSSQL unless you do it explicitly
     253                        for f in self._meta.fields:
     254                            if isinstance(f, AutoField):
     255                                cursor.execute("SET IDENTITY_INSERT %s ON" % qn(self._meta.db_table))
     256                                break
     257                    cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \
     258                        (qn(self._meta.db_table), ','.join(field_names), ','.join(placeholders)), db_values)
     259                finally:
     260                    if pk_set and settings.DATABASE_ENGINE == "mssql":
     261                        # Ensure that the table is restored to the normal state
     262                        try:
     263                            for f in self._meta.fields:
     264                                if isinstance(f, AutoField):
     265                                    cursor.execute("SET IDENTITY_INSERT %s OFF" % qn(self._meta.db_table))
     266                                    break
     267                        except django.db.DatabaseError:
     268                            pass
    252269            else:
    253270                # Create a new record with defaults for everything.
    254271                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    # http://msdn2.microsoft.com/en-us/library/ms179859.aspx
     35    prep_for_like_query = lambda x: smart_unicode(x).replace('[', '[[]').replace("%", "[%]").replace("_", "[_]")
     36else:
     37    prep_for_like_query = lambda x: smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
    3338
    3439# returns the <ul> class for a given radio_admin value
    3540get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
     
    8085        core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True,
    8186        prepopulate_from=None, unique_for_date=None, unique_for_month=None,
    8287        unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
    83         help_text='', db_column=None, db_tablespace=None):
     88        help_text='', db_column=None, db_tablespace=None,
     89        db_collation=None):
    8490        self.name = name
    8591        self.verbose_name = verbose_name
    8692        self.primary_key = primary_key
     
    102108        self.help_text = help_text
    103109        self.db_column = db_column
    104110        self.db_tablespace = db_tablespace
    105 
     111        # TODO: refactor database specific code
     112        if settings.DATABASE_ENGINE == 'mssql':
     113            self.db_collation = (db_collation and "COLLATE %s" % db_collation) or ''
     114       
    106115        # Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
    107116        self.db_index = db_index
    108117
     
    223232                value = int(value)
    224233            except ValueError:
    225234                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]
     235            return ['%s-01-01 00:00:00' % value, '%s-12-31 23:59:59.99' % value]
    227236        raise TypeError("Field has invalid lookup: %s" % lookup_type)
    228237
    229238    def has_default(self):
     
    574583        if value is not None:
    575584            # MySQL will throw a warning if microseconds are given, because it
    576585            # doesn't support microseconds.
    577             if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
     586            if settings.DATABASE_ENGINE in ('mysql', 'ado_mssql','mssql') and hasattr(value, 'microsecond'):
    578587                value = value.replace(microsecond=0)
    579588            value = smart_unicode(value)
    580589        return Field.get_db_prep_save(self, value)
    581590
    582591    def get_db_prep_lookup(self, lookup_type, value):
     592        # MSSQL doesn't like microseconds.
     593        if settings.DATABASE_ENGINE in ('mysql', 'ado_mssql','mssql') and hasattr(value, 'microsecond'):
     594            value = value.replace(microsecond=0)       
    583595        if lookup_type == 'range':
    584596            value = [smart_unicode(v) for v in value]
    585597        else:
     
    940952        Field.__init__(self, verbose_name, name, **kwargs)
    941953
    942954    def get_db_prep_lookup(self, lookup_type, value):
    943         if settings.DATABASE_ENGINE == 'oracle':
     955        if settings.DATABASE_ENGINE in ('oracle', 'mssql'):
    944956            # Oracle requires a date in order to parse.
    945957            def prep(value):
    946958                if isinstance(value, datetime.time):
     
    966978        # Casts dates into string format for entry into database.
    967979        if value is not None:
    968980            # 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'):
     981            # doesn't support microseconds. Ditto MSSQL
     982            if settings.DATABASE_ENGINE in ('mysql', 'ado_mssql','mssql') \
     983                            and hasattr(value, 'microsecond'):
    971984                value = value.replace(microsecond=0)
    972             if settings.DATABASE_ENGINE == 'oracle':
     985            if settings.DATABASE_ENGINE in ('oracle', 'mssql'):
    973986                # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field.
     987                # SQL Server does not have TIME type.
    974988                if isinstance(value, datetime.time):
    975989                    value = datetime.datetime(1900, 1, 1, value.hour, value.minute,
    976990                                              value.second, value.microsecond)
  • db/backends/mssql/base.py

     
     1"""
     2MSSQL database backend for Django.
     3
     4Requires pyodbc 2.0.38 or higher (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
     28version = tuple(map(int, Database.version.split('.')))
     29if version < (2,0,38) :
     30    raise ImportError("pyodbc 2.0.38 or newer is required; you have %s" % Database.version)
     31
     32try:
     33    # Only exists in Python 2.4+
     34    from threading import local
     35except ImportError:
     36    # Import copy of _thread_local.py from Python 2.4
     37    from django.utils._threading_local import local
     38
     39DatabaseError = Database.DatabaseError
     40IntegrityError = Database.IntegrityError
     41
     42class DatabaseFeatures(BaseDatabaseFeatures):
     43    allows_group_by_ordinal = False
     44    allows_unique_and_pk = True
     45    autoindexes_primary_keys = True
     46    needs_datetime_string_cast = True
     47    needs_upper_for_iops = False
     48    supports_constraints = True
     49    supports_tablespaces = True
     50    uses_case_insensitive_names = True
     51    uses_custom_queryset = True
     52
     53class DatabaseWrapper(BaseDatabaseWrapper):
     54    features = DatabaseFeatures()
     55    ops = DatabaseOperations()
     56
     57    # Collations:       http://msdn2.microsoft.com/en-us/library/ms184391.aspx
     58    #                   http://msdn2.microsoft.com/en-us/library/ms179886.aspx
     59    # T-SQL LIKE:       http://msdn2.microsoft.com/en-us/library/ms179859.aspx
     60    # Full-Text search: http://msdn2.microsoft.com/en-us/library/ms142571.aspx
     61    #   CONTAINS:       http://msdn2.microsoft.com/en-us/library/ms187787.aspx
     62    #   FREETEXT:       http://msdn2.microsoft.com/en-us/library/ms176078.aspx
     63   
     64    operators = {
     65        # Since '=' is used not only for string comparision there is no way
     66        # to make it case (in)sensitive. It will simply fallback to the
     67        # database collation.
     68        'exact': '= %s',
     69        'iexact': 'LIKE %s COLLATE Latin1_General_CI_AS',
     70        'contains': 'LIKE %s COLLATE Latin1_General_CS_AS',
     71        'icontains': 'LIKE %s COLLATE Latin1_General_CI_AS',
     72        'gt': '> %s',
     73        'gte': '>= %s',
     74        'lt': '< %s',
     75        'lte': '<= %s',
     76        'startswith': 'LIKE %s COLLATE Latin1_General_CS_AS',
     77        'endswith': 'LIKE %s COLLATE Latin1_General_CS_AS',
     78        'istartswith': 'LIKE %s COLLATE Latin1_General_CI_AS',
     79        'iendswith': 'LIKE %s COLLATE Latin1_General_CI_AS',
     80
     81        # TODO: remove, keep native T-SQL LIKE wildcards support
     82        # or use a "compatibility layer" and replace '*' with '%'
     83        # and '.' with '_'
     84        'regex': 'LIKE %s COLLATE Latin1_General_CS_AS',
     85        'iregex': 'LIKE %s COLLATE Latin1_General_CI_AS',
     86
     87        # TODO: freetext, full-text contains...
     88    }
     89   
     90    def __init__(self, autocommit=False, **kwargs):
     91        super(DatabaseWrapper, self).__init__(autocommit=autocommit, **kwargs)
     92        self.connection = None
     93        self.queries = []
     94
     95    def cursor(self):
     96        from django.conf import settings
     97        if self.connection is None:
     98            if settings.DATABASE_NAME == '':
     99                raise ImproperlyConfigured("You need to specify DATABASE_NAME in your Django settings file.")
     100
     101            if not settings.DATABASE_HOST and not hasattr(settings, "DATABASE_ODBC_DSN"):
     102                raise ImproperlyConfigured("You need to specify DATABASE_HOST or DATABASE_ODBC_DSN  in your Django settings file.")
     103
     104            if settings.DATABASE_PORT:
     105                host_str = '%s:%s' % ( settings.DATABASE_HOST ,settings.DATABASE_PORT)
     106            else:
     107                host_str = settings.DATABASE_HOST
     108
     109            if hasattr(settings, "DATABASE_ODBC_DRIVER"):
     110                odbc_driver = settings.DATABASE_ODBC_DRIVER
     111            else:
     112                odbc_driver = "{Sql Server}"
     113           
     114            odbc_string = "Driver=%s;" % (odbc_driver)
     115
     116            if hasattr(settings, "DATABASE_ODBC_DSN"):
     117                odbc_string += "DSN=%s;" % settings.DATABASE_ODBC_DSN
     118            else:
     119                odbc_string += "Server=%s;" % host_str
     120           
     121            if settings.DATABASE_USER:
     122                odbc_string += "Uid=%s;Pwd=%s;" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD)
     123            else:
     124                odbc_string += "Integrated Security=SSPI;"
     125           
     126            odbc_string += "Database=%s" % settings.DATABASE_NAME
     127           
     128            if hasattr(settings, "DATABASE_ODBC_EXTRA_PARAMS"):
     129                odbc_string +=  ";" + settings.DATABASE_ODBC_EXTRA_PARAMS
     130               
     131            self.connection = Database.connect(odbc_string, self.options["autocommit"])
     132       
     133        self.connection.cursor().execute("SET DATEFORMAT ymd")
     134       
     135        cursor = CursorWrapper(self.connection.cursor())
     136        if settings.DEBUG:
     137            return util.CursorDebugWrapper(cursor, self)
     138        return cursor
     139
     140class CursorWrapper(object):
     141    """
     142    A wrapper around the pyodbc cursor that:
     143        1. Converts input strings to unicde.
     144        2. Replaces '%s' parameter placeholder in sql queries to '?' (pyodbc specific).
     145    """
     146    def __init__(self, cursor):
     147        self.cursor = cursor
     148       
     149    def format_params(self, params, encoding='utf-8', errors='strict'):
     150        new_params = []
     151        for param in params:
     152            if isinstance(param, str):
     153                # Ensure that plain strings are converted to unicode using proper encoding.
     154                # Assumed input encoding is 'utf-8'
     155                # TODO: Verify this with upper layers
     156                param = unicode(param, encoding, errors)
     157            new_params.append(param)
     158        return tuple(new_params)
     159   
     160    def format_sql(self, sql):
     161        # pyodbc uses '?' instead of '%s' as parameter placeholder.
     162        if "%s" in sql:
     163            sql = sql.replace('%s', '?')
     164        return sql
     165                   
     166    def execute(self, sql, params=()):
     167        if params:
     168            params = self.format_params(params)
     169            sql = self.format_sql(sql)
     170        return self.cursor.execute(sql, params)
     171
     172    def executemany(self, sql, param_list):
     173        if param_list:
     174            param_list = [self.format_params(params) for params in param_list]
     175            sql = self.format_sql(sql)
     176        return self.cursor.executemany(sql, param_list)
     177
     178    def fetchone(self):
     179        row = self.cursor.fetchone()
     180        if row is not None:
     181            # Convert row to tuple (pyodbc Rows are not sliceable).
     182            return tuple(row)
     183        return row
     184       
     185    def fetchmany(self, chunk):
     186        return [tuple(row) for row in self.cursor.fetchmany(chunk)]
     187
     188    def fetchall(self):
     189        return [tuple(row) for row in self.cursor.fetchall()]
     190
     191    def __getattr__(self, attr):
     192        if attr in self.__dict__:
     193            return self.__dict__[attr]
     194        else:
     195            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        db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME)
     8        user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER)
     9        passwdord = settings.DATABASE_OPTIONS.get('passwd', settings.DATABASE_PASSWORD)
     10        server = settings.DATABASE_OPTIONS.get('host', settings.DATABASE_HOST)
     11        port = settings.DATABASE_OPTIONS.get('port', settings.DATABASE_PORT)
     12        defaults_file = settings.DATABASE_OPTIONS.get('read_default_file')
     13
     14        args = ['osql']
     15        if server:
     16            args += ["-S", server]
     17        if user:
     18            args += ["-U", user]
     19            if passwdord:
     20                args += ["-P", passwdord]
     21        else:
     22            args += ["-E"] # Try trusted connection instead
     23        if db:
     24            args += ["-d", db]
     25        if defaults_file:
     26            args += ["-i", defaults_file]
     27
     28        import subprocess
     29        try:
     30            retcode = subprocess.call(args, shell=True)
     31            if retcode:
     32                print >>sys.stderr, "error level:", retcode
     33                sys.exit(retcode)
     34        except KeyboardInterrupt:
     35            pass
     36        except OSError, e:
     37            print >> sys.stderr, "Execution failed:", e
     38    else:
     39        raise NotImplementedError
  • db/backends/mssql/__init__.py

     
     1# Placeholder
  • 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    """
     10    Returns a list of table names in the current database.
     11    """
     12    # TABLES: http://msdn2.microsoft.com/en-us/library/ms186224.aspx
     13   
     14    cursor.execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")
     15    return [row[0] for row in cursor.fetchall()]
     16   
     17    # Or pyodbc specific:
     18    #return [row[2] for row in cursor.tables(tableType='TABLE')]
     19
     20def _is_auto_field(cursor, table_name, column_name):
     21    """
     22    Checks whether column is Identity
     23    """
     24    # COLUMNPROPERTY: http://msdn2.microsoft.com/en-us/library/ms174968.aspx
     25   
     26    from django.db import connection
     27    cursor.execute("SELECT COLUMNPROPERTY(OBJECT_ID(%s), %s, 'IsIdentity')",
     28                     (connection.ops.quote_name(table_name), column_name))
     29    return cursor.fetchall()[0][0]
     30
     31def get_table_description(cursor, table_name, identity_check=True):
     32    """Returns a description of the table, with DB-API cursor.description interface.
     33
     34    The 'auto_check' parameter has been added to the function argspec.
     35    If set to True, the function will check each of the table's fields for the
     36    IDENTITY property (the IDENTITY property is the MSSQL equivalent to an AutoField).
     37
     38    When a field is found with an IDENTITY property, it is given a custom field number
     39    of SQL_AUTOFIELD, which maps to the 'AutoField' value in the DATA_TYPES_REVERSE dict.
     40    """
     41
     42    # map pyodbc's cursor.columns to db-api cursor description
     43    columns = [[c[3], c[4], None, c[6], c[6], c[8], c[10]] for c in cursor.columns(table=table_name)]
     44    items = []
     45    for column in columns:
     46        if identity_check and _is_auto_field(cursor, table_name, column[0]):
     47            column[1] = SQL_AUTOFIELD
     48        if column[1] == Database.SQL_WVARCHAR and column[3] < 4000:
     49            column[1] = Database.SQL_WCHAR
     50        items.append(column)
     51    return items
     52
     53def _name_to_index(cursor, table_name):
     54    """
     55    Returns a dictionary of {field_name: field_index} for the given table.
     56    Indexes are 0-based.
     57    """
     58    return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name, identity_check=False))])
     59
     60def get_relations(cursor, table_name):
     61    """
     62    Returns a dictionary of {field_index: (field_index_other_table, other_table)}
     63    representing all relationships to the given table. Indexes are 0-based.   
     64    """
     65    # CONSTRAINT_COLUMN_USAGE: http://msdn2.microsoft.com/en-us/library/ms174431.aspx
     66    # CONSTRAINT_TABLE_USAGE:  http://msdn2.microsoft.com/en-us/library/ms179883.aspx
     67    # REFERENTIAL_CONSTRAINTS: http://msdn2.microsoft.com/en-us/library/ms179987.aspx
     68    # TABLE_CONSTRAINTS:       http://msdn2.microsoft.com/en-us/library/ms181757.aspx
     69
     70    table_index = _name_to_index(cursor, table_name)
     71    sql = """
     72        SELECT e.COLUMN_NAME AS column_name,
     73               c.TABLE_NAME AS referenced_table_name,
     74               d.COLUMN_NAME AS referenced_column_name
     75          FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a
     76          INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS b
     77              ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME
     78          INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE AS c
     79              ON b.UNIQUE_CONSTRAINT_NAME = c.CONSTRAINT_NAME
     80          INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS d
     81              ON c.CONSTRAINT_NAME = d.CONSTRAINT_NAME
     82          INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS e
     83              ON a.CONSTRAINT_NAME = e.CONSTRAINT_NAME
     84          WHERE a.TABLE_NAME = %s AND a.CONSTRAINT_TYPE = 'FOREIGN KEY'
     85        """
     86    cursor.execute(sql, (table_name,))
     87    return dict([(table_index[item[0]], (_name_to_index(cursor, item[1])[item[2]], item[1]))
     88                 for item in cursor.fetchall()])
     89   
     90def get_indexes(cursor, table_name):
     91    """
     92    Returns a dictionary of fieldname -> infodict for the given table,
     93    where each infodict is in the format:
     94        {'primary_key': boolean representing whether it's the primary key,
     95         'unique': boolean representing whether it's a unique index,
     96         'db_index': boolean representing whether it's a non-unique index}
     97    """
     98    # CONSTRAINT_COLUMN_USAGE: http://msdn2.microsoft.com/en-us/library/ms174431.aspx
     99    # TABLE_CONSTRAINTS: http://msdn2.microsoft.com/en-us/library/ms181757.aspx
     100   
     101    pk_uk_sql = """
     102        SELECT b.COLUMN_NAME, a.CONSTRAINT_TYPE
     103          FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS a
     104          INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS b
     105              ON a.CONSTRAINT_NAME = b.CONSTRAINT_NAME AND a.TABLE_NAME = b.TABLE_NAME
     106          WHERE a.TABLE_NAME = %s AND (CONSTRAINT_TYPE = 'PRIMARY KEY' OR CONSTRAINT_TYPE = 'UNIQUE')
     107         
     108    """
     109    # non-unique, non-compound indexes
     110    ix_sql = """
     111        SELECT DISTINCT c.name
     112          FROM sys.columns c
     113          INNER JOIN sys.index_columns ic
     114              ON ic.object_id = c.object_id AND ic.column_id = c.column_id
     115          INNER JOIN sys.indexes ix
     116              ON ix.object_id = ic.object_id AND ix.index_id = ic.index_id
     117          INNER JOIN sys.tables t
     118              ON t.object_id = ix.object_id
     119          WHERE ix.object_id IN (
     120                    SELECT ix.object_id
     121                      FROM sys.indexes ix
     122                      GROUP BY ix.object_id, ix.index_id
     123                      HAVING count(1) = 1)
     124                AND ix.is_primary_key = 0
     125                AND ix.is_unique_constraint = 0
     126                AND t.name = %s
     127    """
     128
     129    field_names = [item[0] for item in get_table_description(cursor, table_name, identity_check=False)]
     130    indexes, results = {}, {}
     131    cursor.execute(pk_uk_sql, (table_name,))
     132    data = cursor.fetchall()
     133    if data:
     134        results.update(data)
     135
     136    # TODO: how should this import look like? from db .. import .. ?
     137    from operations import sql_server_version, SQL_SERVER_2005_VERSION
     138    if sql_server_version() >= SQL_SERVER_2005_VERSION:
     139        cursor.execute(ix_sql, (table_name,))
     140        for column in [r[0] for r in cursor.fetchall()]:
     141            if column not in results:
     142                results[column] = 'IX'
     143
     144    for field in field_names:
     145        val = results.get(field, None)
     146        indexes[field] = dict(primary_key=(val=='PRIMARY KEY'), unique=(val=='UNIQUE'), db_index=(val=='IX'))
     147
     148    return indexes
     149
     150def get_collations_list(cursor):
     151    """
     152    Returns list of available collations and theirs descriptions.
     153    """
     154    # http://msdn2.microsoft.com/en-us/library/ms184391.aspx
     155    # http://msdn2.microsoft.com/en-us/library/ms179886.aspx
     156   
     157    cursor.execute("SELECT name, description FROM ::fn_helpcollations()")
     158    return [tuple(row) for row in cursor.fetchall()]
     159
     160DATA_TYPES_REVERSE = {
     161    SQL_AUTOFIELD:                  'AutoField',
     162    Database.SQL_BIGINT:            'IntegerField',
     163    #Database.SQL_BINARY:            ,
     164    Database.SQL_BIT:               'BooleanField',
     165    Database.SQL_CHAR:              'CharField',
     166    Database.SQL_DECIMAL:           'DecimalField',
     167    Database.SQL_DOUBLE:            'FloatField',
     168    Database.SQL_FLOAT:             'FloatField',
     169    Database.SQL_GUID:              'TextField',
     170    Database.SQL_INTEGER:           'IntegerField',
     171    #Database.SQL_LONGVARBINARY:     ,
     172    #Database.SQL_LONGVARCHAR:       ,
     173    Database.SQL_NUMERIC:           'DecimalField',
     174    Database.SQL_REAL:              'FloatField',
     175    Database.SQL_SMALLINT:          'SmallIntegerField',
     176    Database.SQL_TINYINT:           'SmallIntegerField',
     177    Database.SQL_TYPE_DATE:         'DateField',
     178    Database.SQL_TYPE_TIME:         'TimeField',
     179    Database.SQL_TYPE_TIMESTAMP:    'DateTimeField',
     180    #Database.SQL_VARBINARY:         ,
     181    Database.SQL_VARCHAR:           'TextField',
     182    Database.SQL_WCHAR:             'CharField',
     183    Database.SQL_WLONGVARCHAR:      'TextField',
     184    Database.SQL_WVARCHAR:          'TextField',
     185}
  • db/backends/mssql/operations.py

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