Skip to content

Commit

Permalink
Allow executing messages with OptionSetValue parameters
Browse files Browse the repository at this point in the history
Simplified type name for EntityReference variables
  • Loading branch information
MarkMpn committed Aug 28, 2023
1 parent b572fd1 commit 64ff050
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 11 deletions.
29 changes: 29 additions & 0 deletions MarkMpn.Sql4Cds.Engine.Tests/AdoProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1464,5 +1464,34 @@ UNION ALL
}
}
}

[TestMethod]
public void ExecSetState()
{
using (var con = new Sql4CdsConnection(_localDataSource))
using (var cmd = con.CreateCommand())
{
cmd.CommandText = "INSERT INTO contact (firstname, lastname) VALUES ('Test', 'User'); SELECT @@IDENTITY";
var id = cmd.ExecuteScalar();

cmd.CommandText = @"
DECLARE @id EntityReference
SELECT TOP 1 @id = contactid FROM contact
EXEC SetState @id, 1, 2";

cmd.ExecuteNonQuery();

cmd.CommandText = "SELECT statecode, statuscode FROM contact WHERE contactid = @id";
cmd.Parameters.Add(new Sql4CdsParameter("@id", id));

using (var reader = cmd.ExecuteReader())
{
Assert.IsTrue(reader.Read());
Assert.AreEqual(1, reader.GetInt32(0));
Assert.AreEqual(2, reader.GetInt32(1));
Assert.IsFalse(reader.Read());
}
}
}
}
}
3 changes: 3 additions & 0 deletions MarkMpn.Sql4Cds.Engine.Tests/FakeXrmEasyTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public FakeXrmEasyTestsBase()
_context.CallerId = new EntityReference("systemuser", Guid.NewGuid());
_context.AddFakeMessageExecutor<RetrieveVersionRequest>(new RetrieveVersionRequestExecutor());
_context.AddGenericFakeMessageExecutor(SampleMessageExecutor.MessageName, new SampleMessageExecutor());
_context.AddGenericFakeMessageExecutor(SetStateMessageExecutor.MessageName, new SetStateMessageExecutor());

_service = _context.GetOrganizationService();
_dataSource = new DataSource { Name = "uat", Connection = _service, Metadata = new AttributeMetadataCache(_service), TableSizeCache = new StubTableSizeCache(), MessageCache = new StubMessageCache(), DefaultCollation = Collation.USEnglish };
Expand All @@ -77,6 +78,7 @@ public FakeXrmEasyTestsBase()
_context2.CallerId = _context.CallerId;
_context2.AddFakeMessageExecutor<RetrieveVersionRequest>(new RetrieveVersionRequestExecutor());
_context2.AddGenericFakeMessageExecutor(SampleMessageExecutor.MessageName, new SampleMessageExecutor());
_context2.AddGenericFakeMessageExecutor(SetStateMessageExecutor.MessageName, new SetStateMessageExecutor());

_service2 = _context2.GetOrganizationService();
_dataSource2 = new DataSource { Name = "prod", Connection = _service2, Metadata = new AttributeMetadataCache(_service2), TableSizeCache = new StubTableSizeCache(), MessageCache = new StubMessageCache(), DefaultCollation = Collation.USEnglish };
Expand All @@ -86,6 +88,7 @@ public FakeXrmEasyTestsBase()
_context3.CallerId = _context.CallerId;
_context3.AddFakeMessageExecutor<RetrieveVersionRequest>(new RetrieveVersionRequestExecutor());
_context3.AddGenericFakeMessageExecutor(SampleMessageExecutor.MessageName, new SampleMessageExecutor());
_context3.AddGenericFakeMessageExecutor(SetStateMessageExecutor.MessageName, new SetStateMessageExecutor());

_service3 = _context3.GetOrganizationService();
Collation.TryParse("French_CI_AI", out var frenchCIAI);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
<Compile Include="OptionsWrapper.cs" />
<Compile Include="PropertyEqualityComparer.cs" />
<Compile Include="AdoProviderTests.cs" />
<Compile Include="SetStateMessageExecutor.cs" />
<Compile Include="SampleMessageExecutor.cs" />
<Compile Include="Sql2FetchXmlTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
Expand Down
6 changes: 6 additions & 0 deletions MarkMpn.Sql4Cds.Engine.Tests/Metadata/Contact.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,11 @@ class Contact

