diff --git a/MarkMpn.Sql4Cds.Engine/Ado/Sql4CdsDataReader.cs b/MarkMpn.Sql4Cds.Engine/Ado/Sql4CdsDataReader.cs index c5b5bc47..1155a8eb 100644 --- a/MarkMpn.Sql4Cds.Engine/Ado/Sql4CdsDataReader.cs +++ b/MarkMpn.Sql4Cds.Engine/Ado/Sql4CdsDataReader.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Linq.Expressions; using System.Text; +using System.Xml; using MarkMpn.Sql4Cds.Engine.ExecutionPlan; using Microsoft.SqlServer.TransactSql.ScriptDom; using Microsoft.Xrm.Sdk; @@ -62,13 +63,15 @@ public Sql4CdsDataReader(Sql4CdsCommand command, IQueryExecutionOptions options, private bool Execute(Dictionary parameterTypes, Dictionary parameterValues) { - var context = new NodeExecutionContext(_connection.DataSources, _options, parameterTypes, parameterValues); + IRootExecutionPlanNode logNode = null; + var context = new NodeExecutionContext(_connection.DataSources, _options, parameterTypes, parameterValues, msg => _connection.OnInfoMessage(logNode, msg)); try { while (_instructionPointer < _command.Plan.Length && !_options.CancellationToken.IsCancellationRequested) { var node = _command.Plan[_instructionPointer]; + logNode = node; if (node is IDataReaderExecutionPlanNode dataSetNode) { @@ -90,10 +93,7 @@ private bool Execute(Dictionary parameterTypes, Dicti else if (node is IDmlQueryExecutionPlanNode dmlNode) { dmlNode = (IDmlQueryExecutionPlanNode)dmlNode.Clone(); - var msg = dmlNode.Execute(context, out var recordsAffected); - - if (!String.IsNullOrEmpty(msg)) - _connection.OnInfoMessage(dmlNode, msg); + dmlNode.Execute(context, out var recordsAffected); _command.OnStatementCompleted(dmlNode, recordsAffected); diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/AssignVariablesNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/AssignVariablesNode.cs index 9aea2a30..3b726b9d 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/AssignVariablesNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/AssignVariablesNode.cs @@ -40,7 +40,7 @@ class AssignVariablesNode : BaseDmlNode [Browsable(false)] public override bool ContinueOnError { get; set; } - public override string Execute(NodeExecutionContext context, out int recordsAffected) + public override void Execute(NodeExecutionContext context, out int recordsAffected) { _executionCount++; @@ -63,7 +63,6 @@ public override string Execute(NodeExecutionContext context, out int recordsAffe } recordsAffected = -1; - return null; } /// diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseDmlNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseDmlNode.cs index cfd4fc9e..d5271303 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseDmlNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseDmlNode.cs @@ -129,8 +129,7 @@ public void Dispose() /// /// The context in which the node is being executed /// The number of records that were affected by the query - /// A log message to display - public abstract string Execute(NodeExecutionContext context, out int recordsAffected); + public abstract void Execute(NodeExecutionContext context, out int recordsAffected); /// /// Indicates if some errors returned by the server can be silently ignored @@ -539,8 +538,8 @@ protected class OperationNames /// The metadata of the entity that will be affected /// A function to generate a DML request from a data source entity /// The constant strings to use in log messages - /// The final log message - protected string ExecuteDmlOperation(DataSource dataSource, IQueryExecutionOptions options, List entities, EntityMetadata meta, Func requestGenerator, OperationNames operationNames, out int recordsAffected, IDictionary parameterValues, Action responseHandler = null) + /// A callback function to be executed when a log message is generated + protected void ExecuteDmlOperation(DataSource dataSource, IQueryExecutionOptions options, List entities, EntityMetadata meta, Func requestGenerator, OperationNames operationNames, NodeExecutionContext context, out int recordsAffected, Action responseHandler = null) { var inProgressCount = 0; var count = 0; @@ -618,13 +617,15 @@ protected string ExecuteDmlOperation(DataSource dataSource, IQueryExecutionOptio } catch (FaultException ex) { - if (FilterErrors(ex.Detail)) + if (FilterErrors(context, request, ex.Detail)) { if (ContinueOnError) fault = fault ?? ex.Detail; else throw; } + + Interlocked.Increment(ref errorCount); } } else @@ -650,7 +651,7 @@ protected string ExecuteDmlOperation(DataSource dataSource, IQueryExecutionOptio if (threadLocalState.EMR.Requests.Count == BatchSize) { - ProcessBatch(threadLocalState.EMR, threadCount, ref count, ref inProgressCount, ref errorCount, entities, operationNames, meta, options, dataSource, threadLocalState.Service, responseHandler, ref fault); + ProcessBatch(threadLocalState.EMR, threadCount, ref count, ref inProgressCount, ref errorCount, entities, operationNames, meta, options, dataSource, threadLocalState.Service, context, responseHandler, ref fault); threadLocalState = new { threadLocalState.Service, EMR = default(ExecuteMultipleRequest) }; } @@ -661,7 +662,7 @@ protected string ExecuteDmlOperation(DataSource dataSource, IQueryExecutionOptio (threadLocalState) => { if (threadLocalState.EMR != null) - ProcessBatch(threadLocalState.EMR, threadCount, ref count, ref inProgressCount, ref errorCount, entities, operationNames, meta, options, dataSource, threadLocalState.Service, responseHandler, ref fault); + ProcessBatch(threadLocalState.EMR, threadCount, ref count, ref inProgressCount, ref errorCount, entities, operationNames, meta, options, dataSource, threadLocalState.Service, context, responseHandler, ref fault); Interlocked.Decrement(ref threadCount); @@ -692,8 +693,8 @@ protected string ExecuteDmlOperation(DataSource dataSource, IQueryExecutionOptio } recordsAffected = count; - parameterValues["@@ROWCOUNT"] = (SqlInt32)count; - return $"{count:N0} {GetDisplayName(count, meta)} {operationNames.CompletedLowercase}"; + context.ParameterValues["@@ROWCOUNT"] = (SqlInt32)count; + context.Log($"{count:N0} {GetDisplayName(count, meta)} {operationNames.CompletedLowercase}"); } protected class BulkApiErrorDetail @@ -703,7 +704,7 @@ protected class BulkApiErrorDetail public int StatusCode { get; set; } } - private void ProcessBatch(ExecuteMultipleRequest req, int threadCount, ref int count, ref int inProgressCount, ref int errorCount, List entities, OperationNames operationNames, EntityMetadata meta, IQueryExecutionOptions options, DataSource dataSource, IOrganizationService org, Action responseHandler, ref OrganizationServiceFault fault) + private void ProcessBatch(ExecuteMultipleRequest req, int threadCount, ref int count, ref int inProgressCount, ref int errorCount, List entities, OperationNames operationNames, EntityMetadata meta, IQueryExecutionOptions options, DataSource dataSource, IOrganizationService org, NodeExecutionContext context, Action responseHandler, ref OrganizationServiceFault fault) { var newCount = Interlocked.Add(ref inProgressCount, req.Requests.Count); var progress = (double)newCount / entities.Count; @@ -727,7 +728,7 @@ private void ProcessBatch(ExecuteMultipleRequest req, int threadCount, ref int c Interlocked.Add(ref count, req.Requests.Count - errorResponses.Count); Interlocked.Add(ref errorCount, errorResponses.Count); - var error = errorResponses.FirstOrDefault(item => FilterErrors(item.Fault)); + var error = errorResponses.FirstOrDefault(item => FilterErrors(context, req.Requests[item.RequestIndex], item.Fault)); if (error != null) { @@ -738,7 +739,7 @@ private void ProcessBatch(ExecuteMultipleRequest req, int threadCount, ref int c } } - protected virtual bool FilterErrors(OrganizationServiceFault fault) + protected virtual bool FilterErrors(NodeExecutionContext context, OrganizationRequest request, OrganizationServiceFault fault) { return true; } diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BulkDeleteJobNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BulkDeleteJobNode.cs index ff147d47..a46a6917 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BulkDeleteJobNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BulkDeleteJobNode.cs @@ -43,7 +43,7 @@ public override void AddRequiredColumns(NodeCompilationContext context, IList hints) diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/DeleteNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/DeleteNode.cs index 17980768..9c30c85b 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/DeleteNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/DeleteNode.cs @@ -106,7 +106,7 @@ protected override void RenameSourceColumns(IDictionary columnRe SecondaryIdSource = secondaryIdSourceRenamed; } - public override string Execute(NodeExecutionContext context, out int recordsAffected) + public override void Execute(NodeExecutionContext context, out int recordsAffected) { _executionCount++; @@ -172,7 +172,7 @@ public override string Execute(NodeExecutionContext context, out int recordsAffe using (_timer.Run()) { - return ExecuteDmlOperation( + ExecuteDmlOperation( dataSource, context.Options, entities, @@ -184,8 +184,8 @@ public override string Execute(NodeExecutionContext context, out int recordsAffe InProgressLowercase = "deleting", CompletedLowercase = "deleted" }, - out recordsAffected, - context.ParameterValues); + context, + out recordsAffected); } } catch (QueryExecutionException ex) @@ -250,7 +250,7 @@ private OrganizationRequest CreateDeleteRequest(EntityMetadata meta, Entity enti return req; } - protected override bool FilterErrors(OrganizationServiceFault fault) + protected override bool FilterErrors(NodeExecutionContext context, OrganizationRequest request, OrganizationServiceFault fault) { // Ignore errors trying to delete records that don't exist - record may have been deleted by another // process in parallel. diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ExecuteAsNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ExecuteAsNode.cs index 9eebea94..0c99b53c 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ExecuteAsNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ExecuteAsNode.cs @@ -51,7 +51,7 @@ public override void AddRequiredColumns(NodeCompilationContext context, IList hints) diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs index 1738b130..59c69c25 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs @@ -1185,7 +1185,7 @@ private void AddSchemaAttribute(DataSource dataSource, ColumnList schema, Dictio if (attrMetadata is LookupAttributeMetadata lookup) { - AddSchemaAttribute(schema, aliases, fullName + "name", attrMetadata.LogicalName + "name", DataTypeHelpers.NVarChar(lookup.Targets == null || lookup.Targets.Length == 0 ? 100 : lookup.Targets.Select(e => ((StringAttributeMetadata)dataSource.Metadata[e].Attributes.SingleOrDefault(a => a.LogicalName == dataSource.Metadata[e].PrimaryNameAttribute))?.MaxLength ?? 100).Max(), dataSource.DefaultCollation, CollationLabel.Implicit), notNull); + AddSchemaAttribute(schema, aliases, fullName + "name", attrMetadata.LogicalName + "name", DataTypeHelpers.NVarChar(lookup.Targets == null || lookup.Targets.Length == 0 ? 100 : lookup.Targets.Select(e => (dataSource.Metadata[e].Attributes.SingleOrDefault(a => a.LogicalName == dataSource.Metadata[e].PrimaryNameAttribute) as StringAttributeMetadata)?.MaxLength ?? 100).Max(), dataSource.DefaultCollation, CollationLabel.Implicit), notNull); if (lookup.Targets?.Length != 1 && lookup.AttributeType != AttributeTypeCode.PartyList) AddSchemaAttribute(schema, aliases, fullName + "type", attrMetadata.LogicalName + "type", DataTypeHelpers.NVarChar(MetadataExtensions.EntityLogicalNameMaxLength, dataSource.DefaultCollation, CollationLabel.Implicit), notNull); diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FilterNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FilterNode.cs index efc1c887..976e39f5 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FilterNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FilterNode.cs @@ -1511,7 +1511,7 @@ protected bool TranslateMetadataCriteria(NodeCompilationContext context, Boolean relationshipFilter = null; var expressionCompilationContext = new ExpressionCompilationContext(context, null, null); - var expressionExecutionContext = new ExpressionExecutionContext(new NodeExecutionContext(context.DataSources, context.Options, context.ParameterTypes, null)); + var expressionExecutionContext = new ExpressionExecutionContext(new NodeExecutionContext(context.DataSources, context.Options, context.ParameterTypes, null, null)); if (criteria is BooleanBinaryExpression binary) { diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/IDmlQueryExecutionPlanNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/IDmlQueryExecutionPlanNode.cs index d865ab29..8e254b73 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/IDmlQueryExecutionPlanNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/IDmlQueryExecutionPlanNode.cs @@ -18,7 +18,6 @@ internal interface IDmlQueryExecutionPlanNode : IRootExecutionPlanNodeInternal /// /// The context in which the node is being executed /// The number of records that were affected by the query - /// A status message for the results of the query - string Execute(NodeExecutionContext context, out int recordsAffected); + void Execute(NodeExecutionContext context, out int recordsAffected); } } diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/InsertNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/InsertNode.cs index 914232f4..e4cfc7c8 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/InsertNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/InsertNode.cs @@ -65,6 +65,7 @@ public override void AddRequiredColumns(NodeCompilationContext context, IList) ((r) => SetIdentity(r, context.ParameterValues)) ); } diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/NestedLoopNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/NestedLoopNode.cs index c28072d8..1e58d7c2 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/NestedLoopNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/NestedLoopNode.cs @@ -81,7 +81,7 @@ protected override IEnumerable ExecuteInternal(NodeExecutionContext cont var hasRight = false; - foreach (var right in RightSource.Execute(new NodeExecutionContext(context.DataSources, context.Options, innerParameterTypes, innerParameters))) + foreach (var right in RightSource.Execute(new NodeExecutionContext(context.DataSources, context.Options, innerParameterTypes, innerParameters, context.Log))) { if (rightSchema == null) { diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/PartitionedAggregateNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/PartitionedAggregateNode.cs index fa9857e4..5c82120c 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/PartitionedAggregateNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/PartitionedAggregateNode.cs @@ -202,7 +202,7 @@ protected override IEnumerable ExecuteInternal(NodeExecutionContext cont partitionParameterValues[kvp.Key] = kvp.Value; } - var partitionContext = new NodeExecutionContext(context.DataSources, context.Options, context.ParameterTypes, partitionParameterValues); + var partitionContext = new NodeExecutionContext(context.DataSources, context.Options, context.ParameterTypes, partitionParameterValues, context.Log); return new { Context = partitionContext, Fetch = fetch }; }, diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/PrintNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/PrintNode.cs index 6b7f3c50..45eebaf2 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/PrintNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/PrintNode.cs @@ -47,7 +47,7 @@ public object Clone() }; } - public string Execute(NodeExecutionContext context, out int recordsAffected) + public void Execute(NodeExecutionContext context, out int recordsAffected) { _executionCount++; recordsAffected = -1; @@ -56,10 +56,8 @@ public string Execute(NodeExecutionContext context, out int recordsAffected) { var value = (SqlString)_expression(new ExpressionExecutionContext(context)); - if (value.IsNull) - return null; - - return value.Value; + if (!value.IsNull) + context.Log(value.Value); } } diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/RevertNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/RevertNode.cs index 5029ccb1..aa5b6a66 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/RevertNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/RevertNode.cs @@ -52,7 +52,7 @@ public override void AddRequiredColumns(NodeCompilationContext context, IList columnRe } } - protected override bool FilterErrors(OrganizationServiceFault fault) + protected override bool FilterErrors(NodeExecutionContext context, OrganizationRequest request, OrganizationServiceFault fault) { - // Ignore errors trying to delete records that don't exist - record may have been deleted by another + // Ignore errors trying to update records that don't exist - record may have been deleted by another // process in parallel. return fault.ErrorCode != -2147220969 && fault.ErrorCode != -2147185406 && fault.ErrorCode != -2147220969 && fault.ErrorCode != 404; } diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/WaitForNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/WaitForNode.cs index f6bcb962..6f297c6e 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/WaitForNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/WaitForNode.cs @@ -55,7 +55,7 @@ public override void AddRequiredColumns(NodeCompilationContext context, IListThe options that the query is being executed with /// The names and types of the parameters that are available to the query /// The current value of each parameter + /// A callback function to log messages public NodeExecutionContext( IDictionary dataSources, IQueryExecutionOptions options, IDictionary parameterTypes, - IDictionary parameterValues) + IDictionary parameterValues, + Action log) : base(dataSources, options, parameterTypes) { ParameterValues = parameterValues; + Log = log ?? (msg => { }); } /// /// Returns the current value of each parameter /// public IDictionary ParameterValues { get; } + + public Action Log { get; } } /// @@ -179,8 +184,9 @@ public ExpressionExecutionContext( IQueryExecutionOptions options, IDictionary parameterTypes, IDictionary parameterValues, + Action log, Entity entity) - : base(dataSources, options, parameterTypes, parameterValues) + : base(dataSources, options, parameterTypes, parameterValues, log) { Entity = entity; } @@ -195,7 +201,7 @@ public ExpressionExecutionContext( /// representing each row as it is processed. /// public ExpressionExecutionContext(NodeExecutionContext nodeContext) - : base(nodeContext.DataSources, nodeContext.Options, nodeContext.ParameterTypes, nodeContext.ParameterValues) + : base(nodeContext.DataSources, nodeContext.Options, nodeContext.ParameterTypes, nodeContext.ParameterValues, nodeContext.Log) { Entity = null; } @@ -213,7 +219,7 @@ public ExpressionExecutionContext(NodeExecutionContext nodeContext) /// representing each row as it is processed. /// public ExpressionExecutionContext(ExpressionCompilationContext compilationContext) - : base(compilationContext.DataSources, compilationContext.Options, compilationContext.ParameterTypes, null) + : base(compilationContext.DataSources, compilationContext.Options, compilationContext.ParameterTypes, null, null) { Entity = null; }