Skip to content

Commit

Permalink
Merge pull request #5 in CORE/java-vtl from feature/boolean-expressio…
Browse files Browse the repository at this point in the history
…ns to develop

* commit '5407e4d27523f79cdfaa6d8515d3d15a66507b0c':
  Generalize boolean comparision operation to handle a mix of component and scalar operands
  Impement boolean comparison operators
  Boolean expression with equal to for join
  • Loading branch information
Høiseth-Gilje, Eivind authored and Høiseth-Gilje, Eivind committed Feb 13, 2017
2 parents 1a5e81d + 5407e4d commit a33f7b8
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 47 deletions.
17 changes: 10 additions & 7 deletions java-vtl-parser/src/main/antlr4/no/ssb/vtl/parser/VTL.g4
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,17 @@ aggregate : 'aggregate' ;
//WS : [ \t\n\t] -> skip ;

booleanExpression
: booleanExpression AND booleanExpression
| booleanExpression ( OR booleanExpression | XOR booleanExpression )
| booleanEquallity
: booleanExpression op=AND booleanExpression
| booleanExpression op=(OR|XOR) booleanExpression
| booleanEquality
| BOOLEAN_CONSTANT
;
booleanEquallity
: booleanEquallity ( ( EQ | NE | LE | GE ) booleanEquallity )
| datasetExpression
booleanEquality
: left=booleanParam op=( EQ | NE | LE | LT | GE | GT ) right=booleanParam
;
booleanParam
: componentRef
| constant
// typed constant?
;

//datasetExpression
Expand All @@ -112,7 +113,9 @@ booleanEquallity
EQ : '=' ;
NE : '<>' ;
LE : '<=' ;
LT : '<' ;
GE : '>=' ;
GT : '>' ;

AND : 'and' ;
OR : 'or' ;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package no.ssb.vtl.script.visitors;

import com.google.common.collect.Iterables;
import no.ssb.vtl.model.Component;
import no.ssb.vtl.model.DataPoint;
import no.ssb.vtl.model.Dataset;
import no.ssb.vtl.parser.VTLBaseVisitor;
import no.ssb.vtl.parser.VTLParser;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.misc.ParseCancellationException;

import java.lang.String;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static java.lang.String.*;

