Skip to content

Commit 4abfbb3

Browse files
committed
Changed some frozen-dictionary-of-delegates dispatch into uppercase-span switches with added length limiters.
1 parent 351b349 commit 4abfbb3

8 files changed

Lines changed: 248 additions & 168 deletions

File tree

SqlServerSimulator/Parser/Expressions/CollationProperty.cs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Collections.Frozen;
21
using SqlServerSimulator.Storage;
32

43
namespace SqlServerSimulator.Parser.Expressions;
@@ -17,15 +16,6 @@ namespace SqlServerSimulator.Parser.Expressions;
1716
/// </summary>
1817
internal sealed class CollationProperty : Expression
1918
{
20-
private static readonly FrozenDictionary<string, Func<Collation.CollationMetrics, SqlValue>> Properties = new Dictionary<string, Func<Collation.CollationMetrics, SqlValue>>
21-
{
22-
["CodePage"] = m => SqlValue.FromInt32(m.CodePage),
23-
["LCID"] = m => SqlValue.FromInt32(m.Lcid),
24-
["ComparisonStyle"] = m => SqlValue.FromInt32(m.ComparisonStyle),
25-
["Version"] = m => SqlValue.FromByte(checked((byte)m.Version)),
26-
["Name"] = m => SqlValue.FromNVarchar(m.Name),
27-
}.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
28-
2919
private readonly Expression collationArg;
3020
private readonly Expression propertyArg;
3121

@@ -45,10 +35,29 @@ public override SqlValue Run(RuntimeContext runtime)
4535
var collationValue = this.collationArg.Run(runtime);
4636
var propertyValue = this.propertyArg.Run(runtime);
4737
return collationValue.IsNull || propertyValue.IsNull
48-
|| !Properties.TryGetValue(propertyValue.CoerceTo(SqlType.NVarchar).AsString, out var produce)
4938
|| !Collation.TryGetMetrics(collationValue.CoerceTo(SqlType.NVarchar).AsString, out var metrics)
5039
? SqlValue.Null(SqlType.SqlVariant)
51-
: SqlValue.FromVariant(produce(metrics));
40+
: Produce(propertyValue.CoerceTo(SqlType.NVarchar).AsString, metrics);
41+
}
42+
43+
private static SqlValue Produce(string property, Collation.CollationMetrics metrics)
44+
{
45+
// Longer than any recognized property name; also bounds the stackalloc
46+
// against an adversarially long argument.
47+
if (property.Length > 32)
48+
return SqlValue.Null(SqlType.SqlVariant);
49+
Span<char> upper = stackalloc char[property.Length];
50+
_ = property.AsSpan().ToUpperInvariant(upper);
51+
var inner = upper switch
52+
{
53+
"CODEPAGE" => SqlValue.FromInt32(metrics.CodePage),
54+
"COMPARISONSTYLE" => SqlValue.FromInt32(metrics.ComparisonStyle),
55+
"LCID" => SqlValue.FromInt32(metrics.Lcid),
56+
"NAME" => SqlValue.FromNVarchar(metrics.Name),
57+
"VERSION" => SqlValue.FromByte(checked((byte)metrics.Version)),
58+
_ => SqlValue.Null(SqlType.SqlVariant),
59+
};
60+
return inner.Type is SqlVariantSqlType ? inner : SqlValue.FromVariant(inner);
5261
}
5362

5463
public override SqlType GetSqlType(BatchContext batch, Func<MultiPartName, SqlType> resolveColumnType) => SqlType.SqlVariant;

SqlServerSimulator/Parser/Expressions/DatabasePropertyEx.cs

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Collections.Frozen;
21
using SqlServerSimulator.Storage;
32

