diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index be1776e65f..a4c5a25589 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -86,10 +86,9 @@ class BaseDatabaseOperations(object): Returns the SQL necessary to cast a datetime value so that it will be retrieved as a Python datetime object instead of a string. - This SQL should include a '%s' in place of the field's name. This - method should return None if no casting is necessary. + This SQL should include a '%s' in place of the field's name. """ - return None + return "%s" def deferrable_sql(self): """ @@ -169,6 +168,14 @@ class BaseDatabaseOperations(object): sql += " OFFSET %s" % offset return sql + def lookup_cast(self, lookup_type): + """ + Returns the string to use in a query when performing lookups + ("contains", "like", etc). The resulting string should contain a '%s' + placeholder for the column being searched against. + """ + return "%s" + def max_name_length(self): """ Returns the maximum length of table and column names, or None if there @@ -205,6 +212,17 @@ class BaseDatabaseOperations(object): """ return 'RANDOM()' + def regex_lookup(self, lookup_type): + """ + Returns the string to use in a query when performing regular expression + lookups (using "regex" or "iregex"). The resulting string should + contain a '%s' placeholder for the column being searched against. + + If the feature is not supported (or part of it is not supported), a + NotImplementedError exception can be raised. + """ + raise NotImplementedError + def sql_flush(self, style, tables, sequences): """ Returns a list of SQL statements required to remove all data from diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 70f5688d1a..e5e398920e 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -4,11 +4,12 @@ Oracle database backend for Django. Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/ """ +import datetime +import os + from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util from django.utils.datastructures import SortedDict from django.utils.encoding import smart_str, force_unicode -import datetime -import os # Oracle takes client-side character set encoding from the environment. os.environ['NLS_LANG'] = '.UTF8' @@ -89,6 +90,11 @@ class DatabaseOperations(BaseDatabaseOperations): # Instead, they are handled in django/db/backends/oracle/query.py. return "" + def lookup_cast(self, lookup_type): + if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith'): + return "UPPER(%s)" + return "%s" + def max_name_length(self): return 30 @@ -339,6 +345,16 @@ class DatabaseOperations(BaseDatabaseOperations): def random_function_sql(self): return "DBMS_RANDOM.RANDOM" + def regex_lookup_9(self, lookup_type): + raise NotImplementedError("Regexes are not supported in Oracle before version 10g.") + + def regex_lookup_10(self, lookup_type): + if lookup_type == 'regex': + match_option = 'c' + else: + match_option = 'i' + return 'REGEXP_LIKE(%%s %%s %s)' % match_option + def sql_flush(self, style, tables, sequences): # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', # 'TRUNCATE z;'... style SQL statements @@ -430,6 +446,14 @@ class DatabaseWrapper(BaseDatabaseWrapper): "NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'") try: self.oracle_version = int(self.connection.version.split('.')[0]) + # There's no way for the DatabaseOperations class to know the + # currently active Oracle version, so we do some setups here. + # TODO: Multi-db support will need a better solution (a way to + # communicate the current version). + if self.oracle_version <= 9: + self.ops.regex_lookup = self.ops.regex_lookup_9 + else: + self.ops.regex_lookup = self.ops.regex_lookup_10 except ValueError: pass try: diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index 198c6e2e40..620b140be8 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -99,19 +99,11 @@ class WhereNode(tree.Node): field_sql = connection.ops.field_cast_sql(db_type) % lhs if isinstance(value, datetime.datetime): - # FIXME datetime_cast_sql() should return '%s' by default. - cast_sql = connection.ops.datetime_cast_sql() or '%s' + cast_sql = connection.ops.datetime_cast_sql() else: cast_sql = '%s' - # FIXME: This is out of place. Move to a function like - # datetime_cast_sql() - if (lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith') - and connection.features.needs_upper_for_iops): - format = 'UPPER(%s) %s' - else: - format = '%s %s' - + format = "%s %%s" % connection.ops.lookup_cast(lookup_type) params = field.get_db_prep_lookup(lookup_type, value) if lookup_type in connection.operators: @@ -135,18 +127,7 @@ class WhereNode(tree.Node): elif lookup_type in 'search': return (connection.ops.fulltest_search_sql(field_sql), params) elif lookup_type in ('regex', 'iregex'): - # FIXME: Factor this out in to connection.ops - if settings.DATABASE_ENGINE == 'oracle': - if connection.oracle_version and connection.oracle_version <= 9: - raise NotImplementedError("Regexes are not supported in Oracle before version 10g.") - if lookup_type == 'regex': - match_option = 'c' - else: - match_option = 'i' - return ("REGEXP_LIKE(%s, %s, '%s')" % (field_sql, cast_sql, - match_option), params) - else: - raise NotImplementedError + return connection.ops.regex_lookup % (field_sql, cast_sql), params raise TypeError('Invalid lookup_type: %r' % lookup_type)