bug fixes on field type propagation (#10)

* fixed a bug in resolving non-existent label during schema binding

* bug fix and allow bool? type to evaluate for logical operators

* propagate data type also to property objects in condition expressions during binding

* fix a bug type evaluating IN op's list operand
This commit is contained in:
Jerry Liang 2019-11-12 14:59:39 -08:00 коммит произвёл GitHub
Родитель d27ce77742
Коммит 9e01425272
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 201 добавлений и 142 удалений

Просмотреть файл

@ -11,7 +11,7 @@ namespace openCypherTranspiler.Common.Exceptions
public class TranspilerBindingException : TranspilerException
{
public TranspilerBindingException(string bindingErrorMsg) :
base($"Data binding error error: {bindingErrorMsg}")
base($"Data binding error: {bindingErrorMsg}")
{
}

Просмотреть файл

@ -42,56 +42,60 @@ namespace openCypherTranspiler.LogicalPlanner
ValueField edgeSrcIdField = null;
ValueField edgeSinkIdField = null;
try
if (Entity is NodeEntity)
{
if (Entity is NodeEntity)
var nodeDef = graphDefinition.GetNodeDefinition(Entity.EntityName);
if (nodeDef == null)
{
NodeSchema nodeDef = graphDefinition.GetNodeDefinition(Entity.EntityName);
entityUniqueName = nodeDef.Id;
nodeIdField = new ValueField(nodeDef.NodeIdProperty.PropertyName, nodeDef.NodeIdProperty.DataType);
properties.AddRange(nodeDef.Properties.Select(p => new ValueField(p.PropertyName, p.DataType)));
properties.Add(nodeIdField);
throw new TranspilerBindingException($"Failed to bind entity with alias '{Entity.Alias}' of type '{Entity.EntityName}' to graph definition.");
}
else
{
var edgeEnt = Entity as RelationshipEntity;
EdgeSchema edgeDef = null;
switch (edgeEnt.RelationshipDirection)
{
case RelationshipEntity.Direction.Forward:
edgeDef = graphDefinition.GetEdgeDefinition(edgeEnt.EntityName, edgeEnt.LeftEntityName, edgeEnt.RightEntityName);
break;
case RelationshipEntity.Direction.Backward:
edgeDef = graphDefinition.GetEdgeDefinition(edgeEnt.EntityName, edgeEnt.RightEntityName, edgeEnt.LeftEntityName);
break;
default:
// either direction
// TODO: we don't handle 'both' direction yet
Debug.Assert(edgeEnt.RelationshipDirection == RelationshipEntity.Direction.Both);
edgeDef = graphDefinition.GetEdgeDefinition(edgeEnt.EntityName, edgeEnt.LeftEntityName, edgeEnt.RightEntityName);
if (edgeDef == null)
{
edgeDef = graphDefinition.GetEdgeDefinition(edgeEnt.EntityName, edgeEnt.RightEntityName, edgeEnt.LeftEntityName);
}
break;
}
entityUniqueName = nodeDef.Id;
nodeIdField = new ValueField(nodeDef.NodeIdProperty.PropertyName, nodeDef.NodeIdProperty.DataType);
entityUniqueName = edgeDef.Id;
sourceEntityName = edgeDef.SourceNodeId;
sinkEntityName = edgeDef.SinkNodeId;
edgeSrcIdField = new ValueField(edgeDef.SourceIdProperty.PropertyName, edgeDef.SourceIdProperty.DataType);
edgeSinkIdField = new ValueField(edgeDef.SinkIdProperty.PropertyName, edgeDef.SinkIdProperty.DataType);
properties.AddRange(edgeDef.Properties.Select(p => new ValueField(p.PropertyName, p.DataType)));
properties.Add(edgeSrcIdField);
properties.Add(edgeSinkIdField);
}
properties.AddRange(nodeDef.Properties.Select(p => new ValueField(p.PropertyName, p.DataType)));
properties.Add(nodeIdField);
}
catch (KeyNotFoundException e)
else
{
throw new TranspilerBindingException($"Failed to binding entity with alias '{Entity.Alias}' of type '{Entity.EntityName}' to graph definition. Inner error: {e.GetType().Name}: {e.Message}");
var edgeEnt = Entity as RelationshipEntity;
EdgeSchema edgeDef = null;
switch (edgeEnt.RelationshipDirection)
{
case RelationshipEntity.Direction.Forward:
edgeDef = graphDefinition.GetEdgeDefinition(edgeEnt.EntityName, edgeEnt.LeftEntityName, edgeEnt.RightEntityName);
break;
case RelationshipEntity.Direction.Backward:
edgeDef = graphDefinition.GetEdgeDefinition(edgeEnt.EntityName, edgeEnt.RightEntityName, edgeEnt.LeftEntityName);
break;
default:
// either direction
// TODO: we don't handle 'both' direction yet
Debug.Assert(edgeEnt.RelationshipDirection == RelationshipEntity.Direction.Both);
edgeDef = graphDefinition.GetEdgeDefinition(edgeEnt.EntityName, edgeEnt.LeftEntityName, edgeEnt.RightEntityName);
if (edgeDef == null)
{
edgeDef = graphDefinition.GetEdgeDefinition(edgeEnt.EntityName, edgeEnt.RightEntityName, edgeEnt.LeftEntityName);
}
break;
}
if (edgeDef == null)
{
throw new TranspilerBindingException($"Failed to bind entity with alias '{Entity.Alias}' of type '{Entity.EntityName}' to graph definition.");
}
entityUniqueName = edgeDef.Id;
sourceEntityName = edgeDef.SourceNodeId;
sinkEntityName = edgeDef.SinkNodeId;
edgeSrcIdField = new ValueField(edgeDef.SourceIdProperty.PropertyName, edgeDef.SourceIdProperty.DataType);
edgeSinkIdField = new ValueField(edgeDef.SinkIdProperty.PropertyName, edgeDef.SinkIdProperty.DataType);
properties.AddRange(edgeDef.Properties.Select(p => new ValueField(p.PropertyName, p.DataType)));
properties.Add(edgeSrcIdField);
properties.Add(edgeSinkIdField);
}
Debug.Assert(OutputSchema.Count == 1

Просмотреть файл

@ -87,6 +87,52 @@ namespace openCypherTranspiler.LogicalPlanner
_outOperators.Add(op);
}
internal void UpdatePropertyBasedOnAliasFromInputSchema(QueryExpressionProperty prop)
{
var matchedField = InputSchema.FirstOrDefault(f => f.FieldAlias == prop.VariableName);
if (matchedField == null)
{
throw new TranspilerBindingException($"Alias '{prop.VariableName}' does not exist in the current context");
}
if (string.IsNullOrEmpty(prop.PropertyName))
{
// direct reference to an alias (value column or entity column)
if (matchedField is ValueField)
{
prop.DataType = (matchedField as ValueField).FieldType;
}
else
{
// entity field reference in a single field expression
// this is valid only in handful situations, such as Count(d), Count(distinct(d))
// in such case, we populate the Entity object with correct entity type so that code generator can use it later
Debug.Assert(matchedField is EntityField);
var matchedEntity = matchedField as EntityField;
prop.Entity = matchedEntity.Type == EntityField.EntityType.Node ?
new NodeEntity() { EntityName = matchedEntity.EntityName, Alias = matchedEntity.FieldAlias } as Entity :
new RelationshipEntity() { EntityName = matchedEntity.EntityName, Alias = matchedEntity.FieldAlias } as Entity;
}
}
else
{
// property dereference of an entity column
if (!(matchedField is EntityField))
{
throw new TranspilerBindingException($"Failed to dereference property {prop.PropertyName} for alias {prop.VariableName}, which is not an alias of entity type as expected");
}
var entField = matchedField as EntityField;
var entPropField = entField.EncapsulatedFields.FirstOrDefault(f => f.FieldAlias == prop.PropertyName);
if (entPropField == null)
{
throw new TranspilerBindingException($"Failed to dereference property {prop.PropertyName} for alias {prop.VariableName}, Entity type {entField.BoundEntityName} does not have a property named {prop.PropertyName}");
}
entField.AddReferenceFieldName(prop.PropertyName);
prop.DataType = entPropField.FieldType;
}
}
public override string ToString()
{
var sb = new StringBuilder();

Просмотреть файл

@ -34,6 +34,9 @@ namespace openCypherTranspiler.LogicalPlanner
// we calculate the data type of all the fields in the output schema
// using type evaluation method on the QueryExpression
// Map from out_alias to { projection_expression,
// e.g.: (a.b + c) AS d
// "d" -> (<expression of a.b+c>, <d's field in OutputSchema>)
var exprToOutputMap = ProjectionMap.ToDictionary(
kv => kv.Key, // key is output alias
kv => new { Expr = kv.Value, Field = OutputSchema.First(f => f.FieldAlias == kv.Key) } // value is the corresponding field object and expression
@ -45,12 +48,19 @@ namespace openCypherTranspiler.LogicalPlanner
(map.Value.Expr.GetChildrenQueryExpressionType<QueryExpressionAggregationFunction>().Count() > 0);
var allPropertyReferences = map.Value.Expr.GetChildrenQueryExpressionType<QueryExpressionProperty>();
// update types for all QueryExpressionPropty object first based on InputSchema (so QueryExpression.EvaluteType() will work)
foreach (var prop in allPropertyReferences)
{
UpdatePropertyBasedOnAliasFromInputSchema(prop);
}
// then, update the type in the OutputSchema
if (map.Value.Field is EntityField)
{
// This can only be direct exposure of entity (as opposed to deference of a particular property)
// We just copy of the fields that the entity can potentially be dereferenced
Debug.Assert(allPropertyReferences.Count() == 1);
var varName = allPropertyReferences.First().VariableName;
var matchInputField = InputSchema.First(f => f.FieldAlias == varName);
map.Value.Field.Copy(matchInputField);
@ -60,58 +70,6 @@ namespace openCypherTranspiler.LogicalPlanner
// This can be a complex expression involve multiple field/column references
// We will compute the type of the expression
Debug.Assert(map.Value.Field is ValueField);
// first of all, bind the type to the variable references
foreach (var prop in allPropertyReferences)
{
var varName = prop.VariableName;
var propName = prop.PropertyName;
Debug.Assert(prop.VariableName != null);
var matchedField = InputSchema.FirstOrDefault(f => f.FieldAlias == varName);
if (matchedField == null)
{
throw new TranspilerBindingException($"Failed to find input matching field alias {varName}");
}
if (string.IsNullOrEmpty(propName))
{
// direct reference to an alias (value column or entity column)
if (matchedField is ValueField)
{
prop.DataType = (matchedField as ValueField).FieldType;
}
else
{
// entity field reference in a single field expression
// this is valid only in handful situations, such as Count(d), Count(distinct(d))
// in such case, we populate the Entity object with correct entity type so that code generator can use it later
Debug.Assert(matchedField is EntityField);
var matchedEntity = matchedField as EntityField;
prop.Entity = matchedEntity.Type == EntityField.EntityType.Node ?
new NodeEntity() { EntityName = matchedEntity.EntityName, Alias = matchedEntity.FieldAlias } as Entity:
new RelationshipEntity() { EntityName = matchedEntity.EntityName, Alias = matchedEntity.FieldAlias } as Entity;
}
}
else
{
// property dereference of an entity column
if (!(matchedField is EntityField))
{
throw new TranspilerBindingException($"Failed to dereference property {propName} for alias {varName}, which is not an alias of entity type as expected");
}
var entField = matchedField as EntityField;
var entPropField = entField.EncapsulatedFields.FirstOrDefault(f => f.FieldAlias == propName);
if (entPropField == null)
{
throw new TranspilerBindingException($"Failed to dereference property {propName} for alias {varName}, Entity type {entField.BoundEntityName} does not have a property named {propName}");
}
entField.AddReferenceFieldName(propName);
prop.DataType = entPropField.FieldType;
}
}
// do data type evaluation
var evalutedType = map.Value.Expr.EvaluateType();
var outField = map.Value.Field as ValueField;
outField.FieldType = evalutedType;

Просмотреть файл

@ -138,6 +138,15 @@ namespace openCypherTranspiler.LogicalPlanner
};
});
}
// Update in-place for property references inside FilterExpression
if (FilterExpression != null)
{
foreach (var prop in FilterExpression.GetChildrenOfType<QueryExpressionProperty>())
{
UpdatePropertyBasedOnAliasFromInputSchema(prop);
}
}
}
internal override void AppendReferencedProperties(IDictionary<string, EntityField> entityFields)
@ -194,7 +203,7 @@ namespace openCypherTranspiler.LogicalPlanner
// field member access (i.e. [VariableName].[fieldName]), just append to list
if (!entityFields.ContainsKey(varName))
{
throw new TranspilerSyntaxErrorException($"Failed to access member '{fieldName} for '{varName}' which does not exist");
throw new TranspilerSyntaxErrorException($"Failed to access member '{fieldName}' for '{varName}' which does not exist");
}
var entity = entityFields[varName];
entity.AddReferenceFieldName(fieldName);

