This repository has been archived by the owner on Feb 7, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from brki/add-postgresql-utilities
Add postgresql utilities
- Loading branch information
Showing
3 changed files
with
120 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
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,26 @@ | ||
from __future__ import absolute_import | ||
from django.db import connection, connections | ||
from django import VERSION | ||
|
||
if VERSION >= (1, 7): | ||
from django.apps import apps | ||
else: | ||
from django.db.models import get_app, get_models | ||
|
||
from ..models import Versionable | ||
|
||
def database_connection(dbname=None): | ||
if dbname: | ||
return connections[dbname] | ||
else: | ||
return connection | ||
|
||
def get_app_models(app_name, include_auto_created=False): | ||
if VERSION >= (1, 7): | ||
return apps.get_app_config(app_name).get_models(include_auto_created=include_auto_created) | ||
else: | ||
return get_models(get_app(app_name), include_auto_created=include_auto_created) | ||
|
||
|
||
def versionable_models(app_name, include_auto_created=False): | ||
return [m for m in get_app_models(app_name, include_auto_created) if issubclass(m, Versionable)] |
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,94 @@ | ||
from __future__ import absolute_import | ||
from versions.models import VersionedForeignKey | ||
from .helper import database_connection, versionable_models | ||
|
||
def index_exists(connection, index_name): | ||
cursor = connection.cursor() | ||
cursor.execute("SELECT COUNT(1) FROM pg_indexes WHERE indexname = %s",[index_name]) | ||
return cursor.fetchone()[0] > 0 | ||
|
||
def remove_uuid_id_like_indexes(app_name, database=None): | ||
""" | ||
Remove all of varchar_pattern_ops indexes that django created for uuid columns. | ||
A search is never done with a filter of the style (uuid__like='1ae3c%'), so | ||
all such indexes can be removed from Versionable models. | ||
This will only try to remove indexes if they exist in the database, so it | ||
should be safe to run in a post_migrate signal handler. Running it several | ||
times should leave the database in the same state as running it once. | ||
:param str app_name: application name whose Versionable models will be acted on. | ||
:param str database: database alias to use. If None, use default connection. | ||
:return: number of indexes removed | ||
:rtype: int | ||
""" | ||
|
||
removed_indexes = 0 | ||
cursor = database_connection(database).cursor() | ||
for model in versionable_models(app_name, include_auto_created=True): | ||
# VersionedForeignKey fields as well as the id fields have these useless like indexes | ||
field_names = ["'%s'" % f.column for f in model._meta.fields if isinstance(f, VersionedForeignKey)] | ||
field_names.append("'id'") | ||
|
||
sql = """ | ||
select i.relname as index_name | ||
from pg_class t, | ||
pg_class i, | ||
pg_index ix, | ||
pg_attribute a | ||
where t.oid = ix.indrelid | ||
and i.oid = ix.indexrelid | ||
and a.attrelid = t.oid | ||
and a.attnum = ANY(ix.indkey) | ||
and t.relkind = 'r' | ||
and t.relname = '{0}' | ||
and a.attname in ({1}) | ||
and i.relname like '%_like' | ||
""".format(model._meta.db_table, ','.join(field_names)) | ||
|
||
cursor.execute(sql) | ||
indexes = cursor.fetchall() | ||
if indexes: | ||
index_list = ','.join(['"%s"' % r[0] for r in indexes]) | ||
cursor.execute("DROP INDEX %s" % index_list) | ||
removed_indexes += len(indexes) | ||
|
||
return removed_indexes | ||
|
||
def create_current_version_unique_indexes(app_name, database=None): | ||
""" | ||
Add unique indexes for models which have a VERSION_UNIQUE attribute. | ||
These must be defined as partially unique indexes, which django | ||
does not support. | ||
The unique indexes are defined so that no two *current* versions can have | ||
the same value. | ||
This will only try to create indexes if they do not exist in the database, so it | ||
should be safe to run in a post_migrate signal handler. Running it several | ||
times should leave the database in the same state as running it once. | ||
:param str app_name: application name whose Versionable models will be acted on. | ||
:param str database: database alias to use. If None, use default connection. | ||
:return: number of partial unique indexes created | ||
:rtype: int | ||
""" | ||
|
||
indexes_created = 0 | ||
connection = database_connection(database) | ||
cursor = connection.cursor() | ||
for model in versionable_models(app_name): | ||
unique_field_groups = getattr(model, 'VERSION_UNIQUE', None) | ||
if not unique_field_groups: | ||
continue | ||
|
||
table_name = model._meta.db_table | ||
for group in unique_field_groups: | ||
col_prefixes = [] | ||
columns = [] | ||
for field in group: | ||
column = model._meta.get_field_by_name(field)[0].column | ||
col_prefixes.append(column[0:3]) | ||
columns.append(column) | ||
index_name = '%s_%s_%s_v_uniq' % (app_name, table_name, '_'.join(col_prefixes)) | ||
if not index_exists(connection, index_name): | ||
cursor.execute("CREATE UNIQUE INDEX %s ON %s(%s) WHERE version_end_date IS NULL" % (index_name, table_name, ','.join(columns))) | ||
indexes_created += 1 | ||
|
||
return indexes_created | ||
|