django-postpone-index 0.0.1__py3-none-any.whl → 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-postpone-index
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: Postpone index creation to provide Zero Downtime Migration feature
5
5
  Home-page: https://github.com/nnseva/django-postpone-index
6
6
  Author: Vsevolod Novikov
@@ -0,0 +1,23 @@
1
+ postpone_index/__init__.py,sha256=s1NgdtOn7SO7scs-uAaq1134F7Luh-JjB5Ghra3D2fQ,84
2
+ postpone_index/_version.py,sha256=huLsL1iGeXWQKZ8bjwDdIWC7JOkj3wnzBh-HFMZl1PY,704
3
+ postpone_index/admin.py,sha256=TAZxaciQkEBNhReYulsU5VopPCyABtMLZvCSxk0CfYE,820
4
+ postpone_index/apps.py,sha256=1ap2HY49uXVXjY9UWK3___fjPe22wqFOSOnuH2kIAxs,200
5
+ postpone_index/migration_utils.py,sha256=02rm77mz_MAufl5vdqKPV1CAd1-3bRk6kSy4MIxKsEo,752
6
+ postpone_index/models.py,sha256=jMsA5hdiL8Xgl-hq7bgVl5NUX6LXlSYpMuX9pD68BbU,1771
7
+ postpone_index/testing_utils.py,sha256=ycxp7ovY6JceOvNpotK7OGAebhdHicXJ-0fWJmaq9fo,1224
8
+ postpone_index/utils.py,sha256=wYVPsQRs3LN-ZtvfSWrDDqFFp0OCkVxW9j7w9yqd47o,3823
9
+ postpone_index/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ postpone_index/contrib/postgis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ postpone_index/contrib/postgis/base.py,sha256=a5h1k11BVSOflSSesTxObKAEYNWtisFLRbApzHeW3c0,289
12
+ postpone_index/contrib/postgis/schema.py,sha256=eantuNhFYWDngDnAeURJurpW58UztPnjTIySmUlXx7s,340
13
+ postpone_index/contrib/postgres/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ postpone_index/contrib/postgres/base.py,sha256=Xq90nJkxGSgR8Im9JPnFepeMb0-bur9sRjPpm3zMd6U,281
15
+ postpone_index/contrib/postgres/schema.py,sha256=45xt6mhVgGgFWYH_OVZE0WzcyBv9J6sCQVR4WnxA1Xc,10914
16
+ postpone_index/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ postpone_index/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ postpone_index/management/commands/apply_postponed.py,sha256=wVbw0ugUyPuYwUikGnn5MvP9l9OsEIZja9sb6nsMtOw,9107
19
+ postpone_index/sql/start.sql,sha256=44yFbZTjknjXUvl7MpO1uuPv-CVBsZcnUDuKc8mVj3k,871
20
+ django_postpone_index-0.0.2.dist-info/METADATA,sha256=8jnBu6D07a_Nszgmkk9e9kP7wMJzeE3N9jCsxc5PGOk,10415
21
+ django_postpone_index-0.0.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
22
+ django_postpone_index-0.0.2.dist-info/top_level.txt,sha256=B6q_TICqApvruf_NJfnLFNLZLpxMLebQF6cPdKrWm5A,162
23
+ django_postpone_index-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,6 @@
1
+ postpone_index
2
+ postpone_index/contrib
3
+ postpone_index/contrib/postgis
4
+ postpone_index/contrib/postgres
5
+ postpone_index/management
6
+ postpone_index/management/commands
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.1'
32
- __version_tuple__ = version_tuple = (0, 0, 1)
31
+ __version__ = version = '0.0.2'
32
+ __version_tuple__ = version_tuple = (0, 0, 2)
33
33
 
34
34
  __commit_id__ = commit_id = None
