/// Given a syntax of someCollection { fields, to, selection, from, object } /// it will build a select assuming 'someCollection' is an IEnumerables private IGraphQLNode BuildDynamicSelectOnCollection(CompiledQueryResult queryResult, string name, EntityGraphQLParser.EntityQueryContext context) { var elementType = queryResult.BodyType.GetEnumerableOrArrayType(); var contextParameter = Expression.Parameter(elementType, $"param_{elementType}"); var exp = queryResult.ExpressionResult; var oldContext = selectContext; selectContext = contextParameter; // visit child fields. Will be field or entityQueries again var fieldExpressions = context.fields.children.Select(c => Visit(c)).Where(n => n != null).ToList(); var gqlNode = new GraphQLNode(schemaProvider, fragments, name, null, exp, queryResult.ContextParams, fieldExpressions, contextParameter); selectContext = oldContext; return(gqlNode); }
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; CompiledQueryResult defaultValue = null; if (context.defaultValue != null) { defaultValue = EqlCompiler.CompileWith(context.defaultValue.GetText(), null, schemaProvider, null, variables); } 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.ExpressionResult : null); return(this.operation); }
public object Execute(params object[] args) { // run the mutation to get the context for the query select var mutation = (MutationResult)this.result.ExpressionResult; var result = mutation.Execute(args); if (typeof(LambdaExpression).IsAssignableFrom(result.GetType())) { var mutationLambda = (LambdaExpression)result; var mutationContextParam = mutationLambda.Parameters.First(); var mutationExpression = mutationLambda.Body; // this willtypically be similar to // db => db.Entity.Where(filter) or db => db.Entity.First(filter) // i.e. they'll be returning a list of items or a specific item // We want to take the field selection from the GraphQL query and add a LINQ Select() onto the expression // In the case of a First() we need to insert that select before the first // This is all to have 1 nice expression that can work with ORMs (like EF) // E.g we want db => db.Entity.Select(e => new {name = e.Name, ...}).First(filter) // we dot not want db => new {name = db.Entity.First(filter).Name, ...}) var selectParam = graphQLNode.Parameters.First(); if (!mutationLambda.ReturnType.IsEnumerableOrArray()) { if (mutationExpression.NodeType == ExpressionType.Call) { var call = (MethodCallExpression)mutationExpression; if (call.Method.Name == "First" || call.Method.Name == "FirstOrDefault" || call.Method.Name == "Last" || call.Method.Name == "LastOrDefault") { var baseExp = call.Arguments.First(); if (call.Arguments.Count == 2) { // move the fitler to a Where call var filter = call.Arguments.ElementAt(1); baseExp = ExpressionUtil.MakeExpressionCall(new [] { typeof(Queryable), typeof(Enumerable) }, "Where", new Type[] { selectParam.Type }, baseExp, filter); } // build select var selectExp = ExpressionUtil.MakeExpressionCall(new [] { typeof(Queryable), typeof(Enumerable) }, "Select", new Type[] { selectParam.Type, graphQLNode.GetNodeExpression().Type }, baseExp, Expression.Lambda(graphQLNode.GetNodeExpression(), selectParam)); // add First/Last back var firstExp = ExpressionUtil.MakeExpressionCall(new [] { typeof(Queryable), typeof(Enumerable) }, call.Method.Name, new Type[] { selectExp.Type.GetGenericArguments()[0] }, selectExp); // we're done graphQLNode.SetNodeExpression(firstExp); } } else { // if they just return a constant I.e the entity they just updated. It comes as a memebr access constant if (mutationLambda.Body.NodeType == ExpressionType.MemberAccess) { var me = (MemberExpression)mutationLambda.Body; if (me.Expression.NodeType == ExpressionType.Constant) { graphQLNode.AddConstantParameter(Expression.Parameter(me.Type), Expression.Lambda(me).Compile().DynamicInvoke()); } } else if (mutationLambda.Body.NodeType == ExpressionType.Constant) { var ce = (ConstantExpression)mutationLambda.Body; graphQLNode.AddConstantParameter(Expression.Parameter(ce.Type), ce.Value); } } } else { var exp = ExpressionUtil.MakeExpressionCall(new [] { typeof(Queryable), typeof(Enumerable) }, "Select", new Type[] { selectParam.Type, graphQLNode.GetNodeExpression().Type }, mutationExpression, Expression.Lambda(graphQLNode.GetNodeExpression(), selectParam)); graphQLNode.SetNodeExpression(exp); } // make sure we use the right parameter graphQLNode.Parameters[0] = mutationContextParam; var executionArg = args[0]; result = graphQLNode.Execute(executionArg); return(result); } // run the query select result = graphQLNode.Execute(result); return(result); }
public GraphQLMutationNode(CompiledQueryResult result, IGraphQLNode graphQLNode) { this.result = result; this.graphQLNode = graphQLNode; Fields = new List <IGraphQLNode>(); }
/// <summary> /// We compile each entityQuery with EqlCompiler and build a Select call from the fields /// </summary> /// <param name="context"></param> /// <returns></returns> public override IGraphQLBaseNode VisitEntityQuery(EntityGraphQLParser.EntityQueryContext context) { string name; string query; if (context.alias != null) { name = context.alias.name.GetText(); query = context.entity.GetText(); } else { query = context.entity.GetText(); name = query; if (name.IndexOf(".") > -1) { name = name.Substring(0, name.IndexOf(".")); } if (name.IndexOf("(") > -1) { name = name.Substring(0, name.IndexOf("(")); } } try { CompiledQueryResult result = null; if (selectContext == null) { // top level are queries on the context result = EqlCompiler.Compile(query, schemaProvider, methodProvider, variables); } else { result = EqlCompiler.CompileWith(query, selectContext, schemaProvider, methodProvider, variables); } var exp = result.ExpressionResult; IGraphQLNode graphQLNode = null; if (exp.Type.IsEnumerableOrArray()) { graphQLNode = BuildDynamicSelectOnCollection(result, 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 = Compiler.Util.ExpressionUtil.FindIEnumerable(result.ExpressionResult); if (listExp.Item1 != null) { // yes we can // rebuild the ExpressionResult so we keep any ConstantParameters 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 = BuildDynamicSelectForObjectGraph(query, name, context, result); } } // the query result may be a mutation if (result.IsMutation) { return(new GraphQLMutationNode(result, graphQLNode)); } return(graphQLNode); } catch (EntityGraphQLCompilerException ex) { throw SchemaException.MakeFieldCompileError(query, ex.Message); } }
/// Given a syntax of someField { fields, to, selection, from, object } /// it will build the correct select statement private IGraphQLNode BuildDynamicSelectForObjectGraph(string query, string name, EntityGraphQLParser.EntityQueryContext context, CompiledQueryResult rootField) { var selectWasNull = false; if (selectContext == null) { selectContext = Expression.Parameter(schemaProvider.ContextType); selectWasNull = true; } if (schemaProvider.TypeHasField(selectContext.Type.Name, name, new string[0])) { name = schemaProvider.GetActualFieldName(selectContext.Type.Name, name); } try { var exp = (Expression)rootField.ExpressionResult; var oldContext = selectContext; var rootFieldParam = Expression.Parameter(exp.Type); selectContext = rootField.IsMutation ? rootFieldParam : exp; // visit child fields. Will be field or entityQueries again var fieldExpressions = context.fields.children.Select(c => Visit(c)).Where(n => n != null).ToList(); var graphQLNode = new GraphQLNode(schemaProvider, fragments, name, null, (ExpressionResult)selectContext, (rootField.IsMutation ? new ParameterExpression[] { rootFieldParam } : rootField.ContextParams.ToArray()), fieldExpressions, null); if (rootField != null && rootField.ConstantParameters != null) { graphQLNode.AddConstantParameters(rootField.ConstantParameters); } selectContext = oldContext; if (selectWasNull) { selectContext = null; } return(graphQLNode); } catch (EntityGraphQLCompilerException ex) { throw SchemaException.MakeFieldCompileError(query, ex.Message); } }
public object Execute(params object[] args) { var allArgs = new List <object>(args); // run the mutation to get the context for the query select var mutation = (MutationResult)this.result.ExpressionResult; var result = mutation.Execute(args); if (typeof(LambdaExpression).IsAssignableFrom(result.GetType())) { var mutationLambda = (LambdaExpression)result; var mutationContextParam = mutationLambda.Parameters.First(); var mutationExpression = mutationLambda.Body; // this willtypically be similar to // db => db.Entity.Where(filter) or db => db.Entity.First(filter) // i.e. they'll be returning a list of items or a specific item // We want to take the field selection from the GraphQL query and add a LINQ Select() onto the expression // In the case of a First() we need to insert that select before the first // This is all to have 1 nice expression that can work with ORMs (like EF) // E.g we want db => db.Entity.Select(e => new {name = e.Name, ...}).First(filter) // we dot not want db => new {name = db.Entity.First(filter).Name, ...}) var selectParam = graphQLNode.Parameters.First(); if (!mutationLambda.ReturnType.IsEnumerableOrArray() && mutationExpression.NodeType == ExpressionType.Call) { var call = (MethodCallExpression)mutationExpression; if (call.Method.Name == "First" || call.Method.Name == "FirstOrDefault" || call.Method.Name == "Last" || call.Method.Name == "LastOrDefault") { var baseExp = call.Arguments.First(); if (call.Arguments.Count == 2) { // move the fitler to a Where call var filter = call.Arguments.ElementAt(1); baseExp = ExpressionUtil.MakeExpressionCall(new [] { typeof(Queryable), typeof(Enumerable) }, "Where", new Type[] { selectParam.Type }, baseExp, filter); } // build select var selectExp = ExpressionUtil.MakeExpressionCall(new [] { typeof(Queryable), typeof(Enumerable) }, "Select", new Type[] { selectParam.Type, graphQLNode.GetNodeExpression().Type }, baseExp, Expression.Lambda(graphQLNode.GetNodeExpression(), selectParam)); // add First/Last back var firstExp = ExpressionUtil.MakeExpressionCall(new [] { typeof(Queryable), typeof(Enumerable) }, call.Method.Name, new Type[] { selectExp.Type.GetGenericArguments()[0] }, selectExp); // we're done graphQLNode.SetNodeExpression((ExpressionResult)firstExp); } else { throw new QueryException($"Mutation {Name} has invalid return type of {result.GetType()}. Please return Expression<Func<TConext, TEntity>> or Expression<Func<TConext, IEnumerable<TEntity>>>"); } } else { var exp = Expression.Call(typeof(Queryable), "Select", new Type[] { selectParam.Type, graphQLNode.GetNodeExpression().Type }, mutationExpression, Expression.Lambda(graphQLNode.GetNodeExpression(), selectParam)); graphQLNode.SetNodeExpression((ExpressionResult)exp); } // make sure we use the right parameter graphQLNode.Parameters[0] = mutationContextParam; result = graphQLNode.Execute(args[0]); return(result); } // run the query select result = graphQLNode.Execute(result); return(result); }