/// <summary> /// Initializes a new instance of the <see cref="QueryPlanCacheAddLogEntry" /> class. /// </summary> /// <param name="queryHash">The query hash.</param> /// <param name="queryPlan">The query plan.</param> public QueryPlanCacheAddLogEntry(string queryHash, IGraphQueryPlan queryPlan) : base(LogEventIds.QueryCacheAdd) { this.QueryPlanHashCode = queryHash; _schemaTypeShortName = queryPlan.SchemaType.FriendlyName(); this.SchemaTypeName = queryPlan.SchemaType.FriendlyName(true); this.QueryPlanId = queryPlan.Id; }
/// <summary> /// Initializes a new instance of the <see cref="QueryPlanGeneratedLogEntry" /> class. /// </summary> /// <param name="queryPlan">The query plan.</param> public QueryPlanGeneratedLogEntry(IGraphQueryPlan queryPlan) : base(LogEventIds.QueryPlanGenerationCompleted) { this.SchemaTypeName = queryPlan.SchemaType.FriendlyName(true); this.QueryPlanIsValid = queryPlan.IsValid; this.QueryOperationCount = queryPlan.Operations.Count; this.QueryPlanEstimatedComplexity = queryPlan.EstimatedComplexity; this.QueryPlanMaxDepth = queryPlan.MaxDepth; this.QueryPlanId = queryPlan.Id; }
/// <inheritdoc /> public virtual void QueryPlanGenerated(IGraphQueryPlan queryPlan) { if (!this.IsEnabled(LogLevel.Trace)) { return; } var entry = new QueryPlanGeneratedLogEntry(queryPlan); this.LogEvent(LogLevel.Trace, entry); }
/// <inheritdoc /> public virtual void QueryPlanCached(string queryHash, IGraphQueryPlan queryPlan) { if (!this.IsEnabled(LogLevel.Debug)) { return; } var entry = new QueryPlanCacheAddLogEntry(queryHash, queryPlan); this.LogEvent(LogLevel.Debug, entry); }
/// <summary> /// Inspects the query plan's estimated execution metrics against the configured values for the target schema and should a violation /// occur, a message is recorded to the plan and it is abandoned. /// </summary> /// <param name="plan">The plan.</param> protected void InspectQueryPlanComplexity(IGraphQueryPlan plan) { var maxComplexity = _schema.Configuration?.ExecutionOptions?.MaxQueryComplexity; if (maxComplexity.HasValue && plan.EstimatedComplexity > maxComplexity.Value) { plan.Messages.Critical( $"The generated query plan has an estimated complexity score of {plan.EstimatedComplexity} but this schema has been configured to only accept " + $"queries with a maximum estimated complexity of {maxComplexity.Value}. A high complexity value " + "usually indicates a large number of data store operations and compounding field executions such as fields that yield lists with child fields that also yield lists. " + "Adjust your query and try again.", Constants.ErrorCodes.REQUEST_ABORTED); } }
/// <summary> /// Caches the plan instance for later retrieval. /// </summary> /// <param name="key">The unique hash for the plan of a given schema.</param> /// <param name="plan">The plan to cache.</param> /// <param name="absoluteExpiration">The absolute date, in UTC-0 time, on which the plan will expire and be /// ejected from the cache. (may not be supported by all cache implementations).</param> /// <param name="slidingExpiration">A sliding expiration such that if the plan is not retreived within this timeframe /// the plan will be evicted from the cache (may not be supported by all cache implementations).</param> /// <returns><c>true</c> if the plan was successfully cached, <c>false</c> otherwise.</returns> public Task <bool> TryCachePlanAsync(string key, IGraphQueryPlan plan, DateTimeOffset?absoluteExpiration = null, TimeSpan?slidingExpiration = null) { var policy = new CacheItemPolicy(); if (absoluteExpiration.HasValue) { policy.AbsoluteExpiration = absoluteExpiration.Value; } else if (slidingExpiration.HasValue) { policy.SlidingExpiration = slidingExpiration.Value; } else { policy.SlidingExpiration = this.DefaultSlidingExpiration; } _cachedPlans.Set(key, plan, policy); return(true.AsCompletedTask()); }
/// <summary> /// Attempts to retrieve a query plan from the cache for the given schema if it sexists. /// </summary> /// <param name="key">The unique hash for the plan of a given schema.</param> /// <param name="plan">The plan that was retrieved or null if it was not found.</param> /// <returns><c>true</c> if the plan was successfully retrieved; otherwise, <c>false</c>.</returns> /// where TSchema : class, ISchema public Task <bool> TryGetPlanAsync(string key, out IGraphQueryPlan plan) { plan = _cachedPlans.Get(key) as IGraphQueryPlan; return((plan != null).AsCompletedTask()); }
/// <summary> /// Initializes a new instance of the <see cref="ClientSubscription{TSchema}" /> class. /// </summary> /// <param name="clientProxy">The client proxy that will own this subscription.</param> /// <param name="originalQuerydata">The original querydata that generated this subscription.</param> /// <param name="queryPlan">The query plan.</param> /// <param name="selectedOperation">The selected operation from the query plan /// from which to generate the subscription.</param> /// <param name="subscriptionid">A unique id to assign to this subscription. A guid id /// will be generated if this value is not supplied.</param> public ClientSubscription( ISubscriptionClientProxy clientProxy, GraphQueryData originalQuerydata, IGraphQueryPlan queryPlan, IGraphFieldExecutableOperation selectedOperation, string subscriptionid = null) { this.Client = Validation.ThrowIfNullOrReturn(clientProxy, nameof(clientProxy)); this.QueryData = Validation.ThrowIfNullOrReturn(originalQuerydata, nameof(originalQuerydata)); this.QueryOperation = Validation.ThrowIfNullOrReturn(selectedOperation, nameof(selectedOperation)); this.QueryPlan = Validation.ThrowIfNullOrReturn(queryPlan, nameof(queryPlan)); this.Messages = this.QueryPlan?.Messages ?? new GraphMessageCollection(); this.Id = string.IsNullOrWhiteSpace(subscriptionid) ? Guid.NewGuid().ToString("N") : subscriptionid.Trim(); this.IsValid = false; // parsing the query plan will garuntee that if the document contains // a subscription that it contains only one operation and // that a top level field will be a subscription-ready field. // // However, ensure that the operation that will be executed // does in fact represent a subscription being harnesssed if (this.QueryOperation.OperationType != GraphCollection.Subscription) { this.Messages.Critical( $"The chosen operation is not a subscription operation.", Constants.ErrorCodes.BAD_REQUEST); return; } var currentContext = this.QueryOperation.FieldContexts[0]; // find the first non-virtual field referenced, it should be a controller // its garunteed to exist via the document generation rule engine // but it could be deep, walk down the subscirption tree to find it while (currentContext?.Field != null) { // when pointing at a subscription field we're done if (!currentContext.Field.IsVirtual) { this.Field = currentContext.Field as ISubscriptionGraphField; break; } currentContext = currentContext?.ChildContexts.Count == 1 ? currentContext.ChildContexts?[0] : null; } // just in case it wasn't found... // this is theoretically not possible but just in case // the user swaps out some DI components incorrectly or by mistake... if (this.Field == null) { this.Messages.Add( GraphMessageSeverity.Critical, "An eventable field could not found in the subscription operation. Ensure you include a field declared " + "as a subscription field.", Constants.ErrorCodes.BAD_REQUEST); } this.IsValid = this.Messages.IsSucessful && this.QueryOperation != null && this.Field != null; }