43
namespace SqlServerSimulator.Parser.Expressions;
@@ -7,8 +6,8 @@ namespace SqlServerSimulator.Parser.Expressions;
76
/// SQL <c>DATABASEPROPERTYEX(database_name, property_name)</c>: returns
87
/// the named property of a database. Real SQL Server projects this as
98
/// <c>sql_variant</c> carrying a per-property inner base type; the
10-
/// simulator doesn't model sql_variant, so it surfaces the bare true type
11-
/// instead — numeric properties as <see cref="SqlType.Int32"/> /
9+
/// simulator doesn't model sql_variant here, so it surfaces the bare true
10+
/// type instead — numeric properties as <see cref="SqlType.Int32"/> /
1211
/// <see cref="SqlType.TinyInt"/>, string properties as
1312
/// <see cref="SqlType.NVarchar"/>. When the property-name argument is a
1413
/// compile-time constant the true type flows to the projection schema;
@@ -26,35 +25,13 @@ namespace SqlServerSimulator.Parser.Expressions;
2625
/// <c>SnapshotIsolationState</c>, <c>IsReadCommittedSnapshotOn</c>,
2726
/// <c>ComparisonStyle</c>, <c>LCID</c>, <c>SQLSortOrder</c>, <c>Version</c>.
2827
/// Properties not on the list return NULL (forward-compatible with future
29-
/// tooling that may query newer property names).
28+
/// tooling that may query newer property names). <see cref="Produce"/> and
29+
/// <see cref="TypeOf"/> carry mirrored arm lists — every property appears in
30+
/// both, with <see cref="TypeOf"/> declaring the type the matching
31+
/// <see cref="Produce"/> arm's non-NULL value carries.
3032
/// </remarks>
3133
internal sealed class DatabasePropertyEx : Expression
3234
{
33-
private static readonly FrozenDictionary<string, (SqlType Type, Func<Database, SqlValue> Produce)> Properties = new Dictionary<string, (SqlType Type, Func<Database, SqlValue> Produce)>
34-
{
35-
["Status"] = (SqlType.NVarchar, _ => SqlValue.FromNVarchar("ONLINE")),
36-
["Version"] = (SqlType.Int32, _ => SqlValue.FromInt32(0)),
37-
["Recovery"] = (SqlType.NVarchar, _ => SqlValue.FromNVarchar("FULL")),
38-
// Updateability is always READ_WRITE (the simulator models no
39-
// read-only databases at the DATABASEPROPERTYEX surface). SMO's
40-
// database-properties preamble reads it as [IsUpdateable].
41-
["Updateability"] = (SqlType.NVarchar, _ => SqlValue.FromNVarchar("READ_WRITE")),
42-
// DBCC CHECKDB isn't modeled, so the last-good-checkdb time is NULL —
43-
// typed datetime (not the unknown-property NVarchar) so SMO's
44-
// CAST(ISNULL(..., 0) AS datetime) resolves to 1900-01-01 rather than
45-
// failing to convert the string '0' to datetime.
46-
["LastGoodCheckDbTime"] = (SqlType.DateTime, _ => SqlValue.Null(SqlType.DateTime)),
47-
["Collation"] = (SqlType.NVarchar, db => SqlValue.FromNVarchar(db.CollationName)),
48-
["UserAccess"] = (SqlType.NVarchar, _ => SqlValue.FromNVarchar("MULTI_USER")),
49-
["IsAutoClose"] = (SqlType.Int32, _ => SqlValue.FromInt32(0)),
50-
["IsAutoShrink"] = (SqlType.Int32, _ => SqlValue.FromInt32(0)),
51-
["SnapshotIsolationState"] = (SqlType.Int32, db => SqlValue.FromInt32(db.AllowSnapshotIsolation ? 1 : 0)),
52-
["IsReadCommittedSnapshotOn"] = (SqlType.Int32, db => SqlValue.FromInt32(db.ReadCommittedSnapshot ? 1 : 0)),
53-
["ComparisonStyle"] = (SqlType.Int32, _ => SqlValue.FromInt32(196609)),
54-
["LCID"] = (SqlType.Int32, _ => SqlValue.FromInt32(1033)),
55-
["SQLSortOrder"] = (SqlType.TinyInt, db => SqlValue.FromByte(SortIdFor(db.CollationName))),
56-
}.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
57-
5835
private readonly Expression dbNameArg;
5936
private readonly Expression propertyArg;
6037

@@ -83,21 +60,80 @@ public override SqlValue Run(RuntimeContext runtime)
8360
if (!runtime.Batch.Connection.Simulation.Databases.TryGetValue(dbName, out var db))
8461
return SqlValue.Null(SqlType.NVarchar);
8562

86-
if (!Properties.TryGetValue(property, out var def))
87-
return SqlValue.Null(SqlType.NVarchar);
88-
89-
var value = def.Produce(db);
63+
var value = Produce(property, db);
9064
// A non-constant property name couldn't resolve a true type at parse
9165
// time (GetSqlType fell back to NVarchar); coerce so runtime agrees.
9266
return this.propertyArg is Value ? value : value.CoerceTo(SqlType.NVarchar);
9367
}
9468