[AttributeLogicalName("createdon")]
public DateTime? CreatedOn { get; set; }

[AttributeLogicalName("statecode")]
public OptionSetValue StateCode { get; set; }

[AttributeLogicalName("statuscode")]
public OptionSetValue StatusCode { get; set; }
}
}
31 changes: 31 additions & 0 deletions MarkMpn.Sql4Cds.Engine.Tests/SetStateMessageExecutor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using FakeXrmEasy;
using FakeXrmEasy.FakeMessageExecutors;
using Microsoft.Xrm.Sdk;

namespace MarkMpn.Sql4Cds.Engine.Tests
{
internal class SetStateMessageExecutor : IFakeMessageExecutor
{
public static string MessageName => "SetState";

public bool CanExecute(OrganizationRequest request)
{
return request.RequestName == MessageName;
}

public OrganizationResponse Execute(OrganizationRequest request, XrmFakedContext ctx)
{
var lookup = (EntityReference)request.Parameters["EntityMoniker"];
var e = ctx.Data[lookup.LogicalName][lookup.Id];
e["statecode"] = ((OptionSetValue)request.Parameters["State"]).Value;
e["statuscode"] = ((OptionSetValue)request.Parameters["Status"]).Value;
return new OrganizationResponse();
}

public Type GetResponsibleRequestType()
{
return typeof(OrganizationRequest);
}
}
}
27 changes: 27 additions & 0 deletions MarkMpn.Sql4Cds.Engine.Tests/StubMessageCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Text;
using System.Threading.Tasks;
using MarkMpn.Sql4Cds.Engine.ExecutionPlan;
using Microsoft.Xrm.Sdk;

namespace MarkMpn.Sql4Cds.Engine.Tests
{
Expand Down Expand Up @@ -42,6 +43,32 @@ public StubMessageCache()
}
}.AsReadOnly()
};
_cache["SetState"] = new Message
{
Name = "SetState",
InputParameters = new List<MessageParameter>
{
new MessageParameter
{
Name = "EntityMoniker",
Position = 0,
Type = typeof(EntityReference)
},
new MessageParameter
{
Name = "State",
Position = 1,
Type = typeof(OptionSetValue)
},
new MessageParameter
{
Name = "Status",
Position = 2,
Type = typeof(OptionSetValue)
}
}.AsReadOnly(),
OutputParameters = new List<MessageParameter>().AsReadOnly()
};
}

public IEnumerable<Message> GetAllMessages()
Expand Down
5 changes: 4 additions & 1 deletion MarkMpn.Sql4Cds.Engine/Ado/Sql4CdsParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,10 @@ internal DataTypeReference GetDataType()
break;

case DbType.Object:
_dataType = DataTypeHelpers.Object(GetValue().GetType());
if (Value is EntityReference || Value is SqlEntityReference)
_dataType = DataTypeHelpers.EntityReference;
else
_dataType = DataTypeHelpers.Object(GetValue().GetType());
break;

case DbType.SByte:
Expand Down
11 changes: 8 additions & 3 deletions MarkMpn.Sql4Cds.Engine/DataTypeHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,12 @@ public static SqlDataTypeReference Decimal(short precision, short scale)

public static UserDataTypeReference Object(Type type)
{
return new UserDataTypeReference { Name = new SchemaObjectName { Identifiers = { new Identifier { Value = type.FullName } } } };
return Object(type.FullName);
}

private static UserDataTypeReference Object(string name)
{
return new UserDataTypeReference { Name = new SchemaObjectName { Identifiers = { new Identifier { Value = name } } } };
}

public static SqlDataTypeReference Float { get; } = new SqlDataTypeReference { SqlDataTypeOption = SqlDataTypeOption.Float };
Expand All @@ -92,7 +97,7 @@ public static SqlDataTypeReference Time(short scale)
return new SqlDataTypeReference { SqlDataTypeOption = SqlDataTypeOption.Time, Parameters = { new IntegerLiteral { Value = scale.ToString(CultureInfo.InvariantCulture) } } };
}

public static UserDataTypeReference EntityReference { get; } = Object(typeof(SqlEntityReference));
public static UserDataTypeReference EntityReference { get; } = Object(nameof(EntityReference));

public static XmlDataTypeReference Xml { get; } = new XmlDataTypeReference();

