Skip to content

Commit

Permalink
EntityReference type updates - support strongly typed EntityReference…
Browse files Browse the repository at this point in the history
… columns for simpler conversions
  • Loading branch information
MarkMpn committed Oct 19, 2024
1 parent 4175722 commit 6779318
Show file tree
Hide file tree
Showing 14 changed files with 330 additions and 69 deletions.
10 changes: 6 additions & 4 deletions MarkMpn.Sql4Cds.Engine.Tests/CteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,20 +140,22 @@ WITH cte (id, n) AS (SELECT accountid, name FROM account UNION ALL select contac

var select = AssertNode<SelectNode>(plans[0]);
var concat = AssertNode<ConcatenateNode>(select.Source);
var account = AssertNode<FetchXmlScan>(concat.Sources[0]);
var computeAccount = AssertNode<ComputeScalarNode>(concat.Sources[0]);
var account = AssertNode<FetchXmlScan>(computeAccount.Source);
AssertFetchXml(account, @"
<fetch>
<entity name='account'>
<attribute name='accountid' />
<attribute name='name' />
<attribute name='accountid' />
</entity>
</fetch>");
var contact = AssertNode<FetchXmlScan>(concat.Sources[1]);
var computeContact = AssertNode<ComputeScalarNode>(concat.Sources[1]);
var contact = AssertNode<FetchXmlScan>(computeContact.Source);
AssertFetchXml(contact, @"
<fetch>
<entity name='contact'>
<attribute name='contactid' />
<attribute name='fullname' />
<attribute name='contactid' />
</entity>
</fetch>");
}
Expand Down
5 changes: 5 additions & 0 deletions MarkMpn.Sql4Cds.Engine/Ado/Sql4CdsError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,11 @@ internal static Sql4CdsError InvalidDateFormat(TSqlFragment fragment, string for
return Create(2741, fragment, (SqlInt32)format.Length, Collation.USEnglish.ToSqlString(format));
}

internal static Sql4CdsError InvalidEntityReferenceType(TSqlFragment fragment, string actual, string required)
{
return new Sql4CdsError(16, 50001, $"Cannot convert entity reference from {actual} to {required}", fragment);
}

private static string GetTypeName(DataTypeReference type)
{
if (type is SqlDataTypeReference sqlType)
Expand Down
92 changes: 88 additions & 4 deletions MarkMpn.Sql4Cds.Engine/DataTypeHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ public static SqlDataTypeReference Time(short scale)

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

public static UserDataTypeReference TypedEntityReference(string logicalName)
{
var type = Object(nameof(EntityReference));
type.Parameters.Add(new StringLiteral { Value = logicalName });
return type;
}

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

public static SqlDataTypeReference Variant { get; } = new SqlDataTypeReference { SqlDataTypeOption = SqlDataTypeOption.Sql_Variant };
Expand Down Expand Up @@ -249,8 +256,8 @@ public static int GetSize(this DataTypeReference type)
{
if (!(type is SqlDataTypeReference dataType))
{
if (type.IsSameAs(EntityReference))
dataType = new SqlDataTypeReference { SqlDataTypeOption = SqlDataTypeOption.UniqueIdentifier };
if (type.IsEntityReference())
dataType = UniqueIdentifier;
else if (type is XmlDataTypeReference)
return Int32.MaxValue;
else
Expand Down Expand Up @@ -455,6 +462,46 @@ public static bool IsSameAs(this DataTypeReference x, DataTypeReference y)
return DataTypeComparer.Instance.Equals(x, y);
}

/// <summary>
/// Checks if a data type is an entity reference
/// </summary>
/// <param name="type">The type to check</param>
/// <returns><c>true</c> if <paramref name="type"/> is an entity reference type, or <c>false</c> otherwise</returns>
public static bool IsEntityReference(this DataTypeReference type)
{
return type.IsEntityReference(out _);
}

/// <summary>
/// Checks if a data type is an entity reference
/// </summary>
/// <param name="type">The type to check</param>
/// <param name="logicalName">If the <paramref name="type"/> is a typed entity reference, this will be set to the corresponding entity logical name</param>
/// <returns><c>true</c> if <paramref name="type"/> is an entity reference type, or <c>false</c> otherwise</returns>
public static bool IsEntityReference(this DataTypeReference type, out string logicalName)
{
if (!(type is UserDataTypeReference user))
{
logicalName = null;
return false;
}

if (!user.Name.Identifiers[0].Value.Equals(nameof(EntityReference), StringComparison.OrdinalIgnoreCase))
{
logicalName = null;
return false;
}

if (user.Parameters.Count == 0)
{
logicalName = null;
return true;
}

logicalName = user.Parameters[0].Value;
return true;
}

/// <summary>
/// Parses a data type from a string
/// </summary>
Expand Down Expand Up @@ -498,10 +545,27 @@ public static bool TryParse(ExpressionCompilationContext context, string value,
if (parameters.Count > 0)
return false;

parsedType = DataTypeHelpers.Xml;
parsedType = Xml;
return true;
}

if (name.Equals(nameof(EntityReference), StringComparison.OrdinalIgnoreCase))
{
if (parameters.Count == 1 && parameters[0] is StringLiteral erType)
{
parsedType = TypedEntityReference(erType.Value);
return true;
}

if (parameters.Count == 0)
{
parsedType = EntityReference;
return true;
}

return false;
}

if (!Enum.TryParse<SqlDataTypeOption>(name, true, out var sqlType))
return false;

Expand Down Expand Up @@ -549,7 +613,27 @@ public bool Equals(DataTypeReference x, DataTypeReference y)
var yXml = y as XmlDataTypeReference;

if (xUser != null && yUser != null)
return String.Join(".", xUser.Name.Identifiers.Select(i => i.Value)).Equals(String.Join(".", yUser.Name.Identifiers.Select(i => i.Value)), StringComparison.OrdinalIgnoreCase);
{
if (xUser.Name.Identifiers.Count != yUser.Name.Identifiers.Count)
return false;

for (var i = 0; i < xUser.Name.Identifiers.Count; i++)
{
if (!xUser.Name.Identifiers[i].Value.Equals(yUser.Name.Identifiers[i].Value, StringComparison.OrdinalIgnoreCase))
return false;
}

if (xUser.Parameters.Count != yUser.Parameters.Count)
return false;

for (var i = 0; i < xUser.Parameters.Count; i++)
{
if (!xUser.Parameters[i].Value.Equals(yUser.Parameters[i].Value, StringComparison.OrdinalIgnoreCase))
return false;
}

return true;
}

if (xXml != null && yXml != null)
return xXml.XmlDataTypeOption == yXml.XmlDataTypeOption && xXml.XmlSchemaCollection == yXml.XmlSchemaCollection;
Expand Down
4 changes: 2 additions & 2 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseDmlNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ protected Dictionary<string, Func<ExpressionExecutionContext, object>> CompileCo

if (lookupAttr != null && lookupAttr.AttributeType != AttributeTypeCode.PartyList && metadata.IsIntersect != true)
{
if (sourceSqlType.IsSameAs(DataTypeHelpers.EntityReference))
if (sourceSqlType.IsEntityReference())
{
convertedExpr = expr;
expr = originalExpr;
Expand Down Expand Up @@ -560,7 +560,7 @@ protected Dictionary<string, Func<ExpressionExecutionContext, object>> CompileCo
}
else
{
if (!sourceSqlType.IsSameAs(DataTypeHelpers.EntityReference) ||
if (!sourceSqlType.IsEntityReference() ||
!(attr is LookupAttributeMetadata partyListAttr) ||
partyListAttr.AttributeType != AttributeTypeCode.PartyList)
{
Expand Down
16 changes: 16 additions & 0 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/ComputeScalarNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,22 @@ calc.Value is CastCall c2 && c2.Parameter is Literal ||
Columns.Remove(col);
}

// Remove any columns that re-define an existing column
var toRemove = new List<string>();

foreach (var calc in Columns)
{
if (calc.Value is ColumnReferenceExpression col &&
col.MultiPartIdentifier.Identifiers.Count == 1 &&
col.MultiPartIdentifier.Identifiers[0].Value.Equals(calc.Key, StringComparison.OrdinalIgnoreCase))
{
toRemove.Add(calc.Key);
}
}

foreach (var col in toRemove)
Columns.Remove(col);

// If we don't have any calculations, this node is not needed
if (Columns.Count == 0)
return Source;
Expand Down
20 changes: 10 additions & 10 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/ExecuteMessageNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -687,13 +687,13 @@ public static ExecuteMessageNode FromMessage(SchemaObjectFunctionTableReference
{
var f = expectedInputParameters[i];
var sourceExpression = tvf.Parameters[i];
sourceExpression.GetType(context, out var sourceType);
var expectedType = f.GetSqlDataType(context.PrimaryDataSource);
var sourceType = sourceExpression.GetType(context, out var sourceSqlType);
var expectedSqlType = f.GetSqlDataType(context.PrimaryDataSource);

if (!SqlTypeConverter.CanChangeTypeImplicit(sourceType, expectedType))
throw new NotSupportedQueryFragmentException(Sql4CdsError.TypeClash(tvf.Parameters[f.Position], sourceType, expectedType));
if (!SqlTypeConverter.CanChangeTypeImplicit(sourceSqlType, expectedSqlType))
throw new NotSupportedQueryFragmentException(Sql4CdsError.TypeClash(tvf.Parameters[f.Position], sourceSqlType, expectedSqlType));

if (sourceType.IsSameAs(expectedType))
if (sourceSqlType.IsSameAs(expectedSqlType))
{
node.Values[f.Name] = sourceExpression;
}
Expand All @@ -702,7 +702,7 @@ public static ExecuteMessageNode FromMessage(SchemaObjectFunctionTableReference
node.Values[f.Name] = new ConvertCall
{
Parameter = sourceExpression,
DataType = expectedType
DataType = expectedSqlType
};
}

Expand Down Expand Up @@ -789,17 +789,17 @@ public static ExecuteMessageNode FromMessage(ExecutableProcedureReference sproc,
throw new NotSupportedQueryFragmentException(Sql4CdsError.InvalidParameterName(sproc.Parameters[i], sproc.ProcedureReference.ProcedureReference.Name));

var sourceExpression = sproc.Parameters[i].ParameterValue;
sourceExpression.GetType(context, out var sourceType);
var sourceType = sourceExpression.GetType(context, out var sourceSqlType);
var expectedType = targetParam.GetSqlDataType(context.PrimaryDataSource);

if (!SqlTypeConverter.CanChangeTypeImplicit(sourceType, expectedType))
if (!SqlTypeConverter.CanChangeTypeImplicit(sourceSqlType, expectedType))
{
var err = Sql4CdsError.TypeClash(sproc.Parameters[i].ParameterValue, sourceType, expectedType);
var err = Sql4CdsError.TypeClash(sproc.Parameters[i].ParameterValue, sourceSqlType, expectedType);
err.Procedure = message.Name;
throw new NotSupportedQueryFragmentException(err);
}

if (sourceType.IsSameAs(expectedType))
if (sourceSqlType.IsSameAs(expectedType))
{
node.Values[targetParam.Name] = sproc.Parameters[i].ParameterValue;
}
Expand Down
4 changes: 2 additions & 2 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2138,7 +2138,7 @@ public static Type ToNetType(this DataTypeReference type, out SqlDataTypeReferen
{
if (!(type is SqlDataTypeReference dataType))
{
if (type.IsSameAs(DataTypeHelpers.EntityReference))
if (type.IsEntityReference())
{
sqlDataType = null;
return typeof(SqlEntityReference);
Expand Down Expand Up @@ -2368,7 +2368,7 @@ private static Expression ToExpression(this ParameterlessCall parameterless, Exp
return createExpression ? Expr.Call(() => GetCurrentTimestamp(Expr.Arg<ExpressionExecutionContext>()), contextParam) : null;

default:
sqlType = DataTypeHelpers.EntityReference;
sqlType = DataTypeHelpers.TypedEntityReference("systemuser");
cacheKey = "CURRENT_USER";
return createExpression ? Expr.Call(() => GetCurrentUser(Expr.Arg<ExpressionExecutionContext>()), contextParam) : null;
}
Expand Down
2 changes: 1 addition & 1 deletion MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ private void VerifyFilterValueTypes(string entityName, object[] items, DataSourc
var meta = dataSource.Metadata[conditionEntity];
var attr = meta.Attributes.Single(a => a.LogicalName == condition.attribute);
var attrType = attr.GetAttributeSqlType(dataSource, false);
if (attrType.IsSameAs(DataTypeHelpers.EntityReference))
if (attrType.IsEntityReference())
attrType = DataTypeHelpers.UniqueIdentifier;

// For some operators the value type may be different from the attribute type
Expand Down
5 changes: 3 additions & 2 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/MergeJoinNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,9 @@ private bool IsConsistentSortTypes(DataTypeReference leftType, DataTypeReference
return true;

// Types can be different but have the same logical sort order, e.g. all numeric types
if (leftType.IsSameAs(DataTypeHelpers.UniqueIdentifier) && rightType.IsSameAs(DataTypeHelpers.EntityReference) ||
leftType.IsSameAs(DataTypeHelpers.EntityReference) && rightType.IsSameAs(DataTypeHelpers.UniqueIdentifier))
if (leftType.IsSameAs(DataTypeHelpers.UniqueIdentifier) && rightType.IsEntityReference() ||
leftType.IsEntityReference() && rightType.IsSameAs(DataTypeHelpers.UniqueIdentifier) ||
leftType.IsEntityReference() && rightType.IsEntityReference())
return true;

if (leftType is SqlDataTypeReference leftSqlType && rightType is SqlDataTypeReference rightSqlType)
Expand Down
Loading

0 comments on commit 6779318

Please sign in to comment.