9569
public override SqlType GetSqlType(BatchContext batch, Func<MultiPartName, SqlType> resolveColumnType)
9670
=> this.propertyArg is Value { Constant: { IsNull: false } constant }
97-
&& Properties.TryGetValue(constant.CoerceTo(SqlType.NVarchar).AsString, out var def)
98-
? def.Type
71+
? TypeOf(constant.CoerceTo(SqlType.NVarchar).AsString)
9972
: SqlType.NVarchar;
10073

74+
private static SqlValue Produce(string property, Database db)
75+
{
76+
// Longer than any recognized property name; also bounds the stackalloc
77+
// against an adversarially long argument.
78+
if (property.Length > 32)
79+
return SqlValue.Null(SqlType.NVarchar);
80+
Span<char> upper = stackalloc char[property.Length];
81+
_ = property.AsSpan().ToUpperInvariant(upper);
82+
return upper switch
83+
{
84+
"COLLATION" => SqlValue.FromNVarchar(db.CollationName),
85+
"COMPARISONSTYLE" => SqlValue.FromInt32(196609),
86+
"ISAUTOCLOSE" => SqlValue.FromInt32(0),
87+
"ISAUTOSHRINK" => SqlValue.FromInt32(0),
88+
"ISREADCOMMITTEDSNAPSHOTON" => SqlValue.FromInt32(db.ReadCommittedSnapshot ? 1 : 0),
89+
// DBCC CHECKDB isn't modeled, so the last-good-checkdb time is
90+
// NULL — typed datetime (not the unknown-property NVarchar) so
91+
// SMO's CAST(ISNULL(..., 0) AS datetime) resolves to 1900-01-01
92+
// rather than failing to convert the string '0' to datetime.
93+
"LASTGOODCHECKDBTIME" => SqlValue.Null(SqlType.DateTime),
94+
"LCID" => SqlValue.FromInt32(1033),
95+
"RECOVERY" => SqlValue.FromNVarchar("FULL"),
96+
"SNAPSHOTISOLATIONSTATE" => SqlValue.FromInt32(db.AllowSnapshotIsolation ? 1 : 0),
97+
"SQLSORTORDER" => SqlValue.FromByte(SortIdFor(db.CollationName)),
98+
"STATUS" => SqlValue.FromNVarchar("ONLINE"),
99+
// Updateability is always READ_WRITE (the simulator models no
100+
// read-only databases at the DATABASEPROPERTYEX surface). SMO's
101+
// database-properties preamble reads it as [IsUpdateable].
102+
"UPDATEABILITY" => SqlValue.FromNVarchar("READ_WRITE"),
103+
"USERACCESS" => SqlValue.FromNVarchar("MULTI_USER"),
104+
"VERSION" => SqlValue.FromInt32(0),
105+
_ => SqlValue.Null(SqlType.NVarchar),
106+
};
107+
}
108+
109+
private static SqlType TypeOf(string property)
110+
{
111+
// Same bound as Produce so a long name types as the unknown-property
112+
// NVarchar it evaluates to.
113+
if (property.Length > 32)
114+
return SqlType.NVarchar;
115+
Span<char> upper = stackalloc char[property.Length];
116+
_ = property.AsSpan().ToUpperInvariant(upper);
117+
return upper switch
118+
{
119+
"COLLATION" => SqlType.NVarchar,
120+
"COMPARISONSTYLE" => SqlType.Int32,
121+
"ISAUTOCLOSE" => SqlType.Int32,
122+
"ISAUTOSHRINK" => SqlType.Int32,
123+
"ISREADCOMMITTEDSNAPSHOTON" => SqlType.Int32,
124+
"LASTGOODCHECKDBTIME" => SqlType.DateTime,
125+
"LCID" => SqlType.Int32,
126+
"RECOVERY" => SqlType.NVarchar,
127+
"SNAPSHOTISOLATIONSTATE" => SqlType.Int32,
128+
"SQLSORTORDER" => SqlType.TinyInt,
129+
"STATUS" => SqlType.NVarchar,
130+
"UPDATEABILITY" => SqlType.NVarchar,
131+
"USERACCESS" => SqlType.NVarchar,
132+
"VERSION" => SqlType.Int32,
133+
_ => SqlType.NVarchar,
134+
};
135+
}
136+
101137
// Derive the SQL sort-order id from the collation name; real SQL Server
102138
// reports 0 for collations with no SQL_* sort order.
103139
private static byte SortIdFor(string collationName)

