Skip to content

More quantifiers for SHORTEST&CHEAPEST + ALL SHORTEST #64

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ private static String printConnection(QueryVertex vertexOnTheLeft, VertexPairCon
protected static String printHops(QueryPath path) {
long minHops = path.getMinHops();
long maxHops = path.getMaxHops();
if (minHops == 1 && maxHops == 1) {
if (minHops == 1 && maxHops == 1 && path.getPathFindingGoal() == PathFindingGoal.REACHES) {
return "";
} else if (minHops == 0 && maxHops == -1) {
return "*";
Expand Down
21 changes: 18 additions & 3 deletions graph-query-ir/src/main/java/oracle/pgql/lang/ir/QueryPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,18 @@ public class QueryPath extends VertexPairConnection {

private int kValue;

private boolean withTies;

public QueryPath(QueryVertex src, QueryVertex dst, String name, CommonPathExpression commonPathExpression,
boolean anonymous, long minHops, long maxHops, PathFindingGoal goal, int kValue, Direction direction) {
boolean anonymous, long minHops, long maxHops, PathFindingGoal goal, int kValue, boolean withTies,
Direction direction) {
super(src, dst, name, anonymous, direction);
this.commonPathExpression = commonPathExpression;
this.minHops = minHops;
this.maxHops = maxHops;
this.goal = goal;
this.kValue = kValue;
this.withTies = withTies;
}

public String getPathExpressionName() {
Expand Down Expand Up @@ -110,6 +114,14 @@ public void setKValue(int kValue) {
this.kValue = kValue;
}

public boolean getWithTies() {
return withTies;
}

public void setWithTies(boolean withTies) {
this.withTies = withTies;
}

@Override
public VariableType getVariableType() {
return VariableType.PATH;
Expand All @@ -135,8 +147,9 @@ public String toString() {
}

private String printShortestCheapest(PathFindingGoal goal) {
String kValueAsString = kValue == 1 ? "" : "TOP " + kValue + " ";
String result = kValueAsString + goal + " ( " + getSrc() + " ";
String kValueAsString = kValue > 1 ? "TOP " + kValue + " " : "";
String allAsString = withTies ? "ALL " : "";
String result = kValueAsString + allAsString + goal + " ( " + getSrc() + " ";
String pathExpression = printPathExpression(commonPathExpression, true);
if (pathExpression.contains("WHERE") || pathExpression.contains("COST") || pathExpression.startsWith("(")
|| pathExpression.endsWith(")")) {
Expand Down Expand Up @@ -180,6 +193,8 @@ public boolean equals(Object obj) {
return false;
if (kValue != other.kValue)
return false;
if (withTies != other.withTies)
return false;
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.spoofax.interpreter.terms.IStrategoInt;
import org.spoofax.interpreter.terms.IStrategoString;
import org.spoofax.interpreter.terms.IStrategoTerm;
import org.spoofax.interpreter.terms.TermType;

import oracle.pgql.lang.ir.GraphQuery;
import oracle.pgql.lang.ir.QueryExpression;
Expand Down Expand Up @@ -95,32 +96,32 @@ public class CommonTranslationUtil {
private static final int POS_LENGTH_EXP = 0;

protected static String getString(IStrategoTerm t) {
while (t.getTermType() != IStrategoTerm.STRING) {
while (t.getType() != TermType.STRING) {
t = t.getSubterm(0); // data values are often wrapped multiple times, e.g. Some(LimitClause("10"))
}
return ((IStrategoString) t).stringValue();
}

protected static int getInt(IStrategoTerm t) {
while (t.getTermType() != IStrategoTerm.INT) {
while (t.getType() != TermType.INT) {
t = t.getSubterm(0); // data values are often wrapped multiple times, e.g. Some(LimitClause("10"))
}
return ((IStrategoInt) t).intValue();
}

protected static IStrategoTerm getList(IStrategoTerm t) {
while (t.getTermType() != IStrategoTerm.LIST) {
while (t.getType() != TermType.LIST) {
t = t.getSubterm(0); // data values are often wrapped multiple times, e.g. Some(OrderElems([...]))
}
return t;
}

protected static boolean isNone(IStrategoTerm t) {
return t.getTermType() == IStrategoTerm.APPL && ((IStrategoAppl) t).getConstructor().getName().equals("None");
return t.getType() == TermType.APPL && ((IStrategoAppl) t).getConstructor().getName().equals("None");
}

protected static boolean isSome(IStrategoTerm t) {
return t.getTermType() == IStrategoTerm.APPL && ((IStrategoAppl) t).getConstructor().getName().equals("Some");
return t.getType() == TermType.APPL && ((IStrategoAppl) t).getConstructor().getName().equals("Some");
}

protected static IStrategoTerm getSome(IStrategoTerm t) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import org.spoofax.interpreter.terms.IStrategoAppl;
import org.spoofax.interpreter.terms.IStrategoTerm;
import org.spoofax.interpreter.terms.TermType;

import oracle.pgql.lang.ir.CommonPathExpression;
import oracle.pgql.lang.ir.Direction;
Expand Down Expand Up @@ -142,7 +143,7 @@ public class SpoofaxAstToGraphQuery {
private static final int POS_PATH_NAME = 4;
private static final int POS_PATH_DIRECTION = 5;
private static final int POS_PATH_FINDING_GOAL = 6;
private static final int POS_PATH_K_VALUE = 7;
private static final int POS_PATH_TOP_K_ANY_ALL = 7;

private static final int POS_ORDERBY_EXP = 0;
private static final int POS_ORDERBY_ORDERING = 1;
Expand Down Expand Up @@ -282,7 +283,7 @@ private static Projection translateSelectClause(TranslationContext ctx, IStrateg

IStrategoTerm projectionElemsT = selectOrUpdateT.getSubterm(POS_PROJECTION_ELEMS);
List<ExpAsVar> selectElems;
if (projectionElemsT.getTermType() == IStrategoTerm.APPL
if (projectionElemsT.getType() == TermType.APPL
&& ((IStrategoAppl) projectionElemsT).getConstructor().getName().equals("Star")) {
// GROUP BY in combination with SELECT *. Even though the parser will generate
// an error for it, the
Expand Down Expand Up @@ -660,10 +661,11 @@ private static QueryPath getReaches(IStrategoTerm pathT, TranslationContext ctx,
QueryVertex dst = getQueryVertex(vertexMap, dstName);
PathFindingGoal goal = PathFindingGoal.REACHES;
int kValue = -1;
boolean withTies = false;

QueryPath path = name.contains(GENERATED_VAR_SUBSTR)
? new QueryPath(src, dst, name, commonPathExpression, true, minHops, maxHops, goal, kValue, direction)
: new QueryPath(src, dst, name, commonPathExpression, false, minHops, maxHops, goal, kValue, direction);
? new QueryPath(src, dst, name, commonPathExpression, true, minHops, maxHops, goal, kValue, withTies, direction)
: new QueryPath(src, dst, name, commonPathExpression, false, minHops, maxHops, goal, kValue, withTies, direction);

return path;
}
Expand Down Expand Up @@ -702,12 +704,27 @@ private static QueryPath getShortestCheapest(IStrategoTerm pathT, TranslationCon
IStrategoTerm pathExpressionT = pathT.getSubterm(POS_PATH_EXPRESSION);
CommonPathExpression pathExpression = getPathExpression(pathExpressionT, ctx);

IStrategoTerm kValueT = pathT.getSubterm(POS_PATH_K_VALUE);

int kValue = kValueT.getTermType() == IStrategoTerm.APPL && isNone(kValueT) ? -1
: parseInt(pathT.getSubterm(POS_PATH_K_VALUE));
IStrategoTerm topKAnyAllT = pathT.getSubterm(POS_PATH_TOP_K_ANY_ALL);
boolean withTies = false; // default
int kValue = 1; // default
if (isSome(topKAnyAllT)) {
IStrategoAppl topKAnyAllContent = (IStrategoAppl) getSome(topKAnyAllT);
switch (topKAnyAllContent.getName()) {
case "TopK":
kValue = parseInt(topKAnyAllContent.getSubterm(0));
break;
case "Any":
break;
case "All":
withTies = true;
break;
default:
throw new IllegalArgumentException(topKAnyAllContent.getName());
}
}

QueryPath path = new QueryPath(src, dst, name, pathExpression, true, minHops, maxHops, goal, kValue, direction);
QueryPath path = new QueryPath(src, dst, name, pathExpression, true, minHops, maxHops, goal, kValue, withTies,
direction);

return path;
}
Expand Down
4 changes: 2 additions & 2 deletions pgql-lang/src/test/java/oracle/pgql/lang/BugFixTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,10 @@ public void missingEqualsForCast() throws Exception {
}

@Test
public void errorOnMissingKleeneStar() throws Exception {
public void errorOnMissingQuantifier() throws Exception {
String shortestQuery = "SELECT * MATCH SHORTEST ((n) -[e]-> (m))";
String cheapestQuery = "SELECT * MATCH SHORTEST ((n) -[e]-> (m))";
String errorMessage = "Kleene star (*) required for now";
String errorMessage = "Quantifier of the form * or + or {1,4} expected";
assertTrue(pgql.parse(shortestQuery).getErrorMessages().contains(errorMessage));
assertTrue(pgql.parse(cheapestQuery).getErrorMessages().contains(errorMessage));
}
Expand Down
18 changes: 18 additions & 0 deletions pgql-lang/src/test/java/oracle/pgql/lang/PrettyPrintingTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,24 @@ public void testShortest3() throws Exception {
checkRoundTrip(query);
}

@Test
public void testAnyShortest() throws Exception {
String query = "SELECT 1 FROM MATCH ANY SHORTEST ( (n) -[e]->* (m) )";
checkRoundTrip(query);
}

@Test
public void testAnyCheapest() throws Exception {
String query = "SELECT 1 FROM MATCH ANY CHEAPEST ( (n) (-[e]-> COST e.cost)* (m) )";
checkRoundTrip(query);
}

@Test
public void testAllShortest() throws Exception {
String query = "SELECT 1 FROM MATCH ALL SHORTEST ( (n) -[e]->* (m) )";
checkRoundTrip(query);
}

@Test
public void testDeprecatedDefinitionInGroupBy() throws Exception {
String query = "SELECT age FROM g MATCH (n) GROUP BY n.age AS age";
Expand Down
9 changes: 6 additions & 3 deletions pgql-spoofax/syntax/GraphPattern.sdf3
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@ context-free syntax

Connection.ParenthesizedPath = <( <RelaxedPathPattern> <WhereClause?> <CostClause?> )<PathQuantifier?>>

PathPattern.Shortest = <<TopK?> SHORTEST ( <PathPattern> )> {case-insensitive}
PathPattern.Cheapest = <<TopK?> CHEAPEST ( <PathPattern> )> {case-insensitive}
PathPattern.Shortest = <<TopKAnyAll?> SHORTEST ( <PathPattern> )> {case-insensitive}
PathPattern.Cheapest = <<TopKAnyAll?> CHEAPEST ( <PathPattern> )> {case-insensitive}

TopKAnyAll.TopK = <TOP <UNSIGNED-INT>> {case-insensitive}
TopKAnyAll.Any = <Any> {case-insensitive}
TopKAnyAll.All = <ALL> {case-insensitive}

TopK.TopK = <TOP <UNSIGNED-INT>> {case-insensitive}
CostClause.CostClause = <COST <Exp>> {case-insensitive}

EdgeContents.EdgeContents = <[<ElemContents>]>
Expand Down
6 changes: 3 additions & 3 deletions pgql-spoofax/trans/check.str
Original file line number Diff line number Diff line change
Expand Up @@ -271,16 +271,16 @@ rules // temporary limitation
with <generate-error(|ctx, "Not yet supported: multiple edge patterns in SHORTEST or CHEAPEST")> edges

nabl-constraint(|ctx):
Path(_, _, _, _, _, _, _, kValue) -> <fail>
Path(_, _, _, _, _, _, _, Some(TopK(kValue))) -> <fail>
where <?"0"> kValue
with <generate-error(|ctx, "Value should be greater than 0")> kValue

nabl-constraint(|ctx):
Path(_, _, pathExpression, quantifier, _, _, goal, _) -> <fail>
where <?Shortest() + ?Cheapest()> goal
; <not(?Some(BetweenNAndM("0", "-1")))> quantifier
; <?None() + ?Some(BetweenNAndM("0", "1")); origin-text; ?"?"> quantifier
with error-term := <?Some(_) <+ !pathExpression; collect-one(?Edge(_, _, _, _, _, _))> quantifier
; <generate-error(|ctx, "Kleene star (*) required for now")> error-term
; <generate-error(|ctx, "Quantifier of the form * or + or {1,4} expected")> error-term

nabl-constraint(|ctx):
t@CommonPathExpression(_, _, connections, _, _) -> <fail>
Expand Down
2 changes: 1 addition & 1 deletion pgql-spoofax/trans/common.str
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ rules
origin-text = fail
origin-offset = fail

has-at-most-one-binding = ?None() + ?Some(BetweenNAndM(_, "0")) + ?Some(BetweenNAndM(_, "1"))
has-at-most-one-binding = ?None() + ?Some(BetweenNAndM("0", "1")); origin-text; ?"?"

is-ddl-statement = is-CreatePropertyGraph + is-DropPropertyGraph + is-CreateExternalSchema + is-DropExternalSchema
is-CreatePropertyGraph = ?CreatePropertyGraph(_, _, _)
Expand Down
51 changes: 25 additions & 26 deletions pgql-spoofax/trans/normalize.str
Original file line number Diff line number Diff line change
Expand Up @@ -413,50 +413,49 @@ rules
get-connections-from-paths(|variable-counter):
[pathPattern|otherPaths] -> <conc> (edges, edgesTailPaths)
where not ( <?Shortest(_, _) + ?Cheapest(_, _)> pathPattern)
with kValue := "-1"
with topKAnyAll := None()
; pathFindingGoal := None()
; edges := <pathPattern-to-connections(|variable-counter, pathFindingGoal, kValue)> pathPattern
; edges := <pathPattern-to-connections(|variable-counter, pathFindingGoal, topKAnyAll)> pathPattern
; edgesTailPaths := <get-connections-from-paths(|variable-counter)> otherPaths

// paths with more than one vertex
get-connections-from-paths(|variable-counter):
[Shortest(kValue, pathPattern)|otherPaths] -> result
with result := <get-connections-from-shortest-cheapest-paths(|variable-counter)> (Shortest(), kValue, pathPattern, otherPaths)
[Shortest(topKAnyAll, pathPattern)|otherPaths] -> result
with result := <get-connections-from-shortest-cheapest-paths(|variable-counter)> (Shortest(), topKAnyAll, pathPattern, otherPaths)

// paths with more than one vertex
get-connections-from-paths(|variable-counter):
[Cheapest(kValue, pathPattern)|otherPaths] -> result
with result := <get-connections-from-shortest-cheapest-paths(|variable-counter)> (Cheapest(), kValue, pathPattern, otherPaths)
[Cheapest(topKAnyAll, pathPattern)|otherPaths] -> result
with result := <get-connections-from-shortest-cheapest-paths(|variable-counter)> (Cheapest(), topKAnyAll, pathPattern, otherPaths)

get-connections-from-shortest-cheapest-paths(|variable-counter):
(pathFindingGoal, kValue, pathPattern, otherPaths) -> <conc> ([path'], edgesTailPaths)
with kValue' := <?Some(TopK(<id>)) <+ !"1"> kValue
; edges := <pathPattern-to-connections(|variable-counter, pathFindingGoal, kValue')> pathPattern
; path' := <?[single-path-only]; !single-path-only; try(single-edge-to-path(|variable-counter, pathFindingGoal, kValue)) <+ !ComplexRegularExpressionNotSupported(<id>)> edges
(pathFindingGoal, topKAnyAll, pathPattern, otherPaths) -> <conc> ([path'], edgesTailPaths)
with edges := <pathPattern-to-connections(|variable-counter, pathFindingGoal, topKAnyAll)> pathPattern
; path' := <?[single-path-only]; !single-path-only; try(single-edge-to-path(|variable-counter, pathFindingGoal, topKAnyAll)) <+ !ComplexRegularExpressionNotSupported(<id>)> edges
; edgesTailPaths := <get-connections-from-paths(|variable-counter)> otherPaths

single-edge-to-path(|variable-counter, pathFindingGoal, kValue):
single-edge-to-path(|variable-counter, pathFindingGoal, topKAnyAll):
edge@NormalizedEdge(srcVertex, e, dstVertex, direction, quantifier, constraints) -> path
with costClause := None()
; path := <to-path(|variable-counter, pathFindingGoal, kValue)> (srcVertex, dstVertex, edge, constraints, costClause, quantifier)
; path := <to-path(|variable-counter, pathFindingGoal, topKAnyAll)> (srcVertex, dstVertex, edge, constraints, costClause, quantifier)

pathPattern-to-connections(|variable-counter, pathFindingGoal, kValue):
pathPattern-to-connections(|variable-counter, pathFindingGoal, topKAnyAll):
PathPattern(Vertex(n1), edgeVertices@[EdgeVertex(e, Vertex(n2))|_]) -> <conc> ([edge], edgesPathTail)
with edge := <to-connection(|variable-counter, pathFindingGoal, kValue)> (n1, e, n2)
; edgesPathTail := <get-connections-from-single-path(|variable-counter, pathFindingGoal, kValue)> edgeVertices
with edge := <to-connection(|variable-counter, pathFindingGoal, topKAnyAll)> (n1, e, n2)
; edgesPathTail := <get-connections-from-single-path(|variable-counter, pathFindingGoal, topKAnyAll)> edgeVertices

// base case: only one edgeVertex
get-connections-from-single-path(|variable-counter, pathFindingGoal, kValue):
get-connections-from-single-path(|variable-counter, pathFindingGoal, topKAnyAll):
[EdgeVertex(_, _)] -> []

// two or more edgeVertices
get-connections-from-single-path(|variable-counter, pathFindingGoal, kValue):
get-connections-from-single-path(|variable-counter, pathFindingGoal, topKAnyAll):
[EdgeVertex(_, Vertex(n1)) | edgeVertices@[EdgeVertex(e, Vertex(n2))|_]]
-> <conc> ([edge], edgesPathTail)
with edge := <to-connection(|variable-counter, pathFindingGoal, kValue)> (n1, e, n2)
; edgesPathTail := <get-connections-from-single-path(|variable-counter, pathFindingGoal, kValue)> edgeVertices
with edge := <to-connection(|variable-counter, pathFindingGoal, topKAnyAll)> (n1, e, n2)
; edgesPathTail := <get-connections-from-single-path(|variable-counter, pathFindingGoal, topKAnyAll)> edgeVertices

to-connection(|variable-counter, pathFindingGoal, kValue):
to-connection(|variable-counter, pathFindingGoal, topKAnyAll):
(n1, edgeOrPath, n2) -> connection
where <?OutConn(_, quantifier) + ?InConn(_, quantifier) + ?UndirectedEdge(_, quantifier)> edgeOrPath
with
Expand Down Expand Up @@ -484,33 +483,33 @@ rules
; dstVertex := <unique-name(|variable-counter, edgeOrPath)>
; edge := <origin-track-forced(!NormalizedEdge(srcVertex, e', dstVertex, dir, None(), Constraints([])))> edgeOrPath
; costClause := None()
; connection := <to-path(|variable-counter, pathFindingGoal, kValue)> (n1, n2, edge, Constraints(constraints), costClause, quantifier)
; connection := <to-path(|variable-counter, pathFindingGoal, topKAnyAll)> (n1, n2, edge, Constraints(constraints), costClause, quantifier)
end
case ?Some(Path(Some(pathName), pathPatternName, quantifier')):
connection := Path(src, dst, pathPatternName, quantifier', pathName, dir, Reaches(), -1)
otherwise: fail
end

to-connection(|variable-counter, pathFindingGoal, kValue):
to-connection(|variable-counter, pathFindingGoal, topKAnyAll):
(n1, t@ParenthesizedPath(RelaxedPathPattern(v1, [RelaxedEdgeVertex(edge, v2)|otherEdges]), whereClause, costClause, quantifier), n2) -> path
with if <has-at-most-one-binding> quantifier
then srcVertex := <?Some(Vertex(<id>)) <+ ?None(); !n1> v1
; dstVertex := <?Some(Vertex(<id>)) <+ ?None(); !n2> v2
else srcVertex := <?Some(Vertex(<id>)) <+ ?None(); unique-name(|variable-counter, edge)> v1
; dstVertex := <?Some(Vertex(<id>)) <+ ?None(); unique-name(|variable-counter, edge)> v2
end
; edge' := <to-connection(|variable-counter, pathFindingGoal, kValue)> (srcVertex, edge, dstVertex)
; edge' := <to-connection(|variable-counter, pathFindingGoal, topKAnyAll)> (srcVertex, edge, dstVertex)
; inlinedEdgeConstraints := <?NormalizedEdge(_, _, _, _, _, Constraints(<id>))> edge'
; inlinedSrcVertexConstraints := <?Some(Vertex(<id>)); get-inlined-constraints <+ ![]> v1
; inlinedDstVertexConstraints := <?Some(Vertex(<id>)); get-inlined-constraints <+ ![]> v2
; constraints := <?None(); ![] <+ ?Some(WhereClause(constraint)); ![constraint]> whereClause
; constraints' := Constraints(<conc> (inlinedSrcVertexConstraints, inlinedEdgeConstraints, inlinedDstVertexConstraints, constraints))
; if <?[]> otherEdges
then path := <to-path(|variable-counter, pathFindingGoal, kValue)> (n1, n2, edge', constraints', costClause, quantifier)
then path := <to-path(|variable-counter, pathFindingGoal, topKAnyAll)> (n1, n2, edge', constraints', costClause, quantifier)
else path := ComplexParenthesizedRegularExpressionNotSupported(otherEdges)
end

to-path(|variable-counter, pathFindingGoal, kValue):
to-path(|variable-counter, pathFindingGoal, topKAnyAll):
(srcVertex, dstVertex, edge@NormalizedEdge(src, _, dst, direction, _, _), constraints, costClause, quantifier) -> path
with name := <unique-name(|variable-counter, edge)>
; if <?Incoming()> direction
Expand All @@ -520,7 +519,7 @@ rules
; edges := Edges([edge])
; path-variable-name := <unique-name(|variable-counter, edge)> // for now, all paths are anonymous/generated
; commonPathExpression := CommonPathExpression(name, vertices, edges, constraints, costClause)
; path := <origin-track-forced(!Path(srcVertex, dstVertex, commonPathExpression, quantifier, path-variable-name, Outgoing(), pathFindingGoal, kValue))> edge
; path := <origin-track-forced(!Path(srcVertex, dstVertex, commonPathExpression, quantifier, path-variable-name, Outgoing(), pathFindingGoal, topKAnyAll))> edge

rules

Expand Down
Loading