﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
225	MS SQL Server via ADO	gmilas@…	Adrian Holovaty	"-I need Djago to work with Microsoft SQL Server, so I tried to make the changes and it seams stable, I was able to do the 3 tutorials and some playing around and it is ok for me.

-It uses adodbapi (see below), so users must also install that.

-One problem I had was with using LIMIT/OFFSET which I tried fixing and is ok for me, however is not 100% correct in that it uses a subquery to implement OFFSET and if the user adds GROUP BY or HAVING to his query (can he do that?) that should also be added to the subquery but I could not find a way to do it. (ORDER BY is fine)


== '''Herer is a patch for core.meta''' ==

Index: meta.py

{{{
===================================================================
--- meta.py	(revision 321)
+++ meta.py	(working copy)
@@ -1268,12 +1268,22 @@
     order_by = "", "".join(order_by)
 
     # LIMIT and OFFSET clauses
-    if kwargs.get('limit') is not None:
-        limit_sql = "" LIMIT %s "" % kwargs['limit']
-        if kwargs.get('offset') is not None and kwargs['offset'] != 0:
-            limit_sql += ""OFFSET %s "" % kwargs['offset']
+    #gheorghe:
+    from django.conf.settings import DATABASE_ENGINE
+    limit_sql = """"
+    if DATABASE_ENGINE==""ado_mssql"":
+        if kwargs.get('limit') is not None:
+            select[0] = 'TOP %s %s' % (kwargs['limit'], select[0])
+            if kwargs.get('offset') is not None and kwargs['offset'] != 0:
+                #the problem is if PK is not ID and also if user adds GROUP BY, HAVING etc (can a user do that?), 
+                #those should be added to the subquery too and they can't be 
+                #or at least I don't know how
+                where.append(""id NOT IN (SELECT TOP %s id FROM %s%s)"" % (kwargs['offset'], opts.db_table, (order_by and "" ORDER BY "" + order_by or """")))        
     else:
-        limit_sql = """"
+        if kwargs.get('limit') is not None:
+            limit_sql = "" LIMIT %s "" % kwargs['limit']
+            if kwargs.get('offset') is not None and kwargs['offset'] != 0:
+                limit_sql += ""OFFSET %s "" % kwargs['offset']
 
     return select, "" FROM "" + "","".join(tables) + (where and "" WHERE "" + "" AND "".join(where) or """") + (order_by and "" ORDER BY "" + order_by or """") + limit_sql, params
 
@@ -1302,7 +1312,15 @@
     if field.null:
         kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % (opts.db_table, field.name))
     select, sql, params = function_get_sql_clause(opts, **kwargs)
-    sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (opts.db_table, field.name)), sql)
+    
+    #gheorghe
+    from django.conf.settings import DATABASE_ENGINE
+    if DATABASE_ENGINE==""ado_mssql"":
+        datepart = db.get_date_trunc_sql(kind, '%s.%s' % (opts.db_table, field.name))
+        sql = 'SELECT %s %s GROUP BY %s ORDER BY %s' % (datepart, sql, datepart, datepart)
+    else:
+        sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (opts.db_table, field.name)), sql)
+    
     cursor = db.db.cursor()
     cursor.execute(sql, params)
     # We have to manually run typecast_timestamp(str()) on the results, because

}}}


== '''And here is the content of the new ado_mssql.py backend''': ==

{{{

""""""
ADO MSSQL database backend for Django.
requires adodbapi 2.0.1 from http://adodbapi.sourceforge.net/
""""""

from django.core.db import base
from django.core.db.dicthelpers import *
import adodbapi
try:
    import mx
except:
    mx = None
import datetime
DatabaseError = adodbapi.DatabaseError

CSTR = ""PROVIDER=SQLOLEDB;DATA SOURCE=%s;UID=%s;PWD=%s;DATABASE=%s""

class Connection(adodbapi.Connection):
    def cursor(self):
        return Cursor(self)
#the connect function in adodbapi must call this Connection
adodbapi.Connection = Connection  

class Cursor(adodbapi.Cursor):
    def executeHelper(self, operation, isStoredProcedureCall, parameters=None):
        if parameters != None and ""%s"" in operation:
            operation = operation.replace(""%s"", ""?"")
        adodbapi.Cursor.executeHelper(self, operation, isStoredProcedureCall, parameters)
        
origCVtoP = adodbapi.convertVariantToPython
def variantToPython(variant, adType):
    if type(variant)==type(True) and adType==11: 
        return variant  #bool not 1/0
    res = origCVtoP(variant, adType)
    if not mx is None and type(res) == mx.DateTime.mxDateTime.DateTimeType:
        tv = list(res.tuple()[:7])  #if mxdate then datetime
        tv[-2] = int(tv[-2])    #DepreciationWarning -> int instead of float 
        return datetime.datetime(*tuple(tv))

    if type(res) == type(1.0) and str(res)[-2:]=="".0"":
        return int(res)     #if float but int then int
    return res
adodbapi.convertVariantToPython = variantToPython

class DatabaseWrapper:
    def __init__(self):
        self.connection = None
        self.queries = []

    def cursor(self):
        from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PASSWORD, DEBUG
        if self.connection is None:
            if DATABASE_NAME == '' or DATABASE_USER == '':
                from django.core.exceptions import ImproperlyConfigured
                raise ImproperlyConfigured, ""You need to specify both DATABASE_NAME and DATABASE_USER in your Django settings file.""
            if not DATABASE_HOST:
                DATABASE_HOST = ""127.0.0.1""
            conn_string = CSTR % (DATABASE_HOST, DATABASE_USER, DATABASE_PASSWORD, DATABASE_NAME)
            
            self.connection = adodbapi.connect(conn_string)
        cursor = self.connection.cursor()
        if DEBUG:
            return base.CursorDebugWrapper(cursor, self)
        return cursor

    def commit(self):
        return self.connection.commit()

    def rollback(self):
        if self.connection:
            return self.connection.rollback()

    def close(self):
        if self.connection is not None:
            self.connection.close()
            self.connection = None

def get_last_insert_id(cursor, table_name, pk_name):
    cursor.execute(""SELECT %s FROM %s WHERE %s = @@IDENTITY"" % (pk_name, table_name, pk_name))
    return cursor.fetchone()[0]

# lookup_type is 'year', 'month', 'day'
def get_date_extract_sql(lookup_type, table_name):
    return ""DATEPART(%s, %s)"" % (lookup_type, table_name)

def get_date_trunc_sql(lookup_type, field_name):
    if lookup_type=='year':
        return ""Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/01/01')"" % field_name
    if lookup_type=='month':
        return ""Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/' + Convert(varchar, DATEPART(month, %s)) + '/01')"" % (field_name, field_name)
    if lookup_type=='day':
        return ""Convert(datetime, Convert(varchar(12), %s))"" % field_name
        

OPERATOR_MAPPING = {
    'exact': '=',
    'iexact': 'LIKE',
    'contains': 'LIKE',
    'icontains': 'LIKE',
    'ne': '!=',
    'gt': '>',
    'gte': '>=',
    'lt': '<',
    'lte': '<=',
    'startswith': 'LIKE',
    'endswith': 'LIKE',
    'istartswith': 'LIKE',
    'iendswith': 'LIKE',
}

DATA_TYPES = {
    'AutoField':         'int IDENTITY (1, 1)',
    'BooleanField':      'bit', 
    'CharField':         'varchar(%(maxlength)s)',
    'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
    'DateField':         'smalldatetime',
    'DateTimeField':     'smalldatetime',
    'EmailField':        'varchar(75)',
    'FileField':         'varchar(100)',
    'FloatField':        'numeric(%(max_digits)s, %(decimal_places)s)',
    'ImageField':        'varchar(100)',
    'IntegerField':      'int',
    'IPAddressField':    'char(15)',
    'ManyToManyField':   None,
    'NullBooleanField':  'bit',
    'OneToOneField':     'int',
    'PhoneNumberField':  'varchar(20)',
    'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(name)s] CHECK ([%(name)s] > 0)',
    'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(name)s] CHECK ([%(name)s] > 0)',
    'SlugField':         'varchar(50)',
    'SmallIntegerField': 'smallint',
    'TextField':         'text',
    'TimeField':         'time',
    'URLField':          'varchar(200)',

    'USStateField':      'varchar(2)',
    'XMLField':          'text',
}

}}}
"	defect	closed	Database layer (models, ORM)		normal	fixed			Design decision needed	0	0	0	0	0	0