public class BooleanExpressionVisitor extends VTLBaseVisitor<Predicate<Dataset.Tuple>> {

private final ReferenceVisitor referenceVisitor;

public BooleanExpressionVisitor(ReferenceVisitor referenceVisitor) {
this.referenceVisitor = referenceVisitor;
}

@Override
public Predicate<Dataset.Tuple> visitBooleanExpression(VTLParser.BooleanExpressionContext ctx) {
if (ctx.BOOLEAN_CONSTANT() != null) {
Boolean booleanConstant = Boolean.valueOf(ctx.BOOLEAN_CONSTANT().getText());
return dataPoints -> booleanConstant;
}else if (ctx.op != null) {
Predicate<Dataset.Tuple> left = visit(ctx.booleanExpression(0));
Predicate<Dataset.Tuple> right = visit(ctx.booleanExpression(1));
switch (ctx.op.getType()) {
case VTLParser.AND:
return left.and(right);
case VTLParser.OR:
return left.or(right);
case VTLParser.XOR:
return left.or(right).and(left.and(right).negate());
default:
throw new ParseCancellationException("Unsupported boolean operation: " + ctx.op.getText());
}
} else if (ctx.booleanEquality() != null) {
return visit(ctx.booleanEquality());
} else {
return super.visit(ctx);
}
}

@Override
public Predicate<Dataset.Tuple> visitBooleanEquality(VTLParser.BooleanEqualityContext ctx) {
ParamVisitor paramVisitor = new ParamVisitor(referenceVisitor);
Object left = paramVisitor.visit(ctx.left);
Object right = paramVisitor.visit(ctx.right);

BiPredicate<Object, Object> booleanOperation = getBooleanOperation(ctx.op);

if (isComp(left) && !isComp(right)) {
return tuple -> tuple.stream()
.filter(dataPoint -> left.equals(dataPoint.getComponent()))
.anyMatch(dataPoint -> booleanOperation.test(dataPoint.get(), right));
} else if (!isComp(left) && isComp(right)){
return tuple -> tuple.stream()
.filter(dataPoint -> right.equals(dataPoint.getComponent()))
.anyMatch(dataPoint -> booleanOperation.test(left, dataPoint.get()));
} else if (isComp(left) && isComp(right)) {
return tuple -> {
DataPoint rightDataPoint = getOnlyElement(right, tuple);
DataPoint leftDataPoint = getOnlyElement(left, tuple);
return booleanOperation.test(leftDataPoint.get(), rightDataPoint.get());
};
} else {
return tuple -> booleanOperation.test(left, right);
}
}

private BiPredicate<Object, Object> getBooleanOperation(Token op) {
switch (op.getType()) {
case VTLParser.EQ:
return Object::equals;
case VTLParser.NE:
return (l, r) -> !l.equals(r);
case VTLParser.LE:
return (l, r) -> compare(l, r) <= 0;
case VTLParser.LT:
return (l, r) -> compare(l, r) < 0;
case VTLParser.GE:
return (l, r) -> compare(l, r) >= 0;
case VTLParser.GT:
return (l, r) -> compare(l, r) > 0;
default:
throw new ParseCancellationException("Unsupported boolean equality operator " + op);
}
}

private DataPoint getOnlyElement(Object component, Dataset.Tuple tuple) {
return Iterables.getOnlyElement(tuple.stream()
.filter(dataPoint -> component.equals(dataPoint.getComponent()))
.collect(Collectors.toList()));
}

private boolean isComp(Object o) {
return o instanceof Component;
}

private int compare(Object value, Object scalar) {
if (value instanceof Integer && scalar instanceof Integer) {
return ((Integer) value).compareTo((Integer) scalar);
} else if (value instanceof Float && scalar instanceof Float) {
return ((Float) value).compareTo((Float) scalar);
} else if (value instanceof Boolean && scalar instanceof Boolean) {
return ((Boolean) value).compareTo((Boolean) scalar);
} else if (value instanceof String && scalar instanceof String) {
return ((String) value).compareTo((String) scalar);
}
throw new ParseCancellationException(
format("Cannot compare %s of type %s with %s of type %s",
value, value.getClass(), scalar, scalar.getClass())
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package no.ssb.vtl.script.visitors;

import no.ssb.vtl.parser.VTLBaseVisitor;
import no.ssb.vtl.parser.VTLParser;

public class ParamVisitor extends VTLBaseVisitor<Object> {

private final ReferenceVisitor referenceVisitor;

public ParamVisitor(ReferenceVisitor referenceVisitor) {
this.referenceVisitor = referenceVisitor;
}

@Override
public Object visitComponentRef(VTLParser.ComponentRefContext ctx) {
return referenceVisitor.visit(ctx);
}

@Override
public Object visitConstant(VTLParser.ConstantContext ctx) {
String constant = ctx.getText();
if (ctx.BOOLEAN_CONSTANT() != null) {
return Boolean.valueOf(constant);
} else if (ctx.FLOAT_CONSTANT() != null) {
return Float.valueOf(constant);
} else if (ctx.INTEGER_CONSTANT() != null) {
return Integer.valueOf(constant);
} else if (ctx.NULL_CONSTANT() != null) {
return null;
} else { //String
return constant.replace("\"", "");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,30 @@ public Dataset visitJoinExpression(VTLParser.JoinExpressionContext ctx) {
//
// return joinClause.apply(workingDataset);
}

@Override
protected Dataset aggregateResult(Dataset aggregate, Dataset nextResult) {
// Compute the new scope.
Dataset currentDataset = firstNonNull(nextResult, aggregate);

Set<String> previous = Optional.ofNullable(aggregate)
.map(Dataset::getDataStructure)
.map(ForwardingMap::keySet)
.orElse(Collections.emptySet());
Set<String> current = currentDataset.getDataStructure().keySet();

Set<String> referencesToRemove = Sets.difference(previous, current);
Set<String> referencesToAdd = Sets.difference(current, previous);

for (String key : referencesToRemove) {
joinScope.remove(key);
}
for (String key : referencesToAdd) {
joinScope.put(key, currentDataset.getDataStructure().get(key));
}

return workingDataset = currentDataset;
}

@Override
public Dataset visitJoinCalcClause(VTLParser.JoinCalcClauseContext ctx) {
Expand Down Expand Up @@ -115,30 +139,6 @@ public String toString() {
};
}

@Override
protected Dataset aggregateResult(Dataset aggregate, Dataset nextResult) {
// Compute the new scope.
Dataset currentDataset = firstNonNull(nextResult, aggregate);

Set<String> previous = Optional.ofNullable(aggregate)
.map(Dataset::getDataStructure)
.map(ForwardingMap::keySet)
.orElse(Collections.emptySet());
Set<String> current = currentDataset.getDataStructure().keySet();

Set<String> referencesToRemove = Sets.difference(previous, current);
Set<String> referencesToAdd = Sets.difference(current, previous);

for (String key : referencesToRemove) {
joinScope.remove(key);
}
for (String key : referencesToAdd) {
joinScope.put(key, currentDataset.getDataStructure().get(key));
}

return workingDataset = currentDataset;
}

@Override
public Dataset visitJoinFoldClause(VTLParser.JoinFoldClauseContext ctx) {
JoinFoldClauseVisitor visitor = new JoinFoldClauseVisitor(workingDataset, referenceVisitor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,27 @@
import no.ssb.vtl.parser.VTLBaseVisitor;
import no.ssb.vtl.parser.VTLParser;
import no.ssb.vtl.script.operations.FilterOperator;
import no.ssb.vtl.script.visitors.BooleanExpressionVisitor;
import no.ssb.vtl.script.visitors.ReferenceVisitor;

import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.google.common.base.Preconditions.*;

public class JoinFilterClauseVisitor extends VTLBaseVisitor<FilterOperator> {

private final Dataset dataset;
private final ReferenceVisitor referenceVisitor;

JoinFilterClauseVisitor(Dataset dataset) {
this.dataset = checkNotNull(dataset, "dataset was null");
}

public JoinFilterClauseVisitor(Dataset dataset, ReferenceVisitor referenceVisitor) {
this.dataset = checkNotNull(dataset, "dataset was null");
this.referenceVisitor = referenceVisitor;
}

@Override
public FilterOperator visitJoinFilterClause(VTLParser.JoinFilterClauseContext ctx) {
Set<String> components = Stream.of("id1").collect(Collectors.toSet());
Predicate<Dataset.Tuple> predicate = tuple -> tuple.ids().stream()
.filter(dataPoint -> components.contains(dataPoint.getComponent().getName()))
.anyMatch(dataPoint -> dataPoint.get().equals("1")); //TODO do not hardcode filter criteria
FilterOperator filterOperator = new FilterOperator(dataset, predicate);
System.out.println("Created new filterOperator: " + filterOperator);
return filterOperator; //TODO: Do not hardcode component to filter on
BooleanExpressionVisitor booleanExpressionVisitor = new BooleanExpressionVisitor(referenceVisitor);
Predicate<Dataset.Tuple> predicate = booleanExpressionVisitor.visit(ctx.joinFilterExpression().booleanExpression());
return new FilterOperator(dataset, predicate);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public void testJoin() throws Exception {

engine.eval("" +
"ds3 := [ds1, ds2]{" + // id1, id2, ds1.m1, ds1.m2, d2.m1, d2.m2, at1, at2
" filter id1 = 1," + // id1, id2, ds1.m1, ds1.m2, d2.m1, d2.m2, at1, at2
" filter id1 = \"1\" and m1 = 30 or m1 = 10," + //TODO: precedence
" ident = ds1.m1 + ds2.m2 - ds1.m2 - ds2.m1," + // id1, id2, ds1.m1, ds1.m2, d2.m1, d2.m2, at1, at2, ident
" keep ident, ds1.m1, ds2.m1, ds2.m2," + // id1, id2, ds1.m1, ds2.m1, ds2.m2, ident
" drop ds2.m1," + // id1, id2, ds1.m1, ds2.m2, ident
Expand All @@ -172,7 +172,7 @@ public void testJoin() throws Exception {
assertThat(ds3.getDataStructure().values())
.haveAtLeastOne(componentWith("renamedId1", Role.IDENTIFIER))
.haveAtLeastOne(componentWith("id2", Role.IDENTIFIER))
.haveAtLeastOne(componentWith("ds2.m2", Role.MEASURE))
.haveAtLeastOne(componentWith("m2", Role.MEASURE))
.haveAtLeastOne(componentWith("m1", Role.MEASURE))
.haveAtLeastOne(componentWith("ident", Role.MEASURE));

Expand Down
Loading

0 comments on commit a33f7b8

Please sign in to comment.