SqlServerSimulator/Parser/Expressions/FullTextServiceProperty.cs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Collections.Frozen;
21
using SqlServerSimulator.Storage;
32

43
namespace SqlServerSimulator.Parser.Expressions;
@@ -19,15 +18,6 @@ namespace SqlServerSimulator.Parser.Expressions;
1918
/// </summary>
2019
internal sealed class FullTextServiceProperty : Expression
2120
{
22-
private static readonly FrozenDictionary<string, int> Properties = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
23-
{
24-
["IsFullTextInstalled"] = 1,
25-
["ConnectTimeout"] = 0,
26-
["LoadOSResources"] = 0,
27-
["ResourceUsage"] = 0,
28-
["VerifyResourceUsage"] = 0,
29-
}.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
30-
3121
private readonly Expression nameArg;
3222

3323
public FullTextServiceProperty(ParserContext context)
@@ -43,9 +33,21 @@ public override SqlValue Run(RuntimeContext runtime)
4333
if (n.IsNull)
4434
return SqlValue.Null(SqlType.Int32);
4535
var name = n.CoerceTo(SqlType.NVarchar).AsString;
46-
return Properties.TryGetValue(name, out var value)
47-
? SqlValue.FromInt32(value)
48-
: SqlValue.Null(SqlType.Int32);
36+
// Longer than any recognized property name; also bounds the stackalloc
37+
// against an adversarially long argument.
38+
if (name.Length > 32)
39+
return SqlValue.Null(SqlType.Int32);
40+
Span<char> upper = stackalloc char[name.Length];
41+
_ = name.AsSpan().ToUpperInvariant(upper);
42+
return upper switch
43+
{
44+
"CONNECTTIMEOUT" => SqlValue.FromInt32(0),
45+
"ISFULLTEXTINSTALLED" => SqlValue.FromInt32(1),
46+
"LOADOSRESOURCES" => SqlValue.FromInt32(0),
47+
"RESOURCEUSAGE" => SqlValue.FromInt32(0),
48+
"VERIFYRESOURCEUSAGE" => SqlValue.FromInt32(0),
49+
_ => SqlValue.Null(SqlType.Int32),
50+
};
4951
}
5052

5153
public override SqlType GetSqlType(BatchContext batch, Func<MultiPartName, SqlType> resolveColumnType)

SqlServerSimulator/Parser/Expressions/LoginProperty.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ public override SqlValue Run(RuntimeContext runtime)
7979
return SqlValue.Null(SqlType.SqlVariant);
8080

8181
var property = propertyValue.CoerceTo(SqlType.NVarchar).AsString;
82+
// Longer than any recognized property name; also bounds the stackalloc
83+
// against an adversarially long argument.
84+
if (property.Length > 32)
85+
return SqlValue.Null(SqlType.SqlVariant);
8286
Span<char> upper = stackalloc char[property.Length];
8387
_ = property.AsSpan().ToUpperInvariant(upper);
8488
// Each property carries its probed inner base type; the null-valued

0 commit comments

Comments
 (0)