Skip to content

Commit

Permalink
Merge pull request #452 from MarkMpn/executeas
Browse files Browse the repository at this point in the history
Error reporting
  • Loading branch information
MarkMpn authored Apr 12, 2024
2 parents d019ee3 + 2f929fa commit 052d59c
Show file tree
Hide file tree
Showing 9 changed files with 16,462 additions and 125 deletions.
24 changes: 23 additions & 1 deletion MarkMpn.Sql4Cds.Engine/Ado/Sql4CdsError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ internal Sql4CdsError(byte @class, int number, string message, TSqlFragment frag
/// </summary>
public TSqlFragment Fragment { get; }

internal static IEnumerable<Sql4CdsError> GetAllErrors()
{
return _errorNumberToDetails.Values;
}

internal static Sql4CdsError Create(int number, TSqlFragment fragment)
{
var template = _errorNumberToDetails[number];
Expand Down Expand Up @@ -183,6 +188,18 @@ internal static Sql4CdsError InvalidObjectName(SchemaObjectName obj)
return Create(208, obj, (SqlInt32)name.Length, Collation.USEnglish.ToSqlString(name));
}

internal static Sql4CdsError NonFunctionCalledWithParameters(SchemaObjectName obj)
{
var name = obj.ToSql();
return Create(215, obj, (SqlInt32)name.Length, Collation.USEnglish.ToSqlString(name));
}

internal static Sql4CdsError FunctionCalledWithoutParameters(SchemaObjectName obj)
{
var name = obj.ToSql();
return Create(216, obj, (SqlInt32)name.Length, Collation.USEnglish.ToSqlString(name));
}

internal static Sql4CdsError InvalidSprocName(SchemaObjectName sproc)
{
var name = sproc.ToSql();
Expand Down Expand Up @@ -386,7 +403,7 @@ internal static Sql4CdsError InvalidOptionValue(TSqlFragment value, string type)

internal static Sql4CdsError InvalidFunction(Identifier identifier)
{
return Create(195, identifier, (SqlInt32)identifier.Value.Length, Collation.USEnglish.ToSqlString(identifier.Value), "built-in function name");
return Create(195, identifier, (SqlInt32)identifier.Value.Length, Collation.USEnglish.ToSqlString(identifier.Value), Collation.USEnglish.ToSqlString("built-in function name"));
}

internal static Sql4CdsError InvalidFunctionParameterCount(Identifier identifier, int expectedArgs)
Expand Down Expand Up @@ -569,6 +586,11 @@ internal static Sql4CdsError InvalidColumnForFullTextSearch(ColumnReferenceExpre
return Create(7670, col, (SqlInt32)colName.Length, Collation.USEnglish.ToSqlString(colName));
}

internal static Sql4CdsError InvalidErrorNumber(int number)
{
return Create(2732, null, (SqlInt32)number);
}

internal static Sql4CdsError InvalidSeverityLevel(int maxSeverity)
{
return Create(2754, null, (SqlInt32)maxSeverity);
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 @@ -1241,7 +1241,7 @@ private void AddNotNullFilters(ColumnList schema, Dictionary<string, IReadOnlyLi

private void AddSchemaAttribute(DataSource dataSource, ColumnList schema, Dictionary<string, IReadOnlyList<string>> aliases, string fullName, string simpleName, DataTypeReference type, EntityMetadata entityMetadata, AttributeMetadata attrMetadata, bool innerJoin)
{
var notNull = innerJoin && (attrMetadata.LogicalName == entityMetadata.PrimaryIdAttribute || attrMetadata.LogicalName == "createdon" || (attrMetadata.EntityLogicalName != "systemuser" && (attrMetadata.LogicalName == "createdby" || attrMetadata.AttributeOf == "createdby")));
var notNull = innerJoin && (attrMetadata.LogicalName == entityMetadata.PrimaryIdAttribute || attrMetadata.LogicalName == "createdon");

// Add the logical attribute
AddSchemaAttribute(schema, aliases, fullName, simpleName, type, notNull);
Expand Down
78 changes: 55 additions & 23 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/RaiseErrorNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ class RaiseErrorNode : BaseNode, IDmlQueryExecutionPlanNode
private int _executionCount;
private readonly Timer _timer = new Timer();

[Category("Raise Error")]
[Description("The error number that is generated")]
[DisplayName("Error Number")]
public ScalarExpression ErrorNumber { get; set; }

[Category("Raise Error")]
[Description("The error message that is generated")]
[DisplayName("Error Message")]
Expand Down Expand Up @@ -72,7 +77,8 @@ public object Clone()
{
return new RaiseErrorNode
{
ErrorMessage = ErrorMessage.Clone(),
ErrorNumber = ErrorNumber?.Clone(),
ErrorMessage = ErrorMessage?.Clone(),
Severity = Severity.Clone(),
State = State.Clone(),
Parameters = Parameters.Select(p => p.Clone()).ToArray(),
Expand All @@ -95,29 +101,55 @@ public override IEnumerable<IExecutionPlanNode> GetSources()

public void Execute(NodeExecutionContext context, out int recordsAffected, out string message)
{
var ecc = new ExpressionCompilationContext(context, null, null);
var eec = new ExpressionExecutionContext(context);

var msg = Execute<SqlString>(ErrorMessage, ecc, eec, DataTypeHelpers.NVarChar(Int32.MaxValue, context.PrimaryDataSource.DefaultCollation, CollationLabel.CoercibleDefault));
var severity = Execute<SqlInt32>(Severity, ecc, eec, DataTypeHelpers.Int);
var state = Execute<SqlInt32>(State, ecc, eec, DataTypeHelpers.Int);

if (severity.Value > 18)
throw new QueryExecutionException(Sql4CdsError.InvalidSeverityLevel(18));

if (severity.Value < 0)
severity = 0;

if (state.Value > 255)
state = 255;
else if (state.Value < 0)
state = 1;

msg = ExpressionFunctions.FormatMessage(msg, eec, Parameters.Select(p => (INullable)p.Compile(ecc)(eec)).ToArray());
try
{
var ecc = new ExpressionCompilationContext(context, null, null);
var eec = new ExpressionExecutionContext(context);

SqlInt32 num;
SqlString msg;

if (ErrorNumber == null)
{
num = 50000;
msg = Execute<SqlString>(ErrorMessage, ecc, eec, DataTypeHelpers.NVarChar(Int32.MaxValue, context.PrimaryDataSource.DefaultCollation, CollationLabel.CoercibleDefault));
}
else
{
num = Execute<SqlInt32>(ErrorNumber, ecc, eec, DataTypeHelpers.Int);
msg = Sql4CdsError.GetAllErrors().SingleOrDefault(e => e.Number == num.Value)?.Message;

if (num < 13000 || num == 50000)
throw new QueryExecutionException(Sql4CdsError.InvalidErrorNumber(num.Value));
}

var severity = Execute<SqlInt32>(Severity, ecc, eec, DataTypeHelpers.Int);
var state = Execute<SqlInt32>(State, ecc, eec, DataTypeHelpers.Int);

if (severity.Value > 18)
throw new QueryExecutionException(Sql4CdsError.InvalidSeverityLevel(18));

if (severity.Value < 0)
severity = 0;

if (state.Value > 255)
state = 255;
else if (state.Value < 0)
state = 1;

msg = ExpressionFunctions.FormatMessage(msg, eec, Parameters.Select(p => (INullable)p.Compile(ecc)(eec)).ToArray());

context.Log(new Sql4CdsError((byte)severity.Value, -1, num.Value, null, null, (byte)state.Value, msg.IsNull ? null : msg.Value));
recordsAffected = 0;
message = null;
}
catch (QueryExecutionException ex)
{
if (ex.Node == null)
ex.Node = this;

context.Log(new Sql4CdsError((byte)severity.Value, -1, 50000, null, null, (byte)state.Value, msg.IsNull ? null : msg.Value));
recordsAffected = 0;
message = null;
throw;
}
}

private T Execute<T>(ScalarExpression expression, ExpressionCompilationContext ecc, ExpressionExecutionContext eec, DataTypeReference dataType)
Expand Down
50 changes: 49 additions & 1 deletion MarkMpn.Sql4Cds.Engine/ExecutionPlan/SystemFunctionNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ public override INodeSchema GetSchema(NodeCompilationContext context)
aliases: null,
primaryKey: null,
sortOrder: null);

case SystemFunction.messages:
return new NodeSchema(
schema: new ColumnList
{
[PrefixWithAlias("message_id")] = new ColumnDefinition(DataTypeHelpers.Int, false, false),
[PrefixWithAlias("language_id")] = new ColumnDefinition(DataTypeHelpers.SmallInt, false, false),
[PrefixWithAlias("severity")] = new ColumnDefinition(DataTypeHelpers.TinyInt, false, false),
[PrefixWithAlias("is_event_logged")] = new ColumnDefinition(DataTypeHelpers.Bit, false, false),
[PrefixWithAlias("text")] = new ColumnDefinition(DataTypeHelpers.NVarChar(2048, dataSource.DefaultCollation, CollationLabel.CoercibleDefault), false, false)
},
aliases: null,
primaryKey: null,
sortOrder: null);
}

throw new NotSupportedException("Unsupported function " + SystemFunction);
Expand Down Expand Up @@ -98,6 +112,20 @@ protected override IEnumerable<Entity> ExecuteInternal(NodeExecutionContext cont
}
break;

case SystemFunction.messages:
foreach (var err in Sql4CdsError.GetAllErrors())
{
yield return new Entity
{
[PrefixWithAlias("message_id")] = (SqlInt32)err.Number,
[PrefixWithAlias("language_id")] = (SqlInt16)1033,
[PrefixWithAlias("severity")] = (SqlByte)err.Class,
[PrefixWithAlias("is_event_logged")] = SqlBoolean.False,
[PrefixWithAlias("text")] = dataSource.DefaultCollation.ToSqlString(err.Message)
};
}
break;

default:
throw new NotSupportedException("Unsupported function " + SystemFunction);
}
Expand All @@ -119,6 +147,26 @@ public override string ToString()

enum SystemFunction
{
fn_helpcollations
[SystemObjectType(SystemObjectType.Function)]
fn_helpcollations,

[SystemObjectType(SystemObjectType.View)]
messages
}

enum SystemObjectType
{
Function,
View
}

class SystemObjectTypeAttribute : Attribute
{
public SystemObjectTypeAttribute(SystemObjectType type)
{
Type = type;
}

public SystemObjectType Type { get; }
}
}
27 changes: 24 additions & 3 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlanBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using MarkMpn.Sql4Cds.Engine.ExecutionPlan;
using MarkMpn.Sql4Cds.Engine.Visitors;
Expand Down Expand Up @@ -644,8 +645,9 @@ private IRootExecutionPlanNodeInternal[] ConvertRaiseErrorStatement(RaiseErrorSt
raiserror.FirstParameter.GetType(ecc, out var msgType);

// T-SQL supports using integer values for RAISERROR but we don't have sys.messages available so require a string
if (!(msgType is SqlDataTypeReference msgSqlType) || !msgSqlType.SqlDataTypeOption.IsStringType())
throw new NotSupportedQueryFragmentException(Sql4CdsError.NotSupported(raiserror.FirstParameter, "predefined error number")) { Suggestion = "Define a message string instead of a number" };
if ((!(msgType is SqlDataTypeReference msgSqlType) || !msgSqlType.SqlDataTypeOption.IsStringType()) &&
!msgType.IsSameAs(DataTypeHelpers.Int))
throw new NotSupportedQueryFragmentException(Sql4CdsError.InvalidRaiseErrorParameterType(raiserror.FirstParameter, msgType, 1));

// Severity and State must be integers
raiserror.SecondParameter.GetType(ecc, out var severityType);
Expand Down Expand Up @@ -691,7 +693,8 @@ private IRootExecutionPlanNodeInternal[] ConvertRaiseErrorStatement(RaiseErrorSt
{
new RaiseErrorNode
{
ErrorMessage = raiserror.FirstParameter,
ErrorNumber = msgType.IsSameAs(DataTypeHelpers.Int) ? raiserror.FirstParameter : null,
ErrorMessage = msgType.IsSameAs(DataTypeHelpers.Int) ? null : raiserror.FirstParameter,
Severity = raiserror.SecondParameter,
State = raiserror.ThirdParameter,
Parameters = raiserror.OptionalParameters.ToArray()
Expand Down Expand Up @@ -4129,6 +4132,21 @@ private IDataExecutionPlanNodeInternal ConvertTableReference(TableReference refe

throw new NotSupportedQueryFragmentException(Sql4CdsError.InvalidObjectName(table.SchemaObject));
}
else if (table.SchemaObject.SchemaIdentifier?.Value.Equals("sys", StringComparison.OrdinalIgnoreCase) == true)
{
if (!Enum.TryParse<SystemFunction>(table.SchemaObject.BaseIdentifier.Value, true, out var systemFunction))
throw new NotSupportedQueryFragmentException(Sql4CdsError.InvalidObjectName(table.SchemaObject));

if (typeof(SystemFunction).GetField(systemFunction.ToString()).GetCustomAttribute<SystemObjectTypeAttribute>().Type != SystemObjectType.View)
throw new NotSupportedQueryFragmentException(Sql4CdsError.FunctionCalledWithoutParameters(table.SchemaObject));

return new SystemFunctionNode
{
DataSource = dataSource.Name,
Alias = table.Alias?.Value ?? systemFunction.ToString(),
SystemFunction = systemFunction
};
}

if (!String.IsNullOrEmpty(table.SchemaObject.SchemaIdentifier?.Value) &&
!table.SchemaObject.SchemaIdentifier.Value.Equals("dbo", StringComparison.OrdinalIgnoreCase) &&
Expand Down Expand Up @@ -4425,6 +4443,9 @@ private IDataExecutionPlanNodeInternal ConvertTableReference(TableReference refe
if (!Enum.TryParse<SystemFunction>(tvf.SchemaObject.BaseIdentifier.Value, true, out var systemFunction))
throw new NotSupportedQueryFragmentException(Sql4CdsError.InvalidObjectName(tvf.SchemaObject));

if (typeof(SystemFunction).GetField(systemFunction.ToString()).GetCustomAttribute<SystemObjectTypeAttribute>().Type != SystemObjectType.Function)
throw new NotSupportedQueryFragmentException(Sql4CdsError.NonFunctionCalledWithParameters(tvf.SchemaObject));

execute = new SystemFunctionNode
{
DataSource = dataSource.Name,
Expand Down
Loading

0 comments on commit 052d59c

Please sign in to comment.