Skip to content

Commit

Permalink
Added scripting of stored procedure execution
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkMpn committed Jun 16, 2024
1 parent 9bb1b0a commit 5c3f108
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 92 deletions.
6 changes: 3 additions & 3 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/ExecuteMessageNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ private void SetOutputSchema(DataSource dataSource, Message message, TSqlFragmen
if (message.OutputParameters.All(f => f.IsScalarType()))
{
foreach (var value in message.OutputParameters)
AddSchemaColumn(value.Name, SqlTypeConverter.NetToSqlType(value.Type).ToSqlType(dataSource)); // TODO: How are OSV and ER fields represented?
AddSchemaColumn(value.Name, value.GetSqlDataType(dataSource));
}
else
{
Expand Down Expand Up @@ -556,7 +556,7 @@ public static ExecuteMessageNode FromMessage(SchemaObjectFunctionTableReference
var f = expectedInputParameters[i];
var sourceExpression = tvf.Parameters[i];
sourceExpression.GetType(context, out var sourceType);
var expectedType = SqlTypeConverter.NetToSqlType(f.Type).ToSqlType(context.PrimaryDataSource);
var expectedType = f.GetSqlDataType(context.PrimaryDataSource);

if (!SqlTypeConverter.CanChangeTypeImplicit(sourceType, expectedType))
throw new NotSupportedQueryFragmentException(Sql4CdsError.TypeClash(tvf.Parameters[f.Position], sourceType, expectedType));
Expand Down Expand Up @@ -658,7 +658,7 @@ public static ExecuteMessageNode FromMessage(ExecutableProcedureReference sproc,

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

if (!SqlTypeConverter.CanChangeTypeImplicit(sourceType, expectedType))
{
Expand Down
6 changes: 6 additions & 0 deletions MarkMpn.Sql4Cds.Engine/MessageCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MarkMpn.Sql4Cds.Engine.ExecutionPlan;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Metadata;
Expand Down Expand Up @@ -429,5 +430,10 @@ public bool IsScalarType()

return false;
}

/// <summary>
/// Returns the SQL data type for the parameter
/// </summary>
public Microsoft.SqlServer.TransactSql.ScriptDom.DataTypeReference GetSqlDataType(DataSource dataSource) => SqlTypeConverter.NetToSqlType(Type).ToSqlType(dataSource);
}
}
76 changes: 60 additions & 16 deletions MarkMpn.Sql4Cds.Engine/MetadataExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,33 @@

namespace MarkMpn.Sql4Cds.Engine
{
/// <summary>
/// Extension methods to convert from Dataverse metadata to SQL schema
/// </summary>
public static class MetadataExtensions
{
/// <summary>
/// The maximum length of an attribute which contains an entity logical name
/// </summary>
public static int EntityLogicalNameMaxLength { get; } = 64;

/// <summary>
/// The maximum length of an optionset label value
/// </summary>
private const int LabelMaxLength = 200;

/// <summary>
/// The suffixes that can be used for virtual attributes
/// </summary>
public static string[] VirtualLookupAttributeSuffixes { get; } = new[] { "name", "type", "pid" };

/// <summary>
/// Gets the base attribute (optionset, lookup etc.) from the name of a virtual attribute (___name, ___type etc.)
/// </summary>
/// <param name="entity">The entity the attribute is in</param>
/// <param name="virtualAttributeLogicalName">The name of the virtual attribute to get the details for</param>
/// <param name="suffix">The suffix of the virtual attribute</param>
/// <returns>The metadata of the underlying attribute, or <see langword="null"/> if no virtual attribute is found</returns>
public static AttributeMetadata FindBaseAttributeFromVirtualAttribute(this EntityMetadata entity, string virtualAttributeLogicalName, out string suffix)
{
var matchingSuffix = VirtualLookupAttributeSuffixes.SingleOrDefault(s => virtualAttributeLogicalName.EndsWith(s, StringComparison.OrdinalIgnoreCase));
Expand All @@ -31,6 +50,13 @@ public static AttributeMetadata FindBaseAttributeFromVirtualAttribute(this Entit
.SingleOrDefault(a => a.LogicalName.Equals(virtualAttributeLogicalName.Substring(0, virtualAttributeLogicalName.Length - matchingSuffix.Length), StringComparison.OrdinalIgnoreCase));
}

/// <summary>
/// Gets the virtual attributes from a base attribute
/// </summary>
/// <param name="attrMetadata">The underlying base attribute to get the virtual attributes for</param>
/// <param name="dataSource">The datasource that the attribute is from</param>
/// <param name="writeable">Indicates whether to get readable or writeable virtual attributes</param>
/// <returns>A sequence of virtual attributes that are based on this attribute</returns>
public static IEnumerable<VirtualAttribute> GetVirtualAttributes(this AttributeMetadata attrMetadata, DataSource dataSource, bool writeable)
{
if (!writeable)
Expand All @@ -54,22 +80,6 @@ public static IEnumerable<VirtualAttribute> GetVirtualAttributes(this AttributeM
}
}

public class VirtualAttribute
{
public VirtualAttribute(string suffix, DataTypeReference dataType, bool? notNull)
{
Suffix = suffix;
DataType = dataType;
NotNull = notNull;
}

public string Suffix { get; }

public DataTypeReference DataType { get; }

public bool? NotNull { get; }
}

public static Type GetAttributeType(this AttributeMetadata attrMetadata)
{
if (attrMetadata is MultiSelectPicklistAttributeMetadata)
Expand Down Expand Up @@ -240,4 +250,38 @@ public static DataTypeReference GetAttributeSqlType(this AttributeMetadata attrM
throw new ApplicationException("Unknown attribute type " + attrMetadata.GetType());
}
}

/// <summary>
/// Contains the details of a virtual attribute
/// </summary>
public class VirtualAttribute
{
/// <summary>
/// Creates a new <see cref="VirtualAttribute"/>
/// </summary>
/// <param name="suffix">The suffix for this virtual attribute</param>
/// <param name="dataType">The SQL data type of this virtual attribute</param>
/// <param name="notNull">Indicates if this virtual attribute is known to be nullable or not-nullable</param>
internal VirtualAttribute(string suffix, DataTypeReference dataType, bool? notNull)
{
Suffix = suffix;
DataType = dataType;
NotNull = notNull;
}

/// <summary>
/// The suffix for this virtual attribute, e.g. "name", "type", "pid"
/// </summary>
public string Suffix { get; }

/// <summary>
/// The SQL data type for this virtual attribute
/// </summary>
public DataTypeReference DataType { get; }

/// <summary>
/// Indicates if this attribute is known to be nullable, not-nullable or whether it should inherit its nullability from the base attribute
/// </summary>
public bool? NotNull { get; }
}
}
31 changes: 4 additions & 27 deletions MarkMpn.Sql4Cds.LanguageServer/Autocomplete/Autocomplete.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1250,29 +1250,6 @@ public int CompareTo(object obj)
return CompareText.CompareTo(other.CompareText);
}

public static string GetSqlTypeName(Type type)
{
if (type == typeof(string))
return "NVARCHAR(MAX)";

if (type == typeof(int))
return "INT";

if (type == typeof(decimal))
return "NUMERIC(10, 4)";

if (type == typeof(Guid))
return "UNIQUEIDENTIFIER";

if (type == typeof(DateTime))
return "DATETIME";

if (type == typeof(bool))
return "BIT";

return type.Name;
}

public static string EscapeIdentifier(string identifier)
{
var id = new Microsoft.SqlServer.TransactSql.ScriptDom.Identifier { Value = identifier };
Expand Down Expand Up @@ -1591,7 +1568,7 @@ public override string ToolTipText
else
parameters = parameters.OrderBy(p => p.Position);

return _message.Name + "(" + String.Join(", ", parameters.Select(p => p.Name + " " + GetSqlTypeName(p.Type))) + ")";
return _message.Name + "(" + String.Join(", ", parameters.Select(p => p.Name + " " + p.GetSqlDataType(null).ToSql())) + ")";
}
set => base.ToolTipText = value;
}
Expand Down Expand Up @@ -1631,7 +1608,7 @@ public override string ToolTipText
else
parameters = parameters.OrderBy(p => p.Position);

return _message.Name + " " + String.Join(", ", parameters.Select(p => (p.Optional ? "[" : "") + "@" + p.Name + " = " + GetSqlTypeName(p.Type) + (p.Optional ? "]" : ""))) + (_message.OutputParameters.Count == 0 ? "" : ((_message.InputParameters.Count == 0 ? "" : ",") + " " + String.Join(", ", _message.OutputParameters.Select(p => "[@" + p.Name + " = " + GetSqlTypeName(p.Type) + " OUTPUT]"))));
return _message.Name + " " + String.Join(", ", parameters.Select(p => (p.Optional ? "[" : "") + "@" + p.Name + " = " + p.GetSqlDataType(null).ToSql() + (p.Optional ? "]" : ""))) + (_message.OutputParameters.Count == 0 ? "" : ((_message.InputParameters.Count == 0 ? "" : ",") + " " + String.Join(", ", _message.OutputParameters.Select(p => "[@" + p.Name + " = " + p.GetSqlDataType(null).ToSql() + " OUTPUT]"))));
}
set => base.ToolTipText = value;
}
Expand All @@ -1650,13 +1627,13 @@ public SprocParameterAutocompleteItem(Message message, MessageParameter paramete

public override string ToolTipTitle
{
get => _parameter.Name + (_message.OutputParameters.Contains(_parameter) ? " output" : " input") + " parameter (" + GetSqlTypeName(_parameter.Type) + ")";
get => _parameter.Name + (_message.OutputParameters.Contains(_parameter) ? " output" : " input") + " parameter (" + _parameter.GetSqlDataType(null).ToSql() + ")";
set => base.ToolTipTitle = value;
}

public override string ToolTipText
{
get => _message.Name + " " + string.Join(", ", _message.InputParameters.Select(p => (p.Optional ? "[" : "") + "@" + p.Name + " = " + GetSqlTypeName(p.Type) + (p.Optional ? "]" : ""))) + (_message.OutputParameters.Count == 0 ? "" : (_message.InputParameters.Count == 0 ? "" : ",") + " " + string.Join(", ", _message.OutputParameters.Select(p => "[@" + p.Name + " = " + GetSqlTypeName(p.Type) + " OUTPUT]")));
get => _message.Name + " " + string.Join(", ", _message.InputParameters.Select(p => (p.Optional ? "[" : "") + "@" + p.Name + " = " + p.GetSqlDataType(null).ToSql() + (p.Optional ? "]" : ""))) + (_message.OutputParameters.Count == 0 ? "" : (_message.InputParameters.Count == 0 ? "" : ",") + " " + string.Join(", ", _message.OutputParameters.Select(p => "[@" + p.Name + " = " + p.GetSqlDataType(null).ToSql() + " OUTPUT]")));
set => base.ToolTipText = value;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ private string CLRToSQLTypeName(Type type)
return "uniqueidentifier";

if (type == typeof(EntityReference))
return "[MarkMpn.Sql4Cds.Engine.SqlEntityReference]";
return "EntityReference";

if (type == typeof(double))
return "double";
Expand Down
31 changes: 4 additions & 27 deletions MarkMpn.Sql4Cds.XTB/Autocomplete.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1232,29 +1232,6 @@ public int CompareTo(object obj)
return CompareText.CompareTo(other.CompareText);
}

public static string GetSqlTypeName(Type type)
{
if (type == typeof(string))
return "NVARCHAR(MAX)";

if (type == typeof(int))
return "INT";

if (type == typeof(decimal))
return "NUMERIC(10, 4)";

if (type == typeof(Guid))
return "UNIQUEIDENTIFIER";

if (type == typeof(DateTime))
return "DATETIME";

if (type == typeof(bool))
return "BIT";

return type.Name;
}

public static string EscapeIdentifier(string identifier)
{
var id = new Microsoft.SqlServer.TransactSql.ScriptDom.Identifier { Value = identifier };
Expand Down Expand Up @@ -1573,7 +1550,7 @@ public override string ToolTipText
else
parameters = parameters.OrderBy(p => p.Position);

return _message.Name + "(" + String.Join(", ", parameters.Select(p => p.Name + " " + GetSqlTypeName(p.Type))) + ")";
return _message.Name + "(" + String.Join(", ", parameters.Select(p => p.Name + " " + p.GetSqlDataType(null).ToSql())) + ")";
}
set => base.ToolTipText = value;
}
Expand Down Expand Up @@ -1613,7 +1590,7 @@ public override string ToolTipText
else
parameters = parameters.OrderBy(p => p.Position);

