Projet

Général

Profil

0001-sql-use-materialized-views-if-available-for-statisti.patch

Frédéric Péters, 03 mars 2016 18:33

Télécharger (9,29 ko)

Voir les différences:

Subject: [PATCH] sql: use materialized views if available, for statistics
 (#9553)

 tests/test_sql.py       | 27 ++++++++++++++++++++++++---
 wcs/publisher.py        | 14 ++++++++++++++
 wcs/qommon/publisher.py | 10 +++++++---
 wcs/sql.py              | 36 +++++++++++++++++++++++++++++++++++-
 4 files changed, 80 insertions(+), 7 deletions(-)
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
-