0001-sql-use-materialized-views-if-available-for-statisti.patch
tests/test_sql.py | ||
---|---|---|
839 | 839 |
def test_migration_2_formdef_id_in_views(): |
840 | 840 |
conn, cur = sql.get_connection_and_cursor() |
841 | 841 |
cur.execute('UPDATE wcs_meta SET value = 1 WHERE key = %s', ('sql_level',)) |
842 |
cur.execute('DROP VIEW wcs_all_forms') |
|
842 |
cur.execute('DROP VIEW wcs_all_forms CASCADE')
|
|
843 | 843 | |
844 | 844 |
# hack a formdef table the wrong way, to check it is reconstructed |
845 | 845 |
# properly before the views are created |
... | ... | |
882 | 882 |
def test_migration_6_actions_roles(): |
883 | 883 |
conn, cur = sql.get_connection_and_cursor() |
884 | 884 |
cur.execute('UPDATE wcs_meta SET value = 5 WHERE key = %s', ('sql_level',)) |
885 |
cur.execute('DROP VIEW wcs_all_forms') |
|
885 |
cur.execute('DROP VIEW wcs_all_forms CASCADE')
|
|
886 | 886 | |
887 | 887 |
# hack a formdef table the wrong way, to check it is reconstructed |
888 | 888 |
# properly before the views are created |
... | ... | |
907 | 907 |
def test_migration_10_submission_channel(): |
908 | 908 |
conn, cur = sql.get_connection_and_cursor() |
909 | 909 |
cur.execute('UPDATE wcs_meta SET value = 9 WHERE key = %s', ('sql_level',)) |
910 |
cur.execute('DROP VIEW wcs_all_forms') |
|
910 |
cur.execute('DROP VIEW wcs_all_forms CASCADE')
|
|
911 | 911 | |
912 | 912 |
# hack a formdef table the wrong way, to check it is reconstructed |
913 | 913 |
# properly before the views are created |
... | ... | |
1330 | 1330 | |
1331 | 1331 |
cur.execute('''SELECT COUNT(*) FROM wcs_all_forms WHERE submission_channel = %s''', ('mail',)) |
1332 | 1332 |
assert bool(cur.fetchone()[0] == 1) |
1333 | ||
1334 |
@postgresql |
|
1335 |
def test_materialized_view(): |
|
1336 |
conn, cur = sql.get_connection_and_cursor() |
|
1337 |
if pub.pg_version < 94000: |
|
1338 |
pytest.skip('this requires PostgreSQL >= 9.4') |
|
1339 |
return |
|
1340 | ||
1341 |
sql.refresh_materialized_views() |
|
1342 |
test_last_update_time() |
|
1343 |
assert sql.get_yearly_totals() == [(str(datetime.date.today().year), 2)] |
|
1344 |
conn, cur = sql.get_connection_and_cursor() |
|
1345 | ||
1346 |
formdef = FormDef.select()[0] |
|
1347 |
formdata1 = formdef.data_class()() |
|
1348 |
formdata1.status = 'wf-st1' |
|
1349 |
formdata1.just_created() |
|
1350 |
formdata1.store() |
|
1351 |
assert sql.get_yearly_totals() == [(str(datetime.date.today().year), 2)] |
|
1352 |
sql.refresh_materialized_views() |
|
1353 |
assert sql.get_yearly_totals() == [(str(datetime.date.today().year), 3)] |
wcs/publisher.py | ||
---|---|---|
16 | 16 | |
17 | 17 |
import cPickle |
18 | 18 |
import os |
19 |
import random |
|
19 | 20 |
import sys |
20 | 21 |
import zipfile |
21 | 22 | |
... | ... | |
39 | 40 |
from backoffice import RootDirectory as BackofficeRootDirectory |
40 | 41 |
from admin import RootDirectory as AdminRootDirectory |
41 | 42 |
import sessions |
43 |
from qommon.cron import CronJob |
|
42 | 44 | |
43 | 45 |
from users import User |
44 | 46 |
from tracking_code import TrackingCode |
... | ... | |
110 | 112 |
"missing_appdir_redirect") |
111 | 113 |
configure = classmethod(configure) |
112 | 114 | |
115 |
@classmethod |
|
116 |
def register_cronjobs(cls): |
|
117 |
QommonPublisher.register_cronjobs() |
|
118 |
cls.register_cronjob(CronJob(cls.refresh_materialized_views, |
|
119 |
minutes=[random.randint(0, 59)])) |
|
120 | ||
113 | 121 |
def is_using_postgresql(self): |
114 | 122 |
return bool(self.has_site_option('postgresql') and self.cfg.get('postgresql', {})) |
115 | 123 | |
... | ... | |
238 | 246 |
conn.commit() |
239 | 247 |
cur.close() |
240 | 248 | |
249 |
def refresh_materialized_views(self): |
|
250 |
if not self.is_using_postgresql(): |
|
251 |
return |
|
252 |
import sql |
|
253 |
sql.refresh_materialized_views() |
|
254 | ||
241 | 255 |
def migrate_sql(self): |
242 | 256 |
import sql |
243 | 257 |
sql.migrate() |
wcs/qommon/publisher.py | ||
---|---|---|
746 | 746 |
cls.etld = etld.etld(filename) |
747 | 747 |
load_effective_tld_names = classmethod(load_effective_tld_names) |
748 | 748 | |
749 |
@classmethod |
|
750 |
def register_cronjobs(cls): |
|
751 |
cls.register_cronjob(CronJob(cls.clean_sessions, minutes=range(0, 60, 5))) |
|
752 |
cls.register_cronjob(CronJob(cls.clean_afterjobs, minutes=[random.randint(0, 59)])) |
|
753 |
cls.register_cronjob(CronJob(cls.clean_tempfiles, minutes=[random.randint(0, 59)])) |
|
754 | ||
749 | 755 |
def create_publisher(cls, register_cron=True, register_tld_names=True): |
750 | 756 |
cls.load_extra_dirs() |
751 | 757 |
cls.load_translations() |
752 | 758 |
if register_cron: |
753 |
cls.register_cronjob(CronJob(cls.clean_sessions, minutes=range(0, 60, 5))) |
|
754 |
cls.register_cronjob(CronJob(cls.clean_afterjobs, minutes=[random.randint(0, 59)])) |
|
755 |
cls.register_cronjob(CronJob(cls.clean_tempfiles, minutes=[random.randint(0, 59)])) |
|
759 |
cls.register_cronjobs() |
|
756 | 760 | |
757 | 761 |
if register_tld_names: |
758 | 762 |
cls.load_effective_tld_names() |
wcs/sql.py | ||
---|---|---|
229 | 229 |
if new: |
230 | 230 |
raise |
231 | 231 |
get_publisher().pgconn = None |
232 |
else: |
|
233 |
cur = get_publisher().pgconn.cursor() |
|
234 |
cur.execute('SHOW server_version_num') |
|
235 |
get_publisher().pg_version = cur.fetchone()[0] |
|
236 |
cur.close() |
|
232 | 237 |
return get_publisher().pgconn |
233 | 238 | |
234 | 239 |
def cleanup_connection(): |
... | ... | |
646 | 651 |
for view_name in view_names: |
647 | 652 |
cur.execute('''DROP VIEW IF EXISTS %s''' % view_name) |
648 | 653 | |
654 |
if get_publisher().pg_version >= 94000: |
|
655 |
cur.execute('''DROP MATERIALIZED VIEW IF EXISTS wcs_materialized_all_forms''') |
|
649 | 656 |
cur.execute('''DROP VIEW IF EXISTS wcs_all_forms''') |
650 | 657 | |
651 | 658 |
def do_global_views(conn, cur): |
... | ... | |
1621 | 1628 |
return o |
1622 | 1629 | |
1623 | 1630 | |
1631 |
@guard_postgres |
|
1632 |
def create_materialized_view_if_not_exists(conn, cur): |
|
1633 |
if get_publisher().pg_version < 94000: |
|
1634 |
return |
|
1635 |
cur.execute('''CREATE MATERIALIZED VIEW IF NOT EXISTS wcs_materialized_all_forms |
|
1636 |
AS SELECT * from wcs_all_forms''') |
|
1637 | ||
1638 |
@guard_postgres |
|
1639 |
def refresh_materialized_views(): |
|
1640 |
conn, cur = get_connection_and_cursor() |
|
1641 |
if get_publisher().pg_version < 94000: |
|
1642 |
return |
|
1643 |
try: |
|
1644 |
cur.execute('REFRESH MATERIALIZED VIEW wcs_materialized_all_forms') |
|
1645 |
conn.commit() |
|
1646 |
except psycopg2.ProgrammingError: |
|
1647 |
conn.rollback() |
|
1648 |
create_materialized_view_if_not_exists(conn, cur) |
|
1649 |
cur.close() |
|
1650 | ||
1624 | 1651 |
def get_period_query(period_start=None, period_end=None, criterias=None, parameters=None): |
1625 | 1652 |
clause = [NotNull('receipt_time')] |
1626 |
table_name = 'wcs_all_forms' |
|
1653 |
if get_publisher().pg_version >= 94000: |
|
1654 |
table_name = 'wcs_materialized_all_forms' |
|
1655 |
else: |
|
1656 |
table_name = 'wcs_all_forms' |
|
1627 | 1657 |
if criterias: |
1628 | 1658 |
for criteria in criterias: |
1629 | 1659 |
if criteria.__class__.__name__ == 'Equal' and \ |
... | ... | |
1647 | 1677 |
@guard_postgres |
1648 | 1678 |
def get_weekday_totals(period_start=None, period_end=None, criterias=None): |
1649 | 1679 |
conn, cur = get_connection_and_cursor() |
1680 |
create_materialized_view_if_not_exists(conn, cur) |
|
1650 | 1681 |
statement = '''SELECT DATE_PART('dow', receipt_time) AS weekday, COUNT(*)''' |
1651 | 1682 |
parameters = {} |
1652 | 1683 |
statement += get_period_query(period_start, period_end, criterias, parameters) |
... | ... | |
1670 | 1701 |
@guard_postgres |
1671 | 1702 |
def get_hour_totals(period_start=None, period_end=None, criterias=None): |
1672 | 1703 |
conn, cur = get_connection_and_cursor() |
1704 |
create_materialized_view_if_not_exists(conn, cur) |
|
1673 | 1705 |
statement = '''SELECT DATE_PART('hour', receipt_time) AS hour, COUNT(*)''' |
1674 | 1706 |
parameters = {} |
1675 | 1707 |
statement += get_period_query(period_start, period_end, criterias, parameters) |
... | ... | |
1694 | 1726 |
@guard_postgres |
1695 | 1727 |
def get_monthly_totals(period_start=None, period_end=None, criterias=None): |
1696 | 1728 |
conn, cur = get_connection_and_cursor() |
1729 |
create_materialized_view_if_not_exists(conn, cur) |
|
1697 | 1730 |
statement = '''SELECT DATE_TRUNC('month', receipt_time) AS month, COUNT(*) ''' |
1698 | 1731 |
parameters = {} |
1699 | 1732 |
statement += get_period_query(period_start, period_end, criterias, parameters) |
... | ... | |
1723 | 1756 |
@guard_postgres |
1724 | 1757 |
def get_yearly_totals(period_start=None, period_end=None, criterias=None): |
1725 | 1758 |
conn, cur = get_connection_and_cursor() |
1759 |
create_materialized_view_if_not_exists(conn, cur) |
|
1726 | 1760 |
statement = '''SELECT DATE_TRUNC('year', receipt_time) AS year, COUNT(*)''' |
1727 | 1761 |
parameters = {} |
1728 | 1762 |
statement += get_period_query(period_start, period_end, criterias, parameters) |
1729 |
- |