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:
Родитель
d27ce77742
Коммит
9e01425272
|
@ -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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче