Commit 31ee32bf authored by Lucas Fernandes de Oliveira's avatar Lucas Fernandes de Oliveira
Browse files

Merge branch 'refactor/engine' into 'master'

Refactor/engine

See merge request !3
parents c4092417 d0e10959
Pipeline #8827 failed with stage
in 1 minute and 43 seconds
......@@ -18,9 +18,9 @@
* along with blend. If not, see <http://www.gnu.org/licenses/>.
*/
import { View } from "../core/view";
import { Adapter } from "../core/adapter";
import { AggregationType } from "../common/aggregationType";
import { AggregationType } from "../common/types";
import { View } from "../core/view";
interface ParsedView {
query: string;
......
......@@ -18,8 +18,11 @@
* along with blend. If not, see <http://www.gnu.org/licenses/>.
*/
import { Metric } from "../core/metric";
import { Dimension } from "../core/dimension";
export interface Query {
public metrics: string[];
public dimensions: string[];
public metrics: Metric[];
public dimensions: Dimension[];
}
/*
* 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/>.
*/
export interface DimensionOptions {
name: string;
}
export class Dimension {
public readonly name: string;
constructor(options: DimensionOptions) {
this.name = options.name;
}
}
......@@ -21,78 +21,94 @@
import { expect } from "chai";
import { Engine } from "./engine";
import { Metric } from "./metric";
import { Dimension } from "./dimension";
import { View } from "./view";
import { ViewOptions } from "./view";
import { Query } from "../common/query";
import { AggregationType } from "../common/aggregationType";
import { AggregationType } from "../common/types";
describe("engine class", () => {
let query: Query = {
metrics: [],
dimensions: []
};
const engine = new Engine();
let views: View[] = [];
const met1 = new Metric({ name: "met:1", aggregation: AggregationType.SUM });
const met2 = new Metric({ name: "met:2", aggregation: AggregationType.AVG });
const met3 = new Metric({ name: "met:3", aggregation: AggregationType.AVG });
const met4 = new Metric({ name: "met:4", aggregation: AggregationType.SUM });
const met5 = new Metric({ name: "met:5", aggregation: AggregationType.SUM });
const met6 = new Metric({ name: "met:6", aggregation: AggregationType.AVG });
const met7 = new Metric({ name: "met:7", aggregation: AggregationType.COUNT });
const met8 = new Metric({ name: "met:8", aggregation: AggregationType.COUNT });
const met9 = new Metric({ name: "met:9", aggregation: AggregationType.SUM });
const met10 = new Metric({ name: "met:10", aggregation: AggregationType.COUNT });
const met11 = new Metric({ name: "met:11", aggregation: AggregationType.COUNT });
let viewBuilder: Query[] = [
{ metrics: ["met:1", "met:2", "met:3"], dimensions: ["dim:1", "dim:2"]},
{ metrics: ["met:1", "met:3", "met:5"], dimensions: ["dim:1", "dim:2"]},
{ metrics: ["met:3", "met:4", "met:7"], dimensions: ["dim:4", "dim:5"]},
{ metrics: ["met:6", "met:7"], dimensions: ["dim:3", "dim:4", "dim:5", "dim:6"]},
{ metrics: ["met:8", "met:2", "met:3"], dimensions: ["dim:1", "dim:2", "dim:7"]},
{ metrics: ["met:1", "met:2", "met:3"], dimensions: ["dim:1", "dim:2"]},
{ metrics: ["met:2", "met:4"], dimensions: ["dim:1", "dim:2"]},
{ metrics: ["met:8"], dimensions: ["dim:8", "dim:9", "dim:10"]},
{ metrics: ["met:9"], dimensions: ["dim:8", "dim:9", "dim:10"]},
{ metrics: ["met:10"], dimensions: ["dim:8", "dim:9", "dim:10"]}
];
const dim1 = new Dimension({ name: "dim:1" });
const dim2 = new Dimension({ name: "dim:2" });
const dim3 = new Dimension({ name: "dim:3" });
const dim4 = new Dimension({ name: "dim:4" });
const dim5 = new Dimension({ name: "dim:5" });
const dim6 = new Dimension({ name: "dim:6" });
const dim7 = new Dimension({ name: "dim:7" });
const dim8 = new Dimension({ name: "dim:8" });
const dim9 = new Dimension({ name: "dim:9" });
const dim10 = new Dimension({ name: "dim:10" });
const dim11 = new Dimension({ name: "dim:11" });
let iterable: [string, AggregationType][] = [
["met:1", AggregationType.SUM],
["met:2", AggregationType.AVG],
["met:3", AggregationType.AVG],
["met:4", AggregationType.SUM],
["met:5", AggregationType.SUM],
["met:6", AggregationType.AVG],
["met:7", AggregationType.COUNT],
["met:8", AggregationType.COUNT],
["met:9", AggregationType.SUM],
["met:10", AggregationType.COUNT],
];
engine.addMetric(met1);
engine.addMetric(met2);
engine.addMetric(met3);
engine.addMetric(met4);
engine.addMetric(met5);
engine.addMetric(met6);
engine.addMetric(met7);
engine.addMetric(met8);
engine.addMetric(met9);
engine.addMetric(met10);
let map: Map<string, AggregationType> = new Map(iterable);
engine.addDimension(dim1);
engine.addDimension(dim2);
engine.addDimension(dim3);
engine.addDimension(dim4);
engine.addDimension(dim5);
engine.addDimension(dim6);
engine.addDimension(dim7);
engine.addDimension(dim8);
engine.addDimension(dim9);
engine.addDimension(dim10);
for (let i: number = 0; i < 10; ++ i) {
query.metrics.push("met:" + (i + 1));
query.dimensions.push("dim:" + (i + 1));
let options: ViewOptions = {
metrics: viewBuilder[i].metrics,
dimensions: viewBuilder[i].dimensions,
materialized: true,
aggregationMap: map,
childViews: []
};
views.push(new View (options));
}
let views: View[] = [
new View({ metrics: [met1, met2, met3], dimensions: [dim1, dim2]}),
new View({ metrics: [met1, met3, met5], dimensions: [dim1, dim2]}),
new View({ metrics: [met3, met4, met7], dimensions: [dim4, dim5]}),
new View({ metrics: [met6, met7], dimensions: [dim3, dim4, dim5, dim6]}),
new View({ metrics: [met8, met2, met3], dimensions: [dim1, dim2, dim7]}),
new View({ metrics: [met1, met2, met3], dimensions: [dim1, dim2]}),
new View({ metrics: [met2, met4], dimensions: [dim1, dim2]}),
new View({ metrics: [met8], dimensions: [dim8, dim9, dim10]}),
new View({ metrics: [met9], dimensions: [dim8, dim9, dim10]}),
new View({ metrics: [met10], dimensions: [dim8, dim9, dim10]})
];
views.push(new View({
metrics: ["met:1", "met:2", "met:3", "met:4", "met:5"],
dimensions: ["dim:1", "dim:2"],
metrics: [met1, met2, met3, met4, met5],
dimensions: [dim1, dim2],
materialized: false,
aggregationMap: map,
childViews: [views[0], views[6]]
}));
views.push(new View({
metrics: ["met:8", "met:9", "met:10"],
dimensions: ["dim:8", "dim:9", "dim:10"],
metrics: [met8, met9, met10],
dimensions: [dim8, dim9, dim10],
materialized: false,
aggregationMap: map,
childViews: [views[7], views[8], views[9]]
}));
views.forEach((view) => engine.addView(view));
it("should be create a fill that cover the query and has 4 children", () => {
let engine: Engine = new Engine (views);
let query = {
metrics : [met1, met2, met3, met4, met5, met6, met7, met8, met9, met10]
, dimensions : [dim1, dim2, dim3, dim4, dim5, dim6, dim7, dim8, dim9, dim10]
};
let optimalView = engine.query(query);
expect(optimalView).to.be.an("object");
expect(optimalView).to.have.property("metrics");
......@@ -104,11 +120,11 @@ describe("engine class", () => {
expect(optimalView.metrics.length === 10);
expect(optimalView.dimensions.length === 10);
expect(optimalView.childViews.length === 4);
let metAux: number[] = optimalView.metrics.sort().map((item: string) => {
return Number(item.split(":")[1]);
let metAux: number[] = optimalView.metrics.sort().map((met: Metric) => {
return Number(met.name.split(":")[1]);
});
let dimAux: number[] = optimalView.dimensions.sort().map((item: string) => {
return Number(item.split(":")[1]);
let dimAux: number[] = optimalView.dimensions.sort().map((dim: Dimension) => {
return Number(dim.name.split(":")[1]);
});
for (let i: number = 1; i <= 10; ++i) {
expect(dimAux[i] === i);
......@@ -116,10 +132,9 @@ describe("engine class", () => {
}
});
it("should throw an exception, query with non-existent metric", () => {
let engine: Engine = new Engine (views);
let error: boolean = false;
try {
engine.query({metrics: ["met:11"], dimensions: ["dim:1"]});
engine.query({metrics: [met11], dimensions: [dim1]});
}
catch (e){
error = true;
......@@ -130,10 +145,9 @@ describe("engine class", () => {
});
it("should throw an exception, query with non-existent dimension", () => {
let engine: Engine = new Engine (views);
let error: boolean = false;
try {
engine.query({metrics: ["met:1"], dimensions: ["dim:11"]});
engine.query({metrics: [met1], dimensions: [dim11]});
}
catch (e){
error = true;
......
......@@ -18,30 +18,79 @@
* along with blend. If not, see <http://www.gnu.org/licenses/>.
*/
import { Query } from "../common/query";
import { Dimension } from "./dimension";
import { Metric } from "./metric";
import { View } from "./view";
import { AggregationType } from "../common/aggregationType";
import { Query } from "../common/query";
export class Engine {
private views: View[];
private views: View[] = [];
private metrics: Metric[] = [];
private dimensions: Dimension[] = [];
constructor () { }
public getViews() {
return this.views;
}
public addView(view: View) {
this.views.push(view);
return view;
}
constructor (v: View[]) {
this.views = v;
public addMetric(metric: Metric) {
this.metrics.push(metric);
return metric;
}
public getMetricByName(name: string) {
let result = this.metrics.find(metric => metric.name === name);
if (!result) {
throw new Error("The metric named " + name + " was not found");
}
return result;
}
public addDimension(dimension: Dimension) {
this.dimensions.push(dimension);
return dimension;
}
public getDimensionByName(name: string) {
let result = this.dimensions.find(dimension => dimension.name === name);
if (!result) {
throw new Error("The dimension named " + name + " was not found");
}
return result;
}
public query (q: Query) {
return this.selectOptimalView(q);
}
public query (q: Query) { return this.selectOptimalView(q); }
public insert (data: any) { /*to Implement*/ }
private selectOptimalView (q: Query) {
let objective = q.metrics.concat(q.dimensions);
let metricsName = q.metrics.map((met) => met.name);
let dimensionsName = q.dimensions.map((dim) => dim.name);
let objective = metricsName.concat(dimensionsName);
let optimalViews: View[] = [];
let activeViews = this.views;
let activeViews = this.getViews();
// Run this block until all metrics and dimmensions are covered
// run this block until all metrics and dimmensions are covered
while (objective.length > 0) {
let bestView: View;
let shortestDistance = objective.length + 1;
// remove views from the activeViews if they don't intersect
// with the objective
activeViews = activeViews.filter((view: View) => {
let cover = view.metrics.concat(view.dimensions);
metricsName = view.metrics.map((met) => met.name);
dimensionsName = view.dimensions.map((dim) => dim.name);
let cover = metricsName.concat(dimensionsName);
let intersection = cover.filter((item: string) => {
return objective.indexOf(item) !== -1;
});
......@@ -62,40 +111,38 @@ export class Engine {
});
if (shortestDistance === objective.length + 1) {
throw new Error ("Engine views cannot cover the query");
throw new Error("Engine views cannot cover the query");
}
optimalViews.push(bestView);
//remove metrics and dimensions corevered by the bestView
// remove metrics and dimensions corvered by the bestView from the
// objective (i.e. the object is already met for those metrics/dimensions)
objective = objective.filter((item: string) => {
let cover = bestView.dimensions.concat(bestView.metrics);
metricsName = bestView.metrics.map((met) => met.name);
dimensionsName = bestView.dimensions.map((dim) => dim.name);
let cover = dimensionsName.concat(metricsName);
return cover.indexOf(item) === -1;
});
}
if (optimalViews.length === 1) {
// if there is a single view that covers the query, we just return it
return optimalViews.pop();
}
else {
let map = new Map();
optimalViews.forEach((view: View) => {
view.aggregationMap.forEach((value: AggregationType, key: string) => {
map.set(key, value);
});
});
// if more than one view is necessary to cover the query,
// we need to compose them into a new singular virtual view
let options = {
metrics: q.metrics,
dimensions: q.dimensions,
materialized: false,
aggregationMap: map,
childViews: optimalViews
};
let view = new View(options);
this.views.push(view);
this.addView(view);
return view;
}
}
......
/*
* 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/>.
*/
import { AggregationType } from "../common/types";
export interface MetricOptions {
name: string;
aggregation: AggregationType;
}
export class Metric {
public readonly name: string;
public readonly aggregation: AggregationType;
constructor(options: MetricOptions) {
this.name = options.name;
this.aggregation = options.aggregation;
}
}
......@@ -18,40 +18,33 @@
* along with blend. If not, see <http://www.gnu.org/licenses/>.
*/
import { Dimension } from "./dimension";
import { Metric } from "./metric";
import { Hash } from "../util/hash";
import { AggregationType } from "../common/aggregationType";
export interface ViewOptions {
metrics: string[];
dimensions: string[];
materialized: boolean;
aggregationMap: Map <string, AggregationType>;
childViews: View[];
metrics: Metric[];
dimensions: Dimension[];
materialized?: boolean;
childViews?: View[];
}
export class View {
public readonly id: string;
public readonly metrics: string[];
public readonly dimensions: string[];
public readonly metrics: Metric[];
public readonly dimensions: Dimension[];
public readonly materialized: boolean;
public readonly aggregationMap: Map<string, AggregationType>;
public childViews: View[];
constructor (options: ViewOptions) {
this.metrics = options.metrics;
this.dimensions = options.dimensions;
this.materialized = options.materialized;
this.childViews = options.childViews;
this.aggregationMap = options.aggregationMap;
this.id = Hash.sha1(options.metrics.sort(), options.dimensions.sort());
}
public getAggregatationType(metric: string): AggregationType {
if (this.aggregationMap.has(metric)) {
return AggregationType.NONE;
}
this.materialized = options.materialized || true;
this.childViews = options.childViews || [];
return this.aggregationMap.get(metric);
// calculate the id of the view based on it's metrics and dimensions
let metricsNames = this.metrics.map(metric => metric.name);
let dimensionsNames = this.dimensions.map(dimension => dimension.name);
this.id = Hash.sha1(metricsNames.concat(dimensionsNames));
}
}
......@@ -39,7 +39,7 @@ export class Hash {
}
})
.sort()
.map((objStr) => {
.forEach((objStr) => {
hash.update(objStr);
});
......
......@@ -4,16 +4,18 @@
"target": "es6",
"module": "commonjs",
"noImplicitAny": true,
"experimentalDecorators": true,
"outDir": "dist/"
},
"exclude": [
"node_modules"
],
"compileOnSave": false,
"compileOnSave": true,
"typeRoots": [
"node_modules/@types"
],
"types": [
"mocha",
"d3-force"
]
}
......@@ -4,6 +4,7 @@
"no-var-requires": false,
"object-literal-sort-keys": false,
"one-line": false,
"trailing-comma": false
"trailing-comma": false,
"interface-name": false
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment