From 0424536ce190c86039482c304fca84a59613ed3a Mon Sep 17 00:00:00 2001
From: Lucas Fernandes de Oliveira <lfo14@inf.ufpr.br>
Date: Thu, 6 Jul 2017 15:56:25 -0300
Subject: [PATCH] Issue #22: Change cover algorithm to use graph

Signed-off-by: Lucas Fernandes de Oliveira <lfo14@inf.ufpr.br>
---
 src/core/engine.spec.ts |   4 +-
 src/core/engine.ts      | 125 +++-------
 src/util/graph.spec.ts  | 336 +++++++++++++++++++++++++++
 src/util/graph.ts       | 494 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 862 insertions(+), 97 deletions(-)
 create mode 100644 src/util/graph.spec.ts
 create mode 100644 src/util/graph.ts

diff --git a/src/core/engine.spec.ts b/src/core/engine.spec.ts
index 69e1d4d3..2bdbb749 100644
--- a/src/core/engine.spec.ts
+++ b/src/core/engine.spec.ts
@@ -140,7 +140,7 @@ describe("engine class", () => {
             return optView.childViews[0].dimensions.some((item) => item.name === subdim[0].name);
         });
         expect(optimalView).satisfy((optView: View) => {
-            return optView.childViews[0].dimensions.some((item) => item.name === subdim[0].name);
+            return optView.childViews[0].dimensions.some((item) => item.name === subdim[1].name);
         });
     });
 
