diff --git a/django/db/backends/oracle/__init__.py b/django/db/backends/oracle/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py new file mode 100644 index 0000000000..6a2c48e070 --- /dev/null +++ b/django/db/backends/oracle/base.py @@ -0,0 +1,122 @@ +""" +Oracle database backend for Django. + +Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/ +""" + +from django.db.backends import util +import cx_Oracle as Database +import types + +DatabaseError = Database.Error + +try: + # Only exists in Python 2.4+ + from threading import local +except ImportError: + # Import copy of _thread_local.py from Python 2.4 + from django.utils._threading_local import local + +class DatabaseWrapper(local): + def __init__(self): + self.connection = None + self.queries = [] + + def _valid_connection(self): + return self.connection is not None + + def cursor(self): + from django.conf import settings + if not self._valid_connection(): + if len(settings.DATABASE_HOST.strip()) == 0: + settings.DATABASE_HOST = 'localhost' + if len(settings.DATABASE_PORT.strip()) != 0: + dsn = Database.makedsn(settings.DATABASE_HOST, int(settings.DATABASE_PORT), settings.DATABASE_NAME) + self.connection = Database.connect(settings.DATABASE_USER, settings.DATABASE_PASSWORD, dsn) + else: + conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME) + self.connection = Database.connect(conn_string) + return FormatStylePlaceholderCursor(self.connection) + + def _commit(self): + self.connection.commit() + + def _rollback(self): + if self.connection: + try: + self.connection.rollback() + except Database.NotSupportedError: + pass + + def close(self): + if self.connection is not None: + self.connection.close() + self.connection = None + +supports_constraints = True + +class FormatStylePlaceholderCursor(Database.Cursor): + """ + Django uses "format" (e.g. '%s') style placeholders, but Oracle uses ":var" style. + This fixes it -- but note that if you want to use a literal "%s" in a query, + you'll need to use "%%s". + """ + def execute(self, query, params=[]): + query = self.convert_arguments(query, len(params)) + return Database.Cursor.execute(self, query, params) + + def executemany(self, query, params=[]): + query = self.convert_arguments(query, len(params[0])) + return Database.Cursor.executemany(self, query, params) + + def convert_arguments(self, query, num_params): + # replace occurances of "%s" with ":arg" - Oracle requires colons for parameter placeholders. + args = [':arg' for i in range(num_params)] + return query % tuple(args) + +def quote_name(name): + return name + +dictfetchone = util.dictfetchone +dictfetchmany = util.dictfetchmany +dictfetchall = util.dictfetchall + +def get_last_insert_id(cursor, table_name, pk_name): + query = "SELECT %s_sq.currval from dual" % table_name + cursor.execute(query) + return cursor.fetchone()[0] + +def get_date_extract_sql(lookup_type, table_name): + # lookup_type is 'year', 'month', 'day' + # http://www.psoug.org/reference/date_func.html + return "EXTRACT(%s FROM %s)" % (lookup_type, table_name) + +def get_date_trunc_sql(lookup_type, field_name): + return "EXTRACT(%s FROM TRUNC(%s))" % (lookup_type, field_name) + +def get_limit_offset_sql(limit, offset=None): + # Limits and offset are too complicated to be handled here. + # Instead, they are handled in django/db/query.py. + pass + +def get_random_function_sql(): + return "DBMS_RANDOM.RANDOM" + +def get_drop_foreignkey_sql(): + return "DROP FOREIGN KEY" + +OPERATOR_MAPPING = { + 'exact': '= %s', + 'iexact': 'LIKE %s', + 'contains': 'LIKE %s', + 'icontains': 'LIKE %s', + 'ne': '!= %s', + 'gt': '> %s', + 'gte': '>= %s', + 'lt': '< %s', + 'lte': '<= %s', + 'startswith': 'LIKE %s', + 'endswith': 'LIKE %s', + 'istartswith': 'LIKE %s', + 'iendswith': 'LIKE %s', +} diff --git a/django/db/backends/oracle/client.py b/django/db/backends/oracle/client.py new file mode 100644 index 0000000000..7e32ebef2f --- /dev/null +++ b/django/db/backends/oracle/client.py @@ -0,0 +1,10 @@ +from django.conf import settings +import os + +def runshell(): + args = '' + args += settings.DATABASE_USER + if settings.DATABASE_PASSWORD: + args += "/%s" % settings.DATABASE_PASSWORD + args += "@%s" % settings.DATABASE_NAME + os.execvp('sqlplus', args) diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py new file mode 100644 index 0000000000..d45ceb64f5 --- /dev/null +++ b/django/db/backends/oracle/creation.py @@ -0,0 +1,26 @@ +DATA_TYPES = { + 'AutoField': 'number(38)', + 'BooleanField': 'number(1)', + 'CharField': 'varchar2(%(maxlength)s)', + 'CommaSeparatedIntegerField': 'varchar2(%(maxlength)s)', + 'DateField': 'date', + 'DateTimeField': 'date', + 'FileField': 'varchar2(100)', + 'FilePathField': 'varchar2(100)', + 'FloatField': 'number(%(max_digits)s, %(decimal_places)s)', + 'ImageField': 'varchar2(100)', + 'IntegerField': 'integer', + 'IPAddressField': 'char(15)', + 'ManyToManyField': None, + 'NullBooleanField': 'integer', + 'OneToOneField': 'integer', + 'PhoneNumberField': 'varchar(20)', + 'PositiveIntegerField': 'integer', + 'PositiveSmallIntegerField': 'smallint', + 'SlugField': 'varchar(50)', + 'SmallIntegerField': 'smallint', + 'TextField': 'long', + 'TimeField': 'timestamp', + 'URLField': 'varchar(200)', + 'USStateField': 'varchar(2)', +} diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py new file mode 100644 index 0000000000..656741e440 --- /dev/null +++ b/django/db/backends/oracle/introspection.py @@ -0,0 +1,52 @@ +from django.db import transaction +from django.db.backends.oracle.base import quote_name +import re + +foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)") + +def get_table_list(cursor): + "Returns a list of table names in the current database." + cursor.execute("SELECT TABLE_NAME FROM USER_TABLES") + return [row[0] for row in cursor.fetchall()] + +def get_table_description(cursor, table_name): + return table_name + +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): + """ + Returns a dictionary of {field_index: (field_index_other_table, other_table)} + representing all relationships to the given table. Indexes are 0-based. + """ + raise NotImplementedError + +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} + """ + raise NotImplementedError + +# Maps type codes to Django Field types. +DATA_TYPES_REVERSE = { + 16: 'BooleanField', + 21: 'SmallIntegerField', + 23: 'IntegerField', + 25: 'TextField', + 869: 'IPAddressField', + 1043: 'CharField', + 1082: 'DateField', + 1083: 'TimeField', + 1114: 'DateTimeField', + 1184: 'DateTimeField', + 1266: 'TimeField', + 1700: 'FloatField', +}