diff --git a/config/ci_test.yaml.example b/config/ci_test.yaml.example index e532112d09250f186c9f60e7c243daee56b01b5f..8bab1978a1dcc44110c1b9e297e81f2331d2904b 100644 --- a/config/ci_test.yaml.example +++ b/config/ci_test.yaml.example @@ -103,6 +103,8 @@ schema: - "met:5" - "met:6" - "met:7" + clauses: + - "dim:2!=1" metrics: - name: "met:0" diff --git a/src/adapter/postgres.spec.ts b/src/adapter/postgres.spec.ts index 7f1367bcc50919fa32d480375b260bd29bd84841..e002335d9c46549632407a8f089ee49b4867bd6e 100644 --- a/src/adapter/postgres.spec.ts +++ b/src/adapter/postgres.spec.ts @@ -304,7 +304,7 @@ describe("postgres adapter", () => { 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)); diff --git a/src/core/filter.ts b/src/core/filter.ts index 9dff0e9046dc2cb72faa3868197513d4e3eae15f..29538096f560861067d87e593b0a8ff7a08496d7 100644 --- a/src/core/filter.ts +++ b/src/core/filter.ts @@ -138,6 +138,10 @@ export class Filter { } private static isTypeValid(op: FilterOptions): boolean { + if (op.operator === FilterOperator.NONE) { + return false; + } + if (op.operator === FilterOperator.GREATER || op.operator === FilterOperator.LOWER || op.operator === FilterOperator.GREATEREQ || diff --git a/src/util/configParser.spec.ts b/src/util/configParser.spec.ts index 701411dd0b3915b506bbcf0585307dda037ca1ac..2b2a089904e95bc6dbffacc0f0d02a4176e4d737 100644 --- a/src/util/configParser.spec.ts +++ b/src/util/configParser.spec.ts @@ -40,8 +40,6 @@ function strToRelationType (str: string): RelationType { } describe("configParser utility library", () => { - let metMap = new Map(); - let dimMap = new Map(); it("should throw expection for inexistent metric", () => { let opts: ViewParsingOptions = { alias: "Test", @@ -52,7 +50,7 @@ describe("configParser utility library", () => { let error: boolean = false; try { - ConfigParser.parseViewOpt(opts, metMap, dimMap); + ConfigParser.parseViewOpt(opts, {}, {}); } catch (e) { error = true; @@ -73,7 +71,7 @@ describe("configParser utility library", () => { let error: boolean = false; try { - ConfigParser.parseViewOpt(opts, metMap, dimMap); + ConfigParser.parseViewOpt(opts, {}, {}); } catch (e) { error = true; @@ -107,6 +105,62 @@ describe("configParser utility library", () => { expect(error).to.be.true; }); + it("should throw expection for bad formed clause, invalid operator", () => { + let opts: ViewParsingOptions = { + alias: "thisMUSTgoesWrong", + data: "someRandomPath", + origin: true, + dimensions: ["dim:0"], + metrics: [], + clauses: ["dim:0?=1"] + }; + + let dimMap: {[key: string]: Dimension} = { + "dim:0" : new Dimension({name: "dim:0", dataType: "integer"}) + }; + + let error: boolean = false; + try { + ConfigParser.parseViewOpt(opts, {}, dimMap); + } + catch (e) { + error = true; + expect(e.message).to.be + .equal("[Parsing error] Bad formed clause: on view \"" + opts.alias + + "\" the clause \"dim:0?=1\" could not be created"); + } + + expect(error).to.be.true; + }); + + it("should throw expection for bad formed clause, invalid target", () => { + let opts: ViewParsingOptions = { + alias: "thisMUSTgoesWrong", + data: "someRandomPath", + origin: true, + dimensions: ["dim:0"], + metrics: [], + clauses: ["dim:1==1"] + }; + + let dimMap: {[key: string]: Dimension} = { + "dim:0" : new Dimension({name: "dim:0", dataType: "integer"}) + }; + + let error: boolean = false; + try { + ConfigParser.parseViewOpt(opts, {}, dimMap); + } + catch (e) { + error = true; + expect(e.message).to.be + .equal("[Parsing error] Bad formed clause: on view \"" + opts.alias + + "\" the clause \"dim:1==1\" could not be created"); + } + + expect(error).to.be.true; + }); + it("should parse subdimentions relations correctly", () => { let opts: DimensionStrOptions[] = [ { diff --git a/src/util/configParser.ts b/src/util/configParser.ts index 3064e7b13f27ac14380020f276efe65e4376b739..16a70b7e9db6151632d5a83262f612f925e47012 100644 --- a/src/util/configParser.ts +++ b/src/util/configParser.ts @@ -22,6 +22,8 @@ import { Metric, MetricOptions } from "../core/metric"; import { Dimension, DimensionOptions } from "../core/dimension"; import { View, ViewOptions, LoadView } from "../core/view"; import { AggregationType, RelationType } from "../common/types"; +import { Filter } from "../core/filter"; +import { Clause } from "../core/clause"; import { PoolConfig } from "pg"; import * as fs from "fs"; import * as yaml from "js-yaml"; @@ -32,6 +34,7 @@ export interface ViewParsingOptions { origin?: boolean; dimensions: string[]; metrics: string[]; + clauses?: string[]; } interface MetricStrOptions { @@ -73,6 +76,14 @@ export interface ParsedConfig { loadViews: LoadView[]; } +interface DimensionMap { + [key: string]: Dimension; +} + +interface MetricMap { + [key: string]: Metric; +} + export class ConfigParser { public static parse(configPath: string): ParsedConfig { let config: ConfigFile = yaml.safeLoad(fs.readFileSync(configPath, { @@ -91,19 +102,19 @@ export class ConfigParser { loadViews: [] }; - let metMap: Map<string, Metric> = new Map(); - let dimMap: Map<string, Dimension> = new Map(); + let metMap: MetricMap = {}; + let dimMap: DimensionMap = {}; for (let i = 0; i < metricsOpts.length; ++i) { let met = new Metric(this.parseMetOpts(metricsOpts[i])); parsed.metrics.push(met); - metMap.set(met.name, met); + metMap[met.name] = met; } for (let i = 0; i < dimensionsOpts.length; ++i) { let dim = new Dimension(this.parseDimOpts(dimensionsOpts[i], parsed.dimensions)); parsed.dimensions.push(dim); - dimMap.set(dim.name, dim); + dimMap[dim.name] = dim; } for (let i = 0; i < viewsOpts.length; ++i) { @@ -121,8 +132,8 @@ export class ConfigParser { } public static parseViewOpt(opts: ViewParsingOptions, - metMap: Map<string, Metric>, - dimMap: Map<string, Dimension>): ViewOptions { + metMap: MetricMap, + dimMap: DimensionMap): ViewOptions { let viewOpt: ViewOptions = { metrics: [], @@ -130,11 +141,12 @@ export class ConfigParser { materialized: true, origin: opts.origin, childViews: [], + clauses: [] }; for (let i = 0; i < opts.metrics.length; ++i) { - if (metMap.has(opts.metrics[i])) { - viewOpt.metrics.push(metMap.get(opts.metrics[i])); + if (metMap[opts.metrics[i]]) { + viewOpt.metrics.push(metMap[opts.metrics[i]]); } else { @@ -143,14 +155,28 @@ export class ConfigParser { } for (let i = 0; i < opts.dimensions.length; ++i) { - if (dimMap.has(opts.dimensions[i])) { - viewOpt.dimensions.push(dimMap.get(opts.dimensions[i])); + if (dimMap[opts.dimensions[i]]) { + viewOpt.dimensions.push(dimMap[opts.dimensions[i]]); } else { throw new Error("[Parsing error] Non exist dimension set to view " + opts.alias); } } + + if (opts.clauses) { + for (let i = 0; i < opts.clauses.length; ++i) { + const clause = this.parseClause(opts.clauses[i], metMap, dimMap); + if (clause) { + viewOpt.clauses.push(clause); + } + else { + throw new Error("[Parsing error] Bad formed clause: on view \"" + + opts.alias + "\" the clause \"" + opts.clauses[i] + + "\" could not be created"); + } + } + } return viewOpt; } @@ -185,6 +211,43 @@ export class ConfigParser { }; } + private static parseClause (opts: string, metMap: MetricMap, dimMap: DimensionMap): Clause { + const strFilters = opts.split(","); + const filters: Filter[] = strFilters.map((item) => { + return this.parseFilter(item, metMap, dimMap); + }); + + for (let i = 0; i < filters.length; ++i) { + if (!filters[i] || !filters[i].isValid) { + return null; + } + } + + return new Clause ({filters: filters}); + } + + private static parseFilter (opts: string, metMap: MetricMap, dimMap: DimensionMap): Filter { + const strFilter = Filter.segment(opts); + if (!strFilter) { + return null; + } + let target: Metric|Dimension = dimMap[strFilter.target]; + if (!target) { + target = metMap[strFilter.target]; + if (!target) { + return null; + } + } + + const operator = Filter.parseOperator(strFilter.operator); + + return new Filter({ + target: target, + operator: operator, + value: strFilter.value + }); + } + private static strToAggregationType (str: string): AggregationType { switch (str) { case "sum": diff --git a/test/postgres/fixtures/view9.json b/test/postgres/fixtures/view9.json index 4c5726a8f1f56fd49e1c96971a4fa580b1640d9b..1e3e879ec2f19d4ea28c53c64ee6b9db4b691f76 100644 --- a/test/postgres/fixtures/view9.json +++ b/test/postgres/fixtures/view9.json @@ -1,5 +1,4 @@ [ -{"dim:2":"1","met:5":"1","met:6":"1","met:7":"1"}, {"dim:2":"2","met:5":"2","met:6":"1","met:7":"1"}, {"dim:2":"3","met:5":"3","met:6":"1","met:7":"1"}, {"dim:2":"4","met:5":"4","met:6":"1","met:7":"1"}, diff --git a/test/scenario.ts b/test/scenario.ts index 19330b498258c9f25e62202e57b628d82df87227..0d1be6fb815c8f50a76578fc67a47fbbb8f7a72d 100644 --- a/test/scenario.ts +++ b/test/scenario.ts @@ -98,6 +98,11 @@ const filters: { [key: string]: Filter } = { operator: FilterOperator.LOWEREQ, value: "2017-01-04" }), + "dim:2" : new Filter({ + target: dims[2], + operator: FilterOperator.NOTEQUAL, + value: "1" + }), "dim:5" : new Filter({ target: dims[5], operator: FilterOperator.NOTEQUAL, @@ -117,6 +122,7 @@ const clauses: { [key: string]: Clause } = { "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"]]}), "view7dim5": new Clause({filters: [filters["dim:5"]]}) }; @@ -304,7 +310,8 @@ const notOriginCount = new View({ metrics: [mets[5], mets[6], mets[7]], dimensions: [dims[2]], materialized: true, - origin: false + origin: false, + clauses: [clauses.view9dim2] }); export const engineScenario: EngineScenario = {