@@ -177,7 +177,7 @@ describe("engine class", () => {
         }
         catch (e){
             error = true;
-            expect(e.message).to.be.equal("Engine sub-dimention " + subdim[3].name + " with no parent");
+            expect(e.message).to.be.equal("Engine views cannot cover the query");
 
         }
         expect(error).to.be.true;
diff --git a/src/core/engine.ts b/src/core/engine.ts
index af1640a8..39fbfbb5 100644
--- a/src/core/engine.ts
+++ b/src/core/engine.ts
@@ -20,29 +20,43 @@
 
 import { Dimension } from "./dimension";
 import { Metric } from "./metric";
-import { View, ChildView } from "./view";
+import { View } from "./view";
 import { Query } from "../common/query";
-import { RelationType } from "../common/types";
+import { Graph } from "../util/graph";
 
 export class Engine {
     private views: View[] = [];
     private metrics: Metric[] = [];
     private dimensions: Dimension[] = [];
+    private graph: Graph;
 
-    constructor () { }
+    constructor () {
+        this.views = [];
+        this.metrics = [];
+        this.dimensions = [];
+        this.graph = new Graph();
+    }
 
     public getViews() {
         return this.views;
     }
 
     public addView(view: View) {
-        this.views.push(view);
-        return view;
+        if (this.graph.addView(view)) {
+            this.views.push(view);
+            return view;
+        }
+
+        return null;
     }
 
     public addMetric(metric: Metric) {
-        this.metrics.push(metric);
-        return metric;
+        if (this.graph.addMetric(metric)) {
+            this.metrics.push(metric);
+            return metric;
+        }
+
+        return null;
     }
 
     public getMetricByName(name: string) {
@@ -56,8 +70,12 @@ export class Engine {
     }
 
     public addDimension(dimension: Dimension) {
-        this.dimensions.push(dimension);
-        return dimension;
+        if (this.graph.addDimension(dimension)) {
+            this.dimensions.push(dimension);
+            return dimension;
+        }
+
+        return null;
     }
 
     public getDimensionByName(name: string) {
@@ -75,92 +93,9 @@ export class Engine {
     }
 
     private selectOptimalView (q: Query) {
-        let metUncovered = q.metrics.map((met) => met);
-        let dimUncovered = q.dimensions.map((dim) => dim);
-        let optimalViews: ChildView[] = [];
-        let activeViews = this.getViews();
-
-        // run this block until all metrics and dimmensions are covered
-        while (metUncovered.length  > 0 || dimUncovered.length > 0) {
-            let bestView: ChildView;
-            let coverLength = metUncovered.length + dimUncovered.length;
-            let shortestDistance = coverLength + 1;
-
-            // remove views from the activeViews if they don't intersect
-            // with the objective
-            activeViews = activeViews.filter((view: View) => {
-                let metIntersection = metUncovered.filter((met: Metric) => {
-                    return view.metrics.some((item) => item.name === met.name);
-                });
-
-                let dimIntersection = dimUncovered.filter((dim: Dimension) => {
-                    let r: boolean = view.dimensions.some((item) => item.name === dim.name);
-                    while (!r && dim.relation !== RelationType.NONE) {
-                        if (dim.parent === null) {
-                            throw new Error("Engine sub-dimention " + dim.name + " with no parent");
-                        }
-                        r = view.dimensions.some((item) => item.name === dim.parent.name);
-                        dim = dim.parent;
-                    }
-                    return r;
-                });
-
-                let intersection = metIntersection.length + dimIntersection.length;
-
-                if (intersection > 0) {
-                    let distance = coverLength - intersection;
-
-                    if (distance < shortestDistance) {
-                        bestView = {
-                            view: view,
-                            metrics: metIntersection,
-                            dimensions: dimIntersection
-                        };
-                        shortestDistance = distance;
-                    }
-
-                    else if (distance === shortestDistance &&
-                            view.dimensions.length < bestView.dimensions.length) {
-                        // priorizes views with less dimensions
-                        bestView = {
-                            view: view,
-                            metrics: metIntersection,
-                            dimensions: dimIntersection
-                        };
-                    }
-
-                    return true;
-                }
-
-                /*If the intersection is empty,
-                  remove this element from future searches*/
-                return false;
-            });
-
-            if (shortestDistance  === coverLength + 1) {
-                throw new Error("Engine views cannot cover the query");
-            }
-
-            optimalViews.push(bestView);
-
-            // remove metrics and dimensions corvered by the bestView from the
-            // objective (i.e. the object is already met for those metrics/dimensions)
-
-            metUncovered = metUncovered.filter((met: Metric) => {
-                return !bestView.metrics.some((item) => item.name === met.name);
-            });
-
-            dimUncovered = dimUncovered.filter((dim: Dimension) => {
-                let r: boolean = bestView.dimensions.some((item) => item.name === dim.name);
-                while (!r && dim.relation !== RelationType.NONE) {
-                    if (dim.parent === null) {
-                        throw new Error("Engine sub-dimention " + dim.name + " with no parent");
-                    }
-                    r = bestView.dimensions.some((item) => item.name === dim.parent.name);
-                    dim = dim.parent;
-                }
-                return !r;
-            });
+        let optimalViews = this.graph.cover(q.metrics, q.dimensions);
+        if (optimalViews.length === 0) {
+            throw new Error ("Engine views cannot cover the query");
         }
 
         // If all the metrics and dimensions are the same and only exist one child view
diff --git a/src/util/graph.spec.ts b/src/util/graph.spec.ts
new file mode 100644
index 00000000..17de06d0
--- /dev/null
+++ b/src/util/graph.spec.ts
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2017 Centro de Computacao Cientifica e Software Livre
+ * Departamento de Informatica - Universidade Federal do Parana
+ *
+ * This file is part of blendb.
+ *
+ * blendb 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.
+ *
+ * blendb 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 blendb.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import { expect } from "chai";
+
+import { Metric } from "../core/metric";
+import { Dimension } from "../core/dimension";
+import { View } from "../core/view";
+import { Graph } from "./graph";
+import { AggregationType, RelationType } from "../common/types";
+
+describe("graph class", () => {
+
+    it("should not create 2 vertices with the same dimension", () => {
+        let g = new Graph();
+        let dim = new Dimension({name: "dim:test", dataType: "string"});
+
+        expect(g.addDimension(dim)).to.be.true;
+        expect(g.addDimension(dim)).to.be.false;
+    });
+
+    it("should not create 2 vertices with same metric", () => {
+        let g = new Graph();
+        let met = new Metric({
+            name: "met:test",
+            aggregation: AggregationType.SUM,
+            dataType: "string"
+        });
+
+        expect(g.addMetric(met)).to.be.true;
+        expect(g.addMetric(met)).to.be.false;
+    });
+
+    it("should not create a vertex with a invalid dimension", () => {
+        let g = new Graph();
+        let dim: Dimension;
+
+        expect(g.addDimension(dim)).to.be.false;
+    });
+
+    it("should not create a vertex with a invalid metric", () => {
+        let g = new Graph();
+        let met: Metric;
+
+        expect(g.addMetric(met)).to.be.false;
+    });
+
+    it("should not create a vertex with a subdimension when parent is not a vertex", () => {
+        let g = new Graph();
+        let dim = new Dimension({name: "dim:test", dataType: "string"});
+        let subdim = new Dimension({
+            name: "dim:sub_test",
+            dataType: "string",
+            relation: RelationType.MONTH,
+            parent: dim
+        });
+
+        expect(g.addDimension(subdim)).to.be.false;
+    });
+
+    it("should add a set of views", () => {
+        let dims = [
+            new Dimension({name: "dim:0", dataType: "string"}),
+            new Dimension({name: "dim:1", dataType: "string"}),
+            new Dimension({name: "dim:2", dataType: "string"}),
+            new Dimension({name: "dim:3", dataType: "string"})
+        ];
+
+        let g = new Graph();
+
+        for (let i = 0; i < dims.length; ++i) {
+            expect(g.addDimension(dims[i])).to.be.true;
+        }
+
+        let views = [
+            new View({
+                metrics: [],
+                dimensions: [dims[0], dims[1]],
+                materialized: true
+            }),
+            new View({
+                metrics: [],
+                dimensions: [dims[2], dims[3]],
+                materialized: true
+            }),
+            new View({
+                metrics: [],
+                dimensions: dims,
+                materialized: true
+            })
+        ];
+
+        for (let i = 0; i < views.length; ++i) {
+            expect(g.addView(views[i])).to.be.true;
+        }
+    });
+
+    it("should not add a view twice", () => {
+        let dims = [
+            new Dimension({name: "dim:0", dataType: "string"}),
+            new Dimension({name: "dim:1", dataType: "string"}),
+        ];
+
+        let g = new Graph();
+
+        for (let i = 0; i < dims.length; ++i) {
+            expect(g.addDimension(dims[i])).to.be.true;
+        }
+
+        let view = new View({
+            metrics: [],
+            dimensions: [dims[0], dims[1]],
+            materialized: true
+        });
+
+        expect(g.addView(view)).to.be.true;
+        expect(g.addView(view)).to.be.false;
+    });
+
+    it("should not add views when metrics and dimensions are not vertices", () => {
+        let dims = [
+            new Dimension({name: "dim:0", dataType: "string"}),
+            new Dimension({name: "dim:1", dataType: "string"}),
+            new Dimension({name: "dim:2", dataType: "string"})
+        ];
+
+        let g = new Graph();
+
+        expect(g.addDimension(dims[0])).to.be.true;
+
+        let view = new View({
+            metrics: [],
+            dimensions: dims,
+            materialized: true
+        });
+
+        expect(g.addView(view)).to.be.false;
+    });
+
+    it("should not add invalid views", () => {
+        let g = new Graph();
+        expect(g.addView(null)).to.be.false;
+    });
+
+    it("should add only once a view with only one vertex", () => {
+        let dim = new Dimension({name: "dim:0", dataType: "string"});
+
+        let g = new Graph();
+
+        expect(g.addDimension(dim)).to.be.true;
+
+        let view = new View({
+            metrics: [],
+            dimensions: [dim],
+            materialized: true
+        });
+
+        expect(g.addView(view)).to.be.true;
+        expect(g.addView(view)).to.be.false;
+    });
+
+    it("should create a cover for a single vertex", () => {
+        let dim = new Dimension({name: "dim:0", dataType: "string"});
+
+        let g = new Graph();
+
+        expect(g.addDimension(dim)).to.be.true;
+
+        let view = new View({
+            metrics: [],
+            dimensions: [dim],
+            materialized: true
+        });
+
+        expect(g.addView(view)).to.be.true;
+        let children = g.cover([], [dim]);
+        expect(children).to.be.an("array");
+        expect(children).to.have.length(1);
+        expect(children[0].view.id).to.be.equal(view.id);
+    });
+
+    it("should create a cover for several vertices", () => {
+        let dims = [
+            new Dimension({name: "dim:0", dataType: "string"}),
+            new Dimension({name: "dim:1", dataType: "string"}),
+            new Dimension({name: "dim:2", dataType: "string"})
+        ];
+
+        let mets = [
+            new Metric({name: "met:0", dataType: "integer", aggregation: AggregationType.SUM}),
+            new Metric({name: "met:1", dataType: "integer", aggregation: AggregationType.AVG}),
+            new Metric({name: "met:2", dataType: "integer", aggregation: AggregationType.AVG})
+        ];
+
+        let g = new Graph();
+
+        for (let i = 0; i < 3; ++i) {
+            expect(g.addDimension(dims[i])).to.be.true;
+            expect(g.addMetric(mets[i])).to.be.true;
+        }
+
+        let views = [
+            new View({
+                metrics: [mets[0]],
+                dimensions: [dims[0]],
+                materialized: true
+            }),
+            new View({
+                metrics: [mets[1]],
+                dimensions: [dims[1]],
+                materialized: true
+            }),
+            new View({
+                metrics: [mets[2]],
+                dimensions: [dims[2]],
+                materialized: true
+            }),
+            new View({
+                metrics: [],
+                dimensions: dims,
+                materialized: true
+            }),
+            new View({
+                metrics: mets,
+                dimensions: dims,
+                materialized: true
+            }),
+            new View({
+                metrics: [mets[0], mets[1]],
+                dimensions: [dims[0], dims[1]],
+                materialized: true
+            }),
+        ];
+
+        for (let i = 0; i < views.length; ++i) {
+            expect(g.addView(views[i])).to.be.true;
+        }
+
+        let children = g.cover([mets[0], mets[1]], [dims[0], dims[1]]);
+        expect(children).to.be.an("array");
+        expect(children).to.have.length(1);
+        expect(children[0].view.id).to.be.equal(views[views.length - 1].id);
+    });
+
+    it("should create a cover with sub dimensions", () => {
+        let dim = new Dimension({name: "dim:0", dataType: "date"});
+        let dims = [
+            dim,
+            new Dimension({
+                name: "subdim:0",
+                dataType: "string",
+                parent: dim,
+                relation: RelationType.MONTH
+            }),
+            new Dimension({
+                name: "subdim:1",
+                dataType: "string",
+                parent: dim,
+                relation: RelationType.DAY
+            }),
+        ];
+
+        let g = new Graph();
+
+        for (let i = 0; i < 3; ++i) {
+            expect(g.addDimension(dims[i])).to.be.true;
+        }
+
+        let view = new View({
+            metrics: [],
+            dimensions: [dims[0]],
+            materialized: true
+        });
+
+        expect(g.addView(view)).to.be.true;
+
+        let children = g.cover([], [dims[1], dims[2]]);
+        expect(children).to.be.an("array");
+        expect(children).to.have.length(1);
+        expect(children[0].view.id).to.be.equal(view.id);
+    });
+
+    it("should return empty when try to cover a empty list", () => {
+        let dim = new Dimension({name: "dim:0", dataType: "date"});
+        let dims = [
+            dim,
+            new Dimension({
+                name: "subdim:0",
+                dataType: "string",
+                parent: dim,
+                relation: RelationType.MONTH
+            }),
+            new Dimension({
+                name: "subdim:1",
+                dataType: "string",
+                parent: dim,
+                relation: RelationType.DAY
+            }),
+        ];
+
+        let g = new Graph();
+
+        for (let i = 0; i < 3; ++i) {
+            expect(g.addDimension(dims[i])).to.be.true;
+        }
+
+        let view = new View({
+            metrics: [],
+            dimensions: [dims[0]],
+            materialized: true
+        });
+
+        expect(g.addView(view)).to.be.true;
+
+        let children = g.cover([], []);
+        expect(children).to.be.an("array");
+        expect(children).to.be.empty;
+    });
+});
diff --git a/src/util/graph.ts b/src/util/graph.ts
new file mode 100644
index 00000000..7ee9c094
--- /dev/null
+++ b/src/util/graph.ts
@@ -0,0 +1,494 @@
+/*
+ * Copyright (C) 2017 Centro de Computacao Cientifica e Software Livre
+ * Departamento de Informatica - Universidade Federal do Parana
+ *
+ * This file is part of blendb.
+ *
+ * blendb 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.
+ *
+ * blendb 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 blendb.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import { View, ChildView } from "../core/view";
+import { Metric } from "../core/metric";
+import { Dimension } from "../core/dimension";
+
+enum State {
+    UNVISITED,
+    VISITED
+}
+
+/*
+    Class (interface) for a vertex in the graph
+    Created using a dimension or metric
+    Several of the attributes are used just for
+    search proposes like parent and state.
+*/
+
+interface Vertex {
+    id: string; // Name of vertex
+    neighbors: { [key: string]: Edge[] };
+    state: State; // During the search the vertex has a state
+    parent: Vertex; // During the search, what vertex "find" this
+    isSubDimension: boolean; // During insertion, was subdimension
+    dimensionParent: Vertex; // If is a subdimension, the vertex for that
+    dimension: Dimension; // If is a dimension, tha dimension that the vertex represents
+    metric: Metric; // If is a metric, the metric that the vertexrepresents
+}
+
+/*
+    Class (interface) dor a edge of the Graph
+    Normaly is possible to use a list of vertices
+    to represent the edges, however this edges have some
+    information different from the vertex, so a class
+    is needed.
+
+    Edges represent views or parent relationship with
+    dimensions and subdimensions.
+*/
+interface Edge {
+    isView: boolean; // True if edge represent view
+    view: View; // The view if isView is True
+    subDimension: Dimension; // The subDimension if isView is False
+}
+/*
+    Graph Class
+    Used to convert the Views Representation (Tables/Relations)
+    into a graph representation of the database schema.
+    Is not generic Graph, is not possible create vertex and
+    edges at will, only with metrics, dimensions and views.
+
+    Struct:
+    There is a vertex for each Metric and Dimension inserted
+    in the graph.
+    The graph is directed, but mostly vertices has edges in
+    both directions.
+    There is a edge between 2 vertices i and j in 3 cases:
+    I) if i and j are in the same view, there is a edge (i, j)
+    and (j, i)
+    II) if i is parent of the subdimention j , there is a
+    edge (i, j)
+    III) If a view has only one vertex, there is a loop (i, i)
+
+    So there is a edge if vertices are in the same view or
+    there is a parent relationship between vertices.
+*/
+export class Graph {
+    private vertices: Vertex[]; // vertices of the graph
+    private verticeMap: { [key: string]: Vertex };
+    // Map where key is the vertex id and the value is the vertex
+
+    public constructor () {
+        this.vertices = [];
+        this.verticeMap = {};
+    }
+
+    /*
+        Adds a metric
+        returns true if the metric was inserted in the graph
+    */
+    public addMetric(met: Metric): boolean {
+        if (!met) {
+            return false;
+        }
+
+        if (typeof this.verticeMap[met.name] !== "undefined") {
+            // If the metric was already added, not add again
+            return false;
+        }
+        else {
+            let v: Vertex = {
+                id: met.name,
+                neighbors: {},
+                state: State.UNVISITED,
+                parent: null,
+                isSubDimension: false,
+                dimensionParent: null,
+                dimension: null,
+                metric: met
+            };
+            this.vertices.push(v);
+            this.verticeMap[met.name] = v;
+            return true;
+        }
+    }
+
+    /*
+        Adds a dimension
+        returns true if the dimension was inserted in the graph
+    */
+    public addDimension(dim: Dimension): boolean {
+        if (!dim) {
+            return false;
+        }
+
+        if (typeof this.verticeMap[dim.name] !== "undefined") {
+            // If the dimension was already added, not add again
+            return false;
+        }
+        else {
+
+            if (dim.parent === null) {
+                // If is not a subdimension, insert normaly
+                let v: Vertex = {
+                    id: dim.name,
+                    neighbors: {},
+                    state: State.UNVISITED,
+                    parent: null,
+                    isSubDimension: false,
+                    dimensionParent: null,
+                    dimension: dim,
+                    metric: null
+                };
+                this.vertices.push(v);
+                this.verticeMap[dim.name] = v;
+                return true;
+            }
+
+            else {
+                // If is a sub dimension get the parent
+                let p = this.verticeMap[dim.parent.name];
+                if (typeof p === "undefined") {
+                    // If the parent was not inserted, not insert
+                    return false;
+                }
+
+                // Otherwise create a vertex and a edge for the parent
+                // relationship.
+                let v: Vertex = {
+                    id: dim.name,
+                    neighbors: {},
+                    state: State.UNVISITED,
+                    parent: null,
+                    isSubDimension: true,
+                    dimensionParent: p,
+                    dimension: dim,
+                    metric: null
+                };
+                this.vertices.push(v);
+                this.verticeMap[dim.name] = v;
+                p.neighbors[dim.name] = [{
+                    isView: false,
+                    view: null,
+                    subDimension: dim
+                }];
+                return true;
+
+            }
+        }
+    }
+
+    /*
+        Add a View
+        Get the metrics and dimensions of this view
+        and create edges between then
+        returns true when the view in inserted (create all the
+        edges needed) and false otherwise
+        If returns false any edge was created, if true all edges
+        were created.
+    */
+    public addView (view: View): boolean {
+        if (!view) {
+            return false;
+        }
+
+        let vertices = view.dimensions.map((dim) => dim.name);
+        vertices = vertices.concat(view.metrics.map((met) => met.name));
+        // Concat metrics and diemnsions to get all vertices ids
+        if (vertices.length === 1) {
+            // If the view has only one vertex, create a loop
+            if (this.checkEdge(vertices[0], vertices[0], view)) {
+                this.edge(vertices[0], vertices[0], view);
+                return true;
+            }
+
+            return false;
+        }
+        else {
+            let r = true;
+            // Check if all the edges can be created
+            for (let i = 0; i < vertices.length; ++i) {
+                for (let j = (i + 1); j < vertices.length; ++j) {
+                    if (r) {
+                        r = this.checkEdge(vertices[i], vertices[j], view);
+                    }
+                }
+            }
+
+            // If at least one can not, not create any one
+            if (!r) {
+                return false;
+            }
+
+            // Otherwise create then all
+            for (let i = 0; i < vertices.length; ++i) {
+                for (let j = (i + 1); j < vertices.length; ++j) {
+                    this.edge(vertices[i], vertices[j], view);
+                }
+            }
+
+            return true;
+        }
+
+    }
+
+    /*
+        Check if a edge can be create
+        return false when at least one vertex does not exist in the graph
+        or the edge already exists
+        returns true otherwise
+    */
+    private checkEdge(idV: string, idU: string, value: View): boolean {
+        let v: Vertex = this.verticeMap[idV];
+        let u: Vertex = this.verticeMap[idU];
+
+        if  (typeof v !== "undefined" && typeof u !== "undefined") {
+            // Check if the vertices exist
+            if (typeof v.neighbors[idU] !== "undefined") {
+                // Check if a edge exist
+                if (v.neighbors[idU].some((item) => item.view.id === value.id)) {
+                    // Check the id of the edge
+                    return false;
+                }
+            }
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    /*
+        Create a edge between 2 vertices
+
+        Edges are a kind of special, when say create a edge is
+        in fact put a index on the array of a edge.
+
+        The edges have a array of ids, when you try to create a edge
+        and the edge doen not exist, create the edge and a array with
+        a lonely index in it. If the edge exist but the id is not in
+        the array, adds the id and checkEdge would return true,
+        if the id already exists return false
+    */
+    private edge(idV: string, idU: string, value: View) {
+        // Assuming that checkEdge is called first
+        let v: Vertex = this.verticeMap[idV];
+        let u: Vertex = this.verticeMap[idU];
+
+        if (typeof v.neighbors[idU] === "undefined") {
+            v.neighbors[idU] = [{ isView: true, view: value, subDimension: null }];
+        }
+        else {
+            v.neighbors[idU].push({ isView: true, view: value, subDimension: null });
+        }
+        if (typeof u.neighbors[idV] === "undefined") {
+            u.neighbors[idV] = [{ isView: true, view: value, subDimension: null }];
+        }
+        else {
+            u.neighbors[idV].push({ isView: true, view: value, subDimension: null });
+        }
+    }
+
+    /*
+        Given a list of metrics and dimensions returns a set
+        of ChildViews that can be used to create a query that
+        returns the data asked.
+
+        If this set cannot be created, throws a error
+    */
+    public cover(metrics: Metric[], dimensions: Dimension[]): ChildView[] {
+        let output: ChildView[] = [];
+        let verticesIds = metrics.map((met) => met.name);
+        verticesIds = verticesIds.concat(dimensions.map((dim) => dim.name));
+        for (let i = 0; i < this.vertices.length; ++i) {
+            // Get the vertices and set their status and parent
+            this.vertices[i].state = State.UNVISITED;
+            this.vertices[i].parent = null;
+        }
+
+        // Check if dimension/metric is in the graph
+        if (verticesIds.some((item) => typeof this.verticeMap[item] === "undefined")) {
+            return [];
+        }
+
+        // Choose one vertex as root of a bfs
+        // (Breadth-First-Search)
+        let root: Vertex = this.verticeMap[verticesIds.shift()];
+        if (!root) {
+            return [];
+        }
+        while (root.isSubDimension) {
+            root = root.dimensionParent;
+        }
+        root.state = State.VISITED;
+        let queue: Vertex[] = [root];
+        while (queue.length > 0) {
+            let v: Vertex = queue.shift();
+            for (let key in v.neighbors) {
+                let u: Vertex = this.verticeMap[key];
+                if (u.state === State.UNVISITED) {
+                    // Mark all vertices visited by the search
+                    u.state = State.VISITED;
+                    u.parent = v;
+                    queue.push(u);
+                }
+            }
+        }
+
+        while (verticesIds.length > 0  &&
+                 this.verticeMap[verticesIds[0]].state === State.VISITED) {
+            verticesIds.shift();
+        }
+
+        // Removes from the list all the vertices marked
+        // If at least one vertex in not marked, the graph is
+        // no connect and a query cannot be create
+        if (verticesIds.length > 0) {
+            return [];
+        }
+
+        // Recover the list of vertices
+        let dimToCover = dimensions.map((dim) => dim);
+        let metToCover = metrics.map((met) => met);
+        verticesIds = metrics.map((met) => met.name);
+        verticesIds = verticesIds.concat(dimensions.map((dim) => dim.name));
+        while (verticesIds.length > 0) {
+            // Choose a vertex and walks to his ancestors until
+            // reach the root, when walking this path choose
+            // the views based on the edges of the path and
+            // the views already picked.
+            let v = this.verticeMap[verticesIds.shift()];
+            while (v.parent !== null) {
+                let options = v.parent.neighbors[v.id].filter((edge) => {
+                    // Filther the edges ids to get only the ones that
+                    // represent views
+                    return edge.isView;
+                }).map((edge) => edge.view);
+                // Check if there is a intersection between output and options
+                if (output.some((child) => options.some((view) => child.view === view))) {
+                    // If there is a intersection, does not pick any new view
+                    v = v.parent;
+                    continue;
+                }
+
+                if (options.length === 0) {
+                    // If there are no view options this means that a
+                    // subdimension edge was taken, so do not pick any
+                    // new view.
+                    v = v.parent;
+                    continue;
+                }
+
+                // Selects the best edge
+                let bestEdge = this.pickEdge (options, metToCover, dimToCover);
+                output.push(bestEdge);
+
+                // Remove from the list of metrics and dimensions the
+                // covered by the selected view
+                metToCover = metToCover.filter((item) => {
+                    return !bestEdge.metrics.some((met) => met === item);
+                });
+                dimToCover = dimToCover.filter((item) => {
+                    return !bestEdge.dimensions.some((dim) => dim === item);
+                });
+                // walk back on the path
+                v = v.parent;
+            }
+        }
+
+        let views: View[] = [];
+        // Pick all views that contain the root vertex
+        for (let i in root.neighbors) {
+            views = views.concat(root.neighbors[i].filter((item) => {
+                return item.isView;
+            }).map((item) => item.view));
+        }
+
+        // Check if some of its views were picked
+        if (output.some((child) => views.some((view) => child.view === view))) {
+            // If yes, do nothing and return the actual set
+            return output;
+        }
+
+        // If any were picked, choose one
+        let bestEdge = this.pickEdge (views, metToCover, dimToCover);
+        output.push(bestEdge);
+
+        // This is required for the case that only one vertex is required
+        // Or only subdimensions edges are taken
+
+        return output;
+    }
+
+    /*
+        From a edge, coohse the best view, based on the metric and dimensions
+        that are not cover yet, return a childView.
+        The algorithm chooses the view that covers more metrics and dimensions
+        that are not covered yet, if there is a tie chooses the one with
+        less dimensions, if tie again, the earliest in the list.
+    */
+    private pickEdge (views: View[], metToCover: Metric[], dimToCover: Dimension[]): ChildView {
+        // Picks the first option as the best one until now
+        let bestView = views[0];
+        let bestCoverMet = metToCover.filter((met) => {
+            return bestView.metrics.some((item) => item === met);
+        });
+        let bestCoverDim = dimToCover.filter((dim) => {
+            let r = bestView.dimensions.some((item) => item === dim);
+            // If is a subdimension that must be cover, check if
+            // any ancestor can cover it
+            while (!r && dim.parent !== null) {
+                dim = dim.parent;
+                r = bestView.dimensions.some((item) => item === dim);
+            }
+            return r;
+        });
+
+        // Get the score
+        let bestCover = bestCoverMet.length + bestCoverDim.length;
+        for (let i = 1; i < views.length; ++i) {
+            let actual = views[i];
+            let actualCoverMet = metToCover.filter((met) => {
+                return actual.metrics.some((item) => item === met);
+            });
+            let actualCoverDim = dimToCover.filter((dim) => {
+                let r = actual.dimensions.some((item) => item === dim);
+                while (!r && dim.parent !== null) {
+                    // If is a subdimension that must be cover, check if
+                    // any ancestor can cover it
+                    dim = dim.parent;
+                    r = actual.dimensions.some((item) => item === dim);
+                }
+                return r;
+            });
+
+            // Checks if the new option is better than the best until now
+            let actualCover = actualCoverMet.length + actualCoverDim.length;
+            if (actualCover < bestCover ||
+                (actualCover === bestCover &&
+                 bestView.dimensions.length > actual.dimensions.length)) {
+                bestCover = actualCover;
+                bestCoverMet = actualCoverMet;
+                bestCoverDim = actualCoverDim;
+                bestView = actual;
+            }
+        }
+
+        return {
+            view: bestView,
+            metrics: bestCoverMet,
+            dimensions: bestCoverDim
+        };
+
+    }
+
+}
-- 
GitLab