diff --git a/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanNodeTests.cs b/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanNodeTests.cs index 992c5902..3db165ef 100644 --- a/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanNodeTests.cs +++ b/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanNodeTests.cs @@ -37,7 +37,7 @@ public void ConstantScanTest() Alias = "test" }; - var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)).ToArray(); + var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)).ToArray(); Assert.AreEqual(1, results.Length); Assert.AreEqual("Mark", ((SqlString)results[0]["test.firstname"]).Value); @@ -84,7 +84,7 @@ public void FilterNodeTest() } }; - var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)).ToArray(); + var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)).ToArray(); Assert.AreEqual(1, results.Length); Assert.AreEqual("Mark", ((SqlString)results[0]["test.firstname"]).Value); @@ -161,7 +161,7 @@ public void MergeJoinInnerTest() JoinType = QualifiedJoinType.Inner }; - var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)).ToArray(); + var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)).ToArray(); Assert.AreEqual(2, results.Length); Assert.AreEqual("Mark", ((SqlString)results[0]["f.firstname"]).Value); @@ -241,7 +241,7 @@ public void MergeJoinLeftOuterTest() JoinType = QualifiedJoinType.LeftOuter }; - var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)).ToArray(); + var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)).ToArray(); Assert.AreEqual(3, results.Length); Assert.AreEqual("Mark", ((SqlString)results[0]["f.firstname"]).Value); @@ -323,7 +323,7 @@ public void MergeJoinRightOuterTest() JoinType = QualifiedJoinType.RightOuter }; - var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)).ToArray(); + var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)).ToArray(); Assert.AreEqual(3, results.Length); Assert.AreEqual("Mark", ((SqlString)results[0]["f.firstname"]).Value); @@ -362,7 +362,7 @@ public void AssertionTest() ErrorMessage = "Only Mark is allowed" }; - var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)).GetEnumerator(); + var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)).GetEnumerator(); Assert.IsTrue(results.MoveNext()); Assert.AreEqual("Mark", results.Current.GetAttributeValue("test.name").Value); @@ -420,7 +420,7 @@ public void ComputeScalarTest() } }; - var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)) + var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)) .Select(e => e.GetAttributeValue("mul").Value) .ToArray(); @@ -462,7 +462,7 @@ public void DistinctTest() } }; - var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)) + var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)) .Select(e => e.GetAttributeValue("test.value1").Value) .ToArray(); @@ -504,7 +504,7 @@ public void DistinctCaseInsensitiveTest() } }; - var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)) + var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)) .Select(e => e.GetAttributeValue("test.value1").Value) .ToArray(); @@ -562,7 +562,7 @@ public void SortNodeTest() } }; - var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)) + var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)) .Select(e => e.GetAttributeValue("test.expectedorder").Value) .ToArray(); @@ -627,7 +627,7 @@ public void SortNodePresortedTest() } }; - var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)) + var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)) .Select(e => e.GetAttributeValue("test.expectedorder").Value) .ToArray(); @@ -659,11 +659,11 @@ public void TableSpoolTest() var spool = new TableSpoolNode { Source = source }; - var results1 = spool.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)) + var results1 = spool.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)) .Select(e => e.GetAttributeValue("test.value1").Value) .ToArray(); - var results2 = spool.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)) + var results2 = spool.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)) .Select(e => e.GetAttributeValue("test.value1").Value) .ToArray(); @@ -708,7 +708,7 @@ public void CaseInsenstiveHashMatchAggregateNodeTest() } }; - var results = spool.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)) + var results = spool.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)) .Select(e => new { Name = e.GetAttributeValue("src.value1").Value, Count = e.GetAttributeValue("count").Value }) .ToArray(); @@ -749,7 +749,7 @@ public void SqlTransformSingleResult() public void AggregateInitialTest() { var aggregate = CreateAggregateTest(); - var result = aggregate.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)).Single(); + var result = aggregate.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)).Single(); Assert.AreEqual(SqlInt32.Null, result["min"]); Assert.AreEqual(SqlInt32.Null, result["max"]); @@ -767,7 +767,7 @@ public void AggregateInitialTest() public void AggregateSingleValueTest() { var aggregate = CreateAggregateTest(1); - var result = aggregate.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)).Single(); + var result = aggregate.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)).Single(); Assert.AreEqual((SqlInt32)1, result["min"]); Assert.AreEqual((SqlInt32)1, result["max"]); @@ -785,7 +785,7 @@ public void AggregateSingleValueTest() public void AggregateTwoEqualValuesTest() { var aggregate = CreateAggregateTest(1, 1); - var result = aggregate.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)).Single(); + var result = aggregate.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)).Single(); Assert.AreEqual((SqlInt32)1, result["min"]); Assert.AreEqual((SqlInt32)1, result["max"]); @@ -803,7 +803,7 @@ public void AggregateTwoEqualValuesTest() public void AggregateMultipleValuesTest() { var aggregate = CreateAggregateTest(1, 3, 1, 1); - var result = aggregate.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)).Single(); + var result = aggregate.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)).Single(); Assert.AreEqual((SqlInt32)1, result["min"]); Assert.AreEqual((SqlInt32)3, result["max"]); @@ -962,7 +962,7 @@ public void NestedLoopJoinInnerTest() } }; - var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)).ToArray(); + var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)).ToArray(); Assert.AreEqual(2, results.Length); Assert.AreEqual("Mark", ((SqlString)results[0]["f.firstname"]).Value); @@ -1034,7 +1034,7 @@ public void NestedLoopJoinLeftOuterTest() } }; - var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null)).ToArray(); + var results = node.Execute(new NodeExecutionContext(_localDataSource, new StubOptions(), null, null, null)).ToArray(); Assert.AreEqual(3, results.Length); Assert.AreEqual("Mark", ((SqlString)results[0]["f.firstname"]).Value); diff --git a/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs b/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs index c5b6fcf2..8bed3114 100644 --- a/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs +++ b/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs @@ -3273,7 +3273,7 @@ GROUP BY firstname }, }; - var result = select.Execute(new NodeExecutionContext(_localDataSource, this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var result = select.Execute(new NodeExecutionContext(_localDataSource, this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(result); @@ -3722,7 +3722,7 @@ DECLARE @test int { if (plan is IDataReaderExecutionPlanNode selectQuery) { - var results = selectQuery.Execute(new NodeExecutionContext(_dataSources, this, parameterTypes, parameterValues), CommandBehavior.Default); + var results = selectQuery.Execute(new NodeExecutionContext(_dataSources, this, parameterTypes, parameterValues, null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(results); @@ -3732,7 +3732,7 @@ DECLARE @test int } else if (plan is IDmlQueryExecutionPlanNode dmlQuery) { - dmlQuery.Execute(new NodeExecutionContext(_dataSources, this, parameterTypes, parameterValues), out _); + dmlQuery.Execute(new NodeExecutionContext(_dataSources, this, parameterTypes, parameterValues, null), out _); } } } @@ -3776,7 +3776,7 @@ public void SetVariableInDeclaration() { if (plan is IDataReaderExecutionPlanNode selectQuery) { - var results = selectQuery.Execute(new NodeExecutionContext(_dataSources, this, parameterTypes, parameterValues), CommandBehavior.Default); + var results = selectQuery.Execute(new NodeExecutionContext(_dataSources, this, parameterTypes, parameterValues, null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(results); @@ -3786,7 +3786,7 @@ public void SetVariableInDeclaration() } else if (plan is IDmlQueryExecutionPlanNode dmlQuery) { - dmlQuery.Execute(new NodeExecutionContext(_dataSources, this, parameterTypes, parameterValues), out _); + dmlQuery.Execute(new NodeExecutionContext(_dataSources, this, parameterTypes, parameterValues, null), out _); } } } @@ -3834,7 +3834,7 @@ DECLARE @test varchar(3) { if (plan is IDataReaderExecutionPlanNode selectQuery) { - var results = selectQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues), CommandBehavior.Default); + var results = selectQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues, null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(results); @@ -3844,7 +3844,7 @@ DECLARE @test varchar(3) } else if (plan is IDmlQueryExecutionPlanNode dmlQuery) { - dmlQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues), out _); + dmlQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues, null), out _); } } } @@ -3868,7 +3868,7 @@ DECLARE @test varchar(3) { if (plan is IDataReaderExecutionPlanNode selectQuery) { - var results = selectQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues), CommandBehavior.Default); + var results = selectQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues, null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(results); @@ -3878,7 +3878,7 @@ DECLARE @test varchar(3) } else if (plan is IDmlQueryExecutionPlanNode dmlQuery) { - dmlQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues), out _); + dmlQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues, null), out _); } } } @@ -3960,7 +3960,7 @@ DECLARE @test varchar(3) { if (plan is IDataReaderExecutionPlanNode selectQuery) { - var results = selectQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues), CommandBehavior.Default); + var results = selectQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues, null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(results); @@ -3970,7 +3970,7 @@ DECLARE @test varchar(3) } else if (plan is IDmlQueryExecutionPlanNode dmlQuery) { - dmlQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues), out _); + dmlQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues, null), out _); } } } @@ -3994,7 +3994,7 @@ DECLARE @test varchar { if (plan is IDataReaderExecutionPlanNode selectQuery) { - var results = selectQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues), CommandBehavior.Default); + var results = selectQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues, null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(results); @@ -4004,7 +4004,7 @@ DECLARE @test varchar } else if (plan is IDmlQueryExecutionPlanNode dmlQuery) { - dmlQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues), out _); + dmlQuery.Execute(new NodeExecutionContext(_localDataSource, this, parameterTypes, parameterValues, null), out _); } } } @@ -6473,5 +6473,20 @@ public void FoldFilterToCorrectTableAlias() "); } + + [TestMethod] + public void IgnoreDupKeyHint() + { + var planBuilder = new ExecutionPlanBuilder(_localDataSource.Values, this); + + var query = @"INSERT INTO account (accountid, name) VALUES ('{CD503427-E785-40D8-AD0E-FBDF4918D298}', 'Data8') OPTION (USE HINT ('IGNORE_DUP_KEY'))"; + + var plans = planBuilder.Build(query, null, out _); + + Assert.AreEqual(1, plans.Length); + + var insert = AssertNode(plans[0]); + Assert.IsTrue(insert.IgnoreDuplicateKey); + } } } diff --git a/MarkMpn.Sql4Cds.Engine.Tests/Sql2FetchXmlTests.cs b/MarkMpn.Sql4Cds.Engine.Tests/Sql2FetchXmlTests.cs index 16f2d783..f00071ac 100644 --- a/MarkMpn.Sql4Cds.Engine.Tests/Sql2FetchXmlTests.cs +++ b/MarkMpn.Sql4Cds.Engine.Tests/Sql2FetchXmlTests.cs @@ -706,7 +706,7 @@ public void SelectArithmetic() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -754,7 +754,7 @@ public void WhereComparingTwoFields() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -798,7 +798,7 @@ public void WhereComparingExpression() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -839,7 +839,7 @@ public void BackToFrontLikeExpression() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -874,7 +874,7 @@ public void UpdateFieldToField() } }; - ((UpdateNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), out _); + ((UpdateNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), out _); Assert.AreEqual("Carrington", _context.Data["contact"][guid]["firstname"]); } @@ -906,7 +906,7 @@ public void UpdateFieldToExpression() } }; - ((UpdateNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), out _); + ((UpdateNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), out _); Assert.AreEqual("Hello Carrington", _context.Data["contact"][guid]["firstname"]); } @@ -942,7 +942,7 @@ public void UpdateReplace() } }; - ((UpdateNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), out _); + ((UpdateNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), out _); Assert.AreEqual("--CDS--", _context.Data["contact"][guid]["firstname"]); } @@ -973,7 +973,7 @@ public void StringFunctions() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -1013,7 +1013,7 @@ public void SelectExpression() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -1063,7 +1063,7 @@ public void SelectExpressionNullValues() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -1108,7 +1108,7 @@ public void OrderByExpression() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -1153,7 +1153,7 @@ public void OrderByAliasedField() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -1197,7 +1197,7 @@ public void OrderByCalculatedField() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -1241,7 +1241,7 @@ public void OrderByCalculatedFieldByIndex() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -1283,7 +1283,7 @@ public void DateCalculations() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -1368,7 +1368,7 @@ public void CustomFilterAggregateHavingProjectionSortAndTop() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -1431,7 +1431,7 @@ public void FilterCaseInsensitive() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -1488,7 +1488,7 @@ public void GroupCaseInsensitive() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -1541,7 +1541,7 @@ public void AggregateExpressionsWithoutGrouping() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -1596,7 +1596,7 @@ public void AggregateQueryProducesAlternative() } }; - var dataReader = alternativeQuery.Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = alternativeQuery.Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -1650,7 +1650,7 @@ public void GuidEntityReferenceInequality() } }; - var dataReader = select.Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = select.Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -1701,7 +1701,7 @@ public void UpdateGuidToEntityReference() } }; - update.Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), out _); + update.Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), out _); Assert.AreEqual(new EntityReference("contact", contact1), _context.Data["account"][account1].GetAttributeValue("primarycontactid")); Assert.AreEqual(new EntityReference("contact", contact2), _context.Data["account"][account2].GetAttributeValue("primarycontactid")); @@ -1975,7 +1975,7 @@ public void ImplicitTypeConversion() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -2008,7 +2008,7 @@ public void ImplicitTypeConversionComparison() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -2033,7 +2033,7 @@ public void GlobalOptionSet() Assert.AreEqual("name = 'test'", filterNode.Filter.ToSql()); var optionsetNode = (GlobalOptionSetQueryNode)filterNode.Source; - var dataReader = selectNode.Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = selectNode.Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -2055,7 +2055,7 @@ public void EntityDetails() var sortNode = (SortNode)selectNode.Source; var metadataNode = (MetadataQueryNode)sortNode.Source; - var dataReader = selectNode.Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = selectNode.Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -2074,7 +2074,7 @@ public void AttributeDetails() var planBuilder = new ExecutionPlanBuilder(_localDataSource.Values, this); var queries = planBuilder.Build(query, null, out _); - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -2130,7 +2130,7 @@ public void OptionSetNameSelect() CollectionAssert.AreEqual(new[] { "new_optionsetvalue", "new_optionsetvaluename" }, select.ColumnSet.Select(c => c.OutputColumn).ToList()); - var dataReader = select.Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = select.Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -2454,7 +2454,7 @@ public void CharIndex() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -2484,7 +2484,7 @@ public void CastDateTimeToDate() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); @@ -2522,7 +2522,7 @@ public void GroupByPrimaryFunction() } }; - var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary()), CommandBehavior.Default); + var dataReader = ((SelectNode)queries[0]).Execute(new NodeExecutionContext(GetDataSources(_context), this, new Dictionary(), new Dictionary(), null), CommandBehavior.Default); var dataTable = new DataTable(); dataTable.Load(dataReader); diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/InsertNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/InsertNode.cs index e4cfc7c8..d51c1e43 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/InsertNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/InsertNode.cs @@ -5,6 +5,7 @@ using System.ServiceModel; using System.Threading; using System.Threading.Tasks; +using System.Xml; using Microsoft.Crm.Sdk.Messages; using Microsoft.SqlServer.TransactSql.ScriptDom; using Microsoft.Xrm.Sdk; @@ -53,6 +54,11 @@ class InsertNode : BaseDmlNode [Category("Insert")] public override bool ContinueOnError { get; set; } + [Category("Insert")] + [DisplayName("Ignore Duplicate Key")] + [Description("Ignores any duplicate key errors encountered. Errors will be logged but the query will complete.")] + public bool IgnoreDuplicateKey { get; set; } + public override void AddRequiredColumns(NodeCompilationContext context, IList requiredColumns) { foreach (var col in ColumnMappings.Values) @@ -64,7 +70,26 @@ public override void AddRequiredColumns(NodeCompilationContext context, IList hints) + { + IgnoreDuplicateKey = GetIgnoreDuplicateKey(context, hints); + + return base.FoldQuery(context, hints); + } + + private bool GetIgnoreDuplicateKey(NodeCompilationContext context, IList queryHints) + { + if (queryHints == null) + return false; + + var ignoreDupKey = queryHints + .OfType() + .Where(hint => hint.Hints.Any(s => s.Value.Equals("IGNORE_DUP_KEY", StringComparison.OrdinalIgnoreCase))) + .Any(); + + return ignoreDupKey; + } + public override void Execute(NodeExecutionContext context, out int recordsAffected) { _executionCount++; @@ -200,6 +225,43 @@ private OrganizationRequest CreateInsertRequest(EntityMetadata meta, Entity enti return new CreateRequest { Target = insert }; } + protected override bool FilterErrors(NodeExecutionContext context, OrganizationRequest request, OrganizationServiceFault fault) + { + if (IgnoreDuplicateKey) + { + if (fault.ErrorCode == -2147220937 || fault.ErrorCode == -2147088238 || fault.ErrorCode == 409) + { + var logMessage = "Ignoring duplicate key error"; + + if (fault.ErrorCode == -2147088238) + { + // Duplicate alternate key. The duplicated values are available in the fault details + if (fault.ErrorDetails.TryGetValue("DuplicateAttributes", out var value) && + value is string duplicateAttributes) + { + var xml = new XmlDocument(); + xml.LoadXml(duplicateAttributes); + + logMessage += $". The duplicate values were ({String.Join(", ", xml.SelectNodes("/DuplicateAttributes/*").OfType().Select(attr => attr.InnerText))})"; + } + } + else + { + // Duplicate primary key. + if (request is AssociateRequest associate) + logMessage += $". The duplicate values were ({associate.Target.Id}, {associate.RelatedEntities[0].Id})"; + else if (request is CreateRequest create) + logMessage += $". The duplicate values were ({create.Target.Id})"; + } + + context.Log(logMessage); + return false; + } + } + + return true; + } + private void SetIdentity(OrganizationResponse response, IDictionary parameterValues) { var create = (CreateResponse)response; @@ -314,6 +376,7 @@ public override object Clone() Length = Length, LogicalName = LogicalName, MaxDOP = MaxDOP, + IgnoreDuplicateKey = IgnoreDuplicateKey, Source = (IExecutionPlanNodeInternal)Source.Clone(), Sql = Sql }; diff --git a/MarkMpn.Sql4Cds.Engine/Visitors/OptimizerHintValidatingVisitor.cs b/MarkMpn.Sql4Cds.Engine/Visitors/OptimizerHintValidatingVisitor.cs index 7645421a..a27698ac 100644 --- a/MarkMpn.Sql4Cds.Engine/Visitors/OptimizerHintValidatingVisitor.cs +++ b/MarkMpn.Sql4Cds.Engine/Visitors/OptimizerHintValidatingVisitor.cs @@ -57,6 +57,9 @@ class OptimizerHintValidatingVisitor : TSqlFragmentVisitor // Custom hint to use legacy specialized update messages - https://learn.microsoft.com/en-us/power-apps/developer/data-platform/org-service/entity-operations-update-delete?tabs=late#legacy-update-messages "USE_LEGACY_UPDATE_MESSAGES", + + // Ignore duplicate keys on insert, equivalent to IGNORE_DUP_KEY option on creation of index + "IGNORE_DUP_KEY", }; private static readonly HashSet _removableSql4CdsQueryHints = new HashSet(StringComparer.OrdinalIgnoreCase)