diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index dd24878..c89dcd9 100644
a
|
b
|
class BaseDatabaseFeatures(object):
|
340 | 340 | # Do time/datetime fields have microsecond precision? |
341 | 341 | supports_microsecond_precision = True |
342 | 342 | |
| 343 | # Is there support for __regex lookups? |
| 344 | supports_regex = True |
| 345 | |
343 | 346 | # Does the __regex lookup support backreferencing and grouping? |
344 | 347 | supports_regex_backreferencing = True |
345 | 348 | |
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index c197422..2223e6c 100644
a
|
b
|
import datetime
|
9 | 9 | import decimal |
10 | 10 | import sys |
11 | 11 | |
| 12 | from django.utils.functional import cached_property |
| 13 | |
12 | 14 | |
13 | 15 | def _setup_environment(environ): |
14 | 16 | import platform |
… |
… |
class DatabaseFeatures(BaseDatabaseFeatures):
|
83 | 85 | has_bulk_insert = True |
84 | 86 | supports_tablespaces = True |
85 | 87 | |
| 88 | def _supports_regex(self): |
| 89 | if self.connection.oracle_version > 9: |
| 90 | return True |
| 91 | else: |
| 92 | return False |
| 93 | supports_regex = property(_supports_regex) |
| 94 | supports_regex_backreferencing = supports_regex |
| 95 | |
86 | 96 | class DatabaseOperations(BaseDatabaseOperations): |
87 | 97 | compiler_module = "django.db.backends.oracle.compiler" |
88 | 98 | |
… |
… |
WHEN (new.%(col_name)s IS NULL)
|
249 | 259 | def random_function_sql(self): |
250 | 260 | return "DBMS_RANDOM.RANDOM" |
251 | 261 | |
252 | | def regex_lookup_9(self, lookup_type): |
253 | | raise NotImplementedError("Regexes are not supported in Oracle before version 10g.") |
254 | | |
255 | | def regex_lookup_10(self, lookup_type): |
256 | | if lookup_type == 'regex': |
257 | | match_option = "'c'" |
258 | | else: |
259 | | match_option = "'i'" |
260 | | return 'REGEXP_LIKE(%%s, %%s, %s)' % match_option |
261 | | |
262 | 262 | def regex_lookup(self, lookup_type): |
263 | | # If regex_lookup is called before it's been initialized, then create |
264 | | # a cursor to initialize it and recur. |
265 | | self.connection.cursor() |
266 | | return self.connection.ops.regex_lookup(lookup_type) |
| 263 | if self.connection.oracle_version > 9: |
| 264 | if lookup_type == 'regex': |
| 265 | match_option = "'c'" |
| 266 | else: |
| 267 | match_option = "'i'" |
| 268 | return 'REGEXP_LIKE(%%s, %%s, %s)' % match_option |
| 269 | else: |
| 270 | raise NotImplementedError("Regexes are not supported in Oracle before version 10g.") |
267 | 271 | |
268 | 272 | def return_insert_id(self): |
269 | 273 | return "RETURNING %s INTO %%s", (InsertIdVar(),) |
… |
… |
class DatabaseWrapper(BaseDatabaseWrapper):
|
434 | 438 | def __init__(self, *args, **kwargs): |
435 | 439 | super(DatabaseWrapper, self).__init__(*args, **kwargs) |
436 | 440 | |
437 | | self.oracle_version = None |
438 | 441 | self.features = DatabaseFeatures(self) |
439 | 442 | use_returning_into = self.settings_dict["OPTIONS"].get('use_returning_into', True) |
440 | 443 | self.features.can_return_id_from_insert = use_returning_into |
… |
… |
class DatabaseWrapper(BaseDatabaseWrapper):
|
444 | 447 | self.introspection = DatabaseIntrospection(self) |
445 | 448 | self.validation = BaseDatabaseValidation(self) |
446 | 449 | |
| 450 | @cached_property |
| 451 | def oracle_version(self): |
| 452 | if self.connection is None: |
| 453 | # To check the server version, we need a connection - open and |
| 454 | # immediately close a cursor to ensure an open connection. |
| 455 | self.cursor().close() |
| 456 | return int(self.connection.version.split('.')[0]) |
| 457 | |
447 | 458 | def check_constraints(self, table_names=None): |
448 | 459 | """ |
449 | 460 | To check constraints, we set constraints to immediate. Then, when, we're done we must ensure they |
… |
… |
class DatabaseWrapper(BaseDatabaseWrapper):
|
517 | 528 | self.operators = self._likec_operators |
518 | 529 | else: |
519 | 530 | self.operators = self._standard_operators |
520 | | |
521 | | try: |
522 | | self.oracle_version = int(self.connection.version.split('.')[0]) |
523 | | # There's no way for the DatabaseOperations class to know the |
524 | | # currently active Oracle version, so we do some setups here. |
525 | | # TODO: Multi-db support will need a better solution (a way to |
526 | | # communicate the current version). |
527 | | if self.oracle_version <= 9: |
528 | | self.ops.regex_lookup = self.ops.regex_lookup_9 |
529 | | else: |
530 | | self.ops.regex_lookup = self.ops.regex_lookup_10 |
531 | | except ValueError: |
532 | | pass |
533 | 531 | try: |
534 | 532 | self.connection.stmtcachesize = 20 |
535 | 533 | except: |
… |
… |
class FormatStylePlaceholderCursor(object):
|
650 | 648 | self.cursor.arraysize = 100 |
651 | 649 | |
652 | 650 | def _format_params(self, params): |
653 | | return tuple([OracleParam(p, self, True) for p in params]) |
| 651 | return tuple(OracleParam(p, self, True) for p in params) |
654 | 652 | |
655 | 653 | def _guess_input_sizes(self, params_list): |
656 | 654 | sizes = [None] * len(params_list[0]) |
… |
… |
class FormatStylePlaceholderCursor(object):
|
722 | 720 | def fetchmany(self, size=None): |
723 | 721 | if size is None: |
724 | 722 | size = self.arraysize |
725 | | return tuple([_rowfactory(r, self.cursor) |
726 | | for r in self.cursor.fetchmany(size)]) |
| 723 | return tuple(_rowfactory(r, self.cursor) |
| 724 | for r in self.cursor.fetchmany(size)) |
727 | 725 | |
728 | 726 | def fetchall(self): |
729 | | return tuple([_rowfactory(r, self.cursor) |
730 | | for r in self.cursor.fetchall()]) |
| 727 | return tuple(_rowfactory(r, self.cursor) |
| 728 | for r in self.cursor.fetchall()) |
731 | 729 | |
732 | 730 | def var(self, *args): |
733 | 731 | return VariableWrapper(self.cursor.var(*args)) |
diff --git a/tests/modeltests/lookup/tests.py b/tests/modeltests/lookup/tests.py
index 9c2b0c6..068b5f7 100644
a
|
b
|
class LookupTests(TestCase):
|
479 | 479 | self.assertEqual(str(ex), "Join on field 'headline' not permitted. " |
480 | 480 | "Did you misspell 'starts' for the lookup type?") |
481 | 481 | |
| 482 | @skipUnlessDBFeature('supports_regex') |
482 | 483 | def test_regex(self): |
483 | 484 | # Create some articles with a bit more interesting headlines for testing field lookups: |
484 | 485 | for a in Article.objects.all(): |