Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixed #28805 -- Added regular expression database function.
- Loading branch information
Showing
7 changed files
with
470 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import unittest | ||
|
||
from django.db import connection | ||
from django.db.models import F, Value | ||
from django.db.models.functions import Concat, Now, RegexpReplace | ||
from django.test import TestCase | ||
|
||
from ..models import Article, Author | ||
|
||
mariadb = connection.vendor == 'mysql' and connection.mysql_is_mariadb | ||
unsupported_mysql = ( | ||
connection.vendor == 'mysql' and | ||
not connection.mysql_is_mariadb and | ||
connection.mysql_version < (8, 0, 4) | ||
) | ||
|
||
|
||
@unittest.skipIf(unsupported_mysql, "MySQL only supports REGEXP_REPLACE() in 8.0.4+.") | ||
class RegexpReplaceTests(TestCase): | ||
@classmethod | ||
def setUpTestData(cls): | ||
cls.author1 = Author.objects.create(name='George R. R. Martin') | ||
cls.author2 = Author.objects.create(name='J. R. R. Tolkien') | ||
|
||
def test_null(self): | ||
tests = [ | ||
('alias', Value(r'(R\. ){2}'), Value('')), | ||
('name', None, Value('')), | ||
('name', Value(r'(R\. ){2}'), None), | ||
] | ||
expected = '' if connection.features.interprets_empty_strings_as_nulls else None | ||
for field, pattern, replacement in tests: | ||
with self.subTest(field=field, pattern=pattern, replacement=replacement): | ||
expression = RegexpReplace(field, pattern, replacement) | ||
author = Author.objects.annotate(replaced=expression).get(pk=self.author1.pk) | ||
self.assertEqual(author.replaced, expected) | ||
|
||
def test_simple(self): | ||
# The default replacement is an empty string. | ||
expression = RegexpReplace('name', Value(r'(R\. ){2}')) | ||
queryset = Author.objects.annotate(without_middlename=expression) | ||
self.assertQuerysetEqual(queryset, [ | ||
('George R. R. Martin', 'George Martin'), | ||
('J. R. R. Tolkien', 'J. Tolkien'), | ||
], transform=lambda x: (x.name, x.without_middlename), ordered=False) | ||
|
||
@unittest.skipIf(mariadb, 'MariaDB is case-insensitive by default.') | ||
def test_case_sensitive(self): | ||
expression = RegexpReplace('name', Value(r'(r\. ){2}'), Value('')) | ||
queryset = Author.objects.annotate(same_name=expression) | ||
self.assertQuerysetEqual(queryset, [ | ||
('George R. R. Martin', 'George R. R. Martin'), | ||
('J. R. R. Tolkien', 'J. R. R. Tolkien'), | ||
], transform=lambda x: (x.name, x.same_name), ordered=False) | ||
|
||
def test_lookahead(self): | ||
expression = RegexpReplace('name', Value(r'(R\. ){2}(?=Martin)'), Value('')) | ||
queryset = Author.objects.annotate(altered_name=expression) | ||
self.assertQuerysetEqual(queryset, [ | ||
('George R. R. Martin', 'George Martin'), | ||
('J. R. R. Tolkien', 'J. R. R. Tolkien'), | ||
], transform=lambda x: (x.name, x.altered_name), ordered=False) | ||
|
||
def test_lookbehind(self): | ||
expression = RegexpReplace('name', Value(r'(?<=George )(R\. ){2}'), Value('')) | ||
queryset = Author.objects.annotate(altered_name=expression) | ||
self.assertQuerysetEqual(queryset, [ | ||
('George R. R. Martin', 'George Martin'), | ||
('J. R. R. Tolkien', 'J. R. R. Tolkien'), | ||
], transform=lambda x: (x.name, x.altered_name), ordered=False) | ||
|
||
def test_substitution(self): | ||
expression = RegexpReplace('name', Value(r'^(.*(?:R\. ?){2}) (.*)$'), Value(r'\2, \1')) | ||
queryset = Author.objects.annotate(flipped_name=expression) | ||
self.assertQuerysetEqual(queryset, [ | ||
('George R. R. Martin', 'Martin, George R. R.'), | ||
('J. R. R. Tolkien', 'Tolkien, J. R. R.'), | ||
], transform=lambda x: (x.name, x.flipped_name), ordered=False) | ||
|
||
def test_expression(self): | ||
expression = RegexpReplace(Concat(Value('Author: '), 'name'), Value(r'.*: '), Value('')) | ||
queryset = Author.objects.annotate(same_name=expression) | ||
self.assertQuerysetEqual(queryset, [ | ||
('George R. R. Martin', 'George R. R. Martin'), | ||
('J. R. R. Tolkien', 'J. R. R. Tolkien'), | ||
], transform=lambda x: (x.name, x.same_name), ordered=False) | ||
|
||
def test_update(self): | ||
Author.objects.update(name=RegexpReplace('name', Value(r'(R\. ){2}'), Value(''))) | ||
self.assertQuerysetEqual(Author.objects.all(), [ | ||
'George Martin', | ||
'J. Tolkien', | ||
], transform=lambda x: x.name, ordered=False) | ||
|
||
@unittest.skipIf(mariadb, 'MariaDB can only replace all occurrences.') | ||
def test_first_occurrence(self): | ||
expression = RegexpReplace('name', Value(r'R\. '), Value('')) | ||
queryset = Author.objects.annotate(single_middlename=expression) | ||
self.assertQuerysetEqual(queryset, [ | ||
('George R. R. Martin', 'George R. Martin'), | ||
('J. R. R. Tolkien', 'J. R. Tolkien'), | ||
], transform=lambda x: (x.name, x.single_middlename), ordered=False) | ||
|
||
|
||
@unittest.skipIf(unsupported_mysql, "MySQL only supports REGEXP_REPLACE() in 8.0.4+.") | ||
class RegexpReplaceFlagTests(TestCase): | ||
@classmethod | ||
def setUpTestData(cls): | ||
Article.objects.create( | ||
title='Chapter One', | ||
text='First Line.\nSecond Line.\nThird Line.', | ||
written=Now(), | ||
) | ||
|
||
@unittest.skipIf(mariadb, "MariaDB doesn't support passing flags to REGEXP_REPLACE().") | ||
def test_global_flag(self): | ||
expression = RegexpReplace('text', Value(r'Line'), Value('Word'), Value('g')) | ||
article = Article.objects.annotate(result=expression).first() | ||
self.assertEqual(article.result, 'First Word.\nSecond Word.\nThird Word.') | ||
|
||
# FIXME: This is the default behaviour on PostgreSQL. | ||
def test_dotall_flag(self): | ||
expression = RegexpReplace('text', Value(r'\..'), Value(', '), Value('gs')) | ||
article = Article.objects.annotate(result=expression).first() | ||
self.assertEqual(article.result, 'First Line, Second Line, Third Line.') | ||
|
||
def test_multiline_flag(self): | ||
expression = RegexpReplace('text', Value(r' Line\.$'), Value(''), Value('gm')) | ||
article = Article.objects.annotate(result=expression).first() | ||
self.assertEqual(article.result, 'First\nSecond\nThird') | ||
|
||
def test_extended_flag(self): | ||
pattern = Value(r""" | ||
. # Match the space character | ||
Line # Match the word "Line" | ||
\. # Match the period. | ||
""") | ||
expression = RegexpReplace('text', pattern, Value(''), Value('gx')) | ||
article = Article.objects.annotate(result=expression).first() | ||
self.assertEqual(article.result, 'First\nSecond\nThird') | ||
|
||
def test_case_sensitive_flag(self): | ||
expression = RegexpReplace('title', Value(r'chapter'), Value('Section'), Value('c')) | ||
article = Article.objects.annotate(result=expression).first() | ||
self.assertEqual(article.result, 'Chapter One') | ||
|
||
def test_case_insensitive_flag(self): | ||
expression = RegexpReplace('title', Value(r'chapter'), Value('Section'), Value('i')) | ||
article = Article.objects.annotate(result=expression).first() | ||
self.assertEqual(article.result, 'Section One') | ||
|
||
def test_case_sensitive_flag_preferred(self): | ||
expression = RegexpReplace('title', Value(r'chapter'), Value('Section'), Value('ic')) | ||
article = Article.objects.annotate(result=expression).first() | ||
self.assertEqual(article.result, 'Chapter One') | ||
|
||
def test_case_insensitive_flag_preferred(self): | ||
expression = RegexpReplace('title', Value(r'Chapter'), Value('Section'), Value('ci')) | ||
article = Article.objects.annotate(result=expression).first() | ||
self.assertEqual(article.result, 'Section One') |
Oops, something went wrong.