File without changes
File without changes
@@ -0,0 +1,11 @@
1
+ from django.contrib.gis.db.backends.postgis.base import (
2
+ DatabaseWrapper as _DatabaseWrapper,
3
+ )
4
+
5
+ from postpone_index.contrib.postgis.schema import DatabaseSchemaEditor
6
+
7
+
8
+ class DatabaseWrapper(_DatabaseWrapper):
9
+ """Database wrapper"""
10
+
11
+ SchemaEditorClass = DatabaseSchemaEditor
@@ -0,0 +1,11 @@
1
+ """Schema editor with concurrent index creation support."""
2
+
3
+ from django.contrib.gis.db.backends.postgis.schema import (
4
+ PostGISSchemaEditor as _DatabaseSchemaEditor,
5
+ )
6
+
7
+ from postpone_index.contrib.postgres.schema import DatabaseSchemaEditorMixin
8
+
9
+
10
+ class DatabaseSchemaEditor(DatabaseSchemaEditorMixin, _DatabaseSchemaEditor):
11
+ pass
File without changes
@@ -0,0 +1,11 @@
1
+ from django.db.backends.postgresql.base import (
2
+ DatabaseWrapper as _DatabaseWrapper,
3
+ )
4
+
5
+ from postpone_index.contrib.postgres.schema import DatabaseSchemaEditor
6
+
7
+
8
+ class DatabaseWrapper(_DatabaseWrapper):
9
+ """Database wrapper"""
10
+
11
+ SchemaEditorClass = DatabaseSchemaEditor
@@ -0,0 +1,230 @@
1
+ """Schema editor with concurrent index creation support."""
2
+ import logging
3
+ import os
4
+ import os.path
5
+
6
+ from django.conf import settings
7
+ from django.db.backends.postgresql.schema import (
8
+ DatabaseSchemaEditor as _DatabaseSchemaEditor,
9
+ )
10
+
11
+ from postpone_index.utils import Utils
12
+
13
+
14
+ logger = logging.getLogger(__name__)
15
+ package_folder = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
16
+
17
+
18
+ class DatabaseSchemaEditorMixin(Utils):
19
+ """Mixin to embed into DatabaseSchemaEditor"""
20
+ _base_tables_created = False
21
+
22
+ def _create_base_tables(self):
23
+ """
24
+ Create base package tables on the first access to this function
25
+ """
26
+ if self._base_tables_created:
27
+ return
28
+
29
+ # Avoid circular import
30
+ from postpone_index.models import PostponedSQL
31
+
32
+ cursor = self.connection.cursor()
33
+ cursor.execute("SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '%s' limit 1" % PostponedSQL._meta.db_table)
34
+ if cursor.fetchall():
35
+ self._base_tables_created = True
36
+ logger.debug('[%s] The PostponedSQL storage already exists', self.connection.alias)
37
+ return
38
+ logger.info('[%s] The PostponedSQL storage is absent, creating', self.connection.alias)
39
+ with open(os.path.join(package_folder, 'sql/start.sql')) as f:
40
+ sql = f.read()
41
+ cursor.execute(sql)
42
+ self._base_tables_created = True
43
+
44
+ def _ignore(self):
45
+ """Check whether to ignore the extension"""
46
+ return getattr(settings, 'POSTPONE_INDEX_IGNORE', False) or getattr(self, '_postpone_index_ignore', False)
47
+
48
+ def execute(self, sql, params=()):
49
+ """
50
+ Overriden for execute processing with special handling for index operations.
51
+
52
+ All indexing operations are postponed by writing to a special table instead of execution.
53
+
54
+ They will be executed later in CONCURRENTLY manner.
55
+ """
56
+ if self._ignore():
57
+ return super().execute(sql, params)
58
+
59
+ self._create_base_tables()
60
+
61
+ # Avoid circular import
62
+ from postpone_index.models import PostponedSQL
63
+
64
+ if match := self._create_index_re.fullmatch(str(sql)):
65
+ # Postpone index creation
66
+ index_name = match.group('index_nameq') or match.group('index_name')
67
+ table_name = match.group('table_nameq') or match.group('table_name')
68
+ columns = ','.join(self._extract_column_names(match.group('rest')))
69
+ description = 'Create Index "%s" on "%s" (%s)' % (
70
+ index_name,
71
+ table_name,
72
+ columns
73
+ )
74
+ PostponedSQL.objects.using(self.connection.alias).create(
75
+ sql=str(sql),
76
+ description=description,
77
+ table=table_name,
78
+ db_index=index_name,
79
+ fields=columns
80
+ )
81
+ logger.info('[%s] Postponed %s', self.connection.alias, description)
82
+ elif match := self._add_constraint_re.fullmatch(str(sql)):
83
+ # Postpone constraint creation
84
+ index_name = match.group('index_nameq') or match.group('index_name')
85
+ table_name = match.group('table_nameq') or match.group('table_name')
86
+ columns = ','.join(self._extract_column_names(match.group('rest')))
87
+ description = 'Add Unique Constraint "%s" on "%s" (%s)' % (
88
+ index_name,
89
+ table_name,
90
+ columns,
91
+ )
92
+ PostponedSQL.objects.using(self.connection.alias).create(
93
+ sql=str(sql),
94
+ description=description,
95
+ table=table_name,
96
+ db_index=index_name,
97
+ fields=columns
98
+ )
99
+ logger.info('[%s] Postponed %s', self.connection.alias, description)
100
+ else:
101
+ if match := self._drop_index_re.fullmatch(str(sql)):
102
+ # Override to ignore inexistent index drop error
103
+ index_name = match.group('index_nameq') or match.group('index_name')
104
+ if PostponedSQL.objects.using(self.connection.alias).filter(db_index=index_name, done=False).delete()[0]:
105
+ logger.info('[%s] Removed Index %s from postponed', self.connection.alias, index_name)
106
+ # Avoid errors on introspecting inexistent index
107
+ try:
108
+ return super().execute(sql, params)
109
+ logger.info('[%s] Drop index %s success', self.connection.alias, index_name)
110
+ except Exception as ex:
111
+ logger.info('[%s] Drop index %s ignored: %s', self.connection.alias, index_name, ex)
112
+ elif match := self._drop_constraint_re.fullmatch(str(sql)):
113
+ # Override to ignore inexistent constraint drop error
114
+ index_name = match.group('index_nameq') or match.group('index_name')
115
+ table_name = match.group('table_nameq') or match.group('table_name')
116
+ if PostponedSQL.objects.using(self.connection.alias).filter(db_index=index_name, table=table_name, done=False).delete()[0]:
117
+ logger.info('[%s] Removed Constraint %s on %s from postponed', self.connection.alias, index_name, table_name)
118
+ # Avoid errors on introspecting inexistent constraint
119
+ try:
120
+ return super().execute(sql, params)
121
+ logger.info('[%s] Drop constraint %s success', self.connection.alias, index_name)
122
+ except Exception as ex:
123
+ logger.info('[%s] Drop constraint %s ignored: %s', self.connection.alias, index_name, ex)
124
+ elif match := self._drop_table_re.fullmatch(str(sql)):
125
+ # Table dropped cancels all postponed operations related
126
+ table_name = match.group('table_nameq') or match.group('table_name')
127
+ if PostponedSQL.objects.using(self.connection.alias).filter(table=table_name, done=False).delete()[0]:
128
+ logger.info('[%s] Removed all indexes on table %s from postponed', self.connection.alias, table_name)
129
+ return super().execute(sql, params)
130
+ elif match := self._drop_column_re.fullmatch(str(sql)):
131
+ # Table dropped cancels all postponed operations related
132
+ table_name = match.group('table_nameq') or match.group('table_name')
133
+ column_name = match.group('column_nameq') or match.group('column_name')
134
+ if PostponedSQL.objects.using(self.connection.alias).filter(
135
+ table=table_name, fields__contains='"%s"' % column_name, done=False
136
+ ).delete()[0]:
137
+ logger.info(
138
+ '[%s] Removed all indexes on column %s of table %s from postponed',
139
+ self.connection.alias, column_name, table_name
140
+ )
141
+ return super().execute(sql, params)
142
+ else:
143
+ logger.debug('[%s] No special statements: %s', self.connection.alias, sql)
144
+ return super().execute(sql, params)
145
+
146
+ def _alter_field(
147
+ self, model, old_field, new_field, old_type, new_type,
148
+ old_db_params, new_db_params, *args, **kw
149
+ ):
150
+ """Overriden for special field attributes processing"""
151
+
152
+ # The original code uses database introspection to decide whether the
153
+ # index to be removed is present. We override the function before the introspection calls
154
+ # to remove the postponed index creation jobs instead.
155
+
156
+ if self._ignore():
157
+ return super()._alter_field(
158
+ model, old_field, new_field, old_type, new_type,
159
+ old_db_params, new_db_params, *args, **kw
160
+ )
161
+
162
+ self._create_base_tables()
163
+
164
+ # Avoid circular import
165
+ from postpone_index.models import PostponedSQL
166
+
167
+ suffixes = []
168
+
169
+ if old_field.unique and not new_field.unique:
170
+ # Delete unique field attribute cancels all postponed operations related
171
+ suffixes.append('_uniq')
172
+
173
+ if old_field.db_index and not new_field.db_index:
174
+ # Delete db_index field attribute cancels all postponed operations related
175
+ suffixes.append('')
176
+ suffixes.append('_like')
177
+
178
+ if old_db_params['check'] != new_db_params['check'] and old_db_params['check']:
179
+ # Delete check field attribute cancels all postponed operations related
180
+ suffixes.append('_check')
181
+
182
+ for suffix in suffixes:
183
+ index_name = self._create_index_name(
184
+ model._meta.db_table,
185
+ [old_field.column],
186
+ suffix
187
+ )
188
+ if PostponedSQL.objects.using(self.connection.alias).filter(table=model._meta.db_table, db_index=index_name, done=False).delete()[0]:
189
+ logger.info('[%s] Removed single index %s on %s from postponed', self.connection.alias, index_name, model._meta.db_table)
190
+
191
+ return super()._alter_field(
192
+ model, old_field, new_field, old_type, new_type,
193
+ old_db_params, new_db_params, *args, **kw
194
+ )
195
+
196
+ def _delete_composed_index(self, model, fields, constraint_kwargs, sql):
197
+ """Overriden for special composed index processing"""
198
+
199
+ # The original code uses database introspection to decide whether the
200
+ # index to be removed is present. We override the function before the introspection calls
201
+ # to remove the postponed index creation jobs instead.
202
+
203
+ if self._ignore():
204
+ return super()._delete_composed_index(model, fields, constraint_kwargs, sql)
205
+
206
+ self._create_base_tables()
207
+
208
+ # Avoid circular import
209
+ from postpone_index.models import PostponedSQL
210
+
211
+ # Delete composed index cancels all postponed operations related
212
+ index_name = self._create_index_name(
213
+ model._meta.db_table,
214
+ [model._meta.get_field(f).column for f in fields],
215
+ '_uniq' if (constraint_kwargs or {}).get('unique', False) else '_idx'
216
+ )
217
+
218
+ if PostponedSQL.objects.using(self.connection.alias).filter(table=model._meta.db_table, db_index=index_name, done=False).delete()[0]:
219
+ logger.info('[%s] Removed composed index %s on %s from postponed', self.connection.alias, index_name, model._meta.db_table)
220
+
221
+ # Avoid errors on introspecting inexistent compound index
222
+ try:
223
+ super()._delete_composed_index(model, fields, constraint_kwargs, sql)
224
+ logger.info('[%s] Delete composed %s on %s success', self.connection.alias, index_name, model._meta.db_table)
225
+ except Exception as ex:
226
+ logger.info('[%s] Delete composed %s on %s ignored: %s', self.connection.alias, index_name, model._meta.db_table, ex)
227
+
228
+
229
+ class DatabaseSchemaEditor(DatabaseSchemaEditorMixin, _DatabaseSchemaEditor):
230
+ pass
File without changes
File without changes
@@ -0,0 +1,251 @@
1
+ """
2
+ The apply_postponed command applies collected postponed index and constraint creation
3
+ in CONCURRENTLY manner.
4
+ """
5
+ import argparse
6
+ import logging
7
+ import sys
8
+
9
+ from django.core.management.base import BaseCommand
10
+ from django.db import connections
11
+
12
+ from postpone_index.models import PostponedSQL
13
+ from postpone_index.utils import ObjMap, Utils
14
+
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class Command(Utils, BaseCommand):
20
+ __doc__ = __doc__
21
+ help = __doc__
22
+
23
+ _short_format = '%(d)s %(sql)s'
24
+ _descr_format = '%(i)04d: %(d)s %(description)s'
25
+ _long_format = '%(i)04d: %(d)s %(sql)s'
26
+
27
+ class formatter_class(argparse.RawTextHelpFormatter):
28
+ pass
29
+
30
+ def add_arguments(self, parser):
31
+ subparsers = parser.add_subparsers(
32
+ title='Commands',
33
+ dest='command',
34
+ help='Use --help with command to see the command-specific help',
35
+ )
36
+ show = subparsers.add_parser(
37
+ name='list',
38
+ formatter_class=self.formatter_class,
39
+ help='Show applied and not yet applied postponed index creation jobs'
40
+ )
41
+ show.add_argument(
42
+ '-db', '--database',
43
+ dest='database',
44
+ default='default',
45
+ help='Database alias to be applied, default is %(default)s'
46
+ )
47
+ show.add_argument(
48
+ '-r', '--reversed',
49
+ dest='reversed',
50
+ action='store_true',
51
+ help='Show records in reversed order'
52
+ )
53
+ show.add_argument(
54
+ '-u', '--unapplied',
55
+ dest='unapplied',
56
+ action='store_true',
57
+ help='Show only unapplied records'
58
+ )
59
+ show.add_argument(
60
+ '-e', '--erroneous',
61
+ dest='erroneous',
62
+ action='store_true',
63
+ help='Show only erroneous records having non-null error'
64
+ )
65
+ show.add_argument(
66
+ '-f', '--format',
67
+ dest='format',
68
+ default='s',
69
+ help=f"""Output format. Use either:
70
+ - `s` for short SQL-only format `{self._short_format}` (default)
71
+ - `d` for enumerated descriptions format `{self._descr_format}`
72
+ - `l` for enumerated SQL format `{self._long_format}`
73
+ - %-style named format
74
+ Use the PostponedSQL field names for %-style format.
75
+ Additional names are:
76
+ - `i` for enumeration index
77
+ - `d` for the `done` attribute in form of `+` or `-`""".replace('%', '%%')
78
+ )
79
+ run = subparsers.add_parser(
80
+ name='run',
81
+ formatter_class=self.formatter_class,
82
+ help='Apply not yet applied postponed index creation jobs'
83
+ )
84
+ run.add_argument(
85
+ '-x', '--exception',
86
+ dest='exception',
87
+ action='store_true',
88
+ help='Immediately stop on any exception. Continues with the next job by default'
89
+ )
90
+ run.add_argument(
91
+ '-db', '--database',
92
+ dest='database',
93
+ default='default',
94
+ help='Database alias to be applied, default is %(default)s'
95
+ )
96
+ cleanup = subparsers.add_parser(
97
+ name='cleanup',
98
+ formatter_class=self.formatter_class,
99
+ help='Cleanup stored applied index creation jobs'
100
+ )
101
+ cleanup.add_argument(
102
+ '--all',
103
+ dest='all',
104
+ action='store_true',
105
+ help='Cleanup all postponed SQL including not yet applied. Be careful!'
106
+ )
107
+ cleanup.add_argument(
108
+ '-db', '--database',
109
+ dest='database',
110
+ default='default',
111
+ help='Database alias to be applied, default is %(default)s'
112
+ )
113
+
114
+ def handle(self, *args, **options):
115
+ """Handling commands"""
116
+ if not options['command']:
117
+ return self.print_help(sys.argv[0], sys.argv[1])
118
+ return getattr(self, '_handle_%s' % options['command'])(*args, **options)
119
+
120
+ def _handle_list(self, *args, **options):
121
+ """Handle list command"""
122
+ try:
123
+ PostponedSQL.objects.using(options['database']).all().first()
124
+ except Exception:
125
+ # The table has not been created
126
+ return
127
+
128
+ q = PostponedSQL.objects.using(options['database']).all()
129
+ q = q.order_by('-ts' if options['reversed'] else 'ts')
130
+ if options['unapplied']:
131
+ q = q.filter(done=False)
132
+ if options['erroneous']:
133
+ q = q.filter(error__isnull=False)
134
+ match options['format']:
135
+ case 's':
136
+ format = self._short_format
137
+ case 'd':
138
+ format = self._descr_format
139
+ case 'l':
140
+ format = self._long_format
141
+ case _:
142
+ format = options['format']
143
+ for i, j in enumerate(q):
144
+ print(format % ObjMap(j, {
145
+ 'i': i,
146
+ }))
147
+
148
+ def _handle_run(self, *args, **options):
149
+ """Handle run command"""
150
+ try:
151
+ PostponedSQL.objects.using(options['database']).all().first()
152
+ except Exception:
153
+ # The table has not been created
154
+ return
155
+ q = PostponedSQL.objects.using(options['database']).filter(done=False).order_by('ts')
156
+ for j in q:
157
+ try:
158
+ j.error = None
159
+ j.done = False
160
+ self._handle_run_job(j, *args, **options)
161
+ j.save(update_fields=['error', 'done'])
162
+ except Exception as ex:
163
+ logger.warning('Error on running job: %s', ex)
164
+ if not j.error:
165
+ j.error = 'Exception: %s' % ex
166
+ j.save(update_fields=['error', 'done'])
167
+ if options['exception']:
168
+ raise
169
+
170
+ def _handle_cleanup(self, *args, **options):
171
+ """Handle cleanup command"""
172
+ try:
173
+ PostponedSQL.objects.using(options['database']).all().first()
174
+ except Exception:
175
+ # The table has not been created
176
+ return
177
+ q = PostponedSQL.objects.using(options['database'])
178
+ if not options['all']:
179
+ q = q.filter(done=True)
180
+ q.delete()
181
+
182
+ def _handle_run_job(self, job, *args, **options):
183
+ """Handle a single job for the run command"""
184
+ if match := self._create_index_re.fullmatch(job.sql):
185
+ unique = match.group('unique') or ''
186
+ index_name = match.group('index_nameq') or match.group('index_name')
187
+ table_name = match.group('table_nameq') or match.group('table_name')
188
+ rest = match.group('rest')
189
+ sql = 'DROP INDEX IF EXISTS "%s"' % (
190
+ index_name,
191
+ )
192
+ logger.info('[%s] SQL: %s', options['database'], sql)
193
+ cursor = connections[options['database']].cursor()
194
+ cursor.execute(sql)
195
+ cursor.close()
196
+ sql = 'CREATE %sINDEX CONCURRENTLY "%s" ON "%s" %s' % (
197
+ unique,
198
+ index_name,
199
+ table_name,
200
+ rest
201
+ )
202
+ logger.info('[%s] SQL: %s', options['database'], sql)
203
+ cursor = connections[options['database']].cursor()
204
+ cursor.execute(sql)
205
+ cursor.close()
206
+ elif match := self._add_constraint_re.fullmatch(job.sql):
207
+ unique = 'UNIQUE '
208
+ index_name = match.group('index_nameq') or match.group('index_name')
209
+ table_name = match.group('table_nameq') or match.group('table_name')
210
+ rest = match.group('rest')
211
+ sql = 'ALTER TABLE "%s" DROP CONSTRAINT IF EXISTS "%s"' % (
212
+ table_name,
213
+ index_name,
214
+ )
215
+ logger.info('[%s] SQL: %s', options['database'], sql)
216
+ cursor = connections[options['database']].cursor()
217
+ cursor.execute(sql)
218
+ cursor.close()
219
+ sql = 'DROP INDEX IF EXISTS "%s"' % (
220
+ index_name,
221
+ )
222
+ logger.info('[%s] SQL: %s', options['database'], sql)
223
+ cursor = connections[options['database']].cursor()
224
+ cursor.execute(sql)
225
+ cursor.close()
226
+ sql = 'CREATE %sINDEX CONCURRENTLY "%s" ON "%s" %s' % (
227
+ unique,
228
+ index_name,
229
+ table_name,
230
+ rest
231
+ )
232
+ logger.info('[%s] SQL: %s', options['database'], sql)
233
+ cursor = connections[options['database']].cursor()
234
+ cursor.execute(sql)
235
+ cursor.close()
236
+ sql = 'ALTER TABLE "%s" ADD CONSTRAINT "%s" UNIQUE USING INDEX "%s"' % (
237
+ table_name,
238
+ index_name,
239
+ index_name,
240
+ )
241
+ logger.info('[%s] SQL: %s', options['database'], sql)
242
+ cursor = connections[options['database']].cursor()
243
+ cursor.execute(sql)
244
+ cursor.close()
245
+ else:
246
+ logger.info('[%s] Unrecognized: %s', options['database'], sql)
247
+ cursor = connections[options['database']].cursor()
248
+ cursor.execute(sql)
249
+ cursor.close()
250
+ job.error = None
251
+ job.done = True
@@ -0,0 +1,14 @@
1
+ CREATE TABLE IF NOT EXISTS public.postpone_index_postponedsql (
2
+ ts bigint NOT NULL PRIMARY KEY,
3
+ description character varying NOT NULL,
4
+ "sql" text NOT NULL,
5
+ "table" character varying,
6
+ db_index character varying,
7
+ fields character varying,
8
+ "done" boolean NOT NULL DEFAULT FALSE,
9
+ "error" text
10
+ );
11
+ CREATE INDEX IF NOT EXISTS postpone_index_postponedsql_db_index ON public.postpone_index_postponedsql USING btree (db_index);
12
+ CREATE INDEX IF NOT EXISTS postpone_index_postponedsql_db_index_like ON public.postpone_index_postponedsql USING btree (db_index varchar_pattern_ops);
13
+ CREATE INDEX IF NOT EXISTS postpone_index_postponedsql_table ON public.postpone_index_postponedsql USING btree ("table");
14
+ CREATE INDEX IF NOT EXISTS postpone_index_postponedsql_table_like ON public.postpone_index_postponedsql USING btree ("table" varchar_pattern_ops);
@@ -1,12 +0,0 @@
1
- postpone_index/__init__.py,sha256=s1NgdtOn7SO7scs-uAaq1134F7Luh-JjB5Ghra3D2fQ,84
2
- postpone_index/_version.py,sha256=qf6R-J7-UyuABBo8c0HgaquJ8bejVbf07HodXgwAwgQ,704
3
- postpone_index/admin.py,sha256=TAZxaciQkEBNhReYulsU5VopPCyABtMLZvCSxk0CfYE,820
4
- postpone_index/apps.py,sha256=1ap2HY49uXVXjY9UWK3___fjPe22wqFOSOnuH2kIAxs,200
5
- postpone_index/migration_utils.py,sha256=02rm77mz_MAufl5vdqKPV1CAd1-3bRk6kSy4MIxKsEo,752
6
- postpone_index/models.py,sha256=jMsA5hdiL8Xgl-hq7bgVl5NUX6LXlSYpMuX9pD68BbU,1771
7
- postpone_index/testing_utils.py,sha256=ycxp7ovY6JceOvNpotK7OGAebhdHicXJ-0fWJmaq9fo,1224
8
- postpone_index/utils.py,sha256=wYVPsQRs3LN-ZtvfSWrDDqFFp0OCkVxW9j7w9yqd47o,3823
9
- django_postpone_index-0.0.1.dist-info/METADATA,sha256=f6r6FaLnag7wQ4NjfFRPhMDZAffRmXtXK7Bg7o4viLU,10415
10
- django_postpone_index-0.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
- django_postpone_index-0.0.1.dist-info/top_level.txt,sha256=SzF44QR8Fr9ArKXZPY7FSJUlTBKYo_dPNHn2hbqKOrc,15
12
- django_postpone_index-0.0.1.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- postpone_index