#225 closed defect (fixed)
MS SQL Server via ADO
| Reported by: | Owned by: | Adrian Holovaty | |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | |
| Severity: | normal | Keywords: | |
| Cc: | Triage Stage: | Design decision needed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
-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',
}
Attachments (4)
Change History (7)
by , 20 years ago
by , 20 years ago
| Attachment: | ado_mssql.py added |
|---|
by , 20 years ago
comment:1 by , 20 years ago
comment:2 by , 20 years ago
in ado_mssql.py DATA_TYPES there is : 'TimeField': 'time' and I guess it must be 'TimeField': 'timestamp'
by , 20 years ago
comment:3 by , 20 years ago
| Resolution: | → fixed |
|---|---|
| Status: | new → closed |
actualy the patch for the core/meta.py in the text above is not complete, use the meta2 attachment instead