Expand Down Expand Up @@ -241,7 +246,7 @@ public static int GetSize(this DataTypeReference type)
{
if (!(type is SqlDataTypeReference dataType))
{
if (type is UserDataTypeReference udt && udt.Name.BaseIdentifier.Value == typeof(SqlEntityReference).FullName)
if (type.IsSameAs(EntityReference))
dataType = new SqlDataTypeReference { SqlDataTypeOption = SqlDataTypeOption.UniqueIdentifier };
else if (type is XmlDataTypeReference)
return Int32.MaxValue;
Expand Down
10 changes: 6 additions & 4 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/ExecuteMessageNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,13 @@ public override IDataExecutionPlanNodeInternal FoldQuery(NodeCompilationContext
_inputParameters = Values
.ToDictionary(value => value.Key, value =>
{
var exprType = value.Value.GetType(expressionContext, out _);
var expectedType = ValueTypes[value.Key];
var sourceSqlType = value.Value.GetType(expressionContext, out _);
var destNetType = ValueTypes[value.Key];
var destSqlType = SqlTypeConverter.NetToSqlType(destNetType);
var expr = value.Value.Compile(expressionContext);
var conversion = SqlTypeConverter.GetConversion(exprType, expectedType);
return (Func<ExpressionExecutionContext, object>) ((ExpressionExecutionContext ctx) => conversion(expr(ctx)));
var sqlConversion = SqlTypeConverter.GetConversion(sourceSqlType, destSqlType);
var netConversion = SqlTypeConverter.GetConversion(destSqlType, destNetType);
return (Func<ExpressionExecutionContext, object>) ((ExpressionExecutionContext ctx) => netConversion(sqlConversion(expr(ctx))));
});

BypassCustomPluginExecution = GetBypassPluginExecution(hints, context.Options);
Expand Down
14 changes: 11 additions & 3 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/SqlTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,13 @@ public static bool CanMakeConsistentTypes(DataTypeReference lhs, DataTypeReferen
}

// If one or other type is a user-defined type, check it is a known type (SqlEntityReference)
if (lhsUser != null && (lhsUser.Name.Identifiers.Count != 1 || lhsUser.Name.BaseIdentifier.Value != typeof(SqlEntityReference).FullName))
if (lhsUser != null && !lhsUser.IsSameAs(DataTypeHelpers.EntityReference))
{
consistent = null;
return false;
}

if (rhsUser != null && (rhsUser.Name.Identifiers.Count != 1 || rhsUser.Name.BaseIdentifier.Value != typeof(SqlEntityReference).FullName))
if (rhsUser != null && !rhsUser.IsSameAs(DataTypeHelpers.EntityReference))
{
consistent = null;
return false;
Expand Down Expand Up @@ -360,7 +360,7 @@ public static bool CanChangeTypeImplicit(DataTypeReference from, DataTypeReferen
return String.Join(".", fromUser.Name.Identifiers.Select(i => i.Value)).Equals(String.Join(".", toUser.Name.Identifiers.Select(i => i.Value)), StringComparison.OrdinalIgnoreCase);

// If one or other type is a user-defined type, check it is a known type (SqlEntityReference)
if (fromUser != null && (fromUser.Name.Identifiers.Count != 1 || fromUser.Name.BaseIdentifier.Value != typeof(SqlEntityReference).FullName))
if (fromUser != null && !fromUser.IsSameAs(DataTypeHelpers.EntityReference))
return false;

// Nothing can be converted to SqlEntityReference
Expand Down Expand Up @@ -1286,6 +1286,14 @@ private static Func<object, object> CompileConversion(Type sourceType, Type dest
expression = Expr.Call(() => Enum.Parse(Expr.Arg<Type>(), Expr.Arg<string>(), Expr.Arg<bool>()), Expression.Constant(destType), expression, Expression.Constant(true));
expression = Expression.Convert(expression, destType);
}
else if (expression.Type == typeof(SqlInt32) && destType == typeof(OptionSetValue))
{
var nullCheck = NullCheck(expression);
var nullValue = (Expression)Expression.Constant(null, destType);
var parsedValue = (Expression)Expression.Convert(expression, typeof(int));
parsedValue = Expression.New(typeof(OptionSetValue).GetConstructor(new[] { typeof(int) }), parsedValue);
expression = Expression.Condition(nullCheck, nullValue, parsedValue);
}
else
{
expression = Expression.Convert(expression, destType);
Expand Down

0 comments on commit 64ff050

Please sign in to comment.