diff --git a/src/main/antlr4/org/s1ck/gdl/GDL.g4 b/src/main/antlr4/org/s1ck/gdl/GDL.g4 index 905f496..77b9e97 100644 --- a/src/main/antlr4/org/s1ck/gdl/GDL.g4 +++ b/src/main/antlr4/org/s1ck/gdl/GDL.g4 @@ -140,30 +140,27 @@ literal // time-related //________________________ timeFunc - : intervall '.' intervallFunc #intvFunc - | timePoint '.' stampFunc #stmpFunc + : interval '.' intervalFunc #intvF + | timePoint '.' stampFunc #stmpF ; -intervall : intervallAtom #intvAtom - | complexIntervall #cmplxIntv - ; - -intervallAtom - : intervallSelector +interval + : intervalSelector | intervalFromStamps ; -intervallSelector - : Identifier '.' Interval + +intervalSelector + : Identifier '.' IntervalConst ; intervalFromStamps - : 'Interval('('['|'(') timePoint (')'|']') ')' + : 'Interval([' timePoint ',' timePoint '])' ; // TODO: change (only placeholder yet) complexIntervall - : Identifier '.' Interval + : Identifier '.' IntervalConst ; // TODO: add functions that yield timePoint @@ -182,12 +179,12 @@ timeSelector : Identifier '.' TimeProp ; -intervallFunc - : overlapsIntervallOperator #intvOverl - | asOfOperator #intvAsOf +intervalFunc + : overlapsIntervallOperator + | asOfOperator ; overlapsIntervallOperator - : 'overlaps' '(' intervall ')' + : 'overlaps(' interval ')' ; asOfOperator @@ -309,7 +306,7 @@ TimeProp | 'val_to' ; -Interval +IntervalConst : 'tx' | 'val' ; diff --git a/src/main/java/org/s1ck/gdl/GDLLoader.java b/src/main/java/org/s1ck/gdl/GDLLoader.java index 6ac6e67..5319a24 100644 --- a/src/main/java/org/s1ck/gdl/GDLLoader.java +++ b/src/main/java/org/s1ck/gdl/GDLLoader.java @@ -21,6 +21,7 @@ import org.s1ck.gdl.exceptions.InvalidReferenceException; import org.s1ck.gdl.model.*; import org.s1ck.gdl.model.comparables.ElementSelector; +import org.s1ck.gdl.model.comparables.time.*; import org.s1ck.gdl.model.predicates.booleans.And; import org.s1ck.gdl.model.predicates.expressions.Comparison; import org.s1ck.gdl.model.predicates.Predicate; @@ -42,6 +43,9 @@ class GDLLoader extends GDLBaseListener { private final Map userVertexCache; private final Map userEdgeCache; + // used to map graphs to their elements + private Map> graphElements; + // used to cache elements which are assigned to auto-generated variables private final Map autoGraphCache; private final Map autoVertexCache; @@ -122,6 +126,8 @@ class GDLLoader extends GDLBaseListener { userVertexCache = new HashMap<>(); userEdgeCache = new HashMap<>(); + graphElements = new HashMap<>(); + autoGraphCache = new HashMap<>(); autoVertexCache = new HashMap<>(); autoEdgeCache = new HashMap<>(); @@ -385,6 +391,163 @@ public void enterComparisonExpression(GDLParser.ComparisonExpressionContext ctx) currentPredicates.add(buildComparison(ctx)); } + /** + * Builds a {@code Predicate} from the given Intervall-Function (caller is a interval) + * interval functions are e.g. asOf(x), between(x,y).... + * + * @param ctx interval function context + */ + @Override + public void enterIntvF(GDLParser.IntvFContext ctx){ + currentPredicates.add(buildIntervalFunction(ctx)); + } + + /** + * Converts an interval function into a (complex) predicate + * For example, i.between(x,y) would be translated to a predicate ((i.from<= y) AND (i.to>x)) + * @param ctx interval function context + * @return complex predicate that encodes the interval function. Atoms are time stamp comparisons + */ + private Predicate buildIntervalFunction(GDLParser.IntvFContext ctx) { + TimePoint[] intv = buildIntervall(ctx.interval()); + TimePoint from = intv[0]; + TimePoint to = intv[1]; + return createIntervalPredicates(from, to, ctx.intervalFunc()); + } + + + private Predicate createIntervalPredicates(TimePoint from, TimePoint to, GDLParser.IntervalFuncContext intervalFunc) { + if(intervalFunc.overlapsIntervallOperator()!=null){ + return createOverlapsPredicates(from, to, intervalFunc.overlapsIntervallOperator()); + } + else if(intervalFunc.asOfOperator()!=null){ + return createAsOfPredicates(from, to, intervalFunc.asOfOperator()); + } + return null; + } + + private Predicate createOverlapsPredicates(TimePoint from, TimePoint to, GDLParser.OverlapsIntervallOperatorContext ctx) { + TimePoint[] arg = buildIntervall(ctx.interval()); + TimePoint arg_from = arg[0]; + TimePoint arg_to = arg[1]; + TimePoint mx = new MaxTimePoint(from, arg_from); + TimePoint mn = new MinTimePoint(to, arg_to); + // TODO unzip comparison + return new Comparison(mx, Comparator.LT, mn); + } + + private Predicate createAsOfPredicates(TimePoint from, TimePoint to, GDLParser.AsOfOperatorContext ctx){ + TimePoint x = buildTimePoint(ctx.timePoint()); + return new And(new Comparison(from, Comparator.LTE, x), new Comparison(to, Comparator.GTE, x)); + } + + + private TimePoint[] buildIntervall(GDLParser.IntervalContext ctx) { + if (ctx.intervalSelector()!=null){ + GDLParser.IntervalSelectorContext selector = ctx.intervalSelector(); + // throws exception, if variable invalid + String var = resolveIdentifier(selector.Identifier().getText()); + String intId = selector.IntervalConst().getText(); + TimePoint from = new TimeSelector(var, intId+"_from"); + TimePoint to = new TimeSelector(var, intId+"_to"); + return new TimePoint[]{from, to}; + } + else if(ctx.intervalFromStamps()!=null){ + GDLParser.IntervalFromStampsContext fs = ctx.intervalFromStamps(); + TimePoint from = buildTimePoint(fs.timePoint(0)); + TimePoint to = buildTimePoint(fs.timePoint(1)); + return new TimePoint[]{from,to}; + } + return null; + } + + /** + * Builds a {@code Predicate} from the given TimeStamp-Function (caller is a timestamp) + * time stamp functions are e.g. asOf(x), before(x),... + * + * @param ctx stamp function context + */ + @Override + public void enterStmpF(GDLParser.StmpFContext ctx){ + currentPredicates.add(buildStampFunction(ctx)); + } + + /** + * Converts a time stamp function into a (potentially complex) {@code Predicate} + * For example, i.asOf(x) would be translated to a {@code Predicate} i<=x + * + * @param ctx time stamp function context + * @return (potentially complex) {@code Predicate} that encodes the time stamp function. Atoms are time stamp comparisons + */ + private Predicate buildStampFunction(GDLParser.StmpFContext ctx) { + TimePoint tp = buildTimePoint(ctx.timePoint()); + return createStampPredicates(tp, ctx.stampFunc()); + } + + /** + * Returns time stamp {@code Predicate} given the caller (a time stamp) and its context + * + * @param tp the caller + * @param stampFunc context including the operator (e.g. asOf, before,...) and its argument(s) + * @return (potentially complex) {@code Predicate} that encodes the time stamp function. Atoms are time stamp comparisons + */ + private Predicate createStampPredicates(TimePoint tp, GDLParser.StampFuncContext stampFunc) { + if(stampFunc.asOfOperator()!=null){ + return createAsOfPredicates(tp, stampFunc.asOfOperator()); + } + else if(stampFunc.beforePointOperator()!=null){ + return createBeforePredicates(tp, stampFunc.beforePointOperator()); + } + return null; + } + + + /** + * Creates an asOf-{@code Predicate} given the caller (a timestamp) and its context + * + * @param from the caller + * @param ctx its context including the argument + * @return a {@code Predicate} encoding the asOf function: from<=x + */ + private Predicate createAsOfPredicates(TimePoint from, GDLParser.AsOfOperatorContext ctx) { + TimePoint x = buildTimePoint(ctx.timePoint()); + // TODO hier schon Abschätzung reinbringen? + return new Comparison(from, Comparator.LTE, x); + } + + /** + * Creates a before {@code Predicate} given the caller (a timestamp) and its context + * + * @param from the caller + * @param ctx its context including the argument + * @return a {@code Predicate} encoding the before function: from()); + } + graphElements.get(getNextGraphId()).add(graphElement); } } @@ -736,11 +903,13 @@ private ComparableExpression extractComparableExpression(GDLParser.ComparisonEle * @return parsed property selector expression */ private PropertySelector buildPropertySelector(GDLParser.PropertyLookupContext ctx) { - GraphElement element; - - String identifier = ctx.Identifier(0).getText(); + String identifier = resolveIdentifier(ctx.Identifier(0).getText()); String property = ctx.Identifier(1).getText(); + return new PropertySelector(identifier,property); + } + private String resolveIdentifier(String identifier){ + GraphElement element; if(userVertexCache.containsKey(identifier)) { element = userVertexCache.get(identifier); } @@ -748,8 +917,7 @@ else if(userEdgeCache.containsKey(identifier)) { element = userEdgeCache.get(identifier); } else { throw new InvalidReferenceException(identifier);} - - return new PropertySelector(element.getVariable(),property); + return element.getVariable(); } // -------------------------------------------------------------------------------------------- diff --git a/src/main/java/org/s1ck/gdl/model/comparables/time/MaxTimePoint.java b/src/main/java/org/s1ck/gdl/model/comparables/time/MaxTimePoint.java index 2a34f0f..1701691 100644 --- a/src/main/java/org/s1ck/gdl/model/comparables/time/MaxTimePoint.java +++ b/src/main/java/org/s1ck/gdl/model/comparables/time/MaxTimePoint.java @@ -4,10 +4,6 @@ * Represents a MAX(p1,...,pn) term, where p1...pn are TimePoints */ public class MaxTimePoint extends TimeTerm{ - /** - * Operator name - */ - private final static String operator = "MAX"; /** * Creates a MAX(args[0],...,args[args.length-1]) term @@ -15,9 +11,12 @@ public class MaxTimePoint extends TimeTerm{ */ public MaxTimePoint(TimePoint...args){ super(args); + operator = "MAX"; } + + @Override public long evaluate(){ long mn = Long.MIN_VALUE; diff --git a/src/main/java/org/s1ck/gdl/model/comparables/time/MinTimePoint.java b/src/main/java/org/s1ck/gdl/model/comparables/time/MinTimePoint.java index e5db9df..91fc695 100644 --- a/src/main/java/org/s1ck/gdl/model/comparables/time/MinTimePoint.java +++ b/src/main/java/org/s1ck/gdl/model/comparables/time/MinTimePoint.java @@ -4,10 +4,6 @@ * Represents a MAX(p1,...,pn) term, where p1...pn are TimePoints */ public class MinTimePoint extends TimeTerm { - /** - * Operator name - */ - private final static String operator = "MIN"; /** * Creates a MIN(args[0],...,args[args.length-1]) term @@ -15,6 +11,7 @@ public class MinTimePoint extends TimeTerm { */ public MinTimePoint(TimePoint...args){ super(args); + operator = "MIN"; } @Override diff --git a/src/main/java/org/s1ck/gdl/model/comparables/time/TimeTerm.java b/src/main/java/org/s1ck/gdl/model/comparables/time/TimeTerm.java index 8fff2a9..0e66fc7 100644 --- a/src/main/java/org/s1ck/gdl/model/comparables/time/TimeTerm.java +++ b/src/main/java/org/s1ck/gdl/model/comparables/time/TimeTerm.java @@ -11,14 +11,14 @@ public abstract class TimeTerm extends TimePoint { /** - * Operator name (e.g. "MIN", "MAX",...) + * List of arguments (i.g. more than one) */ - private final static String operator=""; + protected ArrayList args; /** - * List of arguments (i.g. more than one) + * String representation of the operator, e.g. "MIN", "MAX", ... */ - ArrayList args; + protected String operator = ""; /** * Initialize a complex expression by its arguments (TimePoints) @@ -29,14 +29,6 @@ protected TimeTerm(TimePoint...args){ Collections.addAll(this.args, args); } - /** - * Get the operator name - * @return operator name - */ - public String getOperator() { - return operator; - } - /** * Get the arguments list * @return list of arguments @@ -65,6 +57,14 @@ public ArrayList getVariables(){ return vars; } + /** + * String representation of the operator (e.g. "MIN", "MAX",...) + * @return operator string + */ + public String getOperator(){ + return operator; + } + @Override public String toString(){ StringBuilder sb = new StringBuilder(operator+"("); diff --git a/src/test/java/org/s1ck/gdl/GDLLoaderTemporalTest.java b/src/test/java/org/s1ck/gdl/GDLLoaderTemporalTest.java new file mode 100644 index 0000000..08deff1 --- /dev/null +++ b/src/test/java/org/s1ck/gdl/GDLLoaderTemporalTest.java @@ -0,0 +1,95 @@ +package org.s1ck.gdl; +import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTreeWalker; +import org.junit.Test; +import org.s1ck.gdl.model.comparables.Literal; +import org.s1ck.gdl.model.comparables.PropertySelector; +import org.s1ck.gdl.model.comparables.time.MaxTimePoint; +import org.s1ck.gdl.model.comparables.time.MinTimePoint; +import org.s1ck.gdl.model.comparables.time.TimeLiteral; +import org.s1ck.gdl.model.comparables.time.TimeSelector; +import org.s1ck.gdl.model.predicates.Predicate; +import org.s1ck.gdl.model.predicates.expressions.Comparison; +import org.s1ck.gdl.utils.Comparator; + +import static org.junit.Assert.assertTrue; + +public class GDLLoaderTemporalTest { + + @Test + public void simpleTimestampFunctionsTest(){ + GDLLoader loader = getLoaderFromGDLString( + "MATCH (alice)-[e1:knows {since : 2014}]->(bob) (alice)-[e2:knows {since : 2013}]->(eve) " + + "WHERE (e1.tx_from.before(2017-01-01) AND e2.val_to.asOf(2018-12-23T15:55:23)) OR e1.knows>2010"); + System.out.println("Edges: "+loader.getEdgeCache()); + System.out.println("Vertices: "+loader.getVertexCache()); + System.out.println("Predicates: "+loader.getPredicates()); + assertTrue(predicateContainedIn( + new Comparison(new TimeSelector("e1", "tx_from"), Comparator.LT, new TimeLiteral("2017-01-01")), + loader.getPredicates().get())); + assertTrue(predicateContainedIn( + new Comparison(new TimeSelector("e2", "val_to"), Comparator.LTE, new TimeLiteral("2018-12-23T15:55:23")), + loader.getPredicates().get())); + assertTrue(predicateContainedIn( + new Comparison(new PropertySelector("e1", "knows"), Comparator.GT, new Literal(2010)), + loader.getPredicates().get())); + } + + @Test + public void graphTimeStampTest(){ + + } + + + + // checks if pred 1 is contained in pred 2 + private boolean predicateContainedIn(Predicate p1, Predicate p2){ + if(p1 instanceof Comparison){ + if(p2 instanceof Comparison){ + // TODO better equals method? + return p1.toString().equals(p2.toString()); + } + else{ + Predicate lhs = p2.getArguments()[0]; + Predicate rhs = p2.getArguments()[1]; + return (predicateContainedIn(p1,lhs) || predicateContainedIn(p1,rhs)); + } + } + else{ + if(p2 instanceof Comparison){ + return false; + } + if(p1.toString().length() < p2.toString().length()){ + return false; + } + else if(p1.toString().length() == p2.toString().length()){ + return p1.toString().equals(p2.toString()); + } + else{ + Predicate lhs = p2.getArguments()[0]; + Predicate rhs = p2.getArguments()[1]; + return (predicateContainedIn(p1,lhs) || predicateContainedIn(p1,rhs)); + } + } + } + + + + + + + private static final String DEFAULT_GRAPH_LABEL = "DefaultGraph"; + private static final String DEFAULT_VERTEX_LABEL = "DefaultVertex"; + private static final String DEFAULT_EDGE_LABEL = "DefaultEdge"; + + private GDLLoader getLoaderFromGDLString(String gdlString) { + GDLLexer lexer = new GDLLexer(new ANTLRInputStream(gdlString)); + GDLParser parser = new GDLParser(new CommonTokenStream(lexer)); + + ParseTreeWalker walker = new ParseTreeWalker(); + GDLLoader loader = new GDLLoader(DEFAULT_GRAPH_LABEL, DEFAULT_VERTEX_LABEL, DEFAULT_EDGE_LABEL); + walker.walk(loader, parser.database()); + return loader; + } +}