Issue #31: Add >, <, >=, <= operators to filters

Signed-off-by: Lucas Fernandes de Oliveira's avatarLucas Fernandes de Oliveira <lfo14@inf.ufpr.br>
parent 329ffcd5
Pipeline #11290 passed with stage
in 43 seconds
......@@ -228,7 +228,45 @@ 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(4);
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;
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 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));
......
......@@ -268,6 +268,14 @@ export class PostgresAdapter extends Adapter {
return " = ";
case FilterOperator.NOTEQUAL:
return " != ";
case FilterOperator.GREATER:
return " > ";
case FilterOperator.LOWER:
return " < ";
case FilterOperator.GREATEREQ:
return " >= ";
case FilterOperator.LOWEREQ:
return " <= ";
default:
return "";
}
......@@ -280,6 +288,8 @@ export class PostgresAdapter extends Adapter {
return "::DATE";
case "integer":
return "::INTEGER";
case "boolean":
return "::BOOLEAN";
default:
return "";
}
......
......@@ -270,4 +270,65 @@ describe("engine class", () => {
}
expect(error).to.be.true;
});
it("should throw an exception, when a operator does not suit", () => {
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\"";
try {
engine.parseClause(strFilter);
}
catch (e){
error = true;
expect(e.message).to.be.equal(exeption);
}
}
});
it("should parse clauses with several operators for dates and integers", () => {
const operators: {[key: string]: FilterOperator} = {
">": FilterOperator.GREATER,
"<": FilterOperator.LOWER,
"<=": FilterOperator.LOWEREQ,
">=": FilterOperator.GREATEREQ,
"==": FilterOperator.EQUAL,
"!=": FilterOperator.NOTEQUAL
};
for (let op in operators) {
const strFilter = "dim:0" + op + "0";
const clause = engine.parseClause(strFilter);
expect(clause).to.be.an("object");
expect(clause).to.have.property("filters");
expect(clause).to.have.property("id");
expect(clause.filters).to.be.an("array");
expect(clause.filters).to.have.length(1);
expect(clause.filters[0]).to.have.property("id");
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].operator).to.be.equal(operators[op]);
}
for (let op in operators) {
const strFilter = "dim:2" + op + "0";
const clause = engine.parseClause(strFilter);
expect(clause).to.be.an("object");
expect(clause).to.have.property("filters");
expect(clause).to.have.property("id");
expect(clause.filters).to.be.an("array");
expect(clause.filters).to.have.length(1);
expect(clause.filters[0]).to.have.property("id");
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].value).to.be.equal("0");
expect(clause.filters[0].operator).to.be.equal(operators[op]);
}
});
});
......@@ -124,11 +124,17 @@ export class Engine {
throw new Error("Filter could not be created: \"" + segment.target + "\" was not found");
}
return new Filter({
const filter = new Filter({
target: target,
operator: op,
value: segment.value
});
if (!filter.isValid) {
throw new Error("Filter could not be created: Operator \"" + segment.operator + "\" is invalid for target \"" + segment.target + "\"");
}
return filter;
}
else {
throw new Error("Filter could not be created: Operator on \"" + strFilter + "\" could not be extracted");
......
......@@ -26,6 +26,10 @@ describe("filter class", () => {
it("should correctly parse the operators", () => {
expect(Filter.parseOperator("==")).to.be.equal(FilterOperator.EQUAL);
expect(Filter.parseOperator("!=")).to.be.equal(FilterOperator.NOTEQUAL);
expect(Filter.parseOperator(">")).to.be.equal(FilterOperator.GREATER);
expect(Filter.parseOperator("<")).to.be.equal(FilterOperator.LOWER);
expect(Filter.parseOperator(">=")).to.be.equal(FilterOperator.GREATEREQ);
expect(Filter.parseOperator("<=")).to.be.equal(FilterOperator.LOWEREQ);
expect(Filter.parseOperator("?=")).to.be.equal(FilterOperator.NONE);
});
});
......@@ -37,7 +37,11 @@ export interface StrFilterOptions {
export enum FilterOperator {
NONE,
EQUAL,
NOTEQUAL
NOTEQUAL,
GREATER,
LOWER,
GREATEREQ,
LOWEREQ
}
export class Filter {
......@@ -45,12 +49,14 @@ export class Filter {
public readonly target: Metric|Dimension;
public readonly operator: FilterOperator;
public readonly value: string;
public readonly isValid: boolean;
constructor (options: FilterOptions) {
this.target = options.target;
this.operator = options.operator;
this.value = options.value;
this.id = Hash.sha1(options.target.name + options.operator + options.value);
this.isValid = Filter.isTypeValid(options);
}
public static parseOperator(op: string): FilterOperator {
......@@ -59,6 +65,14 @@ export class Filter {
return FilterOperator.EQUAL;
case "!=":
return FilterOperator.NOTEQUAL;
case ">":
return FilterOperator.GREATER;
case "<":
return FilterOperator.LOWER;
case ">=":
return FilterOperator.GREATEREQ;
case "<=":
return FilterOperator.LOWEREQ;
default:
return FilterOperator.NONE;
}
......@@ -66,30 +80,80 @@ export class Filter {
public static segment(strFilter: string): StrFilterOptions {
for (let i = 0; i < strFilter.length; ++i) {
if (strFilter[i] === "=") {
if (strFilter[i + 1] === "=") {
return {
target: strFilter.slice(0, i),
operator: "==",
value: strFilter.slice(i + 2)
};
}
switch (strFilter[i]){
case "=":
if (strFilter[i + 1] === "=") {
return {
target: strFilter.slice(0, i),
operator: "==",
value: strFilter.slice(i + 2)
};
}
break;
case "!":
if (strFilter[i + 1] === "=") {
return {
target: strFilter.slice(0, i),
operator: "!=",
value: strFilter.slice(i + 2)
};
}
break;
case ">":
if (strFilter[i + 1] === "=") {
return {
target: strFilter.slice(0, i),
operator: ">=",
value: strFilter.slice(i + 2)
};
}
else {
return {
target: strFilter.slice(0, i),
operator: ">",
value: strFilter.slice(i + 1)
};
}
case "<":
if (strFilter[i + 1] === "=") {
return {
target: strFilter.slice(0, i),
operator: "<=",
value: strFilter.slice(i + 2)
};
}
else {
return {
target: strFilter.slice(0, i),
operator: "<",
value: strFilter.slice(i + 1)
};
}
default:
break;
}
}
return null;
}
if (strFilter[i] === "!") {
if (strFilter[i + 1] === "=") {
return {
target: strFilter.slice(0, i),
operator: "!=",
value: strFilter.slice(i + 2)
};
}
private static isTypeValid(op: FilterOptions): boolean {
if (op.operator === FilterOperator.GREATER ||
op.operator === FilterOperator.LOWER ||
op.operator === FilterOperator.GREATEREQ ||
op.operator === FilterOperator.LOWEREQ) {
if (op.target.dataType === "date" || op.target.dataType === "integer") {
return true;
}
else {
return false;
}
}
return null;
else {
return true;
}
}
}
......@@ -48,6 +48,8 @@ interface AdapterScenario {
multiFilterView: View;
multiClauseView: View;
notEqualView: View;
gtltView: View;
geleView: View;
}
interface DataCtrlScenario {
......@@ -74,10 +76,30 @@ const filters: { [key: string]: Filter } = {
operator: FilterOperator.EQUAL,
value: "2017-01-03"
}),
"dim:3" : new Filter({
target: dims[3],
"dim:0:gt" : new Filter({
target: dims[0],
operator: FilterOperator.GREATER,
value: "2017-01-02"
}),
"dim:0:lt" : new Filter({
target: dims[0],
operator: FilterOperator.LOWER,
value: "2017-01-04"
}),
"dim:0:ge" : new Filter({
target: dims[0],
operator: FilterOperator.GREATEREQ,
value: "2017-01-02"
}),
"dim:0:le" : new Filter({
target: dims[0],
operator: FilterOperator.LOWEREQ,
value: "2017-01-04"
}),
"dim:5" : new Filter({
target: dims[5],
operator: FilterOperator.NOTEQUAL,
value: "dim:3:1"
value: "true"
}),
"dim:7" : new Filter({
target: dims[7],
......@@ -88,8 +110,12 @@ const filters: { [key: string]: Filter } = {
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"]]}),
"view3dim3": new Clause({filters: [filters["dim:3"]]})
"view7dim5": new Clause({filters: [filters["dim:5"]]})
};
const wrongMet = new Metric({
......@@ -206,11 +232,29 @@ const multiClauseView = new View({
const notEqualView = new View({
metrics: [],
dimensions: [dims[3]],
dimensions: [dims[4], dims[5]],
materialized: false,
origin: false,
childViews: [views[7]],
clauses: [clauses.view7dim5]
});
const gtltView = new View({
metrics: [],
dimensions: [dims[0]],
materialized: false,
origin: false,
childViews: [views[0]],
clauses: [clauses.view0gt, clauses.view0lt]
});
const geleView = new View({
metrics: [],
dimensions: [dims[0]],
materialized: false,
origin: false,
childViews: [views[3]],
clauses: [clauses.view3dim3]
childViews: [views[0]],
clauses: [clauses.view0ge, clauses.view0le]
});
const subDimView = new View({
......@@ -265,7 +309,9 @@ export const adapterScenario: AdapterScenario = {
clauseView: clauseView,
multiFilterView: multiFilterView,
multiClauseView: multiClauseView,
notEqualView: notEqualView
notEqualView: notEqualView,
gtltView: gtltView,
geleView: geleView
};
export const dataCtrlScenario: DataCtrlScenario = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment