Skip to content
Projeler
Gruplar
Parçacıklar
Yardım
Yükleniyor...
Oturum aç / Kaydol
Gezinmeyi değiştir
D
django
Proje
Proje
Ayrıntılar
Etkinlik
Cycle Analytics
Depo (repository)
Depo (repository)
Dosyalar
Kayıtlar (commit)
Dallar (branch)
Etiketler
Katkıda bulunanlar
Grafik
Karşılaştır
Grafikler
Konular (issue)
0
Konular (issue)
0
Liste
Pano
Etiketler
Kilometre Taşları
Birleştirme (merge) Talepleri
0
Birleştirme (merge) Talepleri
0
CI / CD
CI / CD
İş akışları (pipeline)
İşler
Zamanlamalar
Grafikler
Paketler
Paketler
Wiki
Wiki
Parçacıklar
Parçacıklar
Üyeler
Üyeler
Collapse sidebar
Close sidebar
Etkinlik
Grafik
Grafikler
Yeni bir konu (issue) oluştur
İşler
Kayıtlar (commit)
Konu (issue) Panoları
Kenar çubuğunu aç
Batuhan Osman TASKAYA
django
Commits
38cada7c
Kaydet (Commit)
38cada7c
authored
Eki 11, 2017
tarafından
Ian Foote
Kaydeden (comit)
Tim Graham
Haz 29, 2018
Dosyalara gözat
Seçenekler
Dosyalara Gözat
İndir
Eposta Yamaları
Sade Fark
Fixed #28077 -- Added support for PostgreSQL opclasses in Index.
Thanks Vinay Karanam for the initial patch.
üst
b4cba4ed
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
143 additions
and
16 deletions
+143
-16
schema.py
django/db/backends/base/schema.py
+5
-2
ddl_references.py
django/db/backends/ddl_references.py
+18
-0
schema.py
django/db/backends/postgresql/schema.py
+8
-4
indexes.py
django/db/models/indexes.py
+11
-2
indexes.txt
docs/ref/models/indexes.txt
+21
-1
2.2.txt
docs/releases/2.2.txt
+1
-1
models.py
tests/indexes/models.py
+5
-0
tests.py
tests/indexes/tests.py
+61
-6
tests.py
tests/model_indexes/tests.py
+13
-0
No files found.
django/db/backends/base/schema.py
Dosyayı görüntüle @
38cada7c
...
...
@@ -907,7 +907,7 @@ class BaseDatabaseSchemaEditor:
return
''
def
_create_index_sql
(
self
,
model
,
fields
,
*
,
name
=
None
,
suffix
=
''
,
using
=
''
,
db_tablespace
=
None
,
col_suffixes
=
(),
sql
=
None
):
db_tablespace
=
None
,
col_suffixes
=
(),
sql
=
None
,
opclasses
=
()
):
"""
Return the SQL statement to create the index for one or several fields.
`sql` can be specified if the syntax differs from the standard (GIS
...
...
@@ -929,10 +929,13 @@ class BaseDatabaseSchemaEditor:
table
=
Table
(
table
,
self
.
quote_name
),
name
=
IndexName
(
table
,
columns
,
suffix
,
create_index_name
),
using
=
using
,
columns
=
Columns
(
table
,
columns
,
self
.
quote_name
,
col_suffixes
=
col_suffix
es
),
columns
=
self
.
_index_columns
(
table
,
columns
,
col_suffixes
,
opclass
es
),
extra
=
tablespace_sql
,
)
def
_index_columns
(
self
,
table
,
columns
,
col_suffixes
,
opclasses
):
return
Columns
(
table
,
columns
,
self
.
quote_name
,
col_suffixes
=
col_suffixes
)
def
_model_indexes_sql
(
self
,
model
):
"""
Return a list of all index SQL statements (field indexes,
...
...
django/db/backends/ddl_references.py
Dosyayı görüntüle @
38cada7c
...
...
@@ -103,6 +103,24 @@ class IndexName(TableColumns):
return
self
.
create_index_name
(
self
.
table
,
self
.
columns
,
self
.
suffix
)
class
IndexColumns
(
Columns
):
def
__init__
(
self
,
table
,
columns
,
quote_name
,
col_suffixes
=
(),
opclasses
=
()):
self
.
opclasses
=
opclasses
super
()
.
__init__
(
table
,
columns
,
quote_name
,
col_suffixes
)
def
__str__
(
self
):
def
col_str
(
column
,
idx
):
try
:
col
=
self
.
quote_name
(
column
)
+
self
.
col_suffixes
[
idx
]
except
IndexError
:
col
=
self
.
quote_name
(
column
)
# Index.__init__() guarantees that self.opclasses is the same
# length as self.columns.
return
'{} {}'
.
format
(
col
,
self
.
opclasses
[
idx
])
return
', '
.
join
(
col_str
(
column
,
idx
)
for
idx
,
column
in
enumerate
(
self
.
columns
))
class
ForeignKeyName
(
TableColumns
):
"""Hold a reference to a foreign key name."""
...
...
django/db/backends/postgresql/schema.py
Dosyayı görüntüle @
38cada7c
import
psycopg2
from
django.db.backends.base.schema
import
BaseDatabaseSchemaEditor
from
django.db.backends.ddl_references
import
IndexColumns
class
DatabaseSchemaEditor
(
BaseDatabaseSchemaEditor
):
...
...
@@ -12,8 +13,6 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
sql_set_sequence_max
=
"SELECT setval('
%(sequence)
s', MAX(
%(column)
s)) FROM
%(table)
s"
sql_create_index
=
"CREATE INDEX
%(name)
s ON
%(table)
s
%(using)
s (
%(columns)
s)
%(extra)
s"
sql_create_varchar_index
=
"CREATE INDEX
%(name)
s ON
%(table)
s (
%(columns)
s varchar_pattern_ops)
%(extra)
s"
sql_create_text_index
=
"CREATE INDEX
%(name)
s ON
%(table)
s (
%(columns)
s text_pattern_ops)
%(extra)
s"
sql_delete_index
=
"DROP INDEX IF EXISTS
%(name)
s"
# Setting the constraint to IMMEDIATE runs any deferred checks to allow
...
...
@@ -49,9 +48,9 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
if
'['
in
db_type
:
return
None
if
db_type
.
startswith
(
'varchar'
):
return
self
.
_create_index_sql
(
model
,
[
field
],
suffix
=
'_like'
,
sql
=
self
.
sql_create_varchar_index
)
return
self
.
_create_index_sql
(
model
,
[
field
],
suffix
=
'_like'
,
opclasses
=
[
'varchar_pattern_ops'
]
)
elif
db_type
.
startswith
(
'text'
):
return
self
.
_create_index_sql
(
model
,
[
field
],
suffix
=
'_like'
,
sql
=
self
.
sql_create_text_index
)
return
self
.
_create_index_sql
(
model
,
[
field
],
suffix
=
'_like'
,
opclasses
=
[
'text_pattern_ops'
]
)
return
None
def
_alter_column_type_sql
(
self
,
model
,
old_field
,
new_field
,
new_type
):
...
...
@@ -132,3 +131,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
if
old_field
.
unique
and
not
(
new_field
.
db_index
or
new_field
.
unique
):
index_to_remove
=
self
.
_create_index_name
(
model
.
_meta
.
db_table
,
[
old_field
.
column
],
suffix
=
'_like'
)
self
.
execute
(
self
.
_delete_constraint_sql
(
self
.
sql_delete_index
,
model
,
index_to_remove
))
def
_index_columns
(
self
,
table
,
columns
,
col_suffixes
,
opclasses
):
if
opclasses
:
return
IndexColumns
(
table
,
columns
,
self
.
quote_name
,
col_suffixes
=
col_suffixes
,
opclasses
=
opclasses
)
return
super
()
.
_index_columns
(
table
,
columns
,
col_suffixes
,
opclasses
)
django/db/models/indexes.py
Dosyayı görüntüle @
38cada7c
...
...
@@ -12,9 +12,15 @@ class Index:
# cross-database compatibility with Oracle)
max_name_length
=
30
def
__init__
(
self
,
*
,
fields
=
(),
name
=
None
,
db_tablespace
=
None
):
def
__init__
(
self
,
*
,
fields
=
(),
name
=
None
,
db_tablespace
=
None
,
opclasses
=
()):
if
opclasses
and
not
name
:
raise
ValueError
(
'An index must be named to use opclasses.'
)
if
not
isinstance
(
fields
,
(
list
,
tuple
)):
raise
ValueError
(
'Index.fields must be a list or tuple.'
)
if
not
isinstance
(
opclasses
,
(
list
,
tuple
)):
raise
ValueError
(
'Index.opclasses must be a list or tuple.'
)
if
opclasses
and
len
(
fields
)
!=
len
(
opclasses
):
raise
ValueError
(
'Index.fields and Index.opclasses must have the same number of elements.'
)
if
not
fields
:
raise
ValueError
(
'At least one field is required to define an index.'
)
self
.
fields
=
list
(
fields
)
...
...
@@ -31,6 +37,7 @@ class Index:
if
errors
:
raise
ValueError
(
errors
)
self
.
db_tablespace
=
db_tablespace
self
.
opclasses
=
opclasses
def
check_name
(
self
):
errors
=
[]
...
...
@@ -49,7 +56,7 @@ class Index:
col_suffixes
=
[
order
[
1
]
for
order
in
self
.
fields_orders
]
return
schema_editor
.
_create_index_sql
(
model
,
fields
,
name
=
self
.
name
,
using
=
using
,
db_tablespace
=
self
.
db_tablespace
,
col_suffixes
=
col_suffixes
,
col_suffixes
=
col_suffixes
,
opclasses
=
self
.
opclasses
,
)
def
remove_sql
(
self
,
model
,
schema_editor
):
...
...
@@ -65,6 +72,8 @@ class Index:
kwargs
=
{
'fields'
:
self
.
fields
,
'name'
:
self
.
name
}
if
self
.
db_tablespace
is
not
None
:
kwargs
[
'db_tablespace'
]
=
self
.
db_tablespace
if
self
.
opclasses
:
kwargs
[
'opclasses'
]
=
self
.
opclasses
return
(
path
,
(),
kwargs
)
def
clone
(
self
):
...
...
docs/ref/models/indexes.txt
Dosyayı görüntüle @
38cada7c
...
...
@@ -21,7 +21,7 @@ options`_.
``Index`` options
=================
.. class:: Index(fields=(), name=None, db_tablespace=None)
.. class:: Index(fields=(), name=None, db_tablespace=None
, opclasses=()
)
Creates an index (B-Tree) in the database.
...
...
@@ -72,3 +72,23 @@ in the same tablespace as the table.
For a list of PostgreSQL-specific indexes, see
:mod:`django.contrib.postgres.indexes`.
``opclasses``
-------------
.. attribute:: Index.opclasses
.. versionadded:: 2.2
The names of the `PostgreSQL operator classes
<https://www.postgresql.org/docs/current/static/indexes-opclass.html>`_ to use for
this index. If you require a custom operator class, you must provide one for
each field in the index.
For example, ``GinIndex(name='json_index', fields=['jsonfield'],
opclasses=['jsonb_path_ops'])`` creates a gin index on ``jsonfield`` using
``jsonb_path_ops``.
``opclasses`` are ignored for databases besides PostgreSQL.
:attr:`Index.name` is required when using ``opclasses``.
docs/releases/2.2.txt
Dosyayı görüntüle @
38cada7c
...
...
@@ -164,7 +164,7 @@ Migrations
Models
~~~~~~
*
..
.
*
Added support for PostgreSQL operator classes (:attr:`.Index.opclasses`)
.
Requests and Responses
~~~~~~~~~~~~~~~~~~~~~~
...
...
tests/indexes/models.py
Dosyayı görüntüle @
38cada7c
...
...
@@ -52,3 +52,8 @@ if connection.vendor == 'postgresql':
headline
=
models
.
CharField
(
max_length
=
100
,
db_index
=
True
)
body
=
models
.
TextField
(
db_index
=
True
)
slug
=
models
.
CharField
(
max_length
=
40
,
unique
=
True
)
class
IndexedArticle2
(
models
.
Model
):
headline
=
models
.
CharField
(
max_length
=
100
)
body
=
models
.
TextField
()
tests/indexes/tests.py
Dosyayı görüntüle @
38cada7c
from
unittest
import
skipUnless
from
unittest
import
skip
If
,
skip
Unless
from
django.db
import
connection
from
django.db.models
import
Index
from
django.db.models.deletion
import
CASCADE
from
django.db.models.fields.related
import
ForeignKey
from
django.test
import
TestCase
,
TransactionTestCase
from
.models
import
Article
,
ArticleTranslation
,
IndexTogetherSingleList
from
.models
import
(
Article
,
ArticleTranslation
,
IndexedArticle2
,
IndexTogetherSingleList
,
)
class
SchemaIndexesTests
(
TestCase
):
...
...
@@ -66,8 +69,33 @@ class SchemaIndexesTests(TestCase):
index_sql
=
connection
.
schema_editor
()
.
_model_indexes_sql
(
IndexTogetherSingleList
)
self
.
assertEqual
(
len
(
index_sql
),
1
)
@skipUnless
(
connection
.
vendor
==
'postgresql'
,
"This is a postgresql-specific issue"
)
def
test_postgresql_text_indexes
(
self
):
@skipIf
(
connection
.
vendor
==
'postgresql'
,
'opclasses are PostgreSQL only'
)
class
SchemaIndexesNotPostgreSQLTests
(
TransactionTestCase
):
available_apps
=
[
'indexes'
]
def
test_create_index_ignores_opclasses
(
self
):
index
=
Index
(
name
=
'test_ops_class'
,
fields
=
[
'headline'
],
opclasses
=
[
'varchar_pattern_ops'
],
)
with
connection
.
schema_editor
()
as
editor
:
# This would error if opclasses weren't ingored.
editor
.
add_index
(
IndexedArticle2
,
index
)
@skipUnless
(
connection
.
vendor
==
'postgresql'
,
'PostgreSQL tests'
)
class
SchemaIndexesPostgreSQLTests
(
TransactionTestCase
):
available_apps
=
[
'indexes'
]
get_opclass_query
=
'''
SELECT opcname, c.relname FROM pg_opclass AS oc
JOIN pg_index as i on oc.oid = ANY(i.indclass)
JOIN pg_class as c on c.oid = i.indexrelid
WHERE c.relname = '
%
s'
'''
def
test_text_indexes
(
self
):
"""Test creation of PostgreSQL-specific text indexes (#12234)"""
from
.models
import
IndexedArticle
index_sql
=
[
str
(
statement
)
for
statement
in
connection
.
schema_editor
()
.
_model_indexes_sql
(
IndexedArticle
)]
...
...
@@ -78,12 +106,39 @@ class SchemaIndexesTests(TestCase):
# index (#19441).
self
.
assertIn
(
'("slug" varchar_pattern_ops)'
,
index_sql
[
4
])
@skipUnless
(
connection
.
vendor
==
'postgresql'
,
"This is a postgresql-specific issue"
)
def
test_postgresql_virtual_relation_indexes
(
self
):
def
test_virtual_relation_indexes
(
self
):
"""Test indexes are not created for related objects"""
index_sql
=
connection
.
schema_editor
()
.
_model_indexes_sql
(
Article
)
self
.
assertEqual
(
len
(
index_sql
),
1
)
def
test_ops_class
(
self
):
index
=
Index
(
name
=
'test_ops_class'
,
fields
=
[
'headline'
],
opclasses
=
[
'varchar_pattern_ops'
],
)
with
connection
.
schema_editor
()
as
editor
:
editor
.
add_index
(
IndexedArticle2
,
index
)
with
editor
.
connection
.
cursor
()
as
cursor
:
cursor
.
execute
(
self
.
get_opclass_query
%
'test_ops_class'
)
self
.
assertEqual
(
cursor
.
fetchall
(),
[(
'varchar_pattern_ops'
,
'test_ops_class'
)])
def
test_ops_class_multiple_columns
(
self
):
index
=
Index
(
name
=
'test_ops_class_multiple'
,
fields
=
[
'headline'
,
'body'
],
opclasses
=
[
'varchar_pattern_ops'
,
'text_pattern_ops'
],
)
with
connection
.
schema_editor
()
as
editor
:
editor
.
add_index
(
IndexedArticle2
,
index
)
with
editor
.
connection
.
cursor
()
as
cursor
:
cursor
.
execute
(
self
.
get_opclass_query
%
'test_ops_class_multiple'
)
expected_ops_classes
=
(
(
'varchar_pattern_ops'
,
'test_ops_class_multiple'
),
(
'text_pattern_ops'
,
'test_ops_class_multiple'
),
)
self
.
assertCountEqual
(
cursor
.
fetchall
(),
expected_ops_classes
)
@skipUnless
(
connection
.
vendor
==
'mysql'
,
'MySQL tests'
)
class
SchemaIndexesMySQLTests
(
TransactionTestCase
):
...
...
tests/model_indexes/tests.py
Dosyayı görüntüle @
38cada7c
...
...
@@ -39,6 +39,19 @@ class IndexesTests(SimpleTestCase):
with
self
.
assertRaisesMessage
(
ValueError
,
msg
):
models
.
Index
()
def
test_opclasses_requires_index_name
(
self
):
with
self
.
assertRaisesMessage
(
ValueError
,
'An index must be named to use opclasses.'
):
models
.
Index
(
opclasses
=
[
'jsonb_path_ops'
])
def
test_opclasses_requires_list_or_tuple
(
self
):
with
self
.
assertRaisesMessage
(
ValueError
,
'Index.opclasses must be a list or tuple.'
):
models
.
Index
(
name
=
'test_opclass'
,
fields
=
[
'field'
],
opclasses
=
'jsonb_path_ops'
)
def
test_opclasses_and_fields_same_length
(
self
):
msg
=
'Index.fields and Index.opclasses must have the same number of elements.'
with
self
.
assertRaisesMessage
(
ValueError
,
msg
):
models
.
Index
(
name
=
'test_opclass'
,
fields
=
[
'field'
,
'other'
],
opclasses
=
[
'jsonb_path_ops'
])
def
test_max_name_length
(
self
):
msg
=
'Index names cannot be longer than 30 characters.'
with
self
.
assertRaisesMessage
(
ValueError
,
msg
):
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment