adamv avatar adamv committed 214232c

Initial source check-in.

Comments (0)

Files changed (6)

+This project contains external database backends for Django that support Microsoft SQL Server.
+
+The only backend present right now is "sqlserver_ado", which is based on the "ado_mssql" backend that ships with Django, and is meant as a replacement.
+
+Copy the entire "sqlserver_ado" folder from src/ to somewhere on your Python path, visible to both Django and the command-line tools.
+This could be in your lib/site_packages, django/db/backends (if you don't mind mixing in external code), or anywhere else on the Python path.
Add a comment to this file

src/sqlserver_ado/__init__.py

Empty file added.

src/sqlserver_ado/base.py

+"""
+ADO MSSQL database backend for Django.
+
+Requires adodbapi 2.0.1: http://adodbapi.sourceforge.net/
+"""
+
+from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
+try:
+    import adodbapi as Database
+except ImportError, e:
+    from django.core.exceptions import ImproperlyConfigured
+    raise ImproperlyConfigured("Error loading adodbapi module: %s" % e)
+import datetime
+try:
+    import mx
+except ImportError:
+    mx = None
+
+DatabaseError = Database.DatabaseError
+IntegrityError = Database.IntegrityError
+
+# We need to use a special Cursor class because adodbapi expects question-mark
+# param style, but Django expects "%s". This cursor converts question marks to
+# format-string style.
+class ConvertParamsToQuestionMarkCursor(Database.Cursor):
+    def _executeHelper(self, operation, isStoredProcedureCall, parameters=None):
+        if parameters is not None and "%s" in operation:
+            operation = operation.replace("%s", "?")
+        Database.Cursor._executeHelper(self, operation, isStoredProcedureCall, parameters)
+
+
+origCVtoP = Database.convertVariantToPython
+def variantToPython(variant, adType):
+    if type(variant) == bool and adType == 11:
+        return variant  # bool not 1/0
+    res = origCVtoP(variant, adType)
+    if mx is not None and type(res) == mx.DateTime.mxDateTime.DateTimeType:
+        # Convert ms.DateTime objects to Python datetime.datetime objects.
+        tv = list(res.tuple()[:7])
+        tv[-2] = int(tv[-2])
+        return datetime.datetime(*tuple(tv))
+    if type(res) == float and str(res)[-2:] == ".0":
+        return int(res) # If float but int, then int.
+    return res
+Database.convertVariantToPython = variantToPython
+
+class DatabaseFeatures(BaseDatabaseFeatures):
+    supports_tablespaces = True
+
+class DatabaseOperations(BaseDatabaseOperations):
+    def date_extract_sql(self, lookup_type, field_name):
+        return "DATEPART(%s, %s)" % (lookup_type, field_name)
+
+    def date_trunc_sql(self, 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
+
+    def last_insert_id(self, cursor, table_name, pk_name):
+        cursor.execute("SELECT %s FROM %s WHERE %s = @@IDENTITY" % (pk_name, table_name, pk_name))
+        return cursor.fetchone()[0]
+
+    def quote_name(self, name):
+        if name.startswith('[') and name.endswith(']'):
+            return name # Quoting once is enough.
+        return '[%s]' % name
+
+    def random_function_sql(self):
+        return 'RAND()'
+
+    def tablespace_sql(self, tablespace, inline=False):
+        return "ON %s" % self.quote_name(tablespace)
+
+class DatabaseWrapper(BaseDatabaseWrapper):
+    features = DatabaseFeatures()
+    ops = DatabaseOperations()
+    operators = {
+        'exact': '= %s',
+        'iexact': 'LIKE %s',
+        'contains': 'LIKE %s',
+        'icontains': 'LIKE %s',
+        'gt': '> %s',
+        'gte': '>= %s',
+        'lt': '< %s',
+        'lte': '<= %s',
+        'startswith': 'LIKE %s',
+        'endswith': 'LIKE %s',
+        'istartswith': 'LIKE %s',
+        'iendswith': 'LIKE %s',
+    }
+
+    def _cursor(self, settings):
+        if self.connection is None:
+            if settings.DATABASE_NAME == '' or settings.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 settings.DATABASE_HOST:
+                settings.DATABASE_HOST = "127.0.0.1"
+            # TODO: Handle DATABASE_PORT.
+            conn_string = "PROVIDER=SQLOLEDB;DATA SOURCE=%s;UID=%s;PWD=%s;DATABASE=%s" % (settings.DATABASE_HOST, settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
+            self.connection = Database.connect(conn_string)
+        return ConvertParamsToQuestionMarkCursor(self.connection)

src/sqlserver_ado/client.py

+def runshell():
+    raise NotImplementedError

src/sqlserver_ado/creation.py

+DATA_TYPES = {
+    'AutoField':         'int IDENTITY (1, 1)',
+    'BooleanField':      'bit',
+    'CharField':         'varchar(%(max_length)s)',
+    'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
+    'DateField':         'smalldatetime',
+    'DateTimeField':     'smalldatetime',
+    'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)',
+    'FileField':         'varchar(%(max_length)s)',
+    'FilePathField':     'varchar(%(max_length)s)',
+    'FloatField':        'double precision',
+    'ImageField':        'varchar(%(max_length)s)',
+    'IntegerField':      'int',
+    'IPAddressField':    'char(15)',
+    'NullBooleanField':  'bit',
+    'OneToOneField':     'int',
+    'PhoneNumberField':  'varchar(20)',
+    'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(column)s] CHECK ([%(column)s] > 0)',
+    'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(column)s] CHECK ([%(column)s] > 0)',
+    'SlugField':         'varchar(%(max_length)s)',
+    'SmallIntegerField': 'smallint',
+    'TextField':         'text',
+    'TimeField':         'time',
+    'USStateField':      'varchar(2)',
+}

src/sqlserver_ado/introspection.py

+def get_table_list(cursor):
+    "Returns a list of table names in the current database."
+    cursor.execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")
+    return [row[0] for row in cursor.fetchall()]
+
+def get_table_description(cursor, table_name):
+    "Returns a description of the table, with the DB-API cursor.description interface."
+    cursor.execute("SELECT * FROM %s where 1=0" % (table_name))
+    return cursor.description
+
+def _name_to_index(cursor, table_name):
+    """
+    Returns a dictionary of {field_name: field_index} for the given table.
+    Indexes are 0-based.
+    """
+    return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name))])
+
+def get_relations(cursor, table_name):
+    source_field_dict = _name_to_index(cursor, table_name)
+    
+    sql = '''SELECT
+COLUMN_NAME = FK_COLS.COLUMN_NAME,
+REFERENCED_TABLE_NAME = PK.TABLE_NAME,
+REFERENCED_COLUMN_NAME = PK_COLS.COLUMN_NAME
+FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS REF_CONST
+JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK
+	ON REF_CONST.CONSTRAINT_CATALOG = FK.CONSTRAINT_CATALOG
+	AND REF_CONST.CONSTRAINT_SCHEMA = FK.CONSTRAINT_SCHEMA
+	AND REF_CONST.CONSTRAINT_NAME = FK.CONSTRAINT_NAME
+	AND FK.CONSTRAINT_TYPE = 'FOREIGN KEY'
+
+JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK 
+	ON REF_CONST.UNIQUE_CONSTRAINT_CATALOG = PK.CONSTRAINT_CATALOG
+	AND REF_CONST.UNIQUE_CONSTRAINT_SCHEMA = PK.CONSTRAINT_SCHEMA
+	AND REF_CONST.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME
+	AND PK.CONSTRAINT_TYPE = 'PRIMARY KEY'
+
+JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE FK_COLS 
+	ON REF_CONST.CONSTRAINT_NAME = FK_COLS.CONSTRAINT_NAME
+
+JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE PK_COLS 
+	ON PK.CONSTRAINT_NAME = PK_COLS.CONSTRAINT_NAME
+where
+	FK.TABLE_NAME = %s'''
+    cursor.execute(sql,[table_name])
+    relations = cursor.fetchall()
+    relation_map = dict()
+    
+    for source_column, target_table, target_column in relations:
+        target_field_dict = _name_to_index(cursor, target_table)
+        target_index = target_field_dict[target_column]
+        source_index = source_field_dict[source_column]
+        
+        relation_map[source_index] = (target_index, target_table)
+        
+    return relation_map
+
+def get_indexes(cursor, table_name):
+#    Returns a dictionary of fieldname -> infodict for the given table,
+#    where each infodict is in the format:
+#        {'primary_key': boolean representing whether it's the primary key,
+#         'unique': boolean representing whether it's a unique index}
+    sql = '''
+select
+	C.name as [column_name],
+	IX.is_unique as [unique], 
+    IX.is_primary_key as [primary_key]
+from
+	sys.tables T
+	join sys.index_columns IC on IC.object_id = T.object_id
+	join sys.columns C on C.object_id = T.object_id and C.column_id = IC.column_id
+	join sys.indexes Ix on Ix.object_id = T.object_id and Ix.index_id = IC.index_id
+where
+	T.name = %s
+	and (Ix.is_unique=1 or Ix.is_primary_key=1)
+    -- Omit multi-column keys
+	and not exists (
+		select * 
+		from sys.index_columns cols
+		where
+			cols.object_id = T.object_id
+			and cols.index_id = IC.index_id
+			and cols.key_ordinal > 1
+	)
+'''
+    
+    cursor.execute(sql,[table_name])
+    constraints = cursor.fetchall()
+    indexes = dict()
+    
+    for column_name, unique, primary_key in constraints:
+        column_name = column_name.lower()
+        indexes[column_name] = {"primary_key":primary_key, "unique":unique}
+    
+    return indexes
+    
+
+    # Copied out of adodbapi for reference:
+adArray                       =0x2000     # from enum DataTypeEnum
+adBSTR                        =0x8        # from enum DataTypeEnum
+adBigInt                      =0x14       # from enum DataTypeEnum
+adBinary                      =0x80       # from enum DataTypeEnum
+adBoolean                     =0xb        # from enum DataTypeEnum
+adChapter                     =0x88       # from enum DataTypeEnum
+adChar                        =0x81       # from enum DataTypeEnum
+adCurrency                    =0x6        # from enum DataTypeEnum
+adDBDate                      =0x85       # from enum DataTypeEnum
+adDBTime                      =0x86       # from enum DataTypeEnum
+adDBTimeStamp                 =0x87       # from enum DataTypeEnum
+adDate                        =0x7        # from enum DataTypeEnum
+adDecimal                     =0xe        # from enum DataTypeEnum
+adDouble                      =0x5        # from enum DataTypeEnum
+adEmpty                       =0x0        # from enum DataTypeEnum
+adError                       =0xa        # from enum DataTypeEnum
+adFileTime                    =0x40       # from enum DataTypeEnum
+adGUID                        =0x48       # from enum DataTypeEnum
+adIDispatch                   =0x9        # from enum DataTypeEnum
+adIUnknown                    =0xd        # from enum DataTypeEnum
+adInteger                     =0x3        # from enum DataTypeEnum
+adLongVarBinary               =0xcd       # from enum DataTypeEnum
+adLongVarChar                 =0xc9       # from enum DataTypeEnum
+adLongVarWChar                =0xcb       # from enum DataTypeEnum
+adNumeric                     =0x83       # from enum DataTypeEnum
+adPropVariant                 =0x8a       # from enum DataTypeEnum
+adSingle                      =0x4        # from enum DataTypeEnum
+adSmallInt                    =0x2        # from enum DataTypeEnum
+adTinyInt                     =0x10       # from enum DataTypeEnum
+adUnsignedBigInt              =0x15       # from enum DataTypeEnum
+adUnsignedInt                 =0x13       # from enum DataTypeEnum
+adUnsignedSmallInt            =0x12       # from enum DataTypeEnum
+adUnsignedTinyInt             =0x11       # from enum DataTypeEnum
+adUserDefined                 =0x84       # from enum DataTypeEnum
+adVarBinary                   =0xcc       # from enum DataTypeEnum
+adVarChar                     =0xc8       # from enum DataTypeEnum
+adVarNumeric                  =0x8b       # from enum DataTypeEnum
+adVarWChar                    =0xca       # from enum DataTypeEnum
+adVariant                     =0xc        # from enum DataTypeEnum
+adWChar                       =0x82       # from enum DataTypeEnum
+
+
+DATA_TYPES_REVERSE = {
+    adBoolean: 'BooleanField',
+    adChar: 'CharField',
+    adWChar: 'CharField',
+    adDecimal: 'DecimalField',
+    adNumeric: 'DecimalField',
+    adDBTimeStamp: 'DateTimeField',
+    adDouble: 'FloatField',
+    adSingle: 'FloatField',
+    adInteger: 'IntegerField',
+    adBigInt: 'IntegerField',
+    adSmallInt: 'IntegerField',
+    adTinyInt: 'IntegerField',
+    adVarChar: 'CharField',
+    adVarWChar: 'CharField',
+    adLongVarWChar: 'TextField',
+    adLongVarChar: 'TextField',
+}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.