Commit ba1a7642 authored by Lucas Fernandes de Oliveira's avatar Lucas Fernandes de Oliveira

Merge branch 'issue/67' into 'develop'

Issue #67: Add dataType type check

See merge request !63
parents 3da7d2b4 cf4e4912
Pipeline #15693 passed with stages
in 1 minute and 15 seconds
...@@ -271,7 +271,7 @@ sources: ...@@ -271,7 +271,7 @@ sources:
- -
name: "fields:0" name: "fields:0"
description: "first entry" description: "first entry"
dataType: "notValid" dataType: "string"
- -
name: "source_3" name: "source_3"
description: "source with one entry and without description" description: "source with one entry and without description"
......
...@@ -22,6 +22,7 @@ import { SQLAdapter } from "./sql"; ...@@ -22,6 +22,7 @@ import { SQLAdapter } from "./sql";
import { View } from "../core/view"; import { View } from "../core/view";
import { Source } from "../core/source"; import { Source } from "../core/source";
import { FilterOperator } from "../core/filter"; import { FilterOperator } from "../core/filter";
import { DataType } from "../common/types";
const MDB = require("monetdb")(); const MDB = require("monetdb")();
export interface MonetConfig { export interface MonetConfig {
...@@ -87,13 +88,13 @@ export class MonetAdapter extends SQLAdapter { ...@@ -87,13 +88,13 @@ export class MonetAdapter extends SQLAdapter {
this.executeQuery(query, cb); this.executeQuery(query, cb);
} }
protected typeCast(quotedValue: string, dt: string): string { protected typeCast(quotedValue: string, dt: DataType): string {
switch (dt) { switch (dt) {
case "date": case DataType.DATE:
return "CAST(" + quotedValue + " AS TIMESTAMP)"; return "CAST(" + quotedValue + " AS TIMESTAMP)";
case "integer": case DataType.INTEGER:
return "CAST(" + quotedValue + " AS INTEGER)"; return "CAST(" + quotedValue + " AS INTEGER)";
case "boolean": case DataType.BOOLEAN:
return "CAST(" + quotedValue + " AS BOOLEAN)"; return "CAST(" + quotedValue + " AS BOOLEAN)";
default: default:
return quotedValue; return quotedValue;
......
...@@ -23,6 +23,7 @@ import { View } from "../core/view"; ...@@ -23,6 +23,7 @@ import { View } from "../core/view";
import { Source } from "../core/source"; import { Source } from "../core/source";
import { FilterOperator } from "../core/filter"; import { FilterOperator } from "../core/filter";
import { Pool, PoolConfig } from "pg"; import { Pool, PoolConfig } from "pg";
import { DataType } from "../common/types";
export class PostgresAdapter extends SQLAdapter { export class PostgresAdapter extends SQLAdapter {
private pool: Pool; private pool: Pool;
...@@ -58,13 +59,13 @@ export class PostgresAdapter extends SQLAdapter { ...@@ -58,13 +59,13 @@ export class PostgresAdapter extends SQLAdapter {
return false; return false;
} }
protected typeCast(quotedValue: string, dt: string): string { protected typeCast(quotedValue: string, dt: DataType): string {
switch (dt) { switch (dt) {
case "date": case DataType.DATE:
return quotedValue + "::DATE"; return quotedValue + "::DATE";
case "integer": case DataType.INTEGER:
return quotedValue + "::INTEGER"; return quotedValue + "::INTEGER";
case "boolean": case DataType.BOOLEAN:
return quotedValue + "::BOOLEAN"; return quotedValue + "::BOOLEAN";
default: default:
return quotedValue; return quotedValue;
......
...@@ -24,7 +24,7 @@ import { Source } from "../core/source"; ...@@ -24,7 +24,7 @@ import { Source } from "../core/source";
import { Dimension } from "../core/dimension"; import { Dimension } from "../core/dimension";
import { Clause } from "../core/clause"; import { Clause } from "../core/clause";
import { Filter, FilterOperator } from "../core/filter"; import { Filter, FilterOperator } from "../core/filter";
import { AggregationType, RelationType } from "../common/types"; import { AggregationType, RelationType, DataType } from "../common/types";
import { View } from "../core/view"; import { View } from "../core/view";
interface ExpandedView { interface ExpandedView {
...@@ -793,7 +793,7 @@ export abstract class SQLAdapter extends Adapter { ...@@ -793,7 +793,7 @@ export abstract class SQLAdapter extends Adapter {
protected abstract applyOperator(leftSide: string, rightSide: string, op: FilterOperator): string; 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 { private compareKeys(a: Dimension[], b: Dimension[], blackList: string[]): number {
/* /*
......
...@@ -76,7 +76,7 @@ describe("API collect controller", () => { ...@@ -76,7 +76,7 @@ describe("API collect controller", () => {
.expect((res: any) => { .expect((res: any) => {
const message = "Query execution failed: " + 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"; const error = "The source named 'thisisjustatest' was not found";
expect(res.body).to.be.an("object"); expect(res.body).to.be.an("object");
expect(res.body).to.have.property("message"); expect(res.body).to.have.property("message");
...@@ -93,7 +93,7 @@ describe("API collect controller", () => { ...@@ -93,7 +93,7 @@ describe("API collect controller", () => {
.expect(500) .expect(500)
.expect((res: any) => { .expect((res: any) => {
const message = "Query execution failed: " + 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"; const error = "The 'fields:0' wasn't informed on json";
expect(res.body).to.be.an("object"); expect(res.body).to.be.an("object");
expect(res.body).to.have.property("message"); expect(res.body).to.have.property("message");
...@@ -124,7 +124,7 @@ describe("API collect controller", () => { ...@@ -124,7 +124,7 @@ describe("API collect controller", () => {
.expect(500) .expect(500)
.expect((res: any) => { .expect((res: any) => {
const message = "Query execution failed: " + 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"; 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.be.an("object");
expect(res.body).to.have.property("message"); expect(res.body).to.have.property("message");
...@@ -157,7 +157,7 @@ describe("API collect controller", () => { ...@@ -157,7 +157,7 @@ describe("API collect controller", () => {
.expect((res: any) => { .expect((res: any) => {
const message = "Query execution failed: " + 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"; const error = "The value 'nope' from 'fields:0' isn't a type integer";
expect(res.body).to.be.an("object"); expect(res.body).to.be.an("object");
expect(res.body).to.have.property("message"); expect(res.body).to.have.property("message");
...@@ -176,7 +176,7 @@ describe("API collect controller", () => { ...@@ -176,7 +176,7 @@ describe("API collect controller", () => {
.expect((res: any) => { .expect((res: any) => {
const message = "Query execution failed: " + 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"; const error = "The value 'notafloat' from 'fields:1' isn't a type float";
expect(res.body).to.be.an("object"); expect(res.body).to.be.an("object");
expect(res.body).to.have.property("message"); expect(res.body).to.have.property("message");
...@@ -195,7 +195,7 @@ describe("API collect controller", () => { ...@@ -195,7 +195,7 @@ describe("API collect controller", () => {
.expect((res: any) => { .expect((res: any) => {
const message = "Query execution failed: " + 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"; const error = "The value '1' from 'fields:2' isn't a type string";
expect(res.body).to.be.an("object"); expect(res.body).to.be.an("object");
expect(res.body).to.have.property("message"); expect(res.body).to.have.property("message");
...@@ -214,7 +214,7 @@ describe("API collect controller", () => { ...@@ -214,7 +214,7 @@ describe("API collect controller", () => {
.expect((res: any) => { .expect((res: any) => {
const message = "Query execution failed: " + 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"; const error = "The value 'notaboolean' from 'fields:3' isn't a type boolean";
expect(res.body).to.be.an("object"); expect(res.body).to.be.an("object");
expect(res.body).to.have.property("message"); expect(res.body).to.have.property("message");
...@@ -233,7 +233,7 @@ describe("API collect controller", () => { ...@@ -233,7 +233,7 @@ describe("API collect controller", () => {
.expect((res: any) => { .expect((res: any) => {
const message = "Query execution failed: " + 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"; 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.be.an("object");
expect(res.body).to.have.property("message"); expect(res.body).to.have.property("message");
...@@ -258,21 +258,4 @@ describe("API collect controller", () => { ...@@ -258,21 +258,4 @@ describe("API collect controller", () => {
}) })
.end(done); .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);
});
}); });
...@@ -22,6 +22,7 @@ import * as express from "express"; ...@@ -22,6 +22,7 @@ import * as express from "express";
import { Request } from "../types"; import { Request } from "../types";
import { Source, Field } from "../../core/source"; import { Source, Field } from "../../core/source";
import { EnumType } from "../../core/enumType"; import { EnumType } from "../../core/enumType";
import { DataType } from "../../common/types";
interface Valid{ interface Valid{
[key: string]: (value: any) => boolean; [key: string]: (value: any) => boolean;
...@@ -114,17 +115,16 @@ export class CollectCtrl { ...@@ -114,17 +115,16 @@ export class CollectCtrl {
} }
for (let i = 0; i < fields.length; i++){ for (let i = 0; i < fields.length; i++){
if (validador[fields[i].dataType] !== undefined){ if (fields[i].dataType !== DataType.NONE){
if (!validador[fields[i].dataType](data[i]) === true){ if (!validador[EnumType.stringfyDataType(fields[i].dataType)](data[i]) === true){
throw new Error( throw new Error(
"The value '" + data[i] + "' from '" + fields[i].name + "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 { else {
enumType = req.engine.getEnumTypeByName(fields[i].dataType); enumType = req.engine.getEnumTypeByName(fields[i].enumType);
types = enumType.values; types = enumType.values;
let found: boolean = false; let found: boolean = false;
for (let j = 0; j < types.length; j++){ for (let j = 0; j < types.length; j++){
...@@ -136,7 +136,7 @@ export class CollectCtrl { ...@@ -136,7 +136,7 @@ export class CollectCtrl {
if (!found) { if (!found) {
throw new Error( throw new Error(
"The value '" + data[i] + "' from '" + fields[i].name + "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 { ...@@ -145,7 +145,7 @@ export class CollectCtrl {
catch (e) { catch (e) {
res.status(500).json({ res.status(500).json({
message: "Query execution failed: " + message: "Query execution failed: " +
"Could not construct query with the paramters given.", "Could not construct query with the parameters given.",
error: e.message error: e.message
}); });
return; return;
......
...@@ -33,3 +33,13 @@ export enum RelationType { ...@@ -33,3 +33,13 @@ export enum RelationType {
MONTH, MONTH,
YEAR, YEAR,
}; };
export enum DataType {
NONE,
INTEGER,
FLOAT,
STRING,
DATE,
BOOLEAN,
ENUMTYPE,
};
...@@ -18,14 +18,16 @@ ...@@ -18,14 +18,16 @@
* along with blend. If not, see <http://www.gnu.org/licenses/>. * 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 { export interface DimensionOptions {
name: string; name: string;
dataType: string; dataType: DataType;
parent?: Dimension; parent?: Dimension;
relation?: RelationType; relation?: RelationType;
description?: string; description?: string;
enumType?: string;
} }
export interface DimensionStrOptions { export interface DimensionStrOptions {
...@@ -38,10 +40,11 @@ export interface DimensionStrOptions { ...@@ -38,10 +40,11 @@ export interface DimensionStrOptions {
export class Dimension { export class Dimension {
public readonly name: string; public readonly name: string;
public readonly dataType: string; public readonly dataType: DataType;
public readonly parent: Dimension; public readonly parent: Dimension;
public readonly relation: RelationType; public readonly relation: RelationType;
public readonly description: string; public readonly description: string;
public readonly enumType: string;
constructor(options: DimensionOptions) { constructor(options: DimensionOptions) {
this.name = options.name; this.name = options.name;
...@@ -49,21 +52,21 @@ export class Dimension { ...@@ -49,21 +52,21 @@ export class Dimension {
this.relation = (options.relation) ? options.relation : RelationType.NONE; this.relation = (options.relation) ? options.relation : RelationType.NONE;
this.parent = (options.parent) ? options.parent : null; this.parent = (options.parent) ? options.parent : null;
this.description = (options.description) ? options.description : ""; this.description = (options.description) ? options.description : "";
this.enumType = (options.enumType) ? (options.enumType) : "";
} }
public strOptions(): DimensionStrOptions { public strOptions(): DimensionStrOptions {
if (this.relation === RelationType.NONE) { if (this.relation === RelationType.NONE) {
return { return {
name: this.name, name: this.name,
dataType: this.dataType, dataType: (this.dataType !== DataType.NONE) ? EnumType.stringfyDataType(this.dataType) : this.enumType ,
description: this.description description: this.description
}; };
} }
else { else {
return { return {
name: this.name, name: this.name,
dataType: this.dataType, dataType: (this.dataType !== DataType.NONE) ? EnumType.stringfyDataType(this.dataType) : this.enumType ,
parent: this.parent.name, parent: this.parent.name,
relation: Dimension.stringifyRelationType(this.relation), relation: Dimension.stringifyRelationType(this.relation),
description: this.description description: this.description
......
...@@ -26,7 +26,7 @@ import { View } from "./view"; ...@@ -26,7 +26,7 @@ import { View } from "./view";
import { Query } from "../common/query"; import { Query } from "../common/query";
import { Graph } from "../util/graph"; import { Graph } from "../util/graph";
import { EnumType, EnumTypeOptions} from "./enumType"; import { EnumType, EnumTypeOptions} from "./enumType";
import { Source , SourceOptions } from "./source"; import { Source , SourceStrOptions } from "./source";
export class Engine { export class Engine {
private views: View[] = []; private views: View[] = [];
...@@ -57,7 +57,7 @@ export class Engine { ...@@ -57,7 +57,7 @@ export class Engine {
return this.enumTypes.map((i) => i.strOptions()); return this.enumTypes.map((i) => i.strOptions());
} }
public getSourcesDescription(): SourceOptions[] { public getSourcesDescription(): SourceStrOptions[] {
return this.sources.map((i) => i.strOptions()); return this.sources.map((i) => i.strOptions());
} }
......
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
* along with blend. If not, see <http://www.gnu.org/licenses/>. * along with blend. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { DataType } from "../common/types";
export interface EnumTypeOptions { export interface EnumTypeOptions {
name: string; name: string;
values: string[]; values: string[];
...@@ -30,10 +32,45 @@ export class EnumType { ...@@ -30,10 +32,45 @@ export class EnumType {
this.name = options.name; this.name = options.name;
this.values = options.values; this.values = options.values;
} }
public strOptions(): EnumTypeOptions { public strOptions(): EnumTypeOptions {
return{ return{
name: this.name, name: this.name,
values: this.values 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;
}
}
} }
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
import { Dimension } from "./dimension"; import { Dimension } from "./dimension";
import { Metric } from "./metric"; import { Metric } from "./metric";
import { Hash } from "../util/hash"; import { Hash } from "../util/hash";
import { DataType } from "../common/types";
export interface FilterOptions { export interface FilterOptions {
target: Metric|Dimension; target: Metric|Dimension;
...@@ -146,7 +147,7 @@ export class Filter { ...@@ -146,7 +147,7 @@ export class Filter {
op.operator === FilterOperator.LOWER || op.operator === FilterOperator.LOWER ||
op.operator === FilterOperator.GREATEREQ || op.operator === FilterOperator.GREATEREQ ||
op.operator === FilterOperator.LOWEREQ) { 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; return true;
} }
......
...@@ -18,12 +18,13 @@ ...@@ -18,12 +18,13 @@
* along with blend. If not, see <http://www.gnu.org/licenses/>. * 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 { export interface MetricOptions {
name: string; name: string;
aggregation: AggregationType; aggregation: AggregationType;
dataType: string; dataType: DataType;
description?: string; description?: string;
} }
...@@ -37,7 +38,7 @@ export interface MetricStrOptions { ...@@ -37,7 +38,7 @@ export interface MetricStrOptions {
export class Metric { export class Metric {
public readonly name: string; public readonly name: string;
public readonly aggregation: AggregationType; public readonly aggregation: AggregationType;
public readonly dataType: string; public readonly dataType: DataType;
public readonly description: string; public readonly description: string;
constructor(options: MetricOptions) { constructor(options: MetricOptions) {
...@@ -51,7 +52,7 @@ export class Metric { ...@@ -51,7 +52,7 @@ export class Metric {
return { return {
name: this.name, name: this.name,
aggregation: Metric.stringifyAggrType(this.aggregation), aggregation: Metric.stringifyAggrType(this.aggregation),
dataType: this.dataType, dataType: EnumType.stringfyDataType(this.dataType),
description: this.description description: this.description
}; };
} }
......
...@@ -18,7 +18,17 @@ ...@@ -18,7 +18,17 @@
* along with blendb. If not, see <http://www.gnu.org/licenses/>. * along with blendb. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { EnumType } from "./enumType";
import { DataType } from "../common/types";
export interface Field { export interface Field {
name: string;
description?: string;
dataType: DataType;
enumType?: string;
}
export interface FieldStr {
name: string; name: string;
description?: string; description?: string;
dataType: string; dataType: string;
...@@ -30,6 +40,12 @@ export interface SourceOptions { ...@@ -30,6 +40,12 @@ export interface SourceOptions {
fields: Field[]; fields: Field[];
} }
export interface SourceStrOptions {
name: string;
description?: string;
fields: FieldStr[];
}
export class Source { export class Source {
public readonly name: string; public readonly name: string;
public readonly description: string; public readonly description: string;
...@@ -42,16 +58,42 @@ export class Source { ...@@ -42,16 +58,42 @@ export class Source {
return { return {
name: item.name, name: item.name,
description: (item.description) ? item.description : "", description: (item.description) ? item.description : "",
dataType: item.dataType dataType: item.dataType,
enumType: (item.enumType) ? item.enumType : ""
}; };
}); });
} }
public strOptions(): SourceOptions { public strOptions(): SourceStrOptions {
return { return {
name: this.name, name: this.name,
description: this.description, 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;
}
} }
...@@ -22,7 +22,10 @@ import { expect } from "chai"; ...@@ -22,7 +22,10 @@ import { expect } from "chai";
import { ConfigParser, ViewParsingOptions } from "./configParser"; import { ConfigParser, ViewParsingOptions } from "./configParser";
import { Dimension, DimensionStrOptions } from "../core/dimension"; 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 { function strToRelationType (str: string): RelationType {
switch (str) {