From 5b370451293455cb4e071f6fa064302aa55a0198 Mon Sep 17 00:00:00 2001
From: rafaelatc3sl <rpd17@c3sl>
Date: Mon, 5 Nov 2018 11:03:49 -0200
Subject: [PATCH] Issue #90: Add class Query

Signed-off-by: rafaelatc3sl <rpd17@c3sl>
---
 src/common/query.ts     |  23 +++++-
 src/core/engine.ts      |  14 +---
 src/util/graph.spec.ts  |  23 +++---
 src/util/graph.ts       |   4 +-
 src/util/viewHandler.ts |  29 ++++----
 test/scenario.ts        | 150 ++++++++++++++++++++++------------------
 6 files changed, 137 insertions(+), 106 deletions(-)

diff --git a/src/common/query.ts b/src/common/query.ts
index 4cb0a9e9..f5cdaf8f 100644
--- a/src/common/query.ts
+++ b/src/common/query.ts
@@ -25,7 +25,7 @@ import { Clause } from "../core/clause";
 /**
  * Internal representation of a query in BlenDB perspective.
  */
-export interface Query {
+export interface QueryOpts {
     /** Set of metrics of the query. */
     metrics: Metric[];
     /** Set of dimensions of the query. */
@@ -35,3 +35,24 @@ export interface Query {
     /** List of metrics and dimensions to sort the query. */
     sort?: (Metric|Dimension)[];
 }
+
+export class Query {
+    /** Set of metrics of the query. */
+    public metrics: Metric[];
+    /** Set of dimensions of the query. */
+    public dimensions: Dimension[];
+    /** Set of clauses of the query. */
+    public clauses: Clause[];
+    /** List of metrics and dimensions to sort the query. */
+    public sort: (Metric|Dimension)[];
+    /**
+     * Create Query
+     * @param opts - Parameters required to create a query.
+     */
+    constructor(opts: QueryOpts){
+        this.metrics = opts.metrics;
+        this.dimensions = opts.dimensions;
+        this.clauses = (opts.clauses) ? opts.clauses : [];
+        this.sort = (opts.sort) ? opts.sort : [];
+    }
+}
diff --git a/src/core/engine.ts b/src/core/engine.ts
index 712d7220..3c0d5f07 100644
--- a/src/core/engine.ts
+++ b/src/core/engine.ts
@@ -280,12 +280,7 @@ export class Engine {
         let queries: Query[] = [];
         if (q.metrics.length > 0) {
             for (let i = 0; i < q.metrics.length; ++i) {
-                queries.push({
-                    metrics: [q.metrics[i]],
-                    dimensions: q.dimensions,
-                    clauses: (q.clauses) ? q.clauses : [],
-                    sort: (q.sort) ? q.sort : []
-                });
+                queries.push(new Query(q));
             }
             const views = queries.map((query) => {
                 return ViewHandler.growView(query, this.getCover(query));
@@ -295,12 +290,7 @@ export class Engine {
         }
 
         else {
-            let query = {
-                metrics: q.metrics,
-                dimensions: q.dimensions,
-                clauses: (q.clauses) ? q.clauses : [],
-                sort: (q.sort) ? q.sort : []
-            };
+            let query = new Query(q);
             return ViewHandler.growView(query, this.getCover(query));
         }
 
diff --git a/src/util/graph.spec.ts b/src/util/graph.spec.ts
index 8dc8ea98..3a19cbb8 100644
--- a/src/util/graph.spec.ts
+++ b/src/util/graph.spec.ts
@@ -27,7 +27,7 @@ import { Filter, FilterOperator } from "../core/filter";
 import { Clause } from "../core/clause";
 import { Graph } from "./graph";
 import { AggregationType, RelationType, DataType } from "../common/types";
-import { Query } from "../common/query";
+import { Query, QueryOpts } from "../common/query";
 
 describe("graph class", () => {
 
@@ -193,7 +193,8 @@ describe("graph class", () => {
         });
 
         expect(g.addView(view)).to.be.true;
-        const query: Query = { metrics: [], dimensions: [dim] };
+        const qOpts: QueryOpts = {metrics: [], dimensions: [dim], sort: [], clauses: []};
+        const query = new Query (qOpts);
         let children = g.cover(query);
         expect(children).to.be.an("array");
         expect(children).to.have.length(1);
@@ -257,7 +258,8 @@ describe("graph class", () => {
             expect(g.addView(views[i])).to.be.true;
         }
 
-        const query: Query = { metrics: [mets[0], mets[1]], dimensions: [dims[0], dims[1]] };
+        const query: Query = { metrics: [mets[0], mets[1]], dimensions: [dims[0], dims[1]],
+            sort: [], clauses: [] };
         let children = g.cover(query);
         expect(children).to.be.an("array");
         expect(children).to.have.length(1);
@@ -296,7 +298,8 @@ describe("graph class", () => {
 
         expect(g.addView(view)).to.be.true;
 
-        const query: Query = { metrics: [], dimensions: [dims[1], dims[2]] };
+        const query: Query = { metrics: [], dimensions: [dims[1], dims[2]],
+            sort: [], clauses: [] };
         let children = g.cover(query);
         expect(children).to.be.an("array");
         expect(children).to.have.length(1);
@@ -335,7 +338,8 @@ describe("graph class", () => {
 
         expect(g.addView(view)).to.be.true;
 
-        const query: Query = { metrics: [], dimensions: [] };
+        const query: Query = { metrics: [], dimensions: [],
+            sort: [], clauses: [] };
         let children = g.cover(query);
         expect(children).to.be.an("array");
         expect(children).to.be.empty;
@@ -390,7 +394,8 @@ describe("graph class", () => {
         expect(g.addView(view2)).to.be.true;
         expect(g.addView(view3)).to.be.true;
 
-        const query: Query = { metrics: [], dimensions: dims, clauses: [clause2] };
+        const query: Query = { metrics: [], dimensions: dims, clauses: [clause2],
+            sort: []};
         let children = g.cover(query);
         expect(children).to.be.an("array");
         expect(children).to.have.length(2);
@@ -503,7 +508,8 @@ describe("graph class", () => {
 
         expect(g.addView(view0)).to.be.true;
 
-        const query: Query = { metrics: [], dimensions: dims, clauses: testClauses };
+        const query: Query = { metrics: [], dimensions: dims, clauses: testClauses,
+            sort: []};
         let children = g.cover(query);
         expect(children).to.have.length(1);
         expect(children[0].id === view0.id).to.be.true;
@@ -627,7 +633,8 @@ describe("graph class", () => {
             expect(g.addView(views[i])).to.be.true;
         }
 
-        const query: Query = { metrics: [], dimensions: dims, clauses: testClauses };
+        const query: Query = { metrics: [], dimensions: dims, clauses: testClauses,
+            sort: []};
         let children = g.cover(query);
         expect(children).to.have.length(1);
         expect(children[0].id === views[0].id).to.be.true;
diff --git a/src/util/graph.ts b/src/util/graph.ts
index 36a5ea10..0d57cf35 100644
--- a/src/util/graph.ts
+++ b/src/util/graph.ts
@@ -321,7 +321,7 @@ export class Graph {
     public cover(q: Query): View[] {
         const metrics = q.metrics;
         const dimensions = q.dimensions;
-        const clauses = (q.clauses) ? q.clauses : [];
+        const clauses = q.clauses;
         let output: View[] = [];
         let verticesIds = this.verticesInQuery(q);
 
@@ -621,7 +621,7 @@ export class Graph {
     private verticesInQuery(q: Query): string[] {
         const metrics = q.metrics;
         const dimensions = q.dimensions;
-        const clauses = (q.clauses) ? q.clauses : [];
+        const clauses = q.clauses;
         let verticesIds = metrics.map((met) => met.name);
         verticesIds = verticesIds.concat(dimensions.map((dim) => dim.name));
         for (let i = 0; i < clauses.length; ++i) {
diff --git a/src/util/viewHandler.ts b/src/util/viewHandler.ts
index 6ecc43f0..bfa929d3 100644
--- a/src/util/viewHandler.ts
+++ b/src/util/viewHandler.ts
@@ -21,7 +21,7 @@
 import { Opcode } from "../common/expression";
 import { View } from "../core/view";
 import { Clause } from "../core/clause";
-import { Query } from "../common/query";
+import { Query, QueryOpts } from "../common/query";
 import { Dimension } from "../core/dimension";
 import { Metric } from "../core/metric";
 
@@ -60,8 +60,8 @@ export class ViewHandler {
                 metrics: q.metrics,
                 dimensions: q.dimensions,
                 origin: false,
-                clauses: (q.clauses) ? q.clauses : [],
-                sort: (q.sort) ? q.sort : [],
+                clauses: q.clauses,
+                sort: q.sort,
                 operation: {
                     opcode: Opcode.JOIN,
                     values: views.map((i) => i)
@@ -85,15 +85,15 @@ export class ViewHandler {
             metrics: q.metrics,
             dimensions: q.dimensions,
             origin: false,
-            clauses: (q.clauses) ? q.clauses : [],
-            sort: (q.sort) ? q.sort : [],
+            clauses: q.clauses,
+            sort: q.sort,
             operation: {
                 opcode: Opcode.REDUCE,
                 values: [view]
             }
         });
 
-        return (v.id !== view.id) ? v : view;
+        return (v.name !== view.name) ? v : view;
     }
 
     /**
@@ -109,14 +109,11 @@ export class ViewHandler {
         let metView: View = null;
         let partialJoin: View[] = null;
         let reduced: ViewsAndClauses = null;
-        let partialQuery: Query = {
-            metrics: q.metrics,
-            dimensions: q.dimensions,
-            clauses: q.clauses,
-            sort: q.sort
-        };
+
+        let partialQuery = new Query(q);
 
         partialJoin = views.map((i) => (i));
+
         if (q.metrics.length === 0) { // ignore metView if there are 0 metrics
             while (partialJoin.length > 1) {
                 partialQuery.clauses = clausesToCover;
@@ -355,11 +352,9 @@ export class ViewHandler {
         let clauses = partial0.clauses.concat(partial1.clauses);
         clauses = ViewHandler.removeDuplicatedClauses(clauses);
 
-        const partialQuery: Query = {
-            metrics: mets,
-            dimensions: dims,
-            clauses: clauses
-        };
+        const qOpts: QueryOpts = {metrics: mets, dimensions: dims, sort: [], clauses: clauses};
+
+        const partialQuery = new Query(qOpts);
 
         const partial = ViewHandler.queryJoin(partialQuery, [partial0, partial1]);
         views.push(partial);
diff --git a/test/scenario.ts b/test/scenario.ts
index 71c10136..587ac38b 100644
--- a/test/scenario.ts
+++ b/test/scenario.ts
@@ -27,11 +27,11 @@ import { Clause } from "../src/core/clause";
 import { AggregationType, RelationType , DataType} from "../src/common/types";
 import { ViewHandler } from "../src/util/viewHandler";
 import { EngineScenario, AdapterScenario, DataCtrlScenario } from "../src/util/scenarioHandler";
+import { Query, QueryOpts } from "../src/common/query";
 
 const configPath =  process.env.BLENDB_SCHEMA_FILE;
 const config = ConfigParser.parse(configPath);
 
-
 const mets : {[key: string]: Metric} = {};
 for (let i in config.metrics) {
     let met = config.metrics[i];
@@ -50,8 +50,16 @@ for (let i in config.buildViews){
     views[view.alias] = view.view;
 }
 
+const wrongMet = new Metric({
+    name: "met:this:is:just:a:test",
+    aggregation: AggregationType.COUNT,
+    dataType: DataType.INTEGER
+});
+
+const wrongDim = new Dimension({ name: "dim:this:is:just:a:test", dataType: DataType.INTEGER });
+
 /**
- * Create new filters to use in clause and test 
+ * Create new filters to use in clause and test
  * the clauses
  */
 const filters: { [key: string]: Filter } = {
@@ -102,8 +110,6 @@ const filters: { [key: string]: Filter } = {
     })
 }
 
-// Subdimensions
-
 const subdims : {[key:string]: Dimension} = {
     "subdims_day" : new Dimension({
         name: "subdims_day",
@@ -131,9 +137,7 @@ const subdims : {[key:string]: Dimension} = {
     })
 }
 
-// Clauses
-
-const clauses: { [key: string]: Clause }  = {    
+const clauses: { [key: string]: Clause }  = {
     "lastDay":
     new Clause({filters: [filters["equal"]]}),
     "undefined":
@@ -157,7 +161,6 @@ const clauses: { [key: string]: Clause }  = {
     new Clause ({filters: [filters["equalFilterView"]]})
 }
 
-// Views
 
 const JoinWithAncestors = ViewHandler.growView({
     metrics: [mets["met:sell:count:quantity"]],
@@ -166,7 +169,7 @@ const JoinWithAncestors = ViewHandler.growView({
 },[views["view:Sell"]]);
 
 const joinWithNoMetrics = ViewHandler.growView({
-    metrics: [],
+    metrics: emptyQuery.metrics,
     dimensions: [dims["dim:product:name"],dims["dim:seller:name"]],
     clauses: []
 }, [views["view:Product"],views["view:Sell"],views["view:Seller"]]);
@@ -193,7 +196,7 @@ const singleClause = ViewHandler.growView({
 }, [views["view:Sell"],views["view:Seller"],views["view:Client"],views["view:Product"]]);
 
 const equalfilter = ViewHandler.queryJoin({
-    metrics: [],
+    metrics: emptyQuery.metrics,
     dimensions: [dims["dim:client:name"],dims["dim:product:validity"]],
     clauses: [clauses["lastDay"]]
 },[views["view:Sell"],views["view:Client"],views["view:Product"]]);
@@ -201,6 +204,7 @@ const equalfilter = ViewHandler.queryJoin({
 const withSortView = ViewHandler.queryJoin({
     metrics: [mets["met:sell:sum:quantity"]],
     dimensions: [dims["dim:client:name"]],
+    clauses: emptyQuery.clauses,
     sort: [mets["met:sell:sum:quantity"]]
     },[ViewHandler.queryReduce({
         metrics: [mets["met:sell:sum:quantity"]],
@@ -208,9 +212,11 @@ const withSortView = ViewHandler.queryJoin({
     },views["view:Sell"]),views["view:Client"]]);
 
 const subDimView = ViewHandler.queryJoin({
-    metrics : [],
+    metrics: emptyQuery.metrics,
     dimensions : [dims["dim:sell:datein"],subdims["subdims_day"],
-    subdims["subdims_month"],subdims["subdims_year"]]},
+    subdims["subdims_month"],subdims["subdims_year"]],
+    clauses: emptyQuery.clauses,
+    sort: emptyQuery.sort},
 [ViewHandler.queryReduce({
     metrics: [],
     dimensions: [dims["dim:sell:datein"]]
@@ -227,13 +233,15 @@ const reduceAsView = ViewHandler.queryReduce({
 mets["met:sell:count:quantity"]],
     dimensions: [dims["dim:sell:registered"], dims["dim:product:id"],
 dims["dim:seller:id"], dims["dim:client:id"],dims["dim:sell:datein"]],
+clauses: emptyQuery.clauses,
 sort: [mets["met:sell:sum:quantity"]]
 },views["view:Sell"]);
 
 const clientAverageBought = ViewHandler.queryReduce({
     metrics: [mets["met:sell:avg:quantity"]],
     dimensions: [dims["dim:client:name"]],
-    clauses: [clauses["averageBought"]]
+    clauses: [clauses["averageBought"]],
+    sort: emptyQuery.sort
 },ViewHandler.queryJoin({
     metrics: [mets["met:sell:avg:quantity"]],
     dimensions: [dims["dim:client:name"], dims["dim:seller:id"]]
@@ -242,60 +250,62 @@ const clientAverageBought = ViewHandler.queryReduce({
 
 const viewProduct = views["view:Product"];
 
-const viewActiveSeller = views["view:ActiveSeller"];
+const queries : {[key: string]: Query} = {};
+for(let i in qOpts){
+    queries[i] = new Query(qOpts[i]);
+}
 
-const wrongMet = new Metric({
-    name: "met:this:is:just:a:test",
-    aggregation: AggregationType.COUNT,
-    dataType: DataType.INTEGER
-});
+const JoinWithAncestors = ViewHandler.growView(queries["JoinWithAncestors"],
+    [views["view:Sell"]]);
 
-// Dimensions
+const joinWithNoMetrics = ViewHandler.growView(queries["joinWithNoMetrics"],
+    [views["view:Product"],
+    views["view:Sell"],
+    views["view:Seller"]]);
 
-const wrongDim = new Dimension({ name: "dim:this:is:just:a:test", dataType: DataType.INTEGER });
+const growOneView = ViewHandler.growView(queries["growOneView"],
+    [views["view:Seller"]]);
 
-// Queries
+const multipleClause = ViewHandler.growView(queries["multipleClause"],
+    [views["view:Sell"],
+    views["view:Seller"],
+    views["view:Client"],
+    views["view:Product"]]);
 
-const queryNoParent = {
-    metrics: [mets["met:sell:count:quantity"]], 
-    dimensions: [subdims["subdims_none"]]
-}
+const singleClause = ViewHandler.growView(queries["singleClause"],
+    [views["view:Sell"],
+    views["view:Seller"],
+    views["view:Client"],
+    views["view:Product"]]);
 
-const queryMetsDims = {
-    metrics : Object.keys(mets).map((key) => mets[key]),
-    dimensions : Object.keys(dims).map((key) => dims[key])
-};
+const equalfilter = ViewHandler.queryJoin(queries["equalfilter"],
+    [views["view:Sell"],
+    views["view:Client"],
+    views["view:Product"]]);
 
-const queryNoMets = {
-    metrics: [wrongMet], 
-    dimensions: [dims["dim:product:name"]]
-};
+const withSortView = ViewHandler.queryJoin(queries["withSortView:0"],
+    [ViewHandler.queryReduce(queries["withSortView:1"],
+    views["view:Sell"]),
+    views["view:Client"]]);
 
-const queryNoDims = { 
-    metrics: [mets["met:buyout:min:quantity"]],
-    dimensions: [wrongDim]
-}
+const subDimView = ViewHandler.queryJoin(queries["subDimView:0"],
+    [ViewHandler.queryReduce(queries["subDimView:1"],
+    views["view:Sell"]),
+    views["view:Sell"]]);
 
-const queryProduct = {
-    metrics: [mets["met:product:avg:pricein"], mets["met:product:max:pricein"], mets["met:product:min:pricein"],
-    mets["met:product:avg:priceout"],mets["met:product:max:priceout"],mets["met:product:min:priceout"]],
-    dimensions: [dims["dim:product:name"], dims["dim:product:validity"],dims["dim:product:id"]]
-};
+const joinOneView = ViewHandler.queryJoin(queries["joinOneView"],
+    [views["view:Product"]]);
 
+const reduceAsView = ViewHandler.queryReduce(queries["reduceAsView"],
+    views["view:Sell"]);
 
-const queryActive = {
-    metrics: [mets["met:seller:max:age"]],
-    dimensions: [dims["dim:seller:name"],dims["dim:seller:status"]],
-    clauses: [clauses["equalClauseView"]]
-}
+const clientAverageBought = ViewHandler.queryReduce(queries["clientAverageBought:0"],
+    ViewHandler.queryJoin(queries["clientAverageBought:1"],
+    [views["view:Sell"],views["view:Client"]]));
 
-// Metrics
+const viewProduct = views["view:Product"];
 
-let emptyMetrics: Metric[] = [];
-const querySubDim = {      
-    metrics : emptyMetrics
-    , dimensions : [subdims["subdims_day"],subdims["subdims_month"]]
-}
+const viewActiveSeller = views["view:ActiveSeller"];
 
 // Exports
 
@@ -306,15 +316,15 @@ export const engineScenario: EngineScenario = {
     wrongDim: wrongDim,
     subDimensions: subdims,
     views: views,
-    queryMetsDims: queryMetsDims,
-    queryNoMets: queryNoMets,
-    queryNoDims: queryNoDims,
-    queryProduct: queryProduct,
+    queryMetsDims: queries["queryMetsDims"],
+    queryNoMets: queries["queryNoMets"],
+    queryNoDims: queries["queryNoDims"],
+    queryProduct: queries["queryProduct"],
     viewProduct: viewProduct,
-    queryActive: queryActive,
+    queryActive: queries["queryActive"],
     viewActiveSeller: viewActiveSeller,
-    querySubDim: querySubDim,
-    queryNoParent: queryNoParent
+    querySubDim: queries["querySubDim"],
+    queryNoParent: queries["queryNoParent"]
 };
 
 export const adapterScenario: AdapterScenario = {
@@ -335,18 +345,26 @@ export const adapterScenario: AdapterScenario = {
 export const dataCtrlScenario: DataCtrlScenario = {
     wrongMet: {
         metrics: [wrongMet],
-        dimensions: [dims["dim:product:id"]]
+        dimensions: [dims["dim:product:id"]],
+        clauses: [],
+        sort: []
     },
     wrongDim: {
         metrics: [mets["met:sell:avg:quantity"]],
-        dimensions: [wrongDim] },
+        dimensions: [wrongDim],
+        clauses: [],
+        sort: [] },
     correct: {
         metrics: [mets["met:buyout:avg:quantity"]],
-        dimensions: [dims["dim:provider:id"]] 
+        dimensions: [dims["dim:provider:id"]],
+        clauses: [],
+        sort: []
     },
     clausal: {
         metrics: [mets["met:product:avg:pricein"]],
         dimensions: [dims["dim:product:name"],
-        dims["dim:product:id"]]
-    }   
+        dims["dim:product:id"]],
+        clauses: [],
+        sort: []
+    }
 };
-- 
GitLab