engine.ts 6.82 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
 * Copyright (C) 2016 Centro de Computacao Cientifica e Software Livre
 * Departamento de Informatica - Universidade Federal do Parana
 *
 * This file is part of blend.
 *
 * blend is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * blend is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with blend.  If not, see <http://www.gnu.org/licenses/>.
 */

21
22
import { Dimension } from "./dimension";
import { Metric } from "./metric";
23
24
import { Clause } from "./clause";
import { Filter } from "./filter";
25
import { View } from "./view";
26
import { Query } from "../common/query";
27
import { Graph } from "../util/graph";
28
29

export class Engine {
30
31
32
    private views: View[] = [];
    private metrics: Metric[] = [];
    private dimensions: Dimension[] = [];
33
    private graph: Graph;
34

35
36
37
38
39
40
    constructor () {
        this.views = [];
        this.metrics = [];
        this.dimensions = [];
        this.graph = new Graph();
    }
41
42
43
44
45

    public getViews() {
        return this.views;
    }

46
47
48
49
50
51
52
53
    public getMetricsDescription() {
       return this.metrics.map((i) => i.strOptions());
    }

    public getDimensionsDescription() {
       return this.dimensions.map((i) => i.strOptions());
    }

54
    public addView(view: View) {
55
56
57
58
59
60
        if (this.graph.addView(view)) {
            this.views.push(view);
            return view;
        }

        return null;
61
62
63
    }

    public addMetric(metric: Metric) {
64
65
66
67
68
69
        if (this.graph.addMetric(metric)) {
            this.metrics.push(metric);
            return metric;
        }

        return null;
70
71
72
73
74
75
    }

    public getMetricByName(name: string) {
        let result = this.metrics.find(metric => metric.name === name);

        if (!result) {
76
            throw new Error("The metric named " + name + " was not found");
77
        }
78

79
80
81
82
        return result;
    }

    public addDimension(dimension: Dimension) {
83
84
85
86
87
88
        if (this.graph.addDimension(dimension)) {
            this.dimensions.push(dimension);
            return dimension;
        }

        return null;
89
90
91
92
93
94
    }

    public getDimensionByName(name: string) {
        let result = this.dimensions.find(dimension => dimension.name === name);

        if (!result) {
95
            throw new Error("The dimension named " + name + " was not found");
96
97
98
99
100
        }

        return result;
    }

101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
    public parseClause(strClause: string): Clause {
        let strFilters = strClause.split(",").filter((item: string) => item !== "");
        let filters: Filter[] = [];
        for (let i = 0; i < strFilters.length; ++i) {
            filters.push(this.parseFilter(strFilters[i]));
        }

        return new Clause({filters: filters});
    }

    public parseFilter(strFilter: string): Filter {
        let segment = Filter.segment(strFilter);
        if (segment) {
            // Segment never returns NONE
            let op = Filter.parseOperator(segment.operator);
            let target: Metric|Dimension = null;
            try {
                target = this.getDimensionByName(segment.target);
            }

            catch (e) {
                try {
                    target = this.getMetricByName(segment.target);
                }

                catch (e) {
                    target = null;
                }
            }

            if (!target) {
                throw new Error("Filter could not be created: \"" + segment.target + "\" was not found");
            }

135
            const filter = new Filter({
136
137
138
139
                target: target,
                operator: op,
                value: segment.value
            });
140
141
142
143
144
145

            if (!filter.isValid) {
                throw new Error("Filter could not be created: Operator \"" + segment.operator + "\" is invalid for target \"" + segment.target + "\"");
            }

            return filter;
146
147
148
149
150
        }
        else {
            throw new Error("Filter could not be created: Operator on \"" + strFilter + "\" could not be extracted");
        }
    }
151
152
    public query (q: Query) {
        return this.selectOptimalView(q);
153
154
155
    }

    private selectOptimalView (q: Query) {
156
        let optimalViews = this.graph.cover(q);
157
158
        if (optimalViews.length === 0) {
            throw new Error ("Engine views cannot cover the query");
159
160
        }

161
162
        // If all the metrics and dimensions are the same and only exist one child view
        // return this single child view
163
164
165
        const metrics =  q.metrics;
        const dimensions =  q.dimensions;
        const clauses =  ((q.clauses) ? q.clauses : []);
166
        const sort =  ((q.sort) ? q.sort : []);
167
        if (optimalViews.length === 1 &&
168
169
170
            optimalViews[0].metrics.length === metrics.length &&
            optimalViews[0].dimensions.length === dimensions.length &&
            optimalViews[0].clauses.length === clauses.length &&
171
            optimalViews[0].sort.length === sort.length &&
172
173
            optimalViews[0].metrics.every((item) => metrics.indexOf(item) !== -1) &&
            optimalViews[0].dimensions.every((item) => dimensions.indexOf(item) !== -1) &&
174
175
            perfectMatch(optimalViews[0].clauses, clauses) &&
            perfectOrder(optimalViews[0].sort, sort)) {
176
            return optimalViews[0];
177
178
179
        }
        else {
            let options = {
180
181
182
                metrics: metrics,
                dimensions: dimensions,
                clauses: clauses,
183
                sort: sort,
184
                materialized: false,
185
                origin: false, // Never a dynamic generated view will be origin
186
187
188
189
                childViews: optimalViews
            };

            let view = new View(options);
190
191
192
193
194
195
196
            // this.addView(view);
            /*
                This line has been removed for now because not all views can be
                re-used by other views (unmaterializeble views), and so far this
                is only detected in the adapter, when this can be detected in
                engine, than the queries can be added again to the engine
            */
197
198
199
200
            return view;
        }
    }
}
201
202

function perfectMatch (array1: Clause[],
203
                       array2: Clause[]): boolean {
204
205
206
207
    return array1.every((item: Clause) => {
        return array2.some((otherItem: Clause) => item.id === otherItem.id);
    });
}
208
209
210
211
212
213
214
215
216
217
218
219

function perfectOrder (array1: (Metric|Dimension)[],
                       array2: (Metric|Dimension)[]): boolean {
    // Assuming that the arrays have the same length
    for (let i = 0; i < array1.length; ++i) {
        if (array1[i].name !== array2[i].name) {
            return false;
        }
    }

    return true;
}