Project

General

Profile

0002-allow-use-of-join-in-measures-fixes-29914.patch

Benjamin Dauvergne, 19 Apr 2019 10:59 AM

Download (9.3 KB)

View differences:

Subject: [PATCH 2/2] allow use of join in measures (fixes #29914)

 bijoe/engine.py                       |  5 +++-
 bijoe/schemas.py                      |  4 ++-
 tests/fixtures/schema1/01_schema.json | 21 ++++++++++++++
 tests/fixtures/schema1/01_schema.sql  | 22 ++++++++++----
 tests/test_schema1.py                 | 42 +++++++++++++++++++++++----
 5 files changed, 81 insertions(+), 13 deletions(-)
bijoe/engine.py
261 261

  
262 262
            for dimension_name in drilldown:
263 263
                dimension = self.dimensions[dimension_name]
264
                joins.update(dimension.join or [])
264
                if dimension.join:
265
                    joins.update(dimension.join)
265 266
                projections.append('%s AS %s' % (dimension.value_label or dimension.value,
266 267
                                                 dimension.name))
267 268
                group_by.append(dimension.group_by or dimension.value)
......
273 274

  
274 275
            for measure_name in measures:
275 276
                measure = self.get_measure(measure_name)
277
                if measure.join:
278
                    joins.update(measure.join)
276 279
                if measure.expression not in projections:
277 280
                    projections.append(measure.expression + ' AS ' + measure.name)
278 281
            sql = 'SELECT ' + ', '.join(projections)
bijoe/schemas.py
125 125

  
126 126

  
127 127
class Measure(Base):
128
    __slots__ = ['name', 'label', 'type', 'expression']
128
    __slots__ = ['name', 'label', 'type', 'expression', 'join']
129 129
    __types__ = {
130 130
        'name': str,
131 131
        'label': six.text_type,
132 132
        'type': type_cast,
133 133
        'expression': str,
134
        'join': [str],
134 135
    }
136
    join = None
135 137

  
136 138

  
137 139
class Dimension(Base):
tests/fixtures/schema1/01_schema.json
65 65
                    "master": "outersubcategory.category_id",
66 66
                    "detail": "id",
67 67
                    "kind": "full"
68
                },
69
                {
70
                    "name": "subfacts",
71
                    "table": "subfacts",
72
                    "master": "id",
73
                    "detail": "fact_id",
74
                    "kind": "left"
68 75
                }
69 76
            ],
70 77
            "dimensions": [
......
189 196
                    "label": "pourcentage des demandes",
190 197
                    "type": "percent",
191 198
                    "expression": "case (select count({fact_table}.id) from {table_expression} where {where_conditions}) when 0 then null else count({fact_table}.id) * 100. / (select count({fact_table}.id) from {table_expression} where {where_conditions}) end"
199
                },
200
                {
201
                    "name": "subfacts_min_delay_integer_1",
202
                    "label": "subfacts min delay with integer=1",
203
                    "type": "duration",
204
                    "expression": "min(subfacts.duration) filter (where subfacts.integer = 1)",
205
                    "join": ["subfacts"]
206
                },
207
                {
208
                    "name": "subfacts_max_delay_integer_1",
209
                    "label": "subfacts max delay with integer=1",
210
                    "type": "duration",
211
                    "expression": "max(subfacts.duration) filter (where subfacts.integer = 1)",
212
                    "join": ["subfacts"]
192 213
                }
193 214
            ]
194 215
        }
tests/fixtures/schema1/01_schema.sql
29 29
    string varchar
30 30
);
31 31

  
32
CREATE TABLE subfacts (
33
    id serial primary key,
34
    fact_id integer references schema1.facts(id),
35
    integer integer,
36
    datetime timestamp with time zone,
37
    duration interval
38
);
39

  
32 40
INSERT INTO category (ord, label) VALUES
33 41
   (1, 'caté1'),
34 42
   (0, 'caté2'),
......
50 58
   ('2017-01-01', '2017-01-01 10:00', 1, FALSE, 10, 1, 1, 1, 1, 'a'),
51 59
   ('2017-01-02', '2017-01-02 10:00', 1, TRUE, 10, 3, 3, 3, 3, 'b'),
52 60
   ('2017-01-03', '2017-01-03 10:00', 1, FALSE, 10, NULL, NULL, NULL, NULL, 'a'),
53
   ('2017-01-04', '2017-01-04 10:00', 1, FALSE, 10, 1, 1, 1, 1, 'a'),
54
   ('2017-01-05', '2017-01-05 10:00', 1, TRUE, 10, 1, 1, 1, 1, 'c'),
55
   ('2017-01-06', '2017-01-06 10:00', 1, FALSE, 10, 1, 1, 1, 1, NULL),
61
   ('2017-01-04', '2017-01-04 10:00', 1, FALSE, 10, 4, 1, 1, 1, 'a'),
62
   ('2017-01-05', '2017-01-05 10:00', 1, TRUE, 10, 6, 1, 1, 1, 'c'),
63
   ('2017-01-06', '2017-01-06 10:00', 1, FALSE, 10, 7, 1, 1, 1, NULL),
56 64
   ('2017-01-07', '2017-01-07 10:00', 1, TRUE, 10, 1, 1, 1, 1, 'a'),