Просмотреть файл

@ -56,6 +56,13 @@ namespace openCypherTranspiler.LogicalPlanner
logicalPlanner.StartingOperators = logicalRoot.GetAllUpstreamOperatorsOfType<StartLogicalOperator>().ToList();
logicalPlanner.TerminalOperators = new List<LogicalOperator>(1) { logicalRoot };
// populate the ids for operators (for debugging purpose)
var i = 0;
foreach (var op in allLogicalOps)
{
op.OperatorDebugId = ++i;
}
// bind logical tree with the graph schema
var bindableStartingOp = logicalPlanner.StartingOperators.Where(op => op is IBindable);
bindableStartingOp.ToList().ForEach(op => (op as IBindable).Bind(graphDef));
@ -66,17 +73,7 @@ namespace openCypherTranspiler.LogicalPlanner
// expand and optimize out columns not used (bottom up)
logicalPlanner.UpdateActualFieldReferencesForEntityFields();
// note: in this function, we only deal with graph definition.
// later in code generatation phase, we deal with Cosmos storage descriptors
// which then worries the details of what streams to pull
// verify that no dangling operator exists
var i = 0;
foreach (var op in allLogicalOps)
{
op.OperatorDebugId = ++i;
}
var allOpsFromTraversal = logicalPlanner.StartingOperators.SelectMany(op => op.GetAllDownstreamOperatorsOfType<LogicalOperator>()).Distinct().OrderBy(op => op.OperatorDebugId).ToList();
var allOpsFromBuildingTree = allLogicalOps.OrderBy(op => op.OperatorDebugId).ToList();
Debug.Assert(allOpsFromTraversal.Count == allOpsFromBuildingTree.Count);
@ -271,9 +268,8 @@ namespace openCypherTranspiler.LogicalPlanner
// do a simple projection to remove extra implicitly projected fields if applies
if (removeExtraImplicitFields)
{
// since all the schema was already mapped to the target aliases in the previous projection operators, only
// simple direct mapping needed, namely 'a AS a'. The gold here is just to remove extra that were not in the
// original projected fields
// by now, all fields need to be projected are already computed in the previous projection operator
// so we just do a project that retains all the fields that are explicitly projected (by doing an 'field AS field')
var trimmedSimpleProjExprs = projExprs.Select(n =>
new QueryExpressionWithAlias
{
@ -281,9 +277,7 @@ namespace openCypherTranspiler.LogicalPlanner
InnerExpression = new QueryExpressionProperty()
{
VariableName = n.Alias,
DataType = null,
Entity = n.TryGetDirectlyExposedEntity(),
PropertyName = null
Entity = n.InnerExpression.TryGetDirectlyExposedEntity(),
}
}
).ToList();

Просмотреть файл

@ -781,7 +781,7 @@ namespace openCypherTranspiler.openCypherParser.AST
var entitiesInFinalReturn = GetDirectlyExposedEntitiesWithAliasApplied(singleQuery.EntityPropertySelected);
if (entitiesInFinalReturn.Count() > 0)
{
throw new TranspilerNotSupportedException($"Entities ({string.Join(", ", entitiesInFinalReturn.Select(e => e.Alias))}) in return statement");
throw new TranspilerNotSupportedException($"Returning the whole entity '{entitiesInFinalReturn.Select(e => e.Alias).First()}' instead of its properties");
}
return singleQuery;

Просмотреть файл

@ -49,8 +49,8 @@ namespace openCypherTranspiler.openCypherParser.AST
case BinaryOperatorType.Logical:
// For logical comparison, we ensure that all operands' type are logical already
// The return type is always boolean (logical)
if (leftType != typeof(bool) || leftType != typeof(bool?) &&
rightType != typeof(bool) || rightType != typeof(bool?))
if ((leftType != typeof(bool) && leftType != typeof(bool?)) ||
(rightType != typeof(bool) && rightType != typeof(bool?)) )
{
throw new TranspilerNotSupportedException($"Logical binary operator {Operator} operating must operate on bool types. Actual types: {leftType}, {rightType}");
}
@ -74,12 +74,22 @@ namespace openCypherTranspiler.openCypherParser.AST
{
if (!TypeCoersionTables.CoersionTableEqualityComparison.TryGetValue((leftTypeUnboxed, rightTypeUnboxed), out resultedTypeRaw))
{
throw new TranspilerInternalErrorException($"Unexpected use of binary operator {Operator.Name} operating between types {leftTypeUnboxed} and {rightTypeUnboxed}");
throw new TranspilerInternalErrorException($"Unexpected use of (un)equality operator {Operator.Name} operating between types {leftTypeUnboxed} and {rightTypeUnboxed}");
}
}
else if (Operator.Name == BinaryOperator.IN)
{
// IN need special handling as right operand is a list
var innerTypes = rightTypeUnboxed.GetGenericArguments();
if (innerTypes == null || innerTypes.Length != 1)
{
throw new TranspilerInternalErrorException($"Unexpected use of IN operator, the right type {rightTypeUnboxed} is not a list of value type");
}
resultedTypeRaw = typeof(bool);
}
else
{
if (!TypeCoersionTables.CoersionTableInequalityComparison.TryGetValue((leftTypeUnboxed, rightTypeUnboxed), out resultedTypeRaw))
if (!TypeCoersionTables.CoersionTableDefaultComparison.TryGetValue((leftTypeUnboxed, rightTypeUnboxed), out resultedTypeRaw))
{
throw new TranspilerInternalErrorException($"Unexpected use of binary operator {Operator.Name} operating between types {leftTypeUnboxed} and {rightTypeUnboxed}");
}

Просмотреть файл

@ -1394,7 +1394,7 @@ namespace openCypherTranspiler.openCypherParser.AST
{ (typeof(bool), typeof(bool)), typeof(bool) },
};
public static readonly IDictionary<(Type O1Type, Type O2Type), Type> CoersionTableInequalityComparison = new Dictionary<(Type Op1Type, Type Op2Type), Type>()
public static readonly IDictionary<(Type O1Type, Type O2Type), Type> CoersionTableDefaultComparison = new Dictionary<(Type Op1Type, Type Op2Type), Type>()
{
{ (typeof(int), typeof(int)), typeof(bool) },
{ (typeof(int), typeof(double)), typeof(bool) },

Просмотреть файл

@ -87,22 +87,12 @@ namespace openCypherTranspiler.CommonTest
public EdgeSchema GetEdgeDefinition(string edgeVerb, string fromNodeName, string toNodeName)
{
var edge = _allEdgeDefinions?.Where(n => n.Name == edgeVerb && n.SourceNodeId == fromNodeName && n.SinkNodeId == toNodeName).FirstOrDefault();
if (edge == null)
{
throw new KeyNotFoundException($"Edge with edgeverb = {edgeVerb}, fromNodeName = {fromNodeName} and toNodename = {toNodeName} not found!");
}
return edge;
return _allEdgeDefinions?.Where(n => n.Name == edgeVerb && n.SourceNodeId == fromNodeName && n.SinkNodeId == toNodeName).FirstOrDefault();
}
public NodeSchema GetNodeDefinition(string nodeName)
{
var node = _allNodeDefinions?.Where(n => n.Name == nodeName).FirstOrDefault();
if (node == null)
{
throw new KeyNotFoundException($"Node with name = {nodeName} not found!");
}
return node;
return _allNodeDefinions?.Where(n => n.Name == nodeName).FirstOrDefault();
}
}

Просмотреть файл

@ -357,6 +357,7 @@ ORDER BY p.Name LIMIT 3
public void TypeEvaluationTest()
{
IGraphSchemaProvider graphDef = new JSONGraphSchema(@"./TestData/MovieGraph.json");
// Basic test covers type coercion and type evaluation
{
var lp = RunQueryAndDumpTree(graphDef, @"
@ -375,6 +376,26 @@ RETURN toInteger(ReleasedStr) as Released, toFloat(ReleasedStr) as ReleasedFloat
Assert.IsTrue(lp.TerminalOperators.First().OutputSchema.Any(o => o.FieldAlias == "ReleasedBool" && ((o as ValueField)?.FieldType ?? default(Type)) == typeof(bool?)));
Assert.IsTrue(lp.TerminalOperators.First().OutputSchema.Any(o => o.FieldAlias == "ReleasedFloat2" && ((o as ValueField)?.FieldType ?? default(Type)) == typeof(float?)));
}
// Logic operator used on converted types
{
var lp = RunQueryAndDumpTree(graphDef, @"
MATCH (p:Person)-[a:ACTED_IN]->(m:Movie)
WITH p, m, toString(m.Released) as ReleasedStr
WITH p, m, tointeger(ReleasedStr) as ReleasedInt
WHERE ReleasedInt > 1998
RETURN p.Name, m.Title, ReleasedInt, (ReleasedInt >= 1998) and (ReleasedInt <= 2000) as IsBetween98and00
"
);
Assert.IsTrue(lp.StartingOperators.Count() > 0);
Assert.IsTrue(lp.TerminalOperators.Count() == 1);
Assert.IsTrue(lp.TerminalOperators.First().OutputSchema.Count == 4);
Assert.IsTrue(lp.TerminalOperators.First().OutputSchema.Any(o => o.FieldAlias == "Name" && ((o as ValueField)?.FieldType ?? default(Type)) == typeof(string)));
Assert.IsTrue(lp.TerminalOperators.First().OutputSchema.Any(o => o.FieldAlias == "Title" && ((o as ValueField)?.FieldType ?? default(Type)) == typeof(string)));
Assert.IsTrue(lp.TerminalOperators.First().OutputSchema.Any(o => o.FieldAlias == "ReleasedInt" && ((o as ValueField)?.FieldType ?? default(Type)) == typeof(int?)));
Assert.IsTrue(lp.TerminalOperators.First().OutputSchema.Any(o => o.FieldAlias == "IsBetween98and00" && ((o as ValueField)?.FieldType ?? default(Type)) == typeof(bool?)));
}
}
[TestMethod]
@ -420,9 +441,10 @@ RETURN p.Name AS Name, p2.Name as CoStarName
}
}
[TestMethod]
public void NegativeTestLogicalPlanner()
{
IGraphSchemaProvider graphDef = new JSONGraphSchema(@".\TestData\Movie\MovieGraph.json");
IGraphSchemaProvider graphDef = new JSONGraphSchema(@"./TestData/MovieGraph.json");
// Our implementation block of returning whole entity instead of its fields
// We don't support packing whole entity into JSON today, as JSON blob in Cosmos
@ -441,7 +463,7 @@ RETURN p
}
catch (TranspilerNotSupportedException e)
{
Assert.IsTrue(e.Message.Contains("Query final return body returns the whole entity"));
Assert.IsTrue(e.Message.Contains("Returning the whole entity"));
}
}
@ -463,8 +485,27 @@ RETURN p.Name, m.Title
}
}
// edge cannot be resolved case
{
try
{
var lp = RunQueryAndDumpTree(graphDef, @"
MATCH (p:Person)<-[a:ACTED_IN]-(m:Movie)
RETURN p.Name, m.Title
"
);
Assert.Fail("Didn't failed as expected.");
}
catch (TranspilerBindingException e)
{
Assert.IsTrue(e.Message.Contains("Failed to bind entity with alias 'a'"));
}
}
}
[TestMethod]
public void NegativeTestLogicalPlannerSchemaBinding()
{
IGraphSchemaProvider graphDef = new JSONGraphSchema(@"./TestData/MovieGraph.json");
@ -475,7 +516,7 @@ RETURN p.Name, m.Title
{
var lp = RunQueryAndDumpTree(graphDef, @"
MATCH (p:Actor)
RETURN p
RETURN p.Name
"
);
Assert.Fail("Didn't failed as expected.");
@ -489,7 +530,7 @@ RETURN p
{
var lp = RunQueryAndDumpTree(graphDef, @"
MATCH (p:Person)-[:Performed]-(m:Movie)
RETURN p
RETURN p.Name
"
);
Assert.Fail("Didn't failed as expected.");

Просмотреть файл

@ -565,9 +565,9 @@ RETURN Title, Name
";
TranspileToSQL(queryText);
}
catch (TranspilerSyntaxErrorException e)
catch (TranspilerBindingException e)
{
Assert.IsTrue(e.Message.Contains("'a' which does not exist"));
Assert.IsTrue(e.Message.Contains("Alias 'a' does not exist in the current context"));
expectedExceptionThrown = true;
}
Assert.IsTrue(expectedExceptionThrown);
@ -587,9 +587,9 @@ RETURN Title, Name
";
TranspileToSQL(queryText);
}
catch (TranspilerSyntaxErrorException e)
catch (TranspilerBindingException e)
{
Assert.IsTrue(e.Message.Contains("'TitleNotExist' does not exist"));
Assert.IsTrue(e.Message.Contains("Alias 'TitleNotExist' does not exist in the current context"));
expectedExceptionThrown = true;
}
Assert.IsTrue(expectedExceptionThrown);
@ -605,7 +605,7 @@ RETURN Title, Name
var queryText = @"
MATCH (p:Person)-[:ACTED_IN]-(m:Movie)
WHERE p.Name in ['Tom Hanks', 'Meg Ryan'] and m.Released >= 1990
RETURN p.Name as Name, m.Title AS Title, m.Released % 100 as ReleasedYear2Digit, m.Released - 2000 as YearSinceY2k, m.Released * 1 / 1 ^ 1 AS TestReleasedYear
RETURN p.Name as Name, p.Name in ['Tom Hanks', 'Meg Ryan'] as NameChecksOut, m.Title AS Title, m.Released % 100 as ReleasedYear2Digit, m.Released - 2000 as YearSinceY2k, m.Released * 1 / 1 ^ 1 AS TestReleasedYear
";
RunQueryAndCompare(queryText);
@ -729,6 +729,13 @@ return p.Name as Name1, p2.Name as Name2
{
var queryText = @"
MATCH (p:Person)-[:DIRECTED]-(m:Movie)
return p.Name as Name, m.Title as Title
";
RunQueryAndCompare(queryText);
}
{
var queryText = @"
MATCH (m:Movie)-[:DIRECTED]-(p:Person)
return p.Name as Name, m.Title as Title
";
RunQueryAndCompare(queryText);