From cf4e49127722624c167f8a4e9a00ea79964d89ab Mon Sep 17 00:00:00 2001 From: Rafael <rpd17@inf.ufpr.br> Date: Fri, 4 May 2018 11:55:56 -0300 Subject: [PATCH] Issue #67: Add dataType type check Signed-off-by: Rafael <rpd17@inf.ufpr.br> --- config/ci_test.yaml.example | 2 +- src/adapter/monet.ts | 9 ++- src/adapter/postgres.ts | 9 ++- src/adapter/sql.ts | 4 +- src/api/controllers/collect.spec.ts | 33 ++------ src/api/controllers/collect.ts | 14 ++-- src/common/types.ts | 10 +++ src/core/dimension.ts | 15 ++-- src/core/engine.ts | 4 +- src/core/enumType.ts | 37 +++++++++ src/core/filter.ts | 3 +- src/core/metric.ts | 9 ++- src/core/source.ts | 48 +++++++++++- src/util/configParser.spec.ts | 115 ++++++++++++++++++++++++++-- src/util/configParser.ts | 65 ++++++++++++---- src/util/graph.spec.ts | 74 +++++++++--------- src/util/graph.ts | 3 +- test/monet/fixture.ts | 11 +-- test/postgres/fixture.ts | 13 ++-- test/scenario.ts | 22 +++--- 20 files changed, 356 insertions(+), 144 deletions(-) diff --git a/config/ci_test.yaml.example b/config/ci_test.yaml.example index b319223d..260fa663 100644 --- a/config/ci_test.yaml.example +++ b/config/ci_test.yaml.example @@ -271,7 +271,7 @@ sources: - name: "fields:0" description: "first entry" - dataType: "notValid" + dataType: "string" - name: "source_3" description: "source with one entry and without description" diff --git a/src/adapter/monet.ts b/src/adapter/monet.ts index 321a3872..68d7ca31 100644 --- a/src/adapter/monet.ts +++ b/src/adapter/monet.ts @@ -22,6 +22,7 @@ import { SQLAdapter } from "./sql"; import { View } from "../core/view"; import { Source } from "../core/source"; import { FilterOperator } from "../core/filter"; +import { DataType } from "../common/types"; const MDB = require("monetdb")(); export interface MonetConfig { @@ -87,13 +88,13 @@ export class MonetAdapter extends SQLAdapter { this.executeQuery(query, cb); } - protected typeCast(quotedValue: string, dt: string): string { + protected typeCast(quotedValue: string, dt: DataType): string { switch (dt) { - case "date": + case DataType.DATE: return "CAST(" + quotedValue + " AS TIMESTAMP)"; - case "integer": + case DataType.INTEGER: return "CAST(" + quotedValue + " AS INTEGER)"; - case "boolean": + case DataType.BOOLEAN: return "CAST(" + quotedValue + " AS BOOLEAN)"; default: return quotedValue; diff --git a/src/adapter/postgres.ts b/src/adapter/postgres.ts index e01b2bb3..ce0168bd 100644 --- a/src/adapter/postgres.ts +++ b/src/adapter/postgres.ts @@ -23,6 +23,7 @@ import { View } from "../core/view"; import { Source } from "../core/source"; import { FilterOperator } from "../core/filter"; import { Pool, PoolConfig } from "pg"; +import { DataType } from "../common/types"; export class PostgresAdapter extends SQLAdapter { private pool: Pool; @@ -58,13 +59,13 @@ export class PostgresAdapter extends SQLAdapter { return false; } - protected typeCast(quotedValue: string, dt: string): string { + protected typeCast(quotedValue: string, dt: DataType): string { switch (dt) { - case "date": + case DataType.DATE: return quotedValue + "::DATE"; - case "integer": + case DataType.INTEGER: return quotedValue + "::INTEGER"; - case "boolean": + case DataType.BOOLEAN: return quotedValue + "::BOOLEAN"; default: return quotedValue; diff --git a/src/adapter/sql.ts b/src/adapter/sql.ts index dc6c3cdc..e3b2d89c 100644 --- a/src/adapter/sql.ts +++ b/src/adapter/sql.ts @@ -24,7 +24,7 @@ import { Source } from "../core/source"; import { Dimension } from "../core/dimension"; import { Clause } from "../core/clause"; import { Filter, FilterOperator } from "../core/filter"; -import { AggregationType, RelationType } from "../common/types"; +import { AggregationType, RelationType, DataType } from "../common/types"; import { View } from "../core/view"; interface ExpandedView { @@ -793,7 +793,7 @@ export abstract class SQLAdapter extends Adapter { protected abstract applyOperator(leftSide: string, rightSide: string, op: FilterOperator): string; - protected abstract typeCast(quotedValue: string, dt: string): string; + protected abstract typeCast(quotedValue: string, dt: DataType): string; private compareKeys(a: Dimension[], b: Dimension[], blackList: string[]): number { /* diff --git a/src/api/controllers/collect.spec.ts b/src/api/controllers/collect.spec.ts index d4b57f0e..57d7cd37 100644 --- a/src/api/controllers/collect.spec.ts +++ b/src/api/controllers/collect.spec.ts @@ -76,7 +76,7 @@ describe("API collect controller", () => { .expect((res: any) => { const message = "Query execution failed: " + - "Could not construct query with the paramters given."; + "Could not construct query with the parameters given."; const error = "The source named 'thisisjustatest' was not found"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); @@ -93,7 +93,7 @@ describe("API collect controller", () => { .expect(500) .expect((res: any) => { const message = "Query execution failed: " + - "Could not construct query with the paramters given."; + "Could not construct query with the parameters given."; const error = "The 'fields:0' wasn't informed on json"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); @@ -124,7 +124,7 @@ describe("API collect controller", () => { .expect(500) .expect((res: any) => { const message = "Query execution failed: " + - "Could not construct query with the paramters given."; + "Could not construct query with the parameters given."; const error = "The value '1' from 'fields:0' isn't listed on enumtype:0"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); @@ -157,7 +157,7 @@ describe("API collect controller", () => { .expect((res: any) => { const message = "Query execution failed: " + - "Could not construct query with the paramters given."; + "Could not construct query with the parameters given."; const error = "The value 'nope' from 'fields:0' isn't a type integer"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); @@ -176,7 +176,7 @@ describe("API collect controller", () => { .expect((res: any) => { const message = "Query execution failed: " + - "Could not construct query with the paramters given."; + "Could not construct query with the parameters given."; const error = "The value 'notafloat' from 'fields:1' isn't a type float"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); @@ -195,7 +195,7 @@ describe("API collect controller", () => { .expect((res: any) => { const message = "Query execution failed: " + - "Could not construct query with the paramters given."; + "Could not construct query with the parameters given."; const error = "The value '1' from 'fields:2' isn't a type string"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); @@ -214,7 +214,7 @@ describe("API collect controller", () => { .expect((res: any) => { const message = "Query execution failed: " + - "Could not construct query with the paramters given."; + "Could not construct query with the parameters given."; const error = "The value 'notaboolean' from 'fields:3' isn't a type boolean"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); @@ -233,7 +233,7 @@ describe("API collect controller", () => { .expect((res: any) => { const message = "Query execution failed: " + - "Could not construct query with the paramters given."; + "Could not construct query with the parameters given."; const error = "The value '1999-25-25' from 'fields:4' isn't a type date"; expect(res.body).to.be.an("object"); expect(res.body).to.have.property("message"); @@ -258,21 +258,4 @@ describe("API collect controller", () => { }) .end(done); }); - it("should respond 500 when dataType does not exist", (done) => { - request(server) - .post("/v1/collect/source_2") - .send({"fields:0" : 1 }) - .expect(500) - .expect((res: any) => { - const message = "Query execution failed: " + - "Could not construct query with the paramters given."; - const error = "The dataType named 'notValid' was not found"; - 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); - }); }); diff --git a/src/api/controllers/collect.ts b/src/api/controllers/collect.ts index 5fc3207e..8ac5b57f 100644 --- a/src/api/controllers/collect.ts +++ b/src/api/controllers/collect.ts @@ -22,6 +22,7 @@ import * as express from "express"; import { Request } from "../types"; import { Source, Field } from "../../core/source"; import { EnumType } from "../../core/enumType"; +import { DataType } from "../../common/types"; interface Valid{ [key: string]: (value: any) => boolean; @@ -114,17 +115,16 @@ export class CollectCtrl { } for (let i = 0; i < fields.length; i++){ - if (validador[fields[i].dataType] !== undefined){ - if (!validador[fields[i].dataType](data[i]) === true){ + if (fields[i].dataType !== DataType.NONE){ + if (!validador[EnumType.stringfyDataType(fields[i].dataType)](data[i]) === true){ throw new Error( "The value '" + data[i] + "' from '" + fields[i].name + - "' isn't a type " + fields[i].dataType); + "' isn't a type " + [EnumType.stringfyDataType(fields[i].dataType)]); } } else { - enumType = req.engine.getEnumTypeByName(fields[i].dataType); - + enumType = req.engine.getEnumTypeByName(fields[i].enumType); types = enumType.values; let found: boolean = false; for (let j = 0; j < types.length; j++){ @@ -136,7 +136,7 @@ export class CollectCtrl { if (!found) { throw new Error( "The value '" + data[i] + "' from '" + fields[i].name + - "' isn't listed on " + fields[i].dataType); + "' isn't listed on " + fields[i].enumType); } } } @@ -145,7 +145,7 @@ export class CollectCtrl { catch (e) { res.status(500).json({ message: "Query execution failed: " + - "Could not construct query with the paramters given.", + "Could not construct query with the parameters given.", error: e.message }); return; diff --git a/src/common/types.ts b/src/common/types.ts index 504dcd68..0bcf1498 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -33,3 +33,13 @@ export enum RelationType { MONTH, YEAR, }; + +export enum DataType { + NONE, + INTEGER, + FLOAT, + STRING, + DATE, + BOOLEAN, + ENUMTYPE, +}; diff --git a/src/core/dimension.ts b/src/core/dimension.ts index 946bc43f..955ee443 100644 --- a/src/core/dimension.ts +++ b/src/core/dimension.ts @@ -18,14 +18,16 @@ * along with blend. If not, see <http://www.gnu.org/licenses/>. */ -import { RelationType } from "../common/types"; +import { RelationType, DataType } from "../common/types"; +import { EnumType } from "./enumType"; export interface DimensionOptions { name: string; - dataType: string; + dataType: DataType; parent?: Dimension; relation?: RelationType; description?: string; + enumType?: string; } export interface DimensionStrOptions { @@ -38,10 +40,11 @@ export interface DimensionStrOptions { export class Dimension { public readonly name: string; - public readonly dataType: string; + public readonly dataType: DataType; public readonly parent: Dimension; public readonly relation: RelationType; public readonly description: string; + public readonly enumType: string; constructor(options: DimensionOptions) { this.name = options.name; @@ -49,21 +52,21 @@ export class Dimension { this.relation = (options.relation) ? options.relation : RelationType.NONE; this.parent = (options.parent) ? options.parent : null; this.description = (options.description) ? options.description : ""; + this.enumType = (options.enumType) ? (options.enumType) : ""; } public strOptions(): DimensionStrOptions { if (this.relation === RelationType.NONE) { return { name: this.name, - dataType: this.dataType, + dataType: (this.dataType !== DataType.NONE) ? EnumType.stringfyDataType(this.dataType) : this.enumType , description: this.description }; } - else { return { name: this.name, - dataType: this.dataType, + dataType: (this.dataType !== DataType.NONE) ? EnumType.stringfyDataType(this.dataType) : this.enumType , parent: this.parent.name, relation: Dimension.stringifyRelationType(this.relation), description: this.description diff --git a/src/core/engine.ts b/src/core/engine.ts index 63331949..8058722d 100644 --- a/src/core/engine.ts +++ b/src/core/engine.ts @@ -26,7 +26,7 @@ import { View } from "./view"; import { Query } from "../common/query"; import { Graph } from "../util/graph"; import { EnumType, EnumTypeOptions} from "./enumType"; -import { Source , SourceOptions } from "./source"; +import { Source , SourceStrOptions } from "./source"; export class Engine { private views: View[] = []; @@ -57,7 +57,7 @@ export class Engine { return this.enumTypes.map((i) => i.strOptions()); } - public getSourcesDescription(): SourceOptions[] { + public getSourcesDescription(): SourceStrOptions[] { return this.sources.map((i) => i.strOptions()); } diff --git a/src/core/enumType.ts b/src/core/enumType.ts index 039b8b4a..bfb5d975 100644 --- a/src/core/enumType.ts +++ b/src/core/enumType.ts @@ -18,6 +18,8 @@ * along with blend. If not, see <http://www.gnu.org/licenses/>. */ +import { DataType } from "../common/types"; + export interface EnumTypeOptions { name: string; values: string[]; @@ -30,10 +32,45 @@ export class EnumType { this.name = options.name; this.values = options.values; } + public strOptions(): EnumTypeOptions { return{ name: this.name, values: this.values }; } + public static stringfyDataType(a: DataType): string { + switch (a) { + case DataType.INTEGER: + return "integer"; + case DataType.FLOAT: + return "float"; + case DataType.STRING: + return "string"; + case DataType.DATE: + return "date"; + case DataType.BOOLEAN: + return "boolean"; + default: + return ""; + } + } + + public static parseDataType (str: string): DataType { + str = str.toLocaleLowerCase(); + switch (str) { + case "integer": + return DataType.INTEGER; + case "float": + return DataType.FLOAT; + case "string": + return DataType.STRING; + case "date": + return DataType.DATE; + case "boolean": + return DataType.BOOLEAN; + default: + return DataType.NONE; + } + } } diff --git a/src/core/filter.ts b/src/core/filter.ts index 29538096..47ec8e15 100644 --- a/src/core/filter.ts +++ b/src/core/filter.ts @@ -21,6 +21,7 @@ import { Dimension } from "./dimension"; import { Metric } from "./metric"; import { Hash } from "../util/hash"; +import { DataType } from "../common/types"; export interface FilterOptions { target: Metric|Dimension; @@ -146,7 +147,7 @@ export class Filter { op.operator === FilterOperator.LOWER || op.operator === FilterOperator.GREATEREQ || op.operator === FilterOperator.LOWEREQ) { - if (op.target.dataType === "date" || op.target.dataType === "integer") { + if (op.target.dataType === DataType.DATE || op.target.dataType === DataType.INTEGER) { return true; } diff --git a/src/core/metric.ts b/src/core/metric.ts index 7812a2c4..441b90cf 100644 --- a/src/core/metric.ts +++ b/src/core/metric.ts @@ -18,12 +18,13 @@ * along with blend. If not, see <http://www.gnu.org/licenses/>. */ -import { AggregationType } from "../common/types"; +import { AggregationType, DataType } from "../common/types"; +import { EnumType } from "./enumType"; export interface MetricOptions { name: string; aggregation: AggregationType; - dataType: string; + dataType: DataType; description?: string; } @@ -37,7 +38,7 @@ export interface MetricStrOptions { export class Metric { public readonly name: string; public readonly aggregation: AggregationType; - public readonly dataType: string; + public readonly dataType: DataType; public readonly description: string; constructor(options: MetricOptions) { @@ -51,7 +52,7 @@ export class Metric { return { name: this.name, aggregation: Metric.stringifyAggrType(this.aggregation), - dataType: this.dataType, + dataType: EnumType.stringfyDataType(this.dataType), description: this.description }; } diff --git a/src/core/source.ts b/src/core/source.ts index 0fe15144..fcd55193 100644 --- a/src/core/source.ts +++ b/src/core/source.ts @@ -18,7 +18,17 @@ * along with blendb. If not, see <http://www.gnu.org/licenses/>. */ +import { EnumType } from "./enumType"; +import { DataType } from "../common/types"; + export interface Field { + name: string; + description?: string; + dataType: DataType; + enumType?: string; +} + +export interface FieldStr { name: string; description?: string; dataType: string; @@ -30,6 +40,12 @@ export interface SourceOptions { fields: Field[]; } +export interface SourceStrOptions { + name: string; + description?: string; + fields: FieldStr[]; +} + export class Source { public readonly name: string; public readonly description: string; @@ -42,16 +58,42 @@ export class Source { return { name: item.name, description: (item.description) ? item.description : "", - dataType: item.dataType + dataType: item.dataType, + enumType: (item.enumType) ? item.enumType : "" }; }); } - public strOptions(): SourceOptions { + public strOptions(): SourceStrOptions { return { name: this.name, description: this.description, - fields: this.fields + fields: Source.stringfyFieldDataType(this.fields), }; } + + public static stringfyFieldDataType(opts: Field[]): FieldStr[] { + let str: FieldStr[]; + str = opts.map((i) => { + return { + name : i.name, + description: i.description, + dataType: (i.dataType !== DataType.NONE) ? EnumType.stringfyDataType(i.dataType) : i.enumType + }; + }); + return str; + } + + public static parseFieldDataType(opts: FieldStr[]): Field[] { + let str: Field[]; + str = opts.map((i) => { + return { + name : i.name, + description: i.description, + dataType: EnumType.parseDataType(i.dataType), + enumType: (EnumType.parseDataType(i.dataType) === DataType.NONE) ? i.dataType : "" + }; + }); + return str; + } } diff --git a/src/util/configParser.spec.ts b/src/util/configParser.spec.ts index 389c6067..8c41cd00 100644 --- a/src/util/configParser.spec.ts +++ b/src/util/configParser.spec.ts @@ -22,7 +22,10 @@ import { expect } from "chai"; import { ConfigParser, ViewParsingOptions } from "./configParser"; import { Dimension, DimensionStrOptions } from "../core/dimension"; -import { RelationType } from "../common/types"; +import { RelationType , DataType} from "../common/types"; +import { EnumType } from "../core/enumType"; +import { MetricStrOptions } from "../core/metric"; +import { SourceStrOptions } from "../core/source"; function strToRelationType (str: string): RelationType { switch (str) { @@ -92,7 +95,7 @@ describe("configParser utility library", () => { let error: boolean = false; try { - ConfigParser.parseDimOpts(opts, dims); + ConfigParser.parseDimOpts(opts, dims, null); } catch (e) { error = true; @@ -114,7 +117,7 @@ describe("configParser utility library", () => { }; let dimMap: {[key: string]: Dimension} = { - "dim:0" : new Dimension({name: "dim:0", dataType: "integer"}) + "dim:0" : new Dimension({name: "dim:0", dataType: DataType.INTEGER}) }; let error: boolean = false; @@ -142,7 +145,7 @@ describe("configParser utility library", () => { }; let dimMap: {[key: string]: Dimension} = { - "dim:0" : new Dimension({name: "dim:0", dataType: "integer"}) + "dim:0" : new Dimension({name: "dim:0", dataType: DataType.INTEGER}) }; let error: boolean = false; @@ -188,17 +191,113 @@ describe("configParser utility library", () => { ]; let dims: Dimension[] = [ - new Dimension({name: "dim:a", dataType: "date"}), - new Dimension({name: "dim:0", dataType: "date"}) + new Dimension({name: "dim:a", dataType: DataType.DATE}), + new Dimension({name: "dim:0", dataType: DataType.DATE}) ]; for (let i = 0; i < opts.length; ++i) { - let parsed = ConfigParser.parseDimOpts(opts[i], dims); + let parsed = ConfigParser.parseDimOpts(opts[i], dims, null); expect(parsed.name).to.be.equal(opts[i].name); - expect(parsed.dataType).to.be.equal(opts[i].dataType); + expect(EnumType.stringfyDataType(parsed.dataType)).to.be.equal(opts[i].dataType); expect(parsed.parent).to.be.equal(dims[1]); expect(parsed.relation).to.be.equal(strToRelationType(opts[i].relation)); } }); + it("should parse correctly enumType with dimension", () => { + + let opts: DimensionStrOptions = { + name: "dim:day", + dataType: "enumtype:5", + parent: "dim:0", + relation: "day" + }; + let dims: Dimension[] = [ + new Dimension({name: "dim:0", dataType: DataType.DATE}) + ]; + let enumMap: {[key: string]: EnumType} = { + "enumtype:5" : new EnumType({name: "enumtype:5", values: ["nope", "test"]}) + }; + let parsed = ConfigParser.parseDimOpts(opts, dims, enumMap); + + expect(parsed.enumType).to.be.equal(enumMap["enumtype:5"].name); + }); + + it("should fail to parse enumType with dimension", () => { + + let opts: DimensionStrOptions = { + name: "dim:day", + dataType: "enumtype:4", + parent: "dim:0", + relation: "day" + }; + let dims: Dimension[] = [ + new Dimension({name: "dim:0", dataType: DataType.INTEGER}) + ]; + let enumMap: {[key: string]: EnumType} = { + "enumtype:5" : new EnumType({name: "enumtype:5", values: ["nope", "test"]}) + }; + let error: boolean = false; + try { + ConfigParser.parseDimOpts(opts, dims, enumMap); + } + catch (e) { + error = true; + expect(e.message).to.be + .equal("[Parsing error] DataType: '" + opts.dataType + "' does not exist on Dimension"); + } + expect(error).to.be.true; + + }); + + it("should fail to parse dataType with Metrics", () => { + let met: MetricStrOptions; + met = { + name: "met:0", + aggregation: "sum", + dataType: "string", + description: "just for test" + }; + let error: boolean = false; + try { + ConfigParser.parseMetOpts(met); + } + catch (e) { + error = true; + expect(e.message).to.be + .equal("[Parsing error] DataType: '" + met.dataType + "' does not exist on Metric"); + } + expect(error).to.be.true; + + }); + it("should fail to parse enumType with Source", () => { + let sourc: SourceStrOptions; + sourc = { + name: "source_10", + description: "source used for test only", + fields: [ + {name: "fields:0", + description: "first entry", + dataType: "string"}, + {name: "fields:1", + description: "second entry", + dataType: "enumtype:10"} + ] + }; + let enumMap: {[key: string]: EnumType} = { + "enumtype:5" : new EnumType({name: "enumtype:5", values: ["nope", "test"]}) + }; + let error: boolean = false; + try { + ConfigParser.parseSourceOpts(sourc, enumMap); + } + catch (e) { + error = true; + expect(e.message).to.be + .equal("[Parsing error] DataType: '" + sourc.fields[1].dataType + "' does not exist on Source"); + } + expect(error).to.be.true; + + }); + }); diff --git a/src/util/configParser.ts b/src/util/configParser.ts index 6e15a86f..8ce6c352 100644 --- a/src/util/configParser.ts +++ b/src/util/configParser.ts @@ -22,10 +22,10 @@ import { Metric, MetricOptions, MetricStrOptions } from "../core/metric"; import { Dimension, DimensionOptions, DimensionStrOptions } from "../core/dimension"; import { View, ViewOptions, LoadView } from "../core/view"; import { EnumType, EnumTypeOptions } from "../core/enumType"; -import { RelationType } from "../common/types"; +import { RelationType, DataType } from "../common/types"; import { Filter } from "../core/filter"; import { Clause } from "../core/clause"; -import { Source, SourceOptions} from "../core/source"; +import { Source, SourceOptions, SourceStrOptions} from "../core/source"; import { Tsort, TsortDep } from "./tsort"; import * as fs from "fs"; import * as yaml from "js-yaml"; @@ -42,7 +42,7 @@ export interface ViewParsingOptions { } interface ConfigSchema { - sources: SourceOptions[]; + sources: SourceStrOptions[]; views: ViewParsingOptions[]; metrics: MetricStrOptions[]; dimensions: DimensionStrOptions[]; @@ -147,16 +147,16 @@ export class ConfigParser { let sourcMap: SourceMap = {}; let dimOptsMap: DimensionOptsMap = {}; - for (let i = 0; i < metricsOpts.length; ++i) { - let met = new Metric(this.parseMetOpts(metricsOpts[i])); - parsed.metrics.push(met); - metMap[met.name] = met; - } for (let i = 0; i < enumTypesOpts.length; i++) { let enumT = new EnumType((enumTypesOpts[i])); parsed.enumTypes.push(enumT); enumMap[enumT.name] = enumT; } + for (let i = 0; i < metricsOpts.length; ++i) { + let met = new Metric(this.parseMetOpts(metricsOpts[i])); + parsed.metrics.push(met); + metMap[met.name] = met; + } let toSort: TsortDep[] = []; for (let i = 0; i < dimensionsOpts.length; ++i) { @@ -183,13 +183,13 @@ export class ConfigParser { }); for (let i = 0; i < dimensionsOpts.length; ++i) { - let dim = new Dimension(this.parseDimOpts(dimensionsOpts[i], parsed.dimensions)); + let dim = new Dimension(this.parseDimOpts(dimensionsOpts[i], parsed.dimensions, enumMap)); parsed.dimensions.push(dim); dimMap[dim.name] = dim; } for (let i = 0; i < sourcesOpts.length; i++) { - let sourc = new Source((sourcesOpts[i])); + let sourc = new Source(this.parseSourceOpts(sourcesOpts[i], enumMap)); parsed.sources.push(sourc); sourcMap[sourc.name] = sourc; } @@ -277,16 +277,23 @@ export class ConfigParser { return viewOpt; } - public static parseDimOpts (opts: DimensionStrOptions, dims: Dimension[]): DimensionOptions { + public static parseDimOpts (opts: DimensionStrOptions, dims: Dimension[], map: EnumTypeMap): DimensionOptions { + let type = EnumType.parseDataType(opts.dataType); + if (type === DataType.NONE) { + if (!(map[opts.dataType])) { + throw new Error("[Parsing error] DataType: '" + opts.dataType + "' does not exist on Dimension"); + } + } if (opts.parent || opts.relation) { for (let i = 0; i < dims.length; ++i) { if (dims[i].name === opts.parent) { return { name: opts.name, - dataType: opts.dataType, + dataType: EnumType.parseDataType(opts.dataType), description: opts.description, parent: dims[i], - relation: Dimension.parseRelationType(opts.relation) + relation: Dimension.parseRelationType(opts.relation), + enumType: (EnumType.parseDataType(opts.dataType) === DataType.NONE) ? opts.dataType : "" }; } } @@ -295,18 +302,25 @@ export class ConfigParser { } return { name: opts.name, - dataType: opts.dataType, + dataType: EnumType.parseDataType(opts.dataType), description: opts.description, parent: null, - relation: RelationType.NONE + relation: RelationType.NONE, + enumType: (EnumType.parseDataType(opts.dataType) === DataType.NONE) ? opts.dataType : "" }; } - private static parseMetOpts (opts: MetricStrOptions): MetricOptions { + public static parseMetOpts (opts: MetricStrOptions): MetricOptions { + let type = EnumType.parseDataType(opts.dataType); + if (!(type === DataType.FLOAT || type === DataType.INTEGER)){ + + throw new Error("[Parsing error] DataType: '" + opts.dataType + "' does not exist on Metric"); + + } return { name: opts.name, aggregation: Metric.parseAggrType(opts.aggregation), - dataType : opts.dataType, + dataType : EnumType.parseDataType(opts.dataType), description: opts.description }; } @@ -326,6 +340,23 @@ export class ConfigParser { return new Clause ({filters: filters}); } + public static parseSourceOpts (opts: SourceStrOptions , map: EnumTypeMap): SourceOptions { + for ( let k = 0; k < opts.fields.length ; k++) { + let type = EnumType.parseDataType(opts.fields[k].dataType); + if (type === DataType.NONE) { + if (!(map[opts.fields[k].dataType])){ + + throw new Error("[Parsing error] DataType: '" + opts.fields[k].dataType + "' does not exist on Source"); + + } + } + } + return { + name: opts.name, + description: opts.description, + fields : Source.parseFieldDataType(opts.fields), + }; + } private static parseFilter (opts: string, metMap: MetricMap, dimMap: DimensionMap): Filter { const strFilter = Filter.segment(opts); if (!strFilter) { diff --git a/src/util/graph.spec.ts b/src/util/graph.spec.ts index 464dd1dc..72e372e0 100644 --- a/src/util/graph.spec.ts +++ b/src/util/graph.spec.ts @@ -26,14 +26,14 @@ import { View } from "../core/view"; import { Filter, FilterOperator } from "../core/filter"; import { Clause } from "../core/clause"; import { Graph } from "./graph"; -import { AggregationType, RelationType } from "../common/types"; +import { AggregationType, RelationType, DataType } from "../common/types"; import { Query } from "../common/query"; describe("graph class", () => { it("should not create 2 vertices with the same dimension", () => { let g = new Graph(); - let dim = new Dimension({name: "dim:test", dataType: "string"}); + let dim = new Dimension({name: "dim:test", dataType: DataType.STRING}); expect(g.addDimension(dim)).to.be.true; expect(g.addDimension(dim)).to.be.false; @@ -44,7 +44,7 @@ describe("graph class", () => { let met = new Metric({ name: "met:test", aggregation: AggregationType.SUM, - dataType: "string" + dataType: DataType.STRING }); expect(g.addMetric(met)).to.be.true; @@ -67,10 +67,10 @@ describe("graph class", () => { it("should not create a vertex with a subdimension when parent is not a vertex", () => { let g = new Graph(); - let dim = new Dimension({name: "dim:test", dataType: "string"}); + let dim = new Dimension({name: "dim:test", dataType: DataType.STRING}); let subdim = new Dimension({ name: "dim:sub_test", - dataType: "string", + dataType: DataType.STRING, relation: RelationType.MONTH, parent: dim }); @@ -80,10 +80,10 @@ describe("graph class", () => { it("should add a set of views", () => { let dims = [ - new Dimension({name: "dim:0", dataType: "string"}), - new Dimension({name: "dim:1", dataType: "string"}), - new Dimension({name: "dim:2", dataType: "string"}), - new Dimension({name: "dim:3", dataType: "string"}) + new Dimension({name: "dim:0", dataType: DataType.STRING}), + new Dimension({name: "dim:1", dataType: DataType.STRING}), + new Dimension({name: "dim:2", dataType: DataType.STRING}), + new Dimension({name: "dim:3", dataType: DataType.STRING}) ]; let g = new Graph(); @@ -120,8 +120,8 @@ describe("graph class", () => { it("should not add a view twice", () => { let dims = [ - new Dimension({name: "dim:0", dataType: "string"}), - new Dimension({name: "dim:1", dataType: "string"}), + new Dimension({name: "dim:0", dataType: DataType.STRING}), + new Dimension({name: "dim:1", dataType: DataType.STRING}), ]; let g = new Graph(); @@ -143,9 +143,9 @@ describe("graph class", () => { it("should not add views when metrics and dimensions are not vertices", () => { let dims = [ - new Dimension({name: "dim:0", dataType: "string"}), - new Dimension({name: "dim:1", dataType: "string"}), - new Dimension({name: "dim:2", dataType: "string"}) + new Dimension({name: "dim:0", dataType: DataType.STRING}), + new Dimension({name: "dim:1", dataType: DataType.STRING}), + new Dimension({name: "dim:2", dataType: DataType.STRING}) ]; let g = new Graph(); @@ -168,7 +168,7 @@ describe("graph class", () => { }); it("should add only once a view with only one vertex", () => { - let dim = new Dimension({name: "dim:0", dataType: "string"}); + let dim = new Dimension({name: "dim:0", dataType: DataType.STRING}); let g = new Graph(); @@ -186,7 +186,7 @@ describe("graph class", () => { }); it("should create a cover for a single vertex", () => { - let dim = new Dimension({name: "dim:0", dataType: "string"}); + let dim = new Dimension({name: "dim:0", dataType: DataType.STRING}); let g = new Graph(); @@ -209,15 +209,15 @@ describe("graph class", () => { it("should create a cover for several vertices", () => { let dims = [ - new Dimension({name: "dim:0", dataType: "string"}), - new Dimension({name: "dim:1", dataType: "string"}), - new Dimension({name: "dim:2", dataType: "string"}) + new Dimension({name: "dim:0", dataType: DataType.STRING}), + new Dimension({name: "dim:1", dataType: DataType.STRING}), + new Dimension({name: "dim:2", dataType: DataType.STRING}) ]; let mets = [ - new Metric({name: "met:0", dataType: "integer", aggregation: AggregationType.SUM}), - new Metric({name: "met:1", dataType: "integer", aggregation: AggregationType.AVG}), - new Metric({name: "met:2", dataType: "integer", aggregation: AggregationType.AVG}) + new Metric({name: "met:0", dataType: DataType.INTEGER, aggregation: AggregationType.SUM}), + new Metric({name: "met:1", dataType: DataType.INTEGER, aggregation: AggregationType.AVG}), + new Metric({name: "met:2", dataType: DataType.INTEGER, aggregation: AggregationType.AVG}) ]; let g = new Graph(); @@ -278,18 +278,18 @@ describe("graph class", () => { }); it("should create a cover with sub dimensions", () => { - let dim = new Dimension({name: "dim:0", dataType: "date"}); + let dim = new Dimension({name: "dim:0", dataType: DataType.DATE}); let dims = [ dim, new Dimension({ name: "subdim:0", - dataType: "string", + dataType: DataType.STRING, parent: dim, relation: RelationType.MONTH }), new Dimension({ name: "subdim:1", - dataType: "string", + dataType: DataType.STRING, parent: dim, relation: RelationType.DAY }), @@ -318,18 +318,18 @@ describe("graph class", () => { }); it("should return empty when try to cover a empty list", () => { - let dim = new Dimension({name: "dim:0", dataType: "date"}); + let dim = new Dimension({name: "dim:0", dataType: DataType.DATE}); let dims = [ dim, new Dimension({ name: "subdim:0", - dataType: "string", + dataType: DataType.STRING, parent: dim, relation: RelationType.MONTH }), new Dimension({ name: "subdim:1", - dataType: "string", + dataType: DataType.STRING, parent: dim, relation: RelationType.DAY }), @@ -358,9 +358,9 @@ describe("graph class", () => { it("should cover the graph, even when a constraint edge can not be used", () => { let dims = [ - new Dimension({name: "dim:0", dataType: "date"}), - new Dimension({name: "dim:1", dataType: "date"}), - new Dimension({name: "dim:2", dataType: "date"}), + new Dimension({name: "dim:0", dataType: DataType.DATE}), + new Dimension({name: "dim:1", dataType: DataType.DATE}), + new Dimension({name: "dim:2", dataType: DataType.DATE}), ]; let filter1 = new Filter({ @@ -418,9 +418,9 @@ describe("graph class", () => { it("should cover the query, using filters of intervals", () => { let dims = [ - new Dimension({name: "dim:0", dataType: "float"}), - new Dimension({name: "dim:1", dataType: "float"}), - new Dimension({name: "dim:2", dataType: "date"}), + new Dimension({name: "dim:0", dataType: DataType.FLOAT}), + new Dimension({name: "dim:1", dataType: DataType.FLOAT}), + new Dimension({name: "dim:2", dataType: DataType.DATE}), ]; const filters = [ @@ -531,9 +531,9 @@ describe("graph class", () => { it("should cover the query, not using filters of intervals", () => { let dims = [ - new Dimension({name: "dim:0", dataType: "float"}), - new Dimension({name: "dim:1", dataType: "float"}), - new Dimension({name: "dim:2", dataType: "date"}), + new Dimension({name: "dim:0", dataType: DataType.FLOAT}), + new Dimension({name: "dim:1", dataType: DataType.FLOAT}), + new Dimension({name: "dim:2", dataType: DataType.DATE}), ]; const filters = [ diff --git a/src/util/graph.ts b/src/util/graph.ts index 145b2228..ad654a93 100644 --- a/src/util/graph.ts +++ b/src/util/graph.ts @@ -24,6 +24,7 @@ import { Dimension } from "../core/dimension"; import { Query } from "../common/query"; import { Clause } from "../core/clause"; import { FilterOperator } from "../core/filter"; +import { DataType } from "../common/types"; enum State { UNVISITED, @@ -548,7 +549,7 @@ export class Graph { let queryValue: number; let viewValue: number; - if (queryFilter.target.dataType === "date") { + if (queryFilter.target.dataType === DataType.DATE) { queryValue = new Date(queryFilter.value).getTime(); viewValue = new Date(viewFilter.value).getTime(); } diff --git a/test/monet/fixture.ts b/test/monet/fixture.ts index 2ff73091..c0cbee72 100644 --- a/test/monet/fixture.ts +++ b/test/monet/fixture.ts @@ -23,6 +23,7 @@ import { View, LoadView } from "../../src/core/view"; import { each, series } from "async"; import { Connection } from "../../src/util/configParser"; import * as fs from "fs"; +import { DataType } from "../../src/common/types"; interface TransSet { init: string; @@ -93,15 +94,15 @@ export class Fixture { }); } - private typeConvertion(t: string): string { + private typeConvertion(t: DataType): string { switch (t) { - case "integer": + case DataType.INTEGER: return "INTEGER"; - case "date": + case DataType.DATE: return "TIMESTAMP"; - case "string": + case DataType.STRING: return "TEXT"; - case "boolean": + case DataType.BOOLEAN: return "BOOLEAN"; default: return ""; diff --git a/test/postgres/fixture.ts b/test/postgres/fixture.ts index 3233453e..d560d7eb 100644 --- a/test/postgres/fixture.ts +++ b/test/postgres/fixture.ts @@ -23,6 +23,7 @@ import { View, LoadView } from "../../src/core/view"; import { Source } from "../../src/core/source"; import { each, series } from "async"; import * as fs from "fs"; +import { DataType } from "../../src/common/types"; interface TransSet { init: string; @@ -103,15 +104,15 @@ export class Fixture { }); } - private typeConvertion(t: string): string { + private typeConvertion(t: DataType): string { switch (t) { - case "integer": + case DataType.INTEGER: return "INTEGER"; - case "date": + case DataType.DATE: return "DATE"; - case "string": + case DataType.STRING: return "TEXT"; - case "boolean": + case DataType.BOOLEAN: return "BOOLEAN"; default: return "TEXT"; @@ -188,7 +189,7 @@ export class Fixture { } private ExtractData(data: Source , create: boolean): string{ let name: string; - let type: string[]; + let type: DataType[]; let fields: string[]; let consult: string; diff --git a/test/scenario.ts b/test/scenario.ts index 9abe77b2..2e1bd1b8 100644 --- a/test/scenario.ts +++ b/test/scenario.ts @@ -24,7 +24,7 @@ import { Dimension } from "../src/core/dimension"; import { View } from "../src/core/view"; import { Filter, FilterOperator } from "../src/core/filter"; import { Clause } from "../src/core/clause"; -import { AggregationType, RelationType } from "../src/common/types"; +import { AggregationType, RelationType , DataType} from "../src/common/types"; import { Query} from "../src/common/query"; interface EngineScenario { @@ -151,13 +151,13 @@ const clauses: { [key: string]: Clause } = { const wrongMet = new Metric({ name: "met:-1", aggregation: AggregationType.COUNT, - dataType: "integer" + dataType: DataType.INTEGER }); -const wrongDim = new Dimension({ name: "dim:-1", dataType: "integer" }); +const wrongDim = new Dimension({ name: "dim:-1", dataType: DataType.INTEGER }); const subdimAux = new Dimension({ name: "sub:0", - dataType: "integer", + dataType: DataType.INTEGER, parent: dims[0], relation: RelationType.DAY }); @@ -166,25 +166,25 @@ const subdims = [ subdimAux, new Dimension({ name: "sub:1", - dataType: "integer", + dataType: DataType.INTEGER, parent: dims[1], relation: RelationType.DAY }), new Dimension({ name: "sub:2", - dataType: "integer", + dataType: DataType.INTEGER, parent: subdimAux, relation: RelationType.DAY }), new Dimension({ name: "sub:3", - dataType: "integer", + dataType: DataType.INTEGER, parent: null, relation: RelationType.DAY }), new Dimension({ name: "sub:4", - dataType: "integer", + dataType: DataType.INTEGER, parent: dims[1], relation: RelationType.DAY }) @@ -197,19 +197,19 @@ const subdims = [ const dateSubDim = [ new Dimension ({ name: "dim:0:month", - dataType: "integer", + dataType: DataType.INTEGER, parent: dims[0], relation: RelationType.MONTH }), new Dimension ({ name: "dim:0:day", - dataType: "integer", + dataType: DataType.INTEGER, parent: dims[0], relation: RelationType.DAY }), new Dimension ({ name: "dim:0:year", - dataType: "integer", + dataType: DataType.INTEGER, parent: dims[0], relation: RelationType.YEAR }) -- GitLab