2017-06-05 15:10:08 +02:00
|
|
|
from typing import Any, Callable, Dict, List, Tuple, Text
|
2016-03-27 11:28:43 +02:00
|
|
|
from django.db.models.query import QuerySet
|
2014-02-26 21:50:36 +01:00
|
|
|
import re
|
|
|
|
import time
|
|
|
|
|
|
|
|
def timed_ddl(db, stmt):
|
2016-03-27 11:28:43 +02:00
|
|
|
# type: (Any, str) -> None
|
2015-11-01 17:11:06 +01:00
|
|
|
print()
|
|
|
|
print(time.asctime())
|
|
|
|
print(stmt)
|
2014-02-26 21:50:36 +01:00
|
|
|
t = time.time()
|
|
|
|
db.execute(stmt)
|
|
|
|
delay = time.time() - t
|
2015-11-01 17:11:06 +01:00
|
|
|
print('Took %.2fs' % (delay,))
|
2014-02-26 21:50:36 +01:00
|
|
|
|
|
|
|
def validate(sql_thingy):
|
2016-03-27 11:28:43 +02:00
|
|
|
# type: (str) -> None
|
2014-02-26 21:50:36 +01:00
|
|
|
# Do basic validation that table/col name is safe.
|
|
|
|
if not re.match('^[a-z][a-z\d_]+$', sql_thingy):
|
|
|
|
raise Exception('Invalid SQL object: %s' % (sql_thingy,))
|
|
|
|
|
2014-03-01 17:20:04 +01:00
|
|
|
def do_batch_update(db, table, cols, vals, batch_size=10000, sleep=0.1):
|
2016-03-27 11:28:43 +02:00
|
|
|
# type: (Any, str, List[str], List[str], int, float) -> None
|
2014-02-26 21:50:36 +01:00
|
|
|
validate(table)
|
2014-03-01 17:20:04 +01:00
|
|
|
for col in cols:
|
|
|
|
validate(col)
|
2014-02-26 21:50:36 +01:00
|
|
|
stmt = '''
|
|
|
|
UPDATE %s
|
2014-03-01 17:20:04 +01:00
|
|
|
SET (%s) = (%s)
|
2014-02-26 21:50:36 +01:00
|
|
|
WHERE id >= %%s AND id < %%s
|
2014-03-01 17:20:04 +01:00
|
|
|
''' % (table, ', '.join(cols), ', '.join(['%s'] * len(cols)))
|
2015-11-01 17:11:06 +01:00
|
|
|
print(stmt)
|
2014-02-26 21:50:36 +01:00
|
|
|
(min_id, max_id) = db.execute("SELECT MIN(id), MAX(id) FROM %s" % (table,))[0]
|
|
|
|
if min_id is None:
|
|
|
|
return
|
2014-03-01 17:20:04 +01:00
|
|
|
|
2015-11-01 17:11:06 +01:00
|
|
|
print("%s rows need updating" % (max_id - min_id,))
|
2014-02-26 21:50:36 +01:00
|
|
|
while min_id <= max_id:
|
|
|
|
lower = min_id
|
|
|
|
upper = min_id + batch_size
|
2015-11-01 17:11:06 +01:00
|
|
|
print('%s about to update range [%s,%s)' % (time.asctime(), lower, upper))
|
2014-02-26 21:50:36 +01:00
|
|
|
db.start_transaction()
|
2014-03-01 17:20:04 +01:00
|
|
|
params = list(vals) + [lower, upper]
|
|
|
|
db.execute(stmt, params=params)
|
2014-02-26 21:50:36 +01:00
|
|
|
db.commit_transaction()
|
|
|
|
min_id = upper
|
|
|
|
time.sleep(sleep)
|
|
|
|
|
2014-03-01 17:20:04 +01:00
|
|
|
def add_bool_columns(db, table, cols):
|
2016-03-27 11:28:43 +02:00
|
|
|
# type: (Any, str, List[str]) -> None
|
2014-02-26 21:50:36 +01:00
|
|
|
validate(table)
|
2014-03-01 17:20:04 +01:00
|
|
|
for col in cols:
|
|
|
|
validate(col)
|
2014-02-26 21:50:36 +01:00
|
|
|
coltype = 'boolean'
|
|
|
|
val = 'false'
|
|
|
|
|
2017-01-24 05:50:04 +01:00
|
|
|
stmt = (('ALTER TABLE %s ' % (table,)) +
|
|
|
|
', '.join(['ADD %s %s' % (col, coltype) for col in cols]))
|
2014-02-26 21:50:36 +01:00
|
|
|
timed_ddl(db, stmt)
|
|
|
|
|
2017-01-24 05:50:04 +01:00
|
|
|
stmt = (('ALTER TABLE %s ' % (table,)) +
|
|
|
|
', '.join(['ALTER %s SET DEFAULT %s' % (col, val) for col in cols]))
|
2014-02-26 21:50:36 +01:00
|
|
|
timed_ddl(db, stmt)
|
|
|
|
|
2014-03-01 17:20:04 +01:00
|
|
|
vals = [val] * len(cols)
|
|
|
|
do_batch_update(db, table, cols, vals)
|
2014-02-26 21:50:36 +01:00
|
|
|
|
|
|
|
stmt = 'ANALYZE %s' % (table,)
|
|
|
|
timed_ddl(db, stmt)
|
|
|
|
|
2017-01-24 05:50:04 +01:00
|
|
|
stmt = (('ALTER TABLE %s ' % (table,)) +
|
|
|
|
', '.join(['ALTER %s SET NOT NULL' % (col,) for col in cols]))
|
2014-02-26 21:50:36 +01:00
|
|
|
timed_ddl(db, stmt)
|
2014-03-01 18:32:42 +01:00
|
|
|
|
2017-06-05 15:10:08 +02:00
|
|
|
def create_index_if_not_exist(index_name, table_name, column_string, where_clause):
|
|
|
|
# type: (Text, Text, Text, Text) -> Text
|
|
|
|
#
|
|
|
|
# FUTURE TODO: When we no longer need to support postgres 9.3 for Trusty,
|
|
|
|
# we can use "IF NOT EXISTS", which is part of postgres 9.5
|
|
|
|
# (and which already is supported on Xenial systems).
|
|
|
|
stmt = '''
|
|
|
|
DO $$
|
|
|
|
BEGIN
|
|
|
|
IF NOT EXISTS (
|
|
|
|
SELECT 1
|
|
|
|
FROM pg_class
|
|
|
|
where relname = '%s'
|
|
|
|
) THEN
|
|
|
|
CREATE INDEX
|
|
|
|
%s
|
|
|
|
ON %s (%s)
|
|
|
|
%s;
|
|
|
|
END IF;
|
|
|
|
END$$;
|
|
|
|
''' % (index_name, index_name, table_name, column_string, where_clause)
|
|
|
|
return stmt
|
2014-03-11 18:17:52 +01:00
|
|
|
|
|
|
|
def act_on_message_ranges(db, orm, tasks, batch_size=5000, sleep=0.5):
|
2016-03-27 11:28:43 +02:00
|
|
|
# type: (Any, Dict[str, Any], List[Tuple[Callable[[QuerySet], QuerySet], Callable[[QuerySet], None]]], int , float) -> None
|
2014-03-11 18:17:52 +01:00
|
|
|
# tasks should be an array of (filterer, action) tuples
|
|
|
|
# where filterer is a function that returns a filtered QuerySet
|
|
|
|
# and action is a function that acts on a QuerySet
|
|
|
|
|
|
|
|
all_objects = orm['zerver.Message'].objects
|
2014-03-12 18:39:24 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
min_id = all_objects.all().order_by('id')[0].id
|
|
|
|
except IndexError:
|
2015-11-01 17:11:06 +01:00
|
|
|
print('There is no work to do')
|
2014-03-12 18:39:24 +01:00
|
|
|
return
|
|
|
|
|
2014-03-11 18:17:52 +01:00
|
|
|
max_id = all_objects.all().order_by('-id')[0].id
|
2015-11-01 17:11:06 +01:00
|
|
|
print("max_id = %d" % (max_id,))
|
2016-11-28 23:29:01 +01:00
|
|
|
overhead = int((max_id + 1 - min_id) / batch_size * sleep / 60)
|
2015-11-01 17:11:06 +01:00
|
|
|
print("Expect this to take at least %d minutes, just due to sleeps alone." % (overhead,))
|
2014-03-11 18:17:52 +01:00
|
|
|
|
|
|
|
while min_id <= max_id:
|
|
|
|
lower = min_id
|
|
|
|
upper = min_id + batch_size - 1
|
|
|
|
if upper > max_id:
|
|
|
|
upper = max_id
|
|
|
|
|
2015-11-01 17:11:06 +01:00
|
|
|
print('%s about to update range %s to %s' % (time.asctime(), lower, upper))
|
2014-03-11 18:17:52 +01:00
|
|
|
|
|
|
|
db.start_transaction()
|
|
|
|
for filterer, action in tasks:
|
|
|
|
objects = all_objects.filter(id__range=(lower, upper))
|
|
|
|
targets = filterer(objects)
|
|
|
|
action(targets)
|
|
|
|
db.commit_transaction()
|
|
|
|
|
|
|
|
min_id = upper + 1
|
|
|
|
time.sleep(sleep)
|