return _message.Name + " " + String.Join(", ", parameters.Select(p => (p.Optional ? "[" : "") + "@" + p.Name + " = " + GetSqlTypeName(p.Type) + (p.Optional ? "]" : ""))) + (_message.OutputParameters.Count == 0 ? "" : ((_message.InputParameters.Count == 0 ? "" : ",") + " " + String.Join(", ", _message.OutputParameters.Select(p => "[@" + p.Name + " = " + GetSqlTypeName(p.Type) + " OUTPUT]"))));
return _message.Name + " " + String.Join(", ", parameters.Select(p => (p.Optional ? "[" : "") + "@" + p.Name + " = " + p.GetSqlDataType(null).ToSql() + (p.Optional ? "]" : ""))) + (_message.OutputParameters.Count == 0 ? "" : ((_message.InputParameters.Count == 0 ? "" : ",") + " " + String.Join(", ", _message.OutputParameters.Select(p => "[@" + p.Name + " = " + p.GetSqlDataType(null).ToSql() + " OUTPUT]"))));
}
set => base.ToolTipText = value;
}
Expand All @@ -1632,13 +1609,13 @@ public SprocParameterAutocompleteItem(Message message, MessageParameter paramete

public override string ToolTipTitle
{
get => _parameter.Name + (_message.OutputParameters.Contains(_parameter) ? " output" : " input") + " parameter (" + GetSqlTypeName(_parameter.Type) + ")";
get => _parameter.Name + (_message.OutputParameters.Contains(_parameter) ? " output" : " input") + " parameter (" + _parameter.GetSqlDataType(null).ToSql() + ")";
set => base.ToolTipTitle = value;
}

public override string ToolTipText
{
get => _message.Name + " " + String.Join(", ", _message.InputParameters.Select(p => (p.Optional ? "[" : "") + "@" + p.Name + " = " + GetSqlTypeName(p.Type) + (p.Optional ? "]" : ""))) + (_message.OutputParameters.Count == 0 ? "" : ((_message.InputParameters.Count == 0 ? "" : ",") + " " + String.Join(", ", _message.OutputParameters.Select(p => "[@" + p.Name + " = " + GetSqlTypeName(p.Type) + " OUTPUT]"))));
get => _message.Name + " " + String.Join(", ", _message.InputParameters.Select(p => (p.Optional ? "[" : "") + "@" + p.Name + " = " + p.GetSqlDataType(null).ToSql() + (p.Optional ? "]" : ""))) + (_message.OutputParameters.Count == 0 ? "" : ((_message.InputParameters.Count == 0 ? "" : ",") + " " + String.Join(", ", _message.OutputParameters.Select(p => "[@" + p.Name + " = " + p.GetSqlDataType(null).ToSql() + " OUTPUT]"))));
set => base.ToolTipText = value;
}
}
Expand Down
Loading

0 comments on commit 5c3f108

Please sign in to comment.