diff --git a/src/common/query.ts b/src/common/query.ts index 4cb0a9e986b6e183287d5bb2e279637080a1095a..f5cdaf8f1da5bb3e6d1db14255d802b224d969ff 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 712d722082d99f418a587f9dff24baf6b93da6af..3c0d5f0753587da8e8f4c6372ee666f3eee4e5f5 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 8dc8ea982468a6e32235da45507d7dae24220dc8..ea80fe9568f43dc387992cbcb52ddca1211dba19 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); @@ -256,8 +257,9 @@ describe("graph class", () => { for (let i = 0; i < views.length; ++i) { expect(g.addView(views[i])).to.be.true; } - - const query: Query = { metrics: [mets[0], mets[1]], dimensions: [dims[0], dims[1]] }; + const qOpts: QueryOpts = { metrics: [mets[0], mets[1]], dimensions: [dims[0], dims[1]], + sort: [], clauses: [] }; + const query = new Query(qOpts); let children = g.cover(query); expect(children).to.be.an("array"); expect(children).to.have.length(1); @@ -295,8 +297,9 @@ describe("graph class", () => { }); expect(g.addView(view)).to.be.true; - - const query: Query = { metrics: [], dimensions: [dims[1], dims[2]] }; + const qOpts: QueryOpts = { metrics: [], dimensions: [dims[1], dims[2]], + sort: [], clauses: [] }; + const query = new Query(qOpts); let children = g.cover(query); expect(children).to.be.an("array"); expect(children).to.have.length(1); @@ -334,8 +337,9 @@ describe("graph class", () => { }); expect(g.addView(view)).to.be.true; - - const query: Query = { metrics: [], dimensions: [] }; + const qOpts: QueryOpts = { metrics: [], dimensions: [], + sort: [], clauses: [] }; + const query = new Query(qOpts); let children = g.cover(query); expect(children).to.be.an("array"); expect(children).to.be.empty; @@ -389,8 +393,9 @@ describe("graph class", () => { expect(g.addView(view1)).to.be.true; expect(g.addView(view2)).to.be.true; expect(g.addView(view3)).to.be.true; - - const query: Query = { metrics: [], dimensions: dims, clauses: [clause2] }; + const qOpts: QueryOpts = { metrics: [], dimensions: dims, clauses: [clause2], + sort: []}; + const query = new Query(qOpts); let children = g.cover(query); expect(children).to.be.an("array"); expect(children).to.have.length(2); @@ -502,8 +507,9 @@ describe("graph class", () => { }); expect(g.addView(view0)).to.be.true; - - const query: Query = { metrics: [], dimensions: dims, clauses: testClauses }; + const qOpts: QueryOpts = { metrics: [], dimensions: dims, clauses: testClauses, + sort: []}; + const query = new Query(qOpts); let children = g.cover(query); expect(children).to.have.length(1); expect(children[0].id === view0.id).to.be.true; @@ -626,8 +632,9 @@ describe("graph class", () => { for (let i = 0; i < views.length; ++i) { expect(g.addView(views[i])).to.be.true; } - - const query: Query = { metrics: [], dimensions: dims, clauses: testClauses }; + const qOpts: QueryOpts = { metrics: [], dimensions: dims, clauses: testClauses, + sort: []}; + const query = new Query(qOpts); 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 36a5ea1070d368b1b145ba7c9a37393b32b74ddc..0d57cf35106818cd8ccada197464d069f295fca7 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 6ecc43f06fbb1eeb2888319080fb4a79e80ddf91..bfa929d3d8d1c060c03296457accab2634d459f5 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 71c10136ed977625cc3ac545ed5febac17724697..67b27830040377bfb783946bbd2387f9da9bef70 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,146 +161,221 @@ const clauses: { [key: string]: Clause } = { new Clause ({filters: [filters["equalFilterView"]]}) } -// Views -const JoinWithAncestors = ViewHandler.growView({ - metrics: [mets["met:sell:count:quantity"]], - dimensions: [dims["dim:seller:id"],dims["dim:provider:id"]], - clauses: [] -},[views["view:Sell"]]); +let qOpts : {[key: string]: QueryOpts} = { + "JoinWithAncestors": + { + metrics: [mets["met:sell:count:quantity"]], + dimensions: [dims["dim:seller:id"],dims["dim:provider:id"]], + clauses: [], + sort: [] + }, + "joinWithNoMetrics": + { + metrics: [], + dimensions: [dims["dim:product:name"],dims["dim:seller:name"]], + clauses: [], + sort: [] + }, + "growOneView": + { + metrics: [mets["met:seller:min:age"]], + dimensions: [dims["dim:seller:name"],dims["dim:seller:sex"]], + clauses: [], + sort: [] + }, + "multipleClause": + { + metrics: [mets["met:sell:avg:quantity"]], + dimensions: [dims["dim:sell:datein"],dims["dim:seller:name"], + dims["dim:client:name"]], + // and between filters => (A) and (B) + clauses: [clauses["expired"],clauses["averageBought"]], + sort: [] + }, + "singleClause": + { + metrics: [mets["met:sell:avg:quantity"]], + dimensions: [dims["dim:sell:datein"],dims["dim:seller:name"], + dims["dim:client:name"]], + clauses: [clauses["expiredAndAverage"]], + sort: [] + }, + "equalfilter": + { + metrics: [], + dimensions: [dims["dim:client:name"],dims["dim:product:validity"]], + clauses: [clauses["lastDay"]], + sort: [] + }, + "withSortView:0": + { + metrics: [mets["met:sell:sum:quantity"]], + dimensions: [dims["dim:client:name"]], + clauses: [], + sort: [mets["met:sell:sum:quantity"]] + }, + "withSortView:1": + { + metrics: [mets["met:sell:sum:quantity"]], + dimensions: [dims["dim:client:id"]], + clauses: [], + sort: [], + }, + "subDimView:0": + { + metrics: [], + dimensions : [dims["dim:sell:datein"],subdims["subdims_day"], + subdims["subdims_month"],subdims["subdims_year"]], + clauses: [], + sort: [] + }, + "subDimView:1": + { + metrics: [], + dimensions: [dims["dim:sell:datein"]], + clauses: [], + sort: [] + }, + "joinOneView": + { + metrics: [mets["met:product:avg:pricein"]], + dimensions: [], + clauses: [], + sort: [] + }, + "reduceAsView": + { + metrics: [mets["met:sell:sum:quantity"],mets["met:sell:avg:quantity"], + 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: [], + sort: [mets["met:sell:sum:quantity"]] + }, + "clientAverageBought:0": + { + metrics: [mets["met:sell:avg:quantity"]], + dimensions: [dims["dim:client:name"]], + clauses: [clauses["averageBought"]], + sort: [] + }, + "clientAverageBought:1": + { + metrics: [mets["met:sell:avg:quantity"]], + dimensions: [dims["dim:client:name"], dims["dim:seller:id"]], + clauses: [], + sort: [] + }, + "queryNoParent": + { + metrics: [mets["met:sell:count:quantity"]], + dimensions: [subdims["subdims_none"]], + clauses: [], + sort: [] + }, + "queryMetsDims": + { + metrics : Object.keys(mets).map((key) => mets[key]), + dimensions : Object.keys(dims).map((key) => dims[key]), + clauses: [], + sort: [] + }, + "queryNoMets": + { + metrics: [wrongMet], + dimensions: [dims["dim:product:name"]], + clauses: [], + sort: [] + }, + "queryNoDims": + { + metrics: [mets["met:buyout:min:quantity"]], + dimensions: [wrongDim], + clauses: [], + sort: [] + }, + "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"]], + clauses: [], + sort: [] + }, + "queryActive": + { + metrics: [mets["met:seller:max:age"]], + dimensions: [dims["dim:seller:name"],dims["dim:seller:status"]], + clauses: [clauses["equalClauseView"]], + sort: [] + }, + "querySubDim": + { + metrics : [], + dimensions : [subdims["subdims_day"],subdims["subdims_month"]], + clauses: [], + sort: [] + }, +} -const joinWithNoMetrics = ViewHandler.growView({ - metrics: [], - dimensions: [dims["dim:product:name"],dims["dim:seller:name"]], - clauses: [] -}, [views["view:Product"],views["view:Sell"],views["view:Seller"]]); +const queries : {[key: string]: Query} = {}; +for(let i in qOpts){ + queries[i] = new Query(qOpts[i]); +} -const growOneView = ViewHandler.growView({ - metrics: [mets["met:seller:min:age"]], - dimensions: [dims["dim:seller:name"],dims["dim:seller:sex"]], - clauses: [] -}, [views["view:Seller"]]); +const JoinWithAncestors = ViewHandler.growView(queries["JoinWithAncestors"], + [views["view:Sell"]]); -const multipleClause = ViewHandler.growView({ - metrics: [mets["met:sell:avg:quantity"]], - dimensions: [dims["dim:sell:datein"],dims["dim:seller:name"], - dims["dim:client:name"]], - // and between filters => (A) and (B) - clauses: [clauses["expired"],clauses["averageBought"]] -}, [views["view:Sell"],views["view:Seller"],views["view:Client"],views["view:Product"]]); +const joinWithNoMetrics = ViewHandler.growView(queries["joinWithNoMetrics"], + [views["view:Product"], + views["view:Sell"], + views["view:Seller"]]); -const singleClause = ViewHandler.growView({ - metrics: [mets["met:sell:avg:quantity"]], - dimensions: [dims["dim:sell:datein"],dims["dim:seller:name"], - dims["dim:client:name"]], - clauses: [clauses["expiredAndAverage"]] -}, [views["view:Sell"],views["view:Seller"],views["view:Client"],views["view:Product"]]); +const growOneView = ViewHandler.growView(queries["growOneView"], + [views["view:Seller"]]); -const equalfilter = ViewHandler.queryJoin({ - metrics: [], - dimensions: [dims["dim:client:name"],dims["dim:product:validity"]], - clauses: [clauses["lastDay"]] -},[views["view:Sell"],views["view:Client"],views["view:Product"]]); +const multipleClause = ViewHandler.growView(queries["multipleClause"], + [views["view:Sell"], + views["view:Seller"], + views["view:Client"], + views["view:Product"]]); -const withSortView = ViewHandler.queryJoin({ - metrics: [mets["met:sell:sum:quantity"]], - dimensions: [dims["dim:client:name"]], - sort: [mets["met:sell:sum:quantity"]] - },[ViewHandler.queryReduce({ - metrics: [mets["met:sell:sum:quantity"]], - dimensions: [dims["dim:client:id"]] - },views["view:Sell"]),views["view:Client"]]); +const singleClause = ViewHandler.growView(queries["singleClause"], + [views["view:Sell"], + views["view:Seller"], + views["view:Client"], + views["view:Product"]]); -const subDimView = ViewHandler.queryJoin({ - metrics : [], - dimensions : [dims["dim:sell:datein"],subdims["subdims_day"], - subdims["subdims_month"],subdims["subdims_year"]]}, -[ViewHandler.queryReduce({ - metrics: [], - dimensions: [dims["dim:sell:datein"]] -},views["view:Sell"]),views["view:Sell"]]); +const equalfilter = ViewHandler.queryJoin(queries["equalfilter"], + [views["view:Sell"], + views["view:Client"], + views["view:Product"]]); +const withSortView = ViewHandler.queryJoin(queries["withSortView:0"], + [ViewHandler.queryReduce(queries["withSortView:1"], + views["view:Sell"]), + views["view:Client"]]); -const joinOneView = ViewHandler.queryJoin({ - metrics: [mets["met:product:avg:pricein"]], - dimensions: [] -},[views["view:Product"]]); +const subDimView = ViewHandler.queryJoin(queries["subDimView:0"], + [ViewHandler.queryReduce(queries["subDimView:1"], + views["view:Sell"]), + views["view:Sell"]]); -const reduceAsView = ViewHandler.queryReduce({ - metrics: [mets["met:sell:sum:quantity"],mets["met:sell:avg:quantity"], -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"]], -sort: [mets["met:sell:sum:quantity"]] -},views["view:Sell"]); +const joinOneView = ViewHandler.queryJoin(queries["joinOneView"], + [views["view:Product"]]); -const clientAverageBought = ViewHandler.queryReduce({ - metrics: [mets["met:sell:avg:quantity"]], - dimensions: [dims["dim:client:name"]], - clauses: [clauses["averageBought"]] -},ViewHandler.queryJoin({ - metrics: [mets["met:sell:avg:quantity"]], - dimensions: [dims["dim:client:name"], dims["dim:seller:id"]] -},[views["view:Sell"],views["view:Client"]])); +const reduceAsView = ViewHandler.queryReduce(queries["reduceAsView"], + views["view:Sell"]); +const clientAverageBought = ViewHandler.queryReduce(queries["clientAverageBought:0"], + ViewHandler.queryJoin(queries["clientAverageBought:1"], + [views["view:Sell"],views["view:Client"]])); const viewProduct = views["view:Product"]; const viewActiveSeller = views["view:ActiveSeller"]; -const wrongMet = new Metric({ - name: "met:this:is:just:a:test", - aggregation: AggregationType.COUNT, - dataType: DataType.INTEGER -}); - -// Dimensions - -const wrongDim = new Dimension({ name: "dim:this:is:just:a:test", dataType: DataType.INTEGER }); - -// Queries - -const queryNoParent = { - metrics: [mets["met:sell:count:quantity"]], - dimensions: [subdims["subdims_none"]] -} - -const queryMetsDims = { - metrics : Object.keys(mets).map((key) => mets[key]), - dimensions : Object.keys(dims).map((key) => dims[key]) -}; - -const queryNoMets = { - metrics: [wrongMet], - dimensions: [dims["dim:product:name"]] -}; - -const queryNoDims = { - metrics: [mets["met:buyout:min:quantity"]], - dimensions: [wrongDim] -} - -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 queryActive = { - metrics: [mets["met:seller:max:age"]], - dimensions: [dims["dim:seller:name"],dims["dim:seller:status"]], - clauses: [clauses["equalClauseView"]] -} - -// Metrics - -let emptyMetrics: Metric[] = []; -const querySubDim = { - metrics : emptyMetrics - , dimensions : [subdims["subdims_day"],subdims["subdims_month"]] -} - // Exports export const engineScenario: EngineScenario = { @@ -306,15 +385,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 +414,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: [] + } };