From c0b8b938eb8fdb3d0c8aaf77609c0714f30d77ba Mon Sep 17 00:00:00 2001 From: Rafael <rpd17@inf.ufpr.br> Date: Thu, 30 Aug 2018 11:57:28 -0300 Subject: [PATCH] Issue #88: Refactor struct of tests Signed-off-by: Rafael <rpd17@inf.ufpr.br> --- config/ci_dimensions.yaml.example | 47 -- config/ci_enumtypes.yaml.example | 17 - config/ci_metrics.yaml.example | 26 - config/ci_metrics_1.yaml.example | 26 - config/ci_sources.yaml.example | 53 -- config/ci_test.yaml.example | 91 ++- config/ci_views.yaml.example | 82 --- config/market_dimensions.yaml.example | 48 +- config/market_enum.yaml.example | 9 + config/market_main.yaml.example | 3 +- config/market_metrics.yaml.example | 5 + config/market_views.yaml.example | 37 +- src/adapter/postgres.spec.ts | 202 +------ src/api/controllers/collect.spec.ts | 173 ++++-- src/api/controllers/collect.ts | 32 +- src/api/controllers/data.spec.ts | 32 +- src/api/controllers/engine.spec.ts | 9 +- src/core/engine.spec.ts | 151 ++--- src/util/scenarioHandler.ts | 167 ++++++ src/util/viewHandler.ts | 12 +- test/postgres/fixtures/activeseller.json | 7 + .../fixtures/{view14.json => buyout.json} | 10 +- .../fixtures/{view12.json => client.json} | 0 test/postgres/fixtures/distribute.json | 7 + .../fixtures/{view11.json => product.json} | 0 .../fixtures/{view15.json => provider.json} | 0 test/postgres/fixtures/sell.json | 17 + test/postgres/fixtures/seller.json | 17 + test/postgres/fixtures/view10.json | 17 - test/postgres/fixtures/view13.json | 17 - test/postgres/fixtures/view16.json | 7 - test/scenario.ts | 566 ++++++++---------- tslint.json | 1 + 33 files changed, 827 insertions(+), 1061 deletions(-) delete mode 100644 config/ci_dimensions.yaml.example delete mode 100644 config/ci_enumtypes.yaml.example delete mode 100644 config/ci_metrics.yaml.example delete mode 100644 config/ci_metrics_1.yaml.example delete mode 100644 config/ci_sources.yaml.example delete mode 100644 config/ci_views.yaml.example create mode 100644 src/util/scenarioHandler.ts create mode 100644 test/postgres/fixtures/activeseller.json rename test/postgres/fixtures/{view14.json => buyout.json} (50%) rename test/postgres/fixtures/{view12.json => client.json} (100%) create mode 100644 test/postgres/fixtures/distribute.json rename test/postgres/fixtures/{view11.json => product.json} (100%) rename test/postgres/fixtures/{view15.json => provider.json} (100%) create mode 100644 test/postgres/fixtures/sell.json create mode 100644 test/postgres/fixtures/seller.json delete mode 100644 test/postgres/fixtures/view10.json delete mode 100644 test/postgres/fixtures/view13.json delete mode 100644 test/postgres/fixtures/view16.json diff --git a/config/ci_dimensions.yaml.example b/config/ci_dimensions.yaml.example deleted file mode 100644 index c7f2e388..00000000 --- a/config/ci_dimensions.yaml.example +++ /dev/null @@ -1,47 +0,0 @@ -- - name: "dim:2" - dataType: "integer" - description: "A dimension of Blendb. Has 5 possible values." -- - name: "dim:3" - dataType: "string" - description: "A dimension of Blendb. Has 5 possible values." -- - name: "dim:4" - dataType: "string" - description: "A dimension of Blendb. Has 5 possible values." -- - name: "dim:5" - dataType: "boolean" - description: "A dimension of Blendb. Has 2 possible values." -- - name: "dim:6" - dataType: "integer" - description: "A dimension of Blendb. Has 5 possible values." -- - name: "dim:7" - dataType: "integer" - description: "A dimension of Blendb. Has 5 possible values." -- - name: "dim:8" - dataType: "integer" - description: "A dimension of Blendb. Has 5 possible values." -- - name: "dim:9" - dataType: "integer" - parent: "dim:0" - relation: "day" - description: "A dimension of Blendb. Has 30 possible values." -- - name: "dim:10" - dataType: "integer" - parent: "dim:0" - relation: "month" - description: "A dimension of Blendb. Has 12 possible values." -- - name: "dim:11" - dataType: "integer" - parent: "dim:0" - relation: "year" - description: "A dimension of Blendb. Has 1 possible value." - diff --git a/config/ci_enumtypes.yaml.example b/config/ci_enumtypes.yaml.example deleted file mode 100644 index b2f45fbc..00000000 --- a/config/ci_enumtypes.yaml.example +++ /dev/null @@ -1,17 +0,0 @@ -- - name: "enumtype:1" - values: - - "test_4" - - "test_5" - - "test_6" - - "string" -- - name: "enumtype:2" - values: - - "test_7" - - "test_8" -- - name: "enumtype:3" - values: - - "test_9" - diff --git a/config/ci_metrics.yaml.example b/config/ci_metrics.yaml.example deleted file mode 100644 index 136dce85..00000000 --- a/config/ci_metrics.yaml.example +++ /dev/null @@ -1,26 +0,0 @@ -- - name: "met:2" - dataType: "integer" - aggregation: "avg" - description: "No meaning, just used for test" -- - name: "met:3" - dataType: "integer" - aggregation: "sum" - description: "No meaning, just used for test" -- - name: "met:4" - dataType: "integer" - aggregation: "sum" - description: "No meaning, just used for test" -- - name: "met:5" - dataType: "integer" - aggregation: "avg" - description: "No meaning, just used for test" -- - name: "met:6" - dataType: "integer" - aggregation: "count" - description: "No meaning, just used for test" - diff --git a/config/ci_metrics_1.yaml.example b/config/ci_metrics_1.yaml.example deleted file mode 100644 index 6cdf7241..00000000 --- a/config/ci_metrics_1.yaml.example +++ /dev/null @@ -1,26 +0,0 @@ -- - name: "met:7" - dataType: "integer" - aggregation: "count" - description: "No meaning, just used for test" -- - name: "met:8" - dataType: "integer" - aggregation: "sum" - description: "No meaning, just used for test" -- - name: "met:9" - dataType: "integer" - aggregation: "count" - description: "No meaning, just used for test" -- - name: "met:10" - dataType: "integer" - aggregation: "max" - description: "No meaning, just used for test" -- - name: "met:11" - dataType: "integer" - aggregation: "min" - description: "No meaning, just used for test" - diff --git a/config/ci_sources.yaml.example b/config/ci_sources.yaml.example deleted file mode 100644 index 20a624bd..00000000 --- a/config/ci_sources.yaml.example +++ /dev/null @@ -1,53 +0,0 @@ -- - name: "source_1" - description: "source with 2 entries" - fields: - - - name: "fields:0" - description: "first entry" - dataType: "enumtype" - enumType: "enumtype:0" - - - name: "fields:1" - description: "second entry" - dataType: "string" -- - name: "source_2" - description: "source with one entry and undefined dataType" - fields: - - - name: "fields:0" - description: "first entry" - dataType: "string" -- - name: "source_3" - description: "source with one entry and without description" - fields: - - - name: "fields:0" - dataType: "string" -- - name: "source_4" - description: "source with all core types from blendb" - fields: - - - name: "fields:0" - description: "first entry" - dataType: "integer" - - - name: "fields:1" - description: "second entry" - dataType: "float" - - - name: "fields:2" - description: "third entry" - dataType: "string" - - - name: "fields:3" - description: "fourth entry" - dataType: "boolean" - - - name: "fields:4" - description: "fifth entry" - dataType: "date" - diff --git a/config/ci_test.yaml.example b/config/ci_test.yaml.example index 165803f6..eed9cb2b 100644 --- a/config/ci_test.yaml.example +++ b/config/ci_test.yaml.example @@ -3,76 +3,71 @@ # so this example file in fact is the CI test file views: links: - - config/ci_views.yaml.example + - config/market_views.yaml.example obj: - - alias: "View 0" - data: "test/postgres/fixtures/view0.json" + alias: "Seller" + data: "test/postgres/fixtures/seller.json" origin: true dimensions: - - "dim:0" - - "dim:7" + - "dim:seller:name" + - "dim:seller:sex" + - "dim:seller:cpf" + - "dim:seller:id" + - "dim:seller:status" metrics: - - "met:0" - - "met:1" - - "met:2" - - "met:10" + - "met:seller:avg:age" + - "met:seller:max:age" + - "met:seller:min:age" + - "met:seller:count:age" metrics: links: - - config/ci_metrics.yaml.example - - config/ci_metrics_1.yaml.example + - config/market_metrics.yaml.example obj: - - - name: "met:0" - dataType: "integer" - aggregation: "sum" - description: "No meaning, just used for test" - - - name: "met:1" - dataType: "integer" + - + name: "met:seller:avg:age" + dataType: "float" aggregation: "avg" - description: "No meaning, just used for test" + description: "The seller average age" dimensions: links: - - config/ci_dimensions.yaml.example + - config/market_dimensions.yaml.example obj: - - name: "dim:0" - dataType: "date" - description: "A dimension of Blendb. Has 5 possible values." - - - name: "dim:1" - dataType: "date" - description: "A dimension of Blendb. Has 5 possible values." + name: "dim:seller:name" + dataType: "string" + description: "Name of the seller from market" enumTypes: links: - - config/ci_enumtypes.yaml.example + - config/market_enum.yaml.example obj: - - - name: "enumtype:0" - values: - - "male" - - "female" - - "binary" - - "undecided" + - [] sources: links: - - config/ci_sources.yaml.example + - config/market_sources.yaml.example obj: - - name: "source_0" - description: "source with 3 entries" + name: "Seller" + description: "Market worker" fields: - - name: "fields:0" - description: "first entry" - dataType: "string" - - - name: "fields:1" - description: "second entry" + name: "name" + description: "Seller name" dataType: "string" - - name: "fields:2" - description: "third entry" + name: "age" + description: "Seller age" + dataType: "integer" + - + name: "sex" + description: "Seller sex" + enumType: "enumsex" + dataType: "enumtype" + - + name: "CPF" + description: "Seller CPF" dataType: "string" - + - + name: "id" + description: "Seller id" + dataType: "integer" diff --git a/config/ci_views.yaml.example b/config/ci_views.yaml.example deleted file mode 100644 index 0c7d954c..00000000 --- a/config/ci_views.yaml.example +++ /dev/null @@ -1,82 +0,0 @@ -- - alias: "View 1" - data: "test/postgres/fixtures/view1.json" - origin: true - dimensions: - - "dim:1" - - "dim:8" - metrics: - - "met:3" - - "met:4" -- - alias: "View 2" - data: "test/postgres/fixtures/view2.json" - origin: true - dimensions: - - "dim:2" - metrics: - - "met:5" - - "met:6" - - "met:11" -- - alias: "View 3" - data: "test/postgres/fixtures/view3.json" - origin: true - dimensions: - - "dim:2" - - "dim:3" - - "dim:4" -- - alias: "View 4" - data: "test/postgres/fixtures/view4.json" - origin: true - dimensions: - - "dim:2" - - "dim:7" - - "dim:8" -- - alias: "View 5" - data: "test/postgres/fixtures/view5.json" - origin: true - dimensions: - - "dim:3" - metrics: - - "met:7" -- - alias: "View 6" - data: "test/postgres/fixtures/view6.json" - origin: true - dimensions: - - "dim:4" - metrics: - - "met:8" -- - alias: "View 7" - data: "test/postgres/fixtures/view7.json" - origin: true - dimensions: - - "dim:4" - - "dim:5" - - "dim:6" -- - alias: "View 8" - data: "test/postgres/fixtures/view8.json" - origin: true - dimensions: - - "dim:5" - - "dim:6" - metrics: - - "met:9" -- - alias: "view 9" - data: "test/postgres/fixtures/view9.json" - origin: false - dimensions: - - "dim:2" - metrics: - - "met:5" - - "met:6" - - "met:7" - clauses: - - "dim:2!=1" - diff --git a/config/market_dimensions.yaml.example b/config/market_dimensions.yaml.example index d3fb7f38..23155f2c 100644 --- a/config/market_dimensions.yaml.example +++ b/config/market_dimensions.yaml.example @@ -1,3 +1,7 @@ +- + name: "dim:seller:name" + dataType: "string" + description: "The name of the seller from market" - name: "dim:seller:sex" dataType: "enumtype" @@ -8,9 +12,10 @@ dataType: "string" description: "CPF of the seller from market" - - name: "dim:seller:id" - dataType: "integer" - description: "id of the seller from market" + name: "dim:seller:status" + dataType: "enumtype" + enumType: "workingStatus" + description: "The status of the seller from market" - name: "dim:product:name" dataType: "string" @@ -19,10 +24,6 @@ name: "dim:product:validity" dataType: "date" description: "Validity of the product from market" -- - name: "dim:product:id" - dataType: "integer" - description: "id of the product from market" - name: "dim:client:name" dataType: "string" @@ -31,24 +32,16 @@ name: "dim:client:cpf" dataType: "string" description: "CPF of the client from market" -- - name: "dim:client:id" - dataType: "integer" - description: "id of the client from market" - name: "dim:sell:registered" dataType: "boolean" - description: "Check if the client is registered" -- - name: "dim:sell:product.id" - dataType: "integer" - description: "id of the product from market" + description: "Check if the client is registered" - - name: "dim:sell:seller.id" + name: "dim:seller:id" dataType: "integer" description: "id of the seller from market" - - name: "dim:sell:client.id" + name: "dim:client:id" dataType: "integer" description: "id of the client from market" - @@ -58,28 +51,17 @@ - name: "dim:buyout:datein" dataType: "date" - description: "Date of the buyout was realized" + description: "Date of the buyout was realized" - - name: "dim:buyout:provider.id" - dataType: "integer" - description: "id of the provider from market" -- - name: "dim:buyout:product.id" + name: "dim:product:id" dataType: "integer" description: "id of the product from market" - name: "dim:provider:name" dataType: "string" - description: "Name of the provider from market" + description: "Name of the provider from market" - name: "dim:provider:id" - dataType: "integer" - description: "id of the provider from market" -- - name: "dim:distribute:provider.id" dataType: "string" description: "id of the provider from market" -- - name: "dim:distribute:product.id" - dataType: "string" - description: "id of the product from market" + diff --git a/config/market_enum.yaml.example b/config/market_enum.yaml.example index 43b7c5aa..b3d9c0b0 100644 --- a/config/market_enum.yaml.example +++ b/config/market_enum.yaml.example @@ -5,4 +5,13 @@ - "female" - "nonbinary" - "undecided" +- + name: "workingStatus" + values: + - "active" + - "inactive" + - "vacation" + - "sick leave" + - "maternity leave" + diff --git a/config/market_main.yaml.example b/config/market_main.yaml.example index 9c95e885..36893eba 100644 --- a/config/market_main.yaml.example +++ b/config/market_main.yaml.example @@ -4,13 +4,14 @@ views: obj: - alias: "Seller" - data: "test/postgres/fixtures/view10.json" + data: "test/postgres/fixtures/seller.json" origin: true dimensions: - "dim:seller:name" - "dim:seller:sex" - "dim:seller:cpf" - "dim:seller:id" + - "dim:seller:status" metrics: - "met:seller:avg:age" - "met:seller:max:age" diff --git a/config/market_metrics.yaml.example b/config/market_metrics.yaml.example index 26046343..4853839c 100644 --- a/config/market_metrics.yaml.example +++ b/config/market_metrics.yaml.example @@ -1,3 +1,8 @@ +- + name: "met:seller:avg:age" + dataType: "float" + aggregation: "avg" + description: "The seller average age" - name: "met:seller:max:age" dataType: "integer" diff --git a/config/market_views.yaml.example b/config/market_views.yaml.example index 783df754..5c3580db 100644 --- a/config/market_views.yaml.example +++ b/config/market_views.yaml.example @@ -1,6 +1,6 @@ - alias: "Product" - data: "test/postgres/fixtures/view11.json" + data: "test/postgres/fixtures/product.json" origin: true dimensions: - "dim:product:name" @@ -15,7 +15,7 @@ - "met:product:min:priceout" - alias: "Client" - data: "test/postgres/fixtures/view12.json" + data: "test/postgres/fixtures/client.json" origin: true dimensions: - "dim:client:name" @@ -23,13 +23,13 @@ - "dim:client:id" - alias: "Sell" - data: "test/postgres/fixtures/view13.json" + data: "test/postgres/fixtures/sell.json" origin: true dimensions: - "dim:sell:registered" - - "dim:sell:product.id" - - "dim:sell:seller.id" - - "dim:sell:client.id" + - "dim:product:id" + - "dim:seller:id" + - "dim:client:id" - "dim:sell:datein" metrics: - "met:sell:sum:quantity" @@ -37,27 +37,38 @@ - "met:sell:count:quantity" - alias: "Buyout" - data: "test/postgres/fixtures/view14.json" + data: "test/postgres/fixtures/buyout.json" origin: true dimensions: - "dim:buyout:datein" - - "dim:buyout:provider.id" - - "dim:buyout:product.id" + - "dim:provider:id" + - "dim:product:id" metrics: - "met:buyout:avg:quantity" - "met:buyout:max:quantity" - "met:buyout:min:quantity" - alias: "Provider" - data: "test/postgres/fixtures/view15.json" + data: "test/postgres/fixtures/provider.json" origin: true dimensions: - "dim:provider:name" - "dim:provider:id" - alias: "Distribute" - data: "test/postgres/fixtures/view16.json" + data: "test/postgres/fixtures/distribute.json" origin: true dimensions: - - "dim:distribute:provider.id" - - "dim:distribute:product.id" + - "dim:provider:id" + - "dim:product:id" +- + alias: "ActiveSeller" + data: "test/postgres/fixtures/activeseller.json" + origin: true + dimensions: + - "dim:seller:name" + - "dim:seller:status" + metrics: + - "met:seller:max:age" + clauses: + - "dim:seller:status==active" diff --git a/src/adapter/postgres.spec.ts b/src/adapter/postgres.spec.ts index a77952af..7bd2ca34 100644 --- a/src/adapter/postgres.spec.ts +++ b/src/adapter/postgres.spec.ts @@ -19,7 +19,6 @@ */ import { expect } from "chai"; - import { PostgresAdapter } from "./postgres"; import { MonetAdapter, MonetConfig } from "./monet"; import { Adapter } from "../core/adapter"; @@ -87,29 +86,12 @@ describe("Sql adapter", () => { done(); }); }); - it("should get data from join of 2 views (without selection)", (done) => { - let view = adapterScenario.noSelectionView; + it("should sort data from join of two views", (done) => { + let view = adapterScenario.sortView; adapter.getDataFromView(view, (err, result) => { expect(err).to.be.a("null"); expect(result).to.be.an("array"); - expect(result).to.have.length(1); - expect(result[0]).to.be.an("object"); - let keys: string[] = []; - keys = keys.concat(view.metrics.map((item) => item.name)); - keys = keys.concat(view.dimensions.map((item) => item.name)); - result.forEach((row) => { - expect(row).to.be.an("object"); - expect(row).to.have.all.keys(keys); - }); - done(); - }); - }); - it("should get data from join of 2 views (with selection)", (done) => { - let view = adapterScenario.withSelectionView; - adapter.getDataFromView(view, (err, result) => { - expect(err).to.be.a("null"); - expect(result).to.be.an("array"); - expect(result).to.have.length(5); + expect(result).to.have.length(4); expect(result[0]).to.be.an("object"); let keys: string[] = []; keys = keys.concat(view.metrics.map((item) => item.name)); @@ -119,7 +101,7 @@ describe("Sql adapter", () => { expect(row).to.have.all.keys(keys); }); done(); - }); + }); }); it("should get data from single view (with sub-dimension)", (done) => { let view = adapterScenario.subDimensionView; @@ -138,8 +120,8 @@ describe("Sql adapter", () => { done(); }); }); - it("should get data from join of 4 views (with selection)", (done) => { - let view = adapterScenario.join4View; + it("should get data from join of one view", (done) => { + let view = adapterScenario.joinWithOneView; adapter.getDataFromView(view, (err, result) => { expect(err).to.be.a("null"); expect(result).to.be.an("array"); @@ -155,28 +137,9 @@ describe("Sql adapter", () => { done(); }); }); - - it("should get data from different sub dimensions with same parent", (done) => { - let view = adapterScenario.dateView; - adapter.getDataFromView(view, (err, result) => { - expect(err).to.be.a("null"); - expect(result).to.be.an("array"); - expect(result).to.have.length(5); - expect(result[0]).to.be.an("object"); - let keys: string[] = []; - keys = keys.concat(view.metrics.map((item) => item.name)); - keys = keys.concat(view.dimensions.map((item) => item.name)); - result.forEach((row) => { - expect(row).to.be.an("object"); - expect(row).to.have.all.keys(keys); - expect(row).to.have.property("dim:0:year", 2017); - }); - done(); - }); - }); - it("should get data from view with all types of agreggation", (done) => { - let view = adapterScenario.aggrView; - adapter.getDataFromView(view, (err, result) => { + it("should get data from view with a single clause", (done) => { + let view = adapterScenario.filterWithEqual; + adapter.getDataFromView(view, (err, result) => { expect(err).to.be.a("null"); expect(result).to.be.an("array"); expect(result).to.have.length(1); @@ -188,22 +151,15 @@ describe("Sql adapter", () => { expect(row).to.be.an("object"); expect(row).to.have.all.keys(keys); }); - - expect(parseInt(result[0]["met:0"], 10)).to.be.equal(15); - expect(parseInt(result[0]["met:1"], 10)).to.be.equal(3); - expect(parseInt(result[0]["met:6"], 10)).to.be.equal(5); - expect(parseInt(result[0]["met:10"], 10)).to.be.equal(5); - expect(parseInt(result[0]["met:11"], 10)).to.be.equal(1); done(); }); }); - - it("should get data from view when a single clause exists", (done) => { - let view = adapterScenario.clauseView; + it("should get data from reduce with the same attributes of view", (done) => { + let view = adapterScenario.reduceAsView; adapter.getDataFromView(view, (err, result) => { expect(err).to.be.a("null"); expect(result).to.be.an("array"); - expect(result).to.have.length(1); + expect(result).to.have.length(5); expect(result[0]).to.be.an("object"); let keys: string[] = []; keys = keys.concat(view.metrics.map((item) => item.name)); @@ -212,21 +168,15 @@ describe("Sql adapter", () => { expect(row).to.be.an("object"); expect(row).to.have.all.keys(keys); }); - - expect(parseInt(result[0]["met:0"], 10)).to.be.equal(1); - expect(parseInt(result[0]["met:1"], 10)).to.be.equal(1); - expect(parseInt(result[0]["met:2"], 10)).to.be.equal(1); - expect(result[0]["dim:0"].getDate()).to.be.equal(1); done(); }); }); - - it("should get data from view with single clause, with more than on filter", (done) => { - let view = adapterScenario.multiFilterView; + it("should get data from join into reduce with a single clause", (done) => { + let view = adapterScenario.filterAverageBought; adapter.getDataFromView(view, (err, result) => { expect(err).to.be.a("null"); expect(result).to.be.an("array"); - expect(result).to.have.length(2); + expect(result).to.have.length(3); expect(result[0]).to.be.an("object"); let keys: string[] = []; keys = keys.concat(view.metrics.map((item) => item.name)); @@ -235,43 +185,11 @@ describe("Sql adapter", () => { expect(row).to.be.an("object"); expect(row).to.have.all.keys(keys); }); - done(); }); }); - it("should get data from view with multiple clauses", (done) => { - let view = adapterScenario.multiClauseView; - adapter.getDataFromView(view, (err, result) => { - expect(err).to.be.a("null"); - expect(result).to.be.an("array"); - expect(result).to.have.length(0); - - done(); - }); - }); - - it("should get data from view with a clause with not equal operator", (done) => { - let view = adapterScenario.notEqualView; - adapter.getDataFromView(view, (err, result) => { - expect(err).to.be.a("null"); - expect(result).to.be.an("array"); - expect(result).to.have.length(2); - expect(result[0]).to.be.an("object"); - let keys: string[] = []; - keys = keys.concat(view.metrics.map((item) => item.name)); - keys = keys.concat(view.dimensions.map((item) => item.name)); - result.forEach((row) => { - expect(row).to.be.an("object"); - expect(row).to.have.all.keys(keys); - }); - - done(); - }); - }); - - it("should get data from view, using > AND < operators on filters", (done) => { - let view = adapterScenario.gtltView; + let view = adapterScenario.multipleClause; adapter.getDataFromView(view, (err, result) => { expect(err).to.be.a("null"); expect(result).to.be.an("array"); @@ -284,51 +202,11 @@ describe("Sql adapter", () => { expect(row).to.be.an("object"); expect(row).to.have.all.keys(keys); }); - - done(); - }); - }); - - it("should get data from view, using >= AND <= operators on filters", (done) => { - let view = adapterScenario.geleView; - adapter.getDataFromView(view, (err, result) => { - expect(err).to.be.a("null"); - expect(result).to.be.an("array"); - expect(result).to.have.length(3); - expect(result[0]).to.be.an("object"); - let keys: string[] = []; - keys = keys.concat(view.metrics.map((item) => item.name)); - keys = keys.concat(view.dimensions.map((item) => item.name)); - result.forEach((row) => { - expect(row).to.be.an("object"); - expect(row).to.have.all.keys(keys); - }); - - done(); - }); - }); - - it("should get data when a filter does not have any effect on the query", (done) => { - let view = adapterScenario.notMatchFilterView; - adapter.getDataFromView(view, (err, result) => { - expect(err).to.be.a("null"); - expect(result).to.be.an("array"); - expect(result).to.have.length(5); - expect(result[0]).to.be.an("object"); - let keys: string[] = []; - keys = keys.concat(view.metrics.map((item) => item.name)); - keys = keys.concat(view.dimensions.map((item) => item.name)); - result.forEach((row) => { - expect(row).to.be.an("object"); - expect(row).to.have.all.keys(keys); - }); - done(); }); }); - - it("should get data using COUNT when the view is not origin", (done) => { - let view = adapterScenario.notOriginCount; + it("should get data from view with single clause, with more than one filter", (done) => { + let view = adapterScenario.singleClause; adapter.getDataFromView(view, (err, result) => { expect(err).to.be.a("null"); expect(result).to.be.an("array"); @@ -341,36 +219,15 @@ describe("Sql adapter", () => { expect(row).to.be.an("object"); expect(row).to.have.all.keys(keys); }); - done(); }); }); - - it("should get data from a unmaterializeble view", (done) => { - let view = adapterScenario.unMaterializebleView; + it("should get data from view with no metrics", (done) => { + let view = adapterScenario.joinWithNoMetrics; adapter.getDataFromView(view, (err, result) => { expect(err).to.be.a("null"); expect(result).to.be.an("array"); - expect(result).to.have.length(2); - expect(result[0]).to.be.an("object"); - let keys: string[] = []; - keys = keys.concat(view.metrics.map((item) => item.name)); - keys = keys.concat(view.dimensions.map((item) => item.name)); - result.forEach((row) => { - expect(row).to.be.an("object"); - expect(row).to.have.all.keys(keys); - }); - - done(); - }); - }); - - it("should get data from partial joins", (done) => { - let view = adapterScenario.partialJoinView; - adapter.getDataFromView(view, (err, result) => { - expect(err).to.be.a("null"); - expect(result).to.be.an("array"); - expect(result).to.have.length(1); + expect(result).to.have.length(5); expect(result[0]).to.be.an("object"); let keys: string[] = []; keys = keys.concat(view.metrics.map((item) => item.name)); @@ -379,17 +236,15 @@ describe("Sql adapter", () => { expect(row).to.be.an("object"); expect(row).to.have.all.keys(keys); }); - done(); }); }); - - it("should get data from view when joins can be propagated", (done) => { - let view = adapterScenario.propagatedClauseView; + it("should get data from join with one metric and one view", (done) => { + let view = adapterScenario.growOneView; adapter.getDataFromView(view, (err, result) => { expect(err).to.be.a("null"); expect(result).to.be.an("array"); - expect(result).to.have.length(2); + expect(result).to.have.length(5); expect(result[0]).to.be.an("object"); let keys: string[] = []; keys = keys.concat(view.metrics.map((item) => item.name)); @@ -398,17 +253,15 @@ describe("Sql adapter", () => { expect(row).to.be.an("object"); expect(row).to.have.all.keys(keys); }); - done(); }); }); - - it("should get data from view when joins propaged is in the aggregation", (done) => { - let view = adapterScenario.propagatedClauseAggrView; + it("should get data from join with unrelated dimension", (done) => { + let view = adapterScenario.JoinWithAncestors; adapter.getDataFromView(view, (err, result) => { expect(err).to.be.a("null"); expect(result).to.be.an("array"); - expect(result).to.have.length(4); + expect(result).to.have.length(5); expect(result[0]).to.be.an("object"); let keys: string[] = []; keys = keys.concat(view.metrics.map((item) => item.name)); @@ -417,7 +270,6 @@ describe("Sql adapter", () => { expect(row).to.be.an("object"); expect(row).to.have.all.keys(keys); }); - done(); }); }); diff --git a/src/api/controllers/collect.spec.ts b/src/api/controllers/collect.spec.ts index 7ad1619a..c8c3947b 100644 --- a/src/api/controllers/collect.spec.ts +++ b/src/api/controllers/collect.spec.ts @@ -74,8 +74,7 @@ describe("API collect controller", () => { .post("/v1/collect/thisisjustatest") .send({"fields:1": 1, "fields:2": 2}) .expect(500) - .expect((res: any) => { - + .expect((res: any) => { const message = "Query execution failed: " + "Could not construct query with the given parameters."; const error = "The source named 'thisisjustatest' was not found"; @@ -83,31 +82,42 @@ describe("API collect controller", () => { expect(res.body).to.have.property("message"); expect(res.body.message).to.be.eql(message); expect(res.body.error).to.be.eql(error); - + }) .end(done); }); - it("should respond 500 when fields:0 does not exist on Source", (done) => { + it("should respond 500 when name does not exist in Product from market", (done) => { request(server) - .post("/v1/collect/source_0") - .send({"fields:1": 1, "fields:2": 2}) - .expect(500) - .expect((res: any) => { - const message = "Query execution failed: " + - "Could not construct query with the given parameters."; - const error = "The 'fields:0' wasn't informed on json"; - expect(res.body).to.be.an("object"); - expect(res.body).to.have.property("message"); - expect(res.body.message).to.be.eql(message); - expect(res.body.error).to.be.eql(error); - + .post("/v1/collect/Product") + .send({ + "pricein": 17.51, + "priceout": 30.55, + "validity": "2018-05-16", + "id": 5 + }) + .expect(500) + .expect((res: any) => { + const message = "Query execution failed: " + + "Could not construct query with the given parameters."; + const error = "The 'name' wasn't informed on json"; + expect(res.body).to.be.an("object"); + expect(res.body).to.have.property("message"); + expect(res.body.message).to.be.eql(message); + expect(res.body.error).to.be.eql(error); }) .end(done); }); - it("should respond 200 when data has been stored on source_0", (done) => { + it("should respond 200 when data has been stored on Product", (done) => { request(server) - .post("/v1/collect/source_0") - .send({"fields:0": "teste", "fields:1": "test1", "fields:2": "teste2"}) + .post("/v1/collect/Sell") + .send({ + "Registered": false, + "Product.id": 1, + "Client.id": 1, + "Seller.id": 3, + "Quantity": 10, + "Datein" : "2017-10-30" + }) .expect(200) .expect((res: any) => { const message = "Data has been successfully received and stored by the server"; @@ -118,15 +128,21 @@ describe("API collect controller", () => { }) .end(done); }); - it("should respond 500 when value isn't defined on enumtype:0 ", (done) => { + it("should respond 500 when value isn't defined on enumtype ", (done) => { request(server) - .post("/v1/collect/source_1") - .send({"fields:0": 1, "fields:1": 2}) + .post("/v1/collect/Seller") + .send({ + "name": "radom", + "age" : 25, + "sex" : "thisisjustatest", + "CPF" : "145.827.483-76", + "id" : 4 + }) .expect(500) .expect((res: any) => { const message = "Query execution failed: " + "Could not construct query with the given parameters."; - const error = "The value '1' from 'fields:0' isn't listed on enumtype:0"; + const error = "The value 'thisisjustatest' from 'sex' isn't listed on enumsex"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); expect(res.body.message).to.be.eql(message); @@ -135,31 +151,44 @@ describe("API collect controller", () => { }) .end(done); }); - it("should respond 200 when data has been stored on source_1", (done) => { + it("should respond 500 when dataType from id isn't integer", (done) => { request(server) - .post("/v1/collect/source_1") - .send({"fields:0": "male", "fields:1": "test1"}) - .expect(200) + .post("/v1/collect/Product") + .send({ + "name":"strategy", + "pricein": 17.51, + "priceout": 30.55, + "validity": "1991-05-16", + "id": "nope" + }) + .expect(500) .expect((res: any) => { - const message = "Data has been successfully received and stored by the server"; + const message = "Query execution failed: " + + "Could not construct query with the given parameters."; + const error = "The value 'nope' from 'id' isn't a type integer"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); expect(res.body.message).to.be.eql(message); - + expect(res.body.error).to.be.eql(error); }) .end(done); }); - it("should respond 500 when dataType from fields:0 isn't integer", (done) => { + it("should respond 500 when dataType from pricein isn't float", (done) => { request(server) - .post("/v1/collect/source_4") - .send({"fields:0" : "nope", "fields:1" : 95.5 , "fields:2" : "justabacon" - , "fields:3" : "1991-25-03" , "fields:4" : 1}) + .post("/v1/collect/Product") + .send({ + "name": "strategy", + "pricein": "notafloat", + "priceout": 30.55, + "validity": "1991-05-16", + "id": 5 + }) .expect(500) .expect((res: any) => { const message = "Query execution failed: " + "Could not construct query with the given parameters."; - const error = "The value 'nope' from 'fields:0' isn't a type integer"; + const error = "The value 'notafloat' from 'pricein' isn't a type float"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); expect(res.body.message).to.be.eql(message); @@ -168,36 +197,43 @@ describe("API collect controller", () => { }) .end(done); }); - it("should respond 500 when dataType from fields:1 isn't float", (done) => { + it("should respond 500 when dataType from name isn't string", (done) => { request(server) - .post("/v1/collect/source_4") - .send({"fields:0" : 1 , "fields:1" : "notafloat" , "fields:2" : "justabacon" - , "fields:3" : "1991-25-03" , "fields:4" : 1}) + .post("/v1/collect/Client") + .send({ + "name": 5, + "CPF" : "500.345.583-65", + "id" : 5 + }) .expect(500) .expect((res: any) => { - const message = "Query execution failed: " + "Could not construct query with the given parameters."; - const error = "The value 'notafloat' from 'fields:1' isn't a type float"; + const error = "The value '5' from 'name' isn't a type string"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); expect(res.body.message).to.be.eql(message); expect(res.body.error).to.be.eql(error); - }) .end(done); }); - it("should respond 500 when dataType from fields:2 isn't string", (done) => { + it("should respond 500 when dataType from fields:3 isn't boolean", (done) => { request(server) - .post("/v1/collect/source_4") - .send({"fields:0" : 1 , "fields:1" : 95.5 , "fields:2" : 1 - , "fields:3" : "1991-25-03" , "fields:4" : 1}) + .post("/v1/collect/Sell") + .send({ + "Registered": "notaboolean", + "Product.id": 1, + "Client.id": 9, + "Seller.id": 12, + "Quantity": 50, + "Datein" : "1980-01-30" + }) .expect(500) .expect((res: any) => { const message = "Query execution failed: " + "Could not construct query with the given parameters."; - const error = "The value '1' from 'fields:2' isn't a type string"; + const error = "The value 'notaboolean' from 'Registered' isn't a type boolean"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); expect(res.body.message).to.be.eql(message); @@ -206,17 +242,23 @@ describe("API collect controller", () => { }) .end(done); }); - it("should respond 500 when dataType from fields:3 isn't boolean", (done) => { + it("should respond 500 when the first dataType from Datein isn't a valid date", (done) => { request(server) - .post("/v1/collect/source_4") - .send({"fields:0" : 1 , "fields:1" : 95.5 , "fields:2" : "teste" - , "fields:3" : "notaboolean" , "fields:4" : "1999-10-10"}) + .post("/v1/collect/Sell") + .send({ + "Registered": "true", + "Product.id": 9, + "Client.id": 6, + "Seller.id": 8, + "Quantity": 23, + "Datein" : "1999-25-25" + }) .expect(500) .expect((res: any) => { const message = "Query execution failed: " + "Could not construct query with the given parameters."; - const error = "The value 'notaboolean' from 'fields:3' isn't a type boolean"; + const error = "The value '1999-25-25' from 'Datein' isn't a type date"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); expect(res.body.message).to.be.eql(message); @@ -225,17 +267,23 @@ describe("API collect controller", () => { }) .end(done); }); - it("should respond 500 when the first dataType from fields:4 isn't date", (done) => { + it("should respond 500 when the first dataType from Datein isn't a valid format date", (done) => { request(server) - .post("/v1/collect/source_4") - .send({"fields:0" : 1 , "fields:1" : 95.5 , "fields:2" : "teste" - , "fields:3" : "true" , "fields:4" : "1999-25-25"}) + .post("/v1/collect/Sell") + .send({ + "Registered": "true", + "Product.id": 5, + "Client.id": 16, + "Seller.id": 13, + "Quantity": 7, + "Datein" : "1999/12/12" + }) .expect(500) .expect((res: any) => { const message = "Query execution failed: " + "Could not construct query with the given parameters."; - const error = "The value '1999-25-25' from 'fields:4' isn't a type date"; + const error = "The value '1999/12/12' from 'Datein' isn't a type date"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); expect(res.body.message).to.be.eql(message); @@ -244,11 +292,16 @@ describe("API collect controller", () => { }) .end(done); }); - it("should respond 200 when sucessfull insert data on source_4", (done) => { + it("should respond 200 when data has been stored on Seller", (done) => { request(server) - .post("/v1/collect/source_4") - .send({"fields:0" : 1 , "fields:1" : 95.5 , "fields:2" : "teste" - , "fields:3" : "true" , "fields:4" : "1999-10-10"}) + .post("/v1/collect/Seller") + .send({ + "name": "radom", + "age" : 25, + "sex" : "male", + "CPF" : "145.827.483-76", + "id" : 4 + }) .expect(200) .expect((res: any) => { const message = "Data has been successfully received and stored by the server"; diff --git a/src/api/controllers/collect.ts b/src/api/controllers/collect.ts index 7d01ef79..edc4d1aa 100644 --- a/src/api/controllers/collect.ts +++ b/src/api/controllers/collect.ts @@ -99,15 +99,18 @@ export class CollectCtrl { }, "boolean": function(value: any) { - let test: string = value; - test = test.toLocaleLowerCase(); - if (test === "true" || test === "false"){ + let test = typeof(value); + if(test === "boolean"){ return true; - } - - else{ - return false; - } + }else{ + let test: string = value; + test = test.toLocaleLowerCase(); + if (test === "true" || test === "false"){ + return true; + }else{ + return false; + } + } } }; @@ -126,7 +129,10 @@ export class CollectCtrl { for (let i = 0; i < fields.length; i++){ data[i] = req.body[fields[i].name]; - if (!data[i]){ + // check if the data is empty, however since data may be + // true/false, it must guarantee that it isn't a boolean + // then it'll test if it's empty + if (!(typeof(data[i]) === "boolean") && !data[i]){ throw new Error( "The '" + fields[i].name + "' wasn't informed on json"); } @@ -158,13 +164,7 @@ export class CollectCtrl { "The value '" + data[i] + "' from '" + fields[i].name + "' isn't a type " + [EnumHandler.stringfyDataType(fields[i].dataType)]); } - } - // undefined DataType - else{ - throw new Error( - "The value '" + data[i] + "' from '" + fields[i].name + - "' isn't listed on " + fields[i].enumType); - } + } } } diff --git a/src/api/controllers/data.spec.ts b/src/api/controllers/data.spec.ts index e11ac61d..96a24943 100644 --- a/src/api/controllers/data.spec.ts +++ b/src/api/controllers/data.spec.ts @@ -20,7 +20,6 @@ import * as request from "supertest"; import { expect } from "chai"; - import * as server from "../../main"; import { dataCtrlScenario as tests } from "../../../test/scenario"; import { Query } from "../../common/query"; @@ -53,7 +52,8 @@ describe("API data controller", () => { .expect((res: any) => { const message = "Query execution failed: " + "Could not construct query with the given parameters."; - const error = "The metric named met:-1 was not found"; + const error = "The metric named met:this:is:just:a:test" + + " was not found"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); expect(res.body).to.have.property("error"); @@ -71,7 +71,8 @@ describe("API data controller", () => { .expect((res: any) => { const message = "Query execution failed: " + "Could not construct query with the given parameters."; - const error = "The dimension named dim:-1 was not found"; + const error = "The dimension named dim:this:is:just:a:test" + + " was not found"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); expect(res.body).to.have.property("error"); @@ -83,7 +84,7 @@ describe("API data controller", () => { it("should respond 500 when query has sort item that is not in query data", (done) => { let query = parseQuery(tests.clausal); - query.sort = "dim:0"; + query.sort = "dim:does:not:exist"; request(server) .get("/v1/data") .query(query) @@ -91,7 +92,7 @@ describe("API data controller", () => { .expect((res: any) => { const message = "Query execution failed: " + "Could not construct query with the given parameters."; - const error = "The item 'dim:0'" + + const error = "The item 'dim:does:not:exist'" + " is not present in neither metrics nor dimensions list"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); @@ -124,10 +125,8 @@ describe("API data controller", () => { }); it("should respond 200 and get some data, using a single filter", (done) => { - // Clause does not come to scenario besause is a lot of work for - // only a single test let query = parseQuery(tests.clausal); - query.filters = "dim:7==1"; + query.filters = "dim:product:name==Bacon"; request(server) .get("/v1/data") .query(query) @@ -149,10 +148,8 @@ describe("API data controller", () => { }); it("should respond 200 and get some data, using filters with OR", (done) => { - // Clause does not come to scenario besause is a lot of work for - // only a single test let query = parseQuery(tests.clausal); - query.filters = "dim:7==1,dim:7==2"; + query.filters = "dim:product:name==Trento,dim:product:name==Meat"; request(server) .get("/v1/data") .query(query) @@ -174,10 +171,8 @@ describe("API data controller", () => { }); it("should respond 200 and get some data, using filters with AND", (done) => { - // Clause does not come to scenario besause is a lot of work for - // only a single test let query = parseQuery(tests.clausal); - query.filters = "dim:7!=1;dim:0!=2017-01-01"; + query.filters = "dim:product:name!=Bacon;dim:product:validity!=2018-05-10"; request(server) .get("/v1/data") .query(query) @@ -185,7 +180,7 @@ describe("API data controller", () => { .expect((res: any) => { let result = res.body; expect(result).to.be.an("array"); - expect(result).to.have.length(4); + expect(result).to.have.length(3); expect(result[0]).to.be.an("object"); let keys: string[] = []; keys = keys.concat(tests.clausal.metrics.map((item) => item.name)); @@ -199,10 +194,8 @@ describe("API data controller", () => { }); it("should respond 200 and get some data, sorted", (done) => { - // Clause does not come to scenario besause is a lot of work for - // only a single test let query = parseQuery(tests.clausal); - query.sort = "dim:7,met:0"; + query.sort = "dim:product:id,met:product:avg:pricein"; request(server) .get("/v1/data") .query(query) @@ -219,7 +212,8 @@ describe("API data controller", () => { const row = result[i]; expect(row).to.be.an("object"); expect(row).to.have.all.keys(keys); - expect(row["dim:7"]).to.be.eql(i + 1); + // the magic number will be the first dim:product:id + expect(row["dim:product:id"]).to.be.eql(i + 53); } }) .end(done); diff --git a/src/api/controllers/engine.spec.ts b/src/api/controllers/engine.spec.ts index 45cb983c..6a9aa098 100644 --- a/src/api/controllers/engine.spec.ts +++ b/src/api/controllers/engine.spec.ts @@ -20,7 +20,6 @@ import * as request from "supertest"; import { expect } from "chai"; - import * as server from "../../main"; describe("API engine controller", () => { @@ -32,7 +31,7 @@ describe("API engine controller", () => { .expect((res: any) => { let result = res.body; expect(result).to.be.an("array"); - expect(result).to.have.length(12); + expect(result).to.have.length(16); }) .end(done); }); @@ -43,7 +42,7 @@ describe("API engine controller", () => { .expect((res: any) => { let result = res.body; expect(result).to.be.an("array"); - expect(result).to.have.length(5); + expect(result).to.have.length(7); }) .end(done); }); @@ -54,7 +53,7 @@ describe("API engine controller", () => { .expect((res: any) => { let result = res.body; expect(result).to.be.an("array"); - expect(result).to.have.length(12); + expect(result).to.have.length(16); }) .end(done); }); @@ -64,7 +63,7 @@ describe("API engine controller", () => { .expect((res: any) => { let result = res.body; expect(result).to.be.an("array"); - expect(result).to.have.length(4); + expect(result).to.have.length(3); }) .end(done); }); diff --git a/src/core/engine.spec.ts b/src/core/engine.spec.ts index 03098f5e..6d8973a9 100644 --- a/src/core/engine.spec.ts +++ b/src/core/engine.spec.ts @@ -19,11 +19,8 @@ */ import { expect } from "chai"; - import { Engine } from "./engine"; -import { Metric } from "./metric"; -import { Filter, FilterOperator } from "./filter"; -import { Clause } from "./clause"; +import { FilterOperator } from "./filter"; import { View } from "./view"; import { engineScenario } from "../../test/scenario"; @@ -35,30 +32,42 @@ describe("engine class", () => { const subdim = engineScenario.subDimensions; const views = engineScenario.views; - met.forEach((item) => engine.addMetric(item)); - dim.forEach((item) => engine.addDimension(item)); - subdim.forEach((item) => engine.addDimension(item)); - - views.forEach((view) => engine.addView(view)); + for (let key in met){ + if (met[key]){ + engine.addMetric(met[key]); + } + } + for (let key in dim){ + if (dim[key]){ + engine.addDimension(dim[key]); + } + } + for (let key in views){ + if (views[key]){ + engine.addView(views[key]); + } + } + for (let key in subdim){ + if (subdim[key]){ + engine.addDimension(subdim[key]); + } + } it("should be create a fill that cover all metrics and dimensions", () => { - let query = { - metrics : met.slice(0) - , dimensions : dim.slice(0) - }; + let query = engineScenario.queryMetsDims; let optimalView = engine.query(query); expect(optimalView).to.be.an("object"); expect(optimalView).to.have.property("metrics"); expect(optimalView).to.have.property("dimensions"); expect(optimalView.metrics).to.be.an("array"); expect(optimalView.dimensions).to.be.an("array"); - expect(optimalView.metrics).to.have.length(12); - expect(optimalView.dimensions).to.have.length(12); + expect(optimalView.metrics).to.have.length(16); + expect(optimalView.dimensions).to.have.length(16); }); it("should throw an exception, query with non-existent metric", () => { let error: boolean = false; try { - engine.query({metrics: [engineScenario.wrongMet], dimensions: [dim[0]]}); + engine.query(engineScenario.queryNoMets); } catch (e){ error = true; @@ -71,7 +80,7 @@ describe("engine class", () => { it("should throw an exception, query with non-existent dimension", () => { let error: boolean = false; try { - engine.query({metrics: [met[0]], dimensions: [engineScenario.wrongDim]}); + engine.query(engineScenario.queryNoDims); } catch (e){ error = true; @@ -82,10 +91,7 @@ describe("engine class", () => { }); it("should be create a fill that cover the query, that match perfectly with a existent view", () => { - let query = { - metrics : [met[0], met[1], met[2], met[10]] - , dimensions : [dim[0], dim[7]] - }; + let query = engineScenario.queryProduct; let optimalView = engine.query(query); expect(optimalView).to.be.an("object"); expect(optimalView).to.have.property("metrics"); @@ -94,26 +100,14 @@ describe("engine class", () => { expect(optimalView.metrics).to.be.an("array"); expect(optimalView.dimensions).to.be.an("array"); expect(optimalView.operation).to.be.an("object"); - expect(optimalView.metrics).to.have.length(4); - expect(optimalView.dimensions).to.have.length(2); + expect(optimalView.metrics).to.have.length(6); + expect(optimalView.dimensions).to.have.length(3); expect(optimalView.operation).to.have.property("opcode"); - - expect(optimalView.id).to.be.equal(views[0].id); + expect(optimalView.id).to.be.equal(engineScenario.viewProduct.id); }); it("should be create a fill that cover the query, that match perfectly with a existent view, with clauses", () => { - const clause = new Clause({ - filters: [new Filter({ - target: dim[2], - operator: FilterOperator.NOTEQUAL, - value: "1" - })] - }); - let query = { - metrics : [met[5], met[6], met[7]] - , dimensions : [dim[2]] - , clauses: [clause] - }; + let query = engineScenario.queryActive; let optimalView = engine.query(query); expect(optimalView).to.be.an("object"); expect(optimalView).to.have.property("metrics"); @@ -122,19 +116,14 @@ describe("engine class", () => { expect(optimalView.metrics).to.be.an("array"); expect(optimalView.dimensions).to.be.an("array"); expect(optimalView.operation).to.be.an("object"); - expect(optimalView.metrics).to.have.length(3); - expect(optimalView.dimensions).to.have.length(1); + expect(optimalView.metrics).to.have.length(1); + expect(optimalView.dimensions).to.have.length(2); expect(optimalView.operation).to.have.property("opcode"); - - expect(optimalView.id).to.be.equal(views[9].id); + expect(optimalView.id).to.be.equal(engineScenario.viewActiveSeller.id); }); it("should be create a fill that cover the query, using sub dimensions", () => { - let emptyMetrics: Metric[] = []; - let query = { - metrics : emptyMetrics - , dimensions : subdim.slice(0, 2) - }; + let query = engineScenario.querySubDim; let optimalView = engine.query(query); expect(optimalView).to.be.an("object"); expect(optimalView).to.have.property("metrics"); @@ -145,40 +134,17 @@ describe("engine class", () => { expect(optimalView.dimensions).to.have.length(2); expect(optimalView).satisfy((optView: View) => { - return optView.dimensions.some((item) => item.name === subdim[0].name); + return optView.dimensions.some((item) => item.name === subdim["subdims_day"].name); }); expect(optimalView).satisfy((optView: View) => { - return optView.dimensions.some((item) => item.name === subdim[1].name); - }); - }); - - it("should be create a fill that cover the query, using the parents of sub dimensions", () => { - let emptyMetrics: Metric[] = []; - let query = { - metrics : emptyMetrics - , dimensions : [subdim[2], subdim[4]] - }; - let optimalView = engine.query(query); - expect(optimalView).to.be.an("object"); - expect(optimalView).to.have.property("metrics"); - expect(optimalView).to.have.property("dimensions"); - expect(optimalView.metrics).to.be.an("array"); - expect(optimalView.dimensions).to.be.an("array"); - expect(optimalView.metrics).to.have.length(0); - expect(optimalView.dimensions).to.have.length(2); - - expect(optimalView).satisfy((optView: View) => { - return optView.dimensions.some((item) => item.name === subdim[2].name); - }); - expect(optimalView).satisfy((optView: View) => { - return optView.dimensions.some((item) => item.name === subdim[4].name); + return optView.dimensions.some((item) => item.name === subdim["subdims_month"].name); }); }); it("should throw an exception, sub-dimension with non-existent parent", () => { let error: boolean = false; try { - engine.query({metrics: [met[0]], dimensions: [subdim[3]]}); + engine.query(engineScenario.queryNoParent); } catch (e){ error = true; @@ -189,7 +155,7 @@ describe("engine class", () => { }); it("should parse a clause, with target as dimension and operator as ==", () => { - const strFilter = "dim:0==0"; + const strFilter = "dim:client:name==Laci"; const clause = engine.parseClause(strFilter); expect(clause).to.be.an("object"); expect(clause).to.have.property("filters"); @@ -200,12 +166,13 @@ describe("engine class", () => { expect(clause.filters[0]).to.have.property("target"); expect(clause.filters[0]).to.have.property("value"); expect(clause.filters[0]).to.have.property("operator"); - expect(clause.filters[0].target).to.be.equal(dim[0]); - expect(clause.filters[0].value).to.be.equal("0"); + expect(clause.filters[0].target).to.be.equal(dim["dim:client:name"]); + expect(clause.filters[0].value).to.be.equal("Laci"); expect(clause.filters[0].operator).to.be.equal(FilterOperator.EQUAL); }); + it("should parse a clause, with target as metric and operator as !=", () => { - const strFilter = "met:0!=0"; + const strFilter = "met:product:avg:pricein!=0"; const clause = engine.parseClause(strFilter); expect(clause).to.be.an("object"); expect(clause).to.have.property("filters"); @@ -216,14 +183,15 @@ describe("engine class", () => { expect(clause.filters[0]).to.have.property("target"); expect(clause.filters[0]).to.have.property("operator"); expect(clause.filters[0]).to.have.property("value"); - expect(clause.filters[0].target).to.be.equal(met[0]); + expect(clause.filters[0].target).to.be.equal(met["met:product:avg:pricein"]); expect(clause.filters[0].value).to.be.equal("0"); expect(clause.filters[0].operator).to.be.equal(FilterOperator.NOTEQUAL); }); + it("should throw an exception, when a dimension is not found", () => { let error: boolean = false; - const strFilter = "dim:-1==0"; - const exeption = "Filter could not be created: \"dim:-1\" was not found"; + const strFilter = "dim:seller:willfail==teste"; + const exeption = "Filter could not be created: \"dim:seller:willfail\" was not found"; try { engine.parseClause(strFilter); } @@ -234,10 +202,11 @@ describe("engine class", () => { } expect(error).to.be.true; }); + it("should throw an exception, when a metric is not found", () => { let error: boolean = false; - const strFilter = "met:-1==0"; - const exeption = "Filter could not be created: \"met:-1\" was not found"; + const strFilter = "met:seller:count:willfail==0"; + const exeption = "Filter could not be created: \"met:seller:count:willfail\" was not found"; try { engine.parseClause(strFilter); } @@ -248,9 +217,10 @@ describe("engine class", () => { } expect(error).to.be.true; }); + it("should throw an exception, when a operator is not found", () => { let error: boolean = false; - let strFilter = "met:-1=?0"; + let strFilter = "met:sell:sum:quantity=?0"; let exeption = "Filter could not be created: Operator on \"" + strFilter + "\" could not be extracted"; try { engine.parseClause(strFilter); @@ -262,7 +232,7 @@ describe("engine class", () => { } expect(error).to.be.true; error = false; - strFilter = "met:-1!?0"; + strFilter = "met:sell:sum:quantity!?0"; exeption = "Filter could not be created: Operator on \"" + strFilter + "\" could not be extracted"; try { engine.parseClause(strFilter); @@ -279,8 +249,8 @@ describe("engine class", () => { const operators = [">", "<", "<=", ">="]; for (let i = 0; i < operators.length; ++i) { let error: boolean = false; - let strFilter = "dim:3" + operators[i] + "joao"; - let exeption = "Filter could not be created: Operator \"" + operators[i] + "\" is invalid for target \"dim:3\""; + let strFilter = "dim:client:name" + operators[i] + "Firoz"; + let exeption = "Filter could not be created: Operator \"" + operators[i] + "\" is invalid for target \"dim:client:name\""; try { engine.parseClause(strFilter); } @@ -289,6 +259,7 @@ describe("engine class", () => { expect(e.message).to.be.equal(exeption); } + expect(error).to.be.true; } }); @@ -302,7 +273,7 @@ describe("engine class", () => { "!=": FilterOperator.NOTEQUAL }; for (let op of Object.keys(operators)) { - const strFilter = "dim:0" + op + "0"; + const strFilter = "dim:sell:datein" + op + "2018-02-17"; const clause = engine.parseClause(strFilter); expect(clause).to.be.an("object"); expect(clause).to.have.property("filters"); @@ -313,13 +284,13 @@ describe("engine class", () => { expect(clause.filters[0]).to.have.property("target"); expect(clause.filters[0]).to.have.property("operator"); expect(clause.filters[0]).to.have.property("value"); - expect(clause.filters[0].target).to.be.equal(dim[0]); - expect(clause.filters[0].value).to.be.equal("0"); + expect(clause.filters[0].target).to.be.equal(dim["dim:sell:datein"]); + expect(clause.filters[0].value).to.be.equal("2018-02-17"); expect(clause.filters[0].operator).to.be.equal(operators[op]); } for (let op of Object.keys(operators)) { - const strFilter = "dim:2" + op + "0"; + const strFilter = "dim:seller:id" + op + "0"; const clause = engine.parseClause(strFilter); expect(clause).to.be.an("object"); expect(clause).to.have.property("filters"); @@ -330,7 +301,7 @@ describe("engine class", () => { expect(clause.filters[0]).to.have.property("target"); expect(clause.filters[0]).to.have.property("operator"); expect(clause.filters[0]).to.have.property("value"); - expect(clause.filters[0].target).to.be.equal(dim[2]); + expect(clause.filters[0].target).to.be.equal(dim["dim:seller:id"]); expect(clause.filters[0].value).to.be.equal("0"); expect(clause.filters[0].operator).to.be.equal(operators[op]); } diff --git a/src/util/scenarioHandler.ts b/src/util/scenarioHandler.ts new file mode 100644 index 00000000..f0b55945 --- /dev/null +++ b/src/util/scenarioHandler.ts @@ -0,0 +1,167 @@ +/* + * 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 } from "../core/view"; +import { Metric } from "../core/metric"; +import { Dimension } from "../core/dimension"; +import { Query } from "../common/query"; + +export interface EngineScenario { + /** + * All metrics from /config/* + */ + metrics: {[key: string]: Metric}; + /** + * All dimensions from /config/* + */ + dimensions: {[key: string]: Dimension}; + /** + * subDimensions from scenario + */ + subDimensions: {[key: string]: Dimension}; + /** + * Metric that does not exist on /config/* + */ + wrongMet: Metric; + /** + * Dimension that does not exist on /config/* + */ + wrongDim: Dimension; + /** + * All views from /config/* + */ + views: {[key: string]: View}; + /** + * Query that have all metrics and dimensions + */ + queryMetsDims: Query; + /** + * Query with an invalid metric + */ + queryNoMets: Query; + /** + * Query with an invalid dimension + */ + queryNoDims: Query; + /** + * Query that matches Product + */ + queryProduct: Query; + /** + * Product View + */ + viewProduct: View; + /** + * Query that matches ActiveSeller + */ + queryActive: Query; + /** + * ActiveSeller View + */ + viewActiveSeller: View; + /** + * Query with no metrics and dimensions formed of subdimensions + */ + querySubDim: Query; + /** + * A query that contains a subdimentions without a parent + */ + queryNoParent: Query; +} + +export interface AdapterScenario { + /** + * Product View + */ + materializedView: View; + /** + * View that contains client name and it's sum quantity, and is + * sorted based on quantity + */ + sortView: View; + /** + * View that contains subDimensions based on date, such as day, + * month and year + */ + subDimensionView: View; + /** + * View that contains itself, which is product's View + */ + joinWithOneView: View; + /** + * View that contains sold items on the day that is equals the last + * day of validity + */ + filterWithEqual: View; + /** + * View that contains metrics and dimensions from View Sell and dimensions + * from View Client + */ + reduceAsView: View; + /** + * View that contains the avererage product's bought with client's name + */ + filterAverageBought: View; + /** + * View that contains the data from operation AND between clauses, + * which is product's that was sold expired and have a value higher than + * ten + */ + multipleClause: View; + /** + * View that represent's the same data as multipleClause however with, + * the operation OR between the filteres , since we use only one clause + */ + singleClause: View; + /** + * View that contais the name of the client and the name of the product, + * that the client has bought + */ + joinWithNoMetrics: View; + /** + * View that contains the name,sex and age from View Seller + */ + growOneView: View; + /** + * View that contains the seller id and how many sell's he has done + */ + JoinWithAncestors: View; +} + +export interface DataCtrlScenario { + /** + * Dimension that does not exist on /config/* + */ + wrongMet: Query; + /** + * Dimension that does not exist on /config/* + */ + wrongDim: Query; + /** + * A query that returns the average quantity of buyouts related + * from provider + */ + correct: Query; + /** + * A query that returns the average pricein related from it's name + * and it's id + */ + clausal: Query; +} diff --git a/src/util/viewHandler.ts b/src/util/viewHandler.ts index 0e0252df..6ecc43f0 100644 --- a/src/util/viewHandler.ts +++ b/src/util/viewHandler.ts @@ -215,7 +215,13 @@ export class ViewHandler { private static applyReduce(v: View[], q: Query): ViewsAndClauses { let changed = true; const views = v.map((i) => i); - let clausesToCover = q.clauses.map((i) => i); + /* + Check if the view have already a clause , so that it won't + cover it twice. + */ + let clausesToCover = q.clauses.filter((clause) => { + return !views.some((view) => view.clauses.some((c) => c.id === clause.id)); + }); while (changed) { changed = false; let map = ViewHandler.dimensionsScore(views, clausesToCover, q.dimensions); @@ -274,9 +280,7 @@ export class ViewHandler { } } - clausesToCover = notCoveredClauses.filter((clause) => { - return !views[i].clauses.some((c) => c.id === clause.id); - }); + clausesToCover = notCoveredClauses.map((clause) => clause); // Intersection between query metrics and view metrics const mets = q.metrics.filter((met) => { diff --git a/test/postgres/fixtures/activeseller.json b/test/postgres/fixtures/activeseller.json new file mode 100644 index 00000000..a4681a2c --- /dev/null +++ b/test/postgres/fixtures/activeseller.json @@ -0,0 +1,7 @@ +[ + {"met:seller:max:age": 23, "dim:seller:name": "Ade", "dim:seller:status": "active"} + ,{"met:seller:max:age": 24, "dim:seller:name": "Luanne", "dim:seller:status": "active"} + ,{"met:seller:max:age": 25, "dim:seller:name": "Florinda", "dim:seller:status": "active"} + ,{"met:seller:max:age": 26, "dim:seller:name": "Bryanna", "dim:seller:status": "active"} + ,{"met:seller:max:age": 27, "dim:seller:name": "Salvatore", "dim:seller:status": "sick leave"} +] diff --git a/test/postgres/fixtures/view14.json b/test/postgres/fixtures/buyout.json similarity index 50% rename from test/postgres/fixtures/view14.json rename to test/postgres/fixtures/buyout.json index fb69ebb8..5423c86b 100644 --- a/test/postgres/fixtures/view14.json +++ b/test/postgres/fixtures/buyout.json @@ -1,12 +1,12 @@ [ -{ "dim:buyout:datein": "2018-01-01", "dim:buyout:provider.id": 20, "dim:buyout:product.id": 53 +{ "dim:buyout:datein": "2018-01-01", "dim:provider:id": 20, "dim:product:id": 53 , "met:buyout:avg:quantity": 100,"met:buyout:max:quantity": 100,"met:buyout:min:quantity": 100}, -{ "dim:buyout:datein": "2018-01-01", "dim:buyout:provider.id": 21, "dim:buyout:product.id": 54 +{ "dim:buyout:datein": "2018-01-01", "dim:provider:id": 21, "dim:product:id": 54 , "met:buyout:avg:quantity": 120,"met:buyout:max:quantity": 120,"met:buyout:min:quantity": 120}, -{ "dim:buyout:datein": "2018-01-01", "dim:buyout:provider.id": 22, "dim:buyout:product.id": 55 +{ "dim:buyout:datein": "2018-01-01", "dim:provider:id": 22, "dim:product:id": 55 , "met:buyout:avg:quantity": 130,"met:buyout:max:quantity": 130,"met:buyout:min:quantity": 130}, -{ "dim:buyout:datein": "2018-01-01", "dim:buyout:provider.id": 23, "dim:buyout:product.id": 56 +{ "dim:buyout:datein": "2018-01-01", "dim:provider:id": 23, "dim:product:id": 56 , "met:buyout:avg:quantity": 140,"met:buyout:max:quantity": 140,"met:buyout:min:quantity": 140}, -{ "dim:buyout:datein": "2018-01-01", "dim:buyout:provider.id": 24, "dim:buyout:product.id": 57 +{ "dim:buyout:datein": "2018-01-01", "dim:provider:id": 24, "dim:product:id": 57 , "met:buyout:avg:quantity": 200,"met:buyout:max:quantity": 200,"met:buyout:min:quantity": 200} ] diff --git a/test/postgres/fixtures/view12.json b/test/postgres/fixtures/client.json similarity index 100% rename from test/postgres/fixtures/view12.json rename to test/postgres/fixtures/client.json diff --git a/test/postgres/fixtures/distribute.json b/test/postgres/fixtures/distribute.json new file mode 100644 index 00000000..b1aa029f --- /dev/null +++ b/test/postgres/fixtures/distribute.json @@ -0,0 +1,7 @@ +[ +{"dim:provider:id": 1,"dim:product:id": 53}, +{"dim:provider:id": 2,"dim:product:id": 54}, +{"dim:provider:id": 3,"dim:product:id": 55}, +{"dim:provider:id": 4,"dim:product:id": 56}, +{"dim:provider:id": 5,"dim:product:id": 57} +] \ No newline at end of file diff --git a/test/postgres/fixtures/view11.json b/test/postgres/fixtures/product.json similarity index 100% rename from test/postgres/fixtures/view11.json rename to test/postgres/fixtures/product.json diff --git a/test/postgres/fixtures/view15.json b/test/postgres/fixtures/provider.json similarity index 100% rename from test/postgres/fixtures/view15.json rename to test/postgres/fixtures/provider.json diff --git a/test/postgres/fixtures/sell.json b/test/postgres/fixtures/sell.json new file mode 100644 index 00000000..3f84218b --- /dev/null +++ b/test/postgres/fixtures/sell.json @@ -0,0 +1,17 @@ +[ +{ "dim:sell:registered": true, "dim:product:id": 53, "dim:seller:id": 1 +, "dim:client:id": 7, "dim:sell:datein": "2018-02-17", "met:sell:sum:quantity": 10 +, "met:sell:avg:quantity": 10 , "met:sell:count:quantity": 1}, +{ "dim:sell:registered": true, "dim:product:id": 54, "dim:seller:id": 2 +, "dim:client:id": 8, "dim:sell:datein": "2018-01-23", "met:sell:sum:quantity": 12 +, "met:sell:avg:quantity": 12, "met:sell:count:quantity": 1}, +{ "dim:sell:registered": false, "dim:product:id": 55, "dim:seller:id": 3 +, "dim:client:id": 0, "dim:sell:datein": "2018-01-25", "met:sell:sum:quantity": 5 +, "met:sell:avg:quantity": 5, "met:sell:count:quantity": 1}, +{ "dim:sell:registered": true, "dim:product:id": 56, "dim:seller:id": 4 +, "dim:client:id": 9, "dim:sell:datein": "2018-03-22", "met:sell:sum:quantity": 2 +, "met:sell:avg:quantity": 2, "met:sell:count:quantity": 1}, +{ "dim:sell:registered": true, "dim:product:id": 57, "dim:seller:id": 5 +, "dim:client:id": 10, "dim:sell:datein": "2018-02-21", "met:sell:sum:quantity": 10 +, "met:sell:avg:quantity": 10, "met:sell:count:quantity": 1} +] diff --git a/test/postgres/fixtures/seller.json b/test/postgres/fixtures/seller.json new file mode 100644 index 00000000..7408e7e6 --- /dev/null +++ b/test/postgres/fixtures/seller.json @@ -0,0 +1,17 @@ +[ +{"dim:seller:name": "Ade", "dim:seller:sex": "undecided", "dim:seller:cpf":"344.805.128-45" +, "dim:seller:id": 1 , "dim:seller:status": "active", "met:seller:avg:age": 23 +, "met:seller:max:age": 23, "met:seller:min:age":23, "met:seller:count:age": 1}, +{"dim:seller:name": "Luanne", "dim:seller:sex": "nonbinary", "dim:seller:cpf":"977.221.375-39" + , "dim:seller:id": 2 , "dim:seller:status": "active", "met:seller:avg:age": 24 + , "met:seller:max:age": 24, "met:seller:min:age":24, "met:seller:count:age": 1}, +{"dim:seller:name": "Florinda", "dim:seller:sex": "female", "dim:seller:cpf":"885.517.020-17" + , "dim:seller:id": 3 , "dim:seller:status": "active", "met:seller:avg:age": 25 + , "met:seller:max:age": 25, "met:seller:min:age":25, "met:seller:count:age": 1}, +{"dim:seller:name": "Bryanna", "dim:seller:sex": "female", "dim:seller:cpf":"575.657.111-60" + , "dim:seller:id": 4 , "dim:seller:status": "active", "met:seller:avg:age": 26 + , "met:seller:max:age": 26, "met:seller:min:age":26, "met:seller:count:age": 1}, +{"dim:seller:name": "Salvatore", "dim:seller:sex": "male", "dim:seller:cpf":"604.424.718-07" + , "dim:seller:id": 5 , "dim:seller:status": "sick leave", "met:seller:avg:age": 27 + , "met:seller:max:age": 27, "met:seller:min:age":27, "met:seller:count:age": 1} +] diff --git a/test/postgres/fixtures/view10.json b/test/postgres/fixtures/view10.json deleted file mode 100644 index f2939ffd..00000000 --- a/test/postgres/fixtures/view10.json +++ /dev/null @@ -1,17 +0,0 @@ -[ -{"dim:seller:name": "Ade", "dim:seller:sex": "undecided" -, "dim:seller:cpf":"344.805.128-45", "dim:seller:id": 1, "dim:seller:age": 23 -, "met:seller:avg:age": 23, "met:seller:max:age": 23, "met:seller:count:age": 1}, -{"dim:seller:name": "Luanne", "dim:seller:sex": "nonbinary" -, "dim:seller:cpf":"977.221.375-39", "dim:seller:id": 2, "dim:seller:age": 24 -, "met:seller:avg:age": 24, "met:seller:max:age": 24,"met:seller:count:age": 1}, -{"dim:seller:name": "Florinda", "dim:seller:sex": "female" -, "dim:seller:cpf":"885.517.020-17", "dim:seller:id": 3, "dim:seller:age": 25 -, "met:seller:avg:age": 25, "met:seller:max:age": 25, "met:seller:count:age": 1}, -{"dim:seller:name": "Bryanna", "dim:seller:sex": "female" -, "dim:seller:cpf":"575.657.111-60", "dim:seller:id": 4, "dim:seller:age": 26 -, "met:seller:avg:age": 26, "met:seller:max:age": 26, "met:seller:count:age": 1}, -{"dim:seller:name": "Salvatore", "dim:seller:sex": "male" -, "dim:seller:cpf":"604.424.718-07", "dim:seller:id": 5, "dim:seller:age": 27 -, "met:seller:avg:age": 27, "met:seller:max:age": 27, "met:seller:count:age": 1} -] diff --git a/test/postgres/fixtures/view13.json b/test/postgres/fixtures/view13.json deleted file mode 100644 index 0aa6b1b6..00000000 --- a/test/postgres/fixtures/view13.json +++ /dev/null @@ -1,17 +0,0 @@ -[ -{ "dim:sell:registered": true, "dim:sell:product.id": 53, "dim:sell:seller.id": 1 -, "dim:sell:client.id": 7, "dim:sell:datein": "2018-02-17", "met:sell:sum:quantity": 10 -, "met:sell:avg:quantity": 10 , "met:sell:count:quantity": 10}, -{ "dim:sell:registered": true, "dim:sell:product.id": 54, "dim:sell:seller.id": 2 -, "dim:sell:client.id": 8, "dim:sell:datein": "2018-01-23", "met:sell:sum:quantity": 12 -, "met:sell:avg:quantity": 12, "met:sell:count:quantity": 12}, -{ "dim:sell:registered": false, "dim:sell:product.id": 55, "dim:sell:seller.id": 3 -, "dim:sell:client.id": 0, "dim:sell:datein": "2018-01-25", "met:sell:sum:quantity": 5 -, "met:sell:avg:quantity": 5, "met:sell:count:quantity": 5}, -{ "dim:sell:registered": true, "dim:sell:product.id": 56, "dim:sell:seller.id": 4 -, "dim:sell:client.id": 9, "dim:sell:datein": "2018-03-22", "met:sell:sum:quantity": 2 -, "met:sell:avg:quantity": 2, "met:sell:count:quantity": 2}, -{ "dim:sell:registered": true, "dim:sell:product.id": 57, "dim:sell:seller.id": 5 -, "dim:sell:client.id": 10, "dim:sell:datein": "2018-02-21", "met:sell:sum:quantity": 10 -, "met:sell:avg:quantity": 10, "met:sell:count:quantity": 10} -] diff --git a/test/postgres/fixtures/view16.json b/test/postgres/fixtures/view16.json deleted file mode 100644 index 0beddf2f..00000000 --- a/test/postgres/fixtures/view16.json +++ /dev/null @@ -1,7 +0,0 @@ -[ -{"dim:distribute:provider.id": 1,"dim:distribute:product.id": 53}, -{"dim:distribute:provider.id": 2,"dim:distribute:product.id": 54}, -{"dim:distribute:provider.id": 3,"dim:distribute:product.id": 55}, -{"dim:distribute:provider.id": 4,"dim:distribute:product.id": 56}, -{"dim:distribute:provider.id": 5,"dim:distribute:product.id": 57} -] \ No newline at end of file diff --git a/test/scenario.ts b/test/scenario.ts index 8aa83ecb..570d6ef8 100644 --- a/test/scenario.ts +++ b/test/scenario.ts @@ -25,327 +25,279 @@ import { View } from "../src/core/view"; import { Filter, FilterOperator } from "../src/core/filter"; import { Clause } from "../src/core/clause"; import { AggregationType, RelationType , DataType} from "../src/common/types"; -import { Query} from "../src/common/query"; import { ViewHandler } from "../src/util/viewHandler"; +import { EngineScenario, AdapterScenario, DataCtrlScenario } from "../src/util/scenarioHandler"; -interface EngineScenario { - metrics: Metric[]; - dimensions: Dimension[]; - subDimensions: Dimension[]; - wrongMet: Metric; - wrongDim: Dimension; - views: View[]; -} +const configPath = process.env.BLENDB_SCHEMA_FILE; +const config = ConfigParser.parse(configPath); -interface AdapterScenario { - materializedView: View; - noSelectionView: View; - withSelectionView: View; - subDimensionView: View; - join4View: View; - dateView: View; - aggrView: View; - clauseView: View; - multiFilterView: View; - multiClauseView: View; - notEqualView: View; - gtltView: View; - geleView: View; - notMatchFilterView: View; - notOriginCount: View; - unMaterializebleView: View; - partialJoinView: View; - propagatedClauseView: View; - propagatedClauseAggrView: View; -} -interface DataCtrlScenario { - wrongMet: Query; - wrongDim: Query; - correct: Query; - clausal: Query; +const mets : {[key: string]: Metric} = {}; +for (let i in config.metrics) { + let met = config.metrics[i]; + mets[met.name] = met; } -const configPath = process.env.BLENDB_SCHEMA_FILE; -const config = ConfigParser.parse(configPath); +const dims : {[key:string]: Dimension} = {}; +for (let i in config.dimensions) { + let dim = config.dimensions[i]; + dims[dim.name] = dim; +} -const mets = config.metrics.sort((a, b) => { - const aValue = parseInt(a.name.split(":")[1], 10); - const bValue = parseInt(b.name.split(":")[1], 10); - return aValue - bValue; -}); -const dims = config.dimensions.sort((a, b) => { - const aValue = parseInt(a.name.split(":")[1], 10); - const bValue = parseInt(b.name.split(":")[1], 10); - return aValue - bValue; -}); -const views = config.buildViews.sort((a, b) => { - const aValue = parseInt(a.alias.split(" ")[1], 10); - const bValue = parseInt(b.alias.split(" ")[1], 10); - return aValue - bValue; -}).map((item) => item.view); +const views : {[key:string]: View} = {}; +for (let i in config.buildViews){ + let view = config.buildViews[i]; + views[view.alias] = view.view; +} +/** + * Create new filters to use in clause and test + * the clauses + */ const filters: { [key: string]: Filter } = { - "dim:0:0" : new Filter({ - target: dims[0], - operator: FilterOperator.EQUAL, - value: "2017-01-02" + "equal" : new Filter({ + target : dims["dim:product:validity"], + operator : FilterOperator.EQUAL, + value: "2018-05-10" }), - "dim:0:1" : new Filter({ - target: dims[0], - operator: FilterOperator.EQUAL, - value: "2017-01-03" + "lower" : new Filter({ + target : dims["dim:product:validity"], + operator : FilterOperator.LOWER, + value: "2018-05-10" }), - "dim:0:gt" : new Filter({ - target: dims[0], - operator: FilterOperator.GREATER, - value: "2017-01-02" + "greater" : new Filter({ + target : dims["dim:product:validity"], + operator : FilterOperator.GREATER, + value: "2018-05-10" }), - "dim:0:lt" : new Filter({ - target: dims[0], - operator: FilterOperator.LOWER, - value: "2017-01-04" + "greaterq" : new Filter({ + target : dims["dim:product:validity"], + operator : FilterOperator.GREATEREQ, + value: "2018-06-10" }), - "dim:0:ge" : new Filter({ - target: dims[0], - operator: FilterOperator.GREATEREQ, - value: "2017-01-02" + "lowerq" : new Filter({ + target : dims["dim:product:validity"], + operator : FilterOperator.LOWEREQ, + value: "2018-06-10" }), - "dim:0:le" : new Filter({ - target: dims[0], - operator: FilterOperator.LOWEREQ, - value: "2017-01-04" - }), - "dim:2" : new Filter({ - target: dims[2], + "noteq" : new Filter({ + target: dims["dim:product:validity"], operator: FilterOperator.NOTEQUAL, - value: "1" + value: "2018-08-23" }), - "dim:4" : new Filter({ - target: dims[4], - operator: FilterOperator.NOTEQUAL, - value: "dim:4:1" + "clientRegistered" : new Filter({ + target: dims["dim:sell:registered"], + operator: FilterOperator.EQUAL, + value: "false" }), - "dim:5" : new Filter({ - target: dims[5], - operator: FilterOperator.NOTEQUAL, - value: "true" + "averageBought": new Filter({ + target: mets["met:sell:avg:quantity"], + operator: FilterOperator.GREATEREQ, + value: "10" }), - "dim:7" : new Filter({ - target: dims[7], + "equalFilterView": new Filter({ + target: dims["dim:seller:status"], operator: FilterOperator.EQUAL, - value: "1" + value: "active" }) -}; - -const clauses: { [key: string]: Clause } = { - "view0dim7": new Clause({filters: [filters["dim:7"]]}), - "view0gt": new Clause({filters: [filters["dim:0:gt"]]}), - "view0lt": new Clause({filters: [filters["dim:0:lt"]]}), - "view0ge": new Clause({filters: [filters["dim:0:ge"]]}), - "view0le": new Clause({filters: [filters["dim:0:le"]]}), - "view0dim0": new Clause({filters: [filters["dim:0:0"], filters["dim:0:1"]]}), - "view9dim2": new Clause({filters: [filters["dim:2"]]}), - "view6dim4": new Clause({filters: [filters["dim:4"]]}), - "view7dim5": new Clause({filters: [filters["dim:5"]]}) -}; - -const wrongMet = new Metric({ - name: "met:-1", - aggregation: AggregationType.COUNT, - dataType: DataType.INTEGER -}); -const wrongDim = new Dimension({ name: "dim:-1", dataType: DataType.INTEGER }); +} -const subdimAux = new Dimension({ - name: "sub:0", - dataType: DataType.INTEGER, - parent: dims[0], - relation: RelationType.DAY -}); +// Subdimensions -const subdims = [ - subdimAux, - new Dimension({ - name: "sub:1", - dataType: DataType.INTEGER, - parent: dims[1], - relation: RelationType.DAY - }), - new Dimension({ - name: "sub:2", +const subdims : {[key:string]: Dimension} = { + "subdims_day" : new Dimension({ + name: "subdims_day", dataType: DataType.INTEGER, - parent: subdimAux, + parent: dims["dim:sell:datein"], relation: RelationType.DAY }), - new Dimension({ - name: "sub:3", + "subdims_month" : new Dimension({ + name: "subdims_month", dataType: DataType.INTEGER, - parent: null, - relation: RelationType.DAY - }), - new Dimension({ - name: "sub:4", - dataType: DataType.INTEGER, - parent: dims[1], - relation: RelationType.DAY - }) -].sort((a, b) => { - const aValue = parseInt(a.name.split(":")[1], 10); - const bValue = parseInt(b.name.split(":")[1], 10); - return aValue - bValue; -}); - -const dateSubDim = [ - new Dimension ({ - name: "dim:0:month", - dataType: DataType.INTEGER, - parent: dims[0], + parent: dims["dim:sell:datein"], relation: RelationType.MONTH }), - new Dimension ({ - name: "dim:0:day", + "subdims_year" : new Dimension({ + name: "subdims_year", dataType: DataType.INTEGER, - parent: dims[0], - relation: RelationType.DAY + parent: dims["dim:sell:datein"], + relation: RelationType.YEAR }), - new Dimension ({ - name: "dim:0:year", + "subdims_none" : new Dimension({ + name: "subdims_none", dataType: DataType.INTEGER, - parent: dims[0], - relation: RelationType.YEAR + parent: null, + relation: RelationType.NONE }) -]; +} -const dateView = ViewHandler.queryReduce({ - metrics: [], - dimensions: dateSubDim, -}, views[0]); - -const aggrView = ViewHandler.queryJoin({ - metrics: [mets[0], mets[1], mets[6], mets[10], mets[11]], - dimensions: [], -}, [ - ViewHandler.queryReduce({ - metrics: [mets[0], mets[1], mets[10]], - dimensions: [] - }, views[0]), - ViewHandler.queryReduce({ - metrics: [mets[6], mets[11]], - dimensions: [] - }, views[2]) -]); - -const clauseView = ViewHandler.queryReduce({ - metrics: [mets[0], mets[1], mets[2]], - dimensions: [dims[0]], - clauses: [clauses.view0dim7] -}, views[0]); - -const multiFilterView = ViewHandler.queryReduce({ - metrics: [mets[0], mets[1]], - dimensions: [dims[0]], - clauses: [clauses.view0dim0] -}, views[0]); - -const multiClauseView = ViewHandler.queryReduce({ - metrics: [mets[0], mets[1]], - dimensions: [dims[0]], - clauses: [clauses.view0dim0, clauses.view0dim7] -}, views[0]); - -const notEqualView = ViewHandler.queryReduce({ - metrics: [], - dimensions: [dims[4], dims[5]], - clauses: [clauses.view7dim5] -}, views[7]); +// Clauses + +const clauses: { [key: string]: Clause } = { + "lastDay": + new Clause({filters: [filters["equal"]]}), + "undefined": + new Clause({filters: [filters["noteq"]]}), + "unexpired": + new Clause({filters: [filters["greater"]]}), + "expired": + new Clause({filters: [filters["lower"]]}), + "unexpiredLastDay": + new Clause({filters: [filters["greaterq"]]}), + "expiredlastDay": + new Clause({filters: [filters["lowerq"]]}), + "clientRegistered": + new Clause({filters: [filters["clientRegistered"]]}), + "averageBought": + new Clause({filters: [filters["averageBought"]]}), + "expiredAndAverage": + // or between filteres => (A) || (B) + new Clause ({filters: [filters["lower"],filters["averageBought"]]}), + "equalClauseView": + 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["Sell"]]); -const gtltView = ViewHandler.queryReduce({ +const joinWithNoMetrics = ViewHandler.growView({ metrics: [], - dimensions: [dims[0]], - clauses: [clauses.view0gt, clauses.view0lt] -}, views[0]); + dimensions: [dims["dim:product:name"],dims["dim:seller:name"]], + clauses: [] +}, [views["Product"],views["Sell"],views["Seller"]]); + +const growOneView = ViewHandler.growView({ + metrics: [mets["met:seller:min:age"]], + dimensions: [dims["dim:seller:name"],dims["dim:seller:sex"]], + clauses: [] +}, [views["Seller"]]); + +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["Sell"],views["Seller"],views["Client"],views["Product"]]); + +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["Sell"],views["Seller"],views["Client"],views["Product"]]); + +const equalfilter = ViewHandler.queryJoin({ + metrics: [], + dimensions: [dims["dim:client:name"],dims["dim:product:validity"]], + clauses: [clauses["lastDay"]] +},[views["Sell"],views["Client"],views["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["Sell"]),views["Client"]]); -const geleView = ViewHandler.queryReduce({ +const subDimView = ViewHandler.queryJoin({ + metrics : [], + dimensions : [dims["dim:sell:datein"],subdims["subdims_day"], + subdims["subdims_month"],subdims["subdims_year"]]}, +[ViewHandler.queryReduce({ metrics: [], - dimensions: [dims[0]], - clauses: [clauses.view0ge, clauses.view0le] -}, views[0]); + dimensions: [dims["dim:sell:datein"]] +},views["Sell"]),views["Sell"]]); -const notMatchFilterView = ViewHandler.queryReduce({ - metrics: [mets[0]], - dimensions: [dims[0]], - clauses: [clauses.view7dim5] -}, views[0]); -const subDimView = ViewHandler.queryJoin({ - metrics: [mets[0]], - dimensions: [subdims[0], subdims[1], dims[7], dims[8]] -}, [ - ViewHandler.queryReduce({ - metrics: [mets[0]], - dimensions: [subdims[0], dims[7]] - }, views[0]), - ViewHandler.queryJoin({ - metrics: [], - dimensions: [subdims[1], dims[7], dims[8]] - }, [views[1], views[4]]) -]); - -const join4View = ViewHandler.queryJoin({ - metrics: [mets[0], mets[1], mets[2], mets[3], mets[4], mets[5]], - dimensions: [dims[2], dims[7], dims[8]] -}, [views[0], views[1], views[2], views[4]]); - -const noSelView = ViewHandler.queryJoin({ - metrics: [mets[0], mets[3]], +const joinOneView = ViewHandler.queryJoin({ + metrics: [mets["met:product:avg:pricein"]], dimensions: [] -}, [views[0], views[1]]); - -const withSelView = ViewHandler.queryJoin({ - metrics: [mets[0], mets[1]], - dimensions: [dims[7], dims[8]] -}, [views[0], views[4]]); - -const notOriginCount = new View({ - metrics: [mets[5], mets[6], mets[7]], - dimensions: [dims[2]], - origin: false, - clauses: [clauses.view9dim2] +},[views["Product"]]); + +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["Sell"]); + +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["Sell"],views["Client"]])); + + +const viewProduct = views["Product"]; + +const viewActiveSeller = views["ActiveSeller"]; + +const wrongMet = new Metric({ + name: "met:this:is:just:a:test", + aggregation: AggregationType.COUNT, + dataType: DataType.INTEGER }); -const unMaterializebleView = ViewHandler.queryJoin({ - metrics: [mets[7], mets[9]], - dimensions: [dims[5]] -}, -[ViewHandler.queryReduce({ - metrics: [mets[9]], - dimensions: [dims[5]] -}, views[8]), -ViewHandler.queryReduce({metrics: [mets[7]], dimensions: [dims[5]]}, -ViewHandler.queryJoin({metrics: [mets[7]], dimensions: [dims[3], dims[5]]}, [ -ViewHandler.queryReduce({metrics: [], dimensions: [dims[3], dims[5]]}, -ViewHandler.queryJoin({metrics: [], dimensions: [dims[3], dims[4], dims[5]]}, [ -ViewHandler.queryReduce({metrics: [], dimensions: [dims[4], dims[5]]}, views[7]), -ViewHandler.queryReduce({metrics: [], dimensions: [dims[3], dims[4]]}, views[3]) -])), views[5]]))]); - -const partialJoinView = ViewHandler.queryJoin({ - metrics: [mets[7], mets[8]], - dimensions: [] -}, [views[3], views[5], views[6]]); +// 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 propagatedClauseView = ViewHandler.queryJoin({ - metrics: [mets[8]], - dimensions: [dims[4]], - clauses: [clauses.view7dim5, clauses.view6dim4] -}, [views[6], views[7]]); -const propagatedClauseAggrView = ViewHandler.queryJoin({ - metrics: [mets[8], mets[5]], - dimensions: [dims[2]], - clauses: [clauses.view7dim5, clauses.view6dim4, clauses.view9dim2] -}, [views[9], views[6], views[7]]); +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 = { metrics: mets, @@ -353,34 +305,48 @@ export const engineScenario: EngineScenario = { wrongMet: wrongMet, wrongDim: wrongDim, subDimensions: subdims, - views: views + views: views, + queryMetsDims: queryMetsDims, + queryNoMets: queryNoMets, + queryNoDims: queryNoDims, + queryProduct: queryProduct, + viewProduct: viewProduct, + queryActive: queryActive, + viewActiveSeller: viewActiveSeller, + querySubDim: querySubDim, + queryNoParent: queryNoParent }; export const adapterScenario: AdapterScenario = { - materializedView: views[0], - noSelectionView: noSelView, - withSelectionView: withSelView, + materializedView: views["Product"], + sortView: withSortView, + joinWithOneView: joinOneView, + singleClause: singleClause, + multipleClause: multipleClause, + filterAverageBought: clientAverageBought, + reduceAsView: reduceAsView, + filterWithEqual: equalfilter, subDimensionView: subDimView, - join4View: join4View, - dateView: dateView, - aggrView: aggrView, - clauseView: clauseView, - multiFilterView: multiFilterView, - multiClauseView: multiClauseView, - notEqualView: notEqualView, - gtltView: gtltView, - geleView: geleView, - notMatchFilterView: notMatchFilterView, - notOriginCount: notOriginCount, - unMaterializebleView: unMaterializebleView, - partialJoinView: partialJoinView, - propagatedClauseView: propagatedClauseView, - propagatedClauseAggrView: propagatedClauseAggrView + joinWithNoMetrics: joinWithNoMetrics, + growOneView: growOneView, + JoinWithAncestors:JoinWithAncestors, }; export const dataCtrlScenario: DataCtrlScenario = { - wrongMet: { metrics: [wrongMet], dimensions: [dims[0]] }, - wrongDim: { metrics: [mets[0]], dimensions: [wrongDim] }, - correct: { metrics: [mets[0]], dimensions: [dims[0]] }, - clausal: { metrics: [mets[0]], dimensions: [dims[7]] } + wrongMet: { + metrics: [wrongMet], + dimensions: [dims["dim:product:id"]] + }, + wrongDim: { + metrics: [mets["met:sell:avg:quantity"]], + dimensions: [wrongDim] }, + correct: { + metrics: [mets["met:buyout:avg:quantity"]], + dimensions: [dims["dim:provider:id"]] + }, + clausal: { + metrics: [mets["met:product:avg:pricein"]], + dimensions: [dims["dim:product:name"], + dims["dim:product:id"]] + } }; diff --git a/tslint.json b/tslint.json index 5d8557a4..01fb378a 100644 --- a/tslint.json +++ b/tslint.json @@ -1,6 +1,7 @@ { "extends": "tslint:recommended", "rules": { + "no-string-literal" : false, "no-var-requires": false, "object-literal-sort-keys": false, "one-line": false, -- GitLab