public GraphQLNode(string name, ExpressionResult exp, Expression relationExpression, IEnumerable <ParameterExpression> constantParameters, IEnumerable <object> constantParameterValues) { Name = name; NodeExpression = exp; Fields = new List <IGraphQLNode>(); if (relationExpression != null) { RelationExpression = relationExpression; } Parameters = constantParameters?.ToList(); ConstantParameterValues = constantParameterValues?.ToList(); }
public IGraphQLBaseNode ParseFieldSelect(ExpressionResult expContext, string name, EntityGraphQLParser.ObjectSelectionContext context) { try { IGraphQLBaseNode graphQLNode = null; if (expContext.Type.IsEnumerableOrArray()) { //var listExp = Compiler.Util.ExpressionUtil.FindDistinct(result.ExpressionResult); //if (listExp.Item1 != null) //{ // var item1 = (ExpressionResult)listExp.Item1; // item1.AddConstantParameters(result.ExpressionResult.ConstantParameters); // graphQLNode = BuildDynamicSelectOnCollection(new CompiledQueryResult(item1, result.ContextParams), name, context); // graphQLNode.SetNodeExpression((ExpressionResult)Compiler.Util.ExpressionUtil.CombineExpressions(graphQLNode.GetNodeExpression(), listExp.Item2)); //} //else //{ // graphQLNode = BuildDynamicSelectOnCollection(result, name, context); //} graphQLNode = BuildDynamicSelectOnCollection(expContext, name, context); } else { // Could be a list.First() that we need to turn into a select, or // other levels are object selection. e.g. from the top level people query I am selecting all their children { field1, etc. } // Can we turn a list.First() into and list.Select().First() var listExp = ExpressionUtil.FindIEnumerable(expContext); if (listExp.Item1 != null) { // yes we can // rebuild the ExpressionResult so we keep any ConstantParameters var item1 = (ExpressionResult)listExp.Item1; item1.AddConstantParameters(expContext.ConstantParameters); item1.AddServices(expContext.Services); graphQLNode = BuildDynamicSelectOnCollection(item1, name, context); graphQLNode.SetCombineExpression(listExp.Item2); } else { graphQLNode = BuildDynamicSelectForObjectGraph(expContext, currentExpressionContext.AsParameter(), name, context); } } return(graphQLNode); } catch (EntityGraphQLCompilerException ex) { throw SchemaException.MakeFieldCompileError($"Error compiling field {name}", ex.Message); } }
/// Given a syntax of someCollection { fields, to, selection, from, object } /// it will build a select assuming 'someCollection' is an IEnumerables private IGraphQLBaseNode BuildDynamicSelectOnCollection(ExpressionResult queryResult, string resultName, EntityGraphQLParser.ObjectSelectionContext context) { var elementType = queryResult.Type.GetEnumerableOrArrayType(); var contextParameter = Expression.Parameter(elementType, $"p_{elementType.Name}"); var exp = queryResult; var oldContext = currentExpressionContext; currentExpressionContext = (ExpressionResult)contextParameter; // visit child fields. Will be more fields var fieldExpressions = context.children.Select(c => Visit(c)).Where(n => n != null).ToList(); var gqlNode = new GraphQLQueryNode(schemaProvider, fragments, resultName, exp, oldContext.AsParameter(), fieldExpressions, (ExpressionResult)contextParameter); currentExpressionContext = oldContext; return(gqlNode); }
/// <summary> /// Given a syntax of { fields, to, selection, from, object } with a context /// it will build the correct select statement /// </summary> /// <param name="name"></param> /// <param name="context"></param> /// <param name="selectContext"></param> /// <returns></returns> private IGraphQLBaseNode BuildDynamicSelectForObjectGraph(ExpressionResult selectFromExp, ParameterExpression selectFromParam, string name, EntityGraphQLParser.ObjectSelectionContext context) { try { // visit child fields. Will be field or entityQueries again // These expression will be built on the element type var oldContext = currentExpressionContext; currentExpressionContext = selectFromExp; var fieldExpressions = context.children.Select(c => Visit(c)).Where(n => n != null).ToList(); currentExpressionContext = oldContext; var graphQLNode = new GraphQLQueryNode(schemaProvider, fragments, name, selectFromExp, selectFromParam, fieldExpressions, selectFromExp); return(graphQLNode); } catch (EntityGraphQLCompilerException ex) { throw SchemaException.MakeFieldCompileError($"Failed compiling field {name}", ex.Message); } }
public override IGraphQLBaseNode VisitGqlFragment(EntityGraphQLParser.GqlFragmentContext context) { // top level syntax part. Add to the fragrments and return null var typeName = context.fragmentType.GetText(); currentExpressionContext = (ExpressionResult)Expression.Parameter(schemaProvider.Type(typeName).ContextType, $"frag_{typeName}"); var fields = new List <IGraphQLBaseNode>(); foreach (var item in context.fields.children) { var f = Visit(item); if (f != null) // white space etc { fields.Add(f); } } fragments.Add(new GraphQLFragment(context.fragmentName.GetText(), typeName, fields, (ParameterExpression)currentExpressionContext)); currentExpressionContext = null; return(null); }
private ExpressionResult BuildEntityQueryExpression(IMethodType fieldArgumentContext, string fieldName, string argName, string query) { if (string.IsNullOrEmpty(query)) { return(null); } var prop = ((Field)fieldArgumentContext).ArgumentTypesObject.GetType().GetProperties().FirstOrDefault(p => p.Name == argName && p.PropertyType.GetGenericTypeDefinition() == typeof(EntityQueryType <>)); if (prop == null) { throw new EntityGraphQLCompilerException($"Can not find argument {argName} of type EntityQuery on field {fieldName}"); } var eqlt = prop.GetValue(((Field)fieldArgumentContext).ArgumentTypesObject) as BaseEntityQueryType; var contextParam = Expression.Parameter(eqlt.QueryType, $"q_{eqlt.QueryType.Name}"); ExpressionResult expressionResult = EntityQueryCompiler.CompileWith(query, contextParam, schemaProvider, claims, methodProvider, variables).ExpressionResult; expressionResult = (ExpressionResult)Expression.Lambda(expressionResult.Expression, contextParam); return(expressionResult); }
public override GraphQLOperation VisitGqlTypeDef(EntityGraphQLParser.GqlTypeDefContext context) { var argName = context.gqlVar().GetText().TrimStart('$'); var isArray = context.arrayType != null; var type = isArray ? context.arrayType.type.GetText() : context.type.GetText(); var required = context.required != null; ExpressionResult defaultValue = null; if (context.defaultValue != null) { defaultValue = constantVisitor.Visit(context.defaultValue); } if (required && !variables.ContainsKey(argName) && defaultValue == null) { throw new QueryException($"Missing required variable '{argName}' on query '{this.operation.Name}'"); } this.operation.AddArgument(argName, type, isArray, required, defaultValue != null ? defaultValue : null); return(this.operation); }
/// <summary> /// Given a syntax of { fields, to, selection, from, object } with a context /// it will build the correct select statement /// </summary> /// <param name="name"></param> /// <param name="context"></param> /// <param name="selectContext"></param> /// <returns></returns> private IGraphQLBaseNode BuildDynamicSelectForObjectGraph(ExpressionResult selectFromExp, ParameterExpression selectFromParam, string name, EntityGraphQLParser.ObjectSelectionContext context) { try { // visit child fields. Will be field or entityQueries again // These expression will be built on the element type var oldContext = currentExpressionContext; currentExpressionContext = selectFromExp; ParameterExpression replacementParameter = null; // we might be using a service i.e. ctx => WithService((T r) => r.DoSomething(ctx.Entities.Select(f => f.Id).ToList())) // if we can we want to avoid calling that multiple times with a expression like // r.DoSomething(ctx.Entities.Select(f => f.Id).ToList()) == null ? null : new { // Field = r.DoSomething(ctx.Entities.Select(f => f.Id).ToList()).Blah // } // by wrapping the whole thing in a method that does the null check once. // This means we build the fieldExpressions on a parameter of the result type // We can only do that if it doesn't use the oldContext unless the oldContext is the root context // i.e we can't do this on a field on a type because we don't have that value as it might be a selection from an ORM bool wrapField = (oldContext.Type == schemaProvider.ContextType || oldContext.NodeType == ExpressionType.Parameter) && currentExpressionContext.Services.Any(); if (wrapField) { // replace with a parameter. The expression is compiled at execution time once replacementParameter = Expression.Parameter(selectFromExp.Type, "null_wrap"); currentExpressionContext = (ExpressionResult)replacementParameter; } var fieldExpressions = context.children.Select(c => Visit(c)).Where(n => n != null).ToList(); currentExpressionContext = oldContext; var graphQLNode = new GraphQLQueryNode(schemaProvider, fragments, name, selectFromExp, selectFromParam, fieldExpressions, selectFromExp) { IsWrapped = wrapField }; return(graphQLNode); } catch (EntityGraphQLCompilerException ex) { throw SchemaException.MakeFieldCompileError($"Failed compiling field {name}", ex.Message); } }
/// <summary> /// This is one of our top level node. /// mutation MyMutation {...} /// </summary> /// <param name="context"></param> /// <returns></returns> public override IGraphQLBaseNode VisitMutationQuery(EntityGraphQLParser.MutationQueryContext context) { var operation = GetOperation(context.operationName()); foreach (var item in operation.Arguments.Where(a => a.DefaultValue != null)) { variables[item.ArgName] = Expression.Lambda(item.DefaultValue.Expression).Compile().DynamicInvoke(); } this.currentExpressionContext = (ExpressionResult)Expression.Parameter(schemaProvider.ContextType, $"ctx"); var mutateFields = new List <IGraphQLBaseNode>(); foreach (var c in context.objectSelection().children) { var n = Visit(c); if (n != null) { mutateFields.Add(n); } } var mutation = new GraphQLQueryNode(schemaProvider, fragments, operation.Name, null, null, mutateFields, null); return(mutation); }
/// <summary> /// This is one of our top level node. /// query MyQuery { /// entityQuery { fields [, field] }, /// entityQuery { fields [, field] }, /// ... /// } /// </summary> /// <param name="context"></param> /// <returns></returns> public override IGraphQLBaseNode VisitDataQuery(EntityGraphQLParser.DataQueryContext context) { var operation = GetOperation(context.operationName()); foreach (var item in operation.Arguments.Where(a => a.DefaultValue != null)) { variables[item.ArgName] = Expression.Lambda(item.DefaultValue.Expression).Compile().DynamicInvoke(); } this.currentExpressionContext = (ExpressionResult)Expression.Parameter(schemaProvider.ContextType, $"ctx"); var rootFields = new List <GraphQLQueryNode>(); // Just visit each child node. All top level will be entityQueries foreach (var c in context.objectSelection().children) { var n = Visit(c); if (n != null) { rootFields.Add((GraphQLQueryNode)n); } } var query = new GraphQLQueryNode(schemaProvider, fragments, operation.Name, currentExpressionContext, (ParameterExpression)currentExpressionContext, rootFields, null); return(query); }
/// <summary> /// The dotnet Expression for this node. Could be as simple as (Person p) => p.Name /// Or as complex as (DbContext ctx) => ctx.People.Where(...).Select(p => new {...}).First() /// If there is a object selection (new {} in a Select() or not) we will build the NodeExpression on /// Execute() so we can look up any query fragment selections /// </summary> /// <value></value> public ExpressionResult GetNodeExpression() { // we might have to build the expression on request as when we prase the query // document the fragment referenced might be defined later in the document if (nodeExpression == null && fieldSelection != null && fieldSelection.Any()) { var replacer = new ParameterReplacer(); var selectionFields = new List <IGraphQLNode>(); bool isSelect = fieldSelectionBaseExpression.Type.IsEnumerableOrArray(); foreach (var field in fieldSelection) { if (field is GraphQLFragmentSelect) { var fragment = queryFragments.FirstOrDefault(i => i.Name == field.Name); if (fragment == null) { throw new EntityQuerySchemaException($"Fragment '{field.Name}' not found in query document"); } foreach (IGraphQLNode fragField in fragment.Fields) { ExpressionResult exp = null; if (isSelect) { exp = (ExpressionResult)replacer.Replace(fragField.GetNodeExpression(), fragment.SelectContext, fieldParameter); } else { exp = (ExpressionResult)replacer.Replace(fragField.GetNodeExpression(), fragment.SelectContext, fieldSelectionBaseExpression); } // new object as we reuse fragments selectionFields.Add(new GraphQLNode(schemaProvider, queryFragments, fragField.Name, exp, null, null, null, null)); // pull any constant values up foreach (var item in fragField.ConstantParameters) { constantParameters.Add(item.Key, item.Value); } } } else { var gfield = (IGraphQLNode)field; selectionFields.Add(gfield); // pull any constant values up foreach (var item in gfield.ConstantParameters) { constantParameters.Add(item.Key, item.Value); } } } if (isSelect) { // build a .Select(...) - returning a list<> nodeExpression = (ExpressionResult)ExpressionUtil.SelectDynamicToList(fieldParameter, fieldSelectionBaseExpression, selectionFields, schemaProvider); } else { // build a new {...} - returning a single object {} var newExp = ExpressionUtil.CreateNewExpression(fieldSelectionBaseExpression, selectionFields, schemaProvider); var anonType = newExp.Type; // make a null check from this new expression newExp = Expression.Condition(Expression.MakeBinary(ExpressionType.Equal, fieldSelectionBaseExpression, Expression.Constant(null)), Expression.Constant(null, anonType), newExp, anonType); nodeExpression = (ExpressionResult)newExp; } foreach (var field in selectionFields) { foreach (var cp in field.ConstantParameters) { if (!constantParameters.ContainsKey(cp.Key)) { constantParameters.Add(cp.Key, cp.Value); } } } foreach (var item in fieldSelectionBaseExpression.ConstantParameters) { constantParameters.Add(item.Key, item.Value); } } return(nodeExpression); }
public void SetNodeExpression(ExpressionResult expr) { nodeExpression = expr; }
public void SetNodeExpression(ExpressionResult expr) { throw new NotImplementedException(); }
public CompiledQueryResult(ExpressionResult expressionResult, List <ParameterExpression> contextParams) { this.ExpressionResult = expressionResult; this.contextParams = contextParams; }
public override IGraphQLBaseNode VisitField(EntityGraphQLParser.FieldContext context) { var fieldName = context.fieldDef.GetText(); string schemaTypeName = schemaProvider.GetSchemaTypeNameForClrType(currentExpressionContext.Type); var actualFieldName = schemaProvider.GetActualFieldName(schemaTypeName, fieldName, claims); var args = context.argsCall != null?ParseGqlCall(actualFieldName, context.argsCall) : null; var alias = context.alias?.name.GetText(); if (schemaProvider.HasMutation(actualFieldName)) { var mutationType = schemaProvider.GetMutations().First(m => m.Name == actualFieldName); if (args == null) { throw new EntityGraphQLCompilerException($"Missing bracets for mutation call {alias ?? fieldName}"); } if (context.select != null) { var expContext = (ExpressionResult)Expression.Parameter(mutationType.ReturnTypeClr, $"mut_{actualFieldName}"); var oldContext = currentExpressionContext; currentExpressionContext = expContext; var select = ParseFieldSelect(expContext, actualFieldName, context.select); currentExpressionContext = oldContext; return(new GraphQLMutationNode(mutationType, args, (GraphQLQueryNode)select)); } else { var resultName = alias ?? actualFieldName; return(new GraphQLMutationNode(mutationType, args, null)); } } else { if (!schemaProvider.TypeHasField(schemaTypeName, actualFieldName, args != null ? args.Select(d => d.Key) : new string[0], claims)) { throw new EntityGraphQLCompilerException($"Field {actualFieldName} not found on type {schemaTypeName}"); } var result = schemaProvider.GetExpressionForField(currentExpressionContext, schemaTypeName, actualFieldName, args, claims); IGraphQLBaseNode fieldResult; var resultName = alias ?? actualFieldName; if (context.select != null) { fieldResult = ParseFieldSelect(result, resultName, context.select); } else { fieldResult = new GraphQLQueryNode(schemaProvider, fragments, resultName, result, currentExpressionContext.AsParameter(), null, null); } if (context.directive != null) { return(ProcessFieldDirective((GraphQLQueryNode)fieldResult, context.directive)); } return(fieldResult); } }
public QueryResult(ExpressionResult expressionResult, List <ParameterExpression> contextParams, IEnumerable <object> parameterValues) { this.expressionResult = expressionResult; this.contextParams = contextParams; this.constantParameterValues = parameterValues; }
public GraphQlOperationArgument(string argName, object type, bool isArray, bool required, ExpressionResult defaultValue) { this.ArgName = argName; this.Type = type; this.IsArray = isArray; this.Required = required; DefaultValue = defaultValue; }
internal void AddArgument(string argName, object type, bool isArray, bool required, ExpressionResult defaultValue) { Arguments.Add(new GraphQlOperationArgument(argName, type, isArray, required, defaultValue)); }
public ExpressionResult ParseGqlarg(IField fieldArgumentContext, EntityGraphQLParser.GqlargContext context) { ExpressionResult gqlVarValue = null; if (context.gqlVar() != null) { string varKey = context.gqlVar().GetText().TrimStart('$'); if (variables == null) { throw new EntityGraphQLCompilerException($"Missing variable {varKey}"); } object value = variables.GetValueFor(varKey); gqlVarValue = (ExpressionResult)Expression.Constant(value); } else { // this is an expression gqlVarValue = constantVisitor.Visit(context.gqlvalue); } string argName = context.gqlfield.GetText(); if (fieldArgumentContext != null && fieldArgumentContext.HasArgumentByName(argName)) { var argType = fieldArgumentContext.GetArgumentType(argName); if (gqlVarValue != null && gqlVarValue.Type == typeof(string) && gqlVarValue.NodeType == ExpressionType.Constant) { string strValue = (string)((ConstantExpression)gqlVarValue).Value; if ( (argType.Type.TypeDotnet == typeof(Guid) || argType.Type.TypeDotnet == typeof(Guid?) || argType.Type.TypeDotnet == typeof(RequiredField <Guid>) || argType.Type.TypeDotnet == typeof(RequiredField <Guid?>)) && ConstantVisitor.GuidRegex.IsMatch(strValue)) { return((ExpressionResult)Expression.Constant(Guid.Parse(strValue))); } if (argType.Type.TypeDotnet.IsConstructedGenericType && argType.Type.TypeDotnet.GetGenericTypeDefinition() == typeof(EntityQueryType <>)) { string query = strValue; if (query.StartsWith("\"")) { query = query.Substring(1, context.gqlvalue.GetText().Length - 2); } return(BuildEntityQueryExpression(fieldArgumentContext, fieldArgumentContext.Name, argName, query)); } var argumentNonNullType = argType.Type.TypeDotnet.IsNullableType() ? Nullable.GetUnderlyingType(argType.Type.TypeDotnet) : argType.Type.TypeDotnet; if (argumentNonNullType.GetTypeInfo().IsEnum) { var enumName = strValue; var valueIndex = Enum.GetNames(argumentNonNullType).ToList().FindIndex(n => n == enumName); if (valueIndex == -1) { throw new EntityGraphQLCompilerException($"Value {enumName} is not valid for argument {context.gqlfield.GetText()}"); } var enumValue = Enum.GetValues(argumentNonNullType).GetValue(valueIndex); return((ExpressionResult)Expression.Constant(enumValue)); } } } return(gqlVarValue); }