57 65
   ('2017-01-08', '2017-01-08 10:00', 1, FALSE, 10, 1, 1, 1, 1, 'a'),
58 66
   ('2017-01-09', '2017-01-09 10:00', 1, TRUE, 10, 1, 1, 1, 1, 'a'),
59
   ('2017-01-10', '2017-01-10 10:00', 1, FALSE, 10, 1, 1, 1, 1, 'a'),
67
   ('2017-01-10', '2017-01-10 10:00', 1, FALSE, 10, 5, 1, 1, 1, 'a'),
60 68
   ('2017-02-01', '2017-02-01 10:00', 1, TRUE, 10, 1, 1, 1, 1, 'a'),
61
   ('2017-03-01', '2017-03-01 10:00', 1, FALSE, 10, 1, 1, 1, 1, 'c'),
69
   ('2017-03-01', '2017-03-01 10:00', 1, FALSE, 10, 9, 1, 1, 1, 'c'),
62 70
   ('2017-04-01', '2017-04-01 10:00', 1, TRUE, 10, 1, 1, 1, 1, 'a'),
63 71
   ('2017-05-01', '2017-05-01 10:00', 1, FALSE, 10, 1, 1, 1, 1, 'a'),
64
   ('2017-06-01', '2017-06-01 10:00', 1, TRUE, 10, 1, 1, 1, 1, 'c'),
72
   ('2017-06-01', '2017-06-01 10:00', 1, TRUE, 10, 8, 1, 1, 1, 'c'),
65 73
   ('2017-07-01', '2017-07-01 10:00', 1, FALSE, 10, 1, 1, 1, 1, 'a'),
66 74
   ('2017-08-01', '2017-08-01 10:00', 1, TRUE, 10, 1, 1, 1, 1, 'b');
75

  
76
INSERT INTO subfacts (fact_id, integer, datetime, duration) SELECT facts.id, floor(random() * 3), val, (serie.val - facts.datetime) FROM facts, LATERAL (SELECT * FROM generate_series(facts.datetime, facts.datetime + '10 hours'::interval, '1 hour')) as serie(val);
tests/test_schema1.py
16 16
    form.set('drilldown_x', 'innersubcategory')
17 17
    response = form.submit('visualize')
18 18
    assert 'big-msg-info' not in response
19
    assert get_table(response) == [
20
        [u'Inner SubCategory', u'subé3', u'subé1'],
21
        ['number of rows', '1', '15'],
22
    ]
19
    assert get_table(response) == [['Inner SubCategory', u'sub\xe94', u'sub\xe95',
20
                                    u'sub\xe96', u'sub\xe98', u'sub\xe99',
21
                                    u'sub\xe97', u'sub\xe93', u'sub\xe91'],
22
                                   ['number of rows', '1', '1', '1', '1', '1',
23
                                    '1', '1', '9']]
23 24
    form = response.form
24 25
    form.set('representation', 'table')
25 26
    form.set('measure', 'simple_count')
......
121 122
    assert get_table(response) == get_ods_table(ods_response)[1:]
122 123
    root = get_ods_document(ods_response)
123 124
    nodes = root.findall('.//{%s}table-cell' % TABLE_NS)
124
    assert len([node for node in nodes if node.attrib['{%s}value-type' % OFFICE_NS] == 'float']) == 2
125
    assert len([node for node in nodes if node.attrib['{%s}value-type' % OFFICE_NS] == 'float']) == 8
125 126

  
126 127

  
127 128
def test_truncated_previous_year_range_on_datetime(schema1, app, admin, freezer):
......
147 148
        ['2017', '10', '1', '1', '1', '1', '1', '1', '1', '17'],
148 149
        ['Total', '10', '1', '1', '1', '1', '1', '1', '1', '17'],
149 150
    ]
151

  
152

  
153
def test_complex_measure_with_join(schema1, app, admin):
154
    login(app, admin)
155
    response = app.get('/').follow()
156
    response = response.click('Facts 1')
157
    form = response.form
158
    form.set('representation', 'table')
159
    form.set('measure', 'subfacts_min_delay_integer_1')
160
    form.set('drilldown_x', 'innercategory')
161
    response = form.submit('visualize')
162
    assert 'big-msg-info' not in response
163
    # skip first line of ODS table as it's a header not present in the HTML display
164
    table = get_table(response)
165
#[['Inner Category', u'cat\xe92', u'cat\xe93', u'cat\xe91'],
166
#['subfacts min delay with integer=1',
167
# "moins d'1 heure",
168
# "moins d'1 heure",
169
# "moins d'1 heure"]]
170
    assert all(len(row) == 4 for row in table)
171
    assert table[0][0] == 'Inner Category'
172
    assert table[1][0] == 'subfacts min delay with integer=1'
173

  
174
    form.set('measure', 'subfacts_max_delay_integer_1')
175
    response = form.submit('visualize')
176
    table = get_table(response)
177
#[['Inner Category', u'cat\xe92', u'cat\xe93', u'cat\xe91'],
178
# ['subfacts max delay with integer=1', '9 heure(s)', '8 heure(s)', '10 heure(s)']]
179
    assert all(len(row) == 4 for row in table)
180
    assert table[0][0] == 'Inner Category'
181
    assert table[1][0] == 'subfacts max delay with integer=1'
150
-