Skip to content

Commit d7622c5

Browse files
committed
Miscellaneous quality improvements.
1 parent 401df3b commit d7622c5

9 files changed

Lines changed: 111 additions & 44 deletions

File tree

SqlServerSimulator.Tests/MergeViewTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ public void OutputThroughView_RaisesNotSupported()
261261
sim.ExecuteBatches(
262262
"create table base_t (id int primary key, v int)",
263263
"create view v_base as select id, v from base_t");
264-
_ = Throws<System.NotSupportedException>(() => sim.ExecuteNonQuery("""
264+
_ = Throws<NotSupportedException>(() => sim.ExecuteNonQuery("""
265265
merge into v_base using (values(1, 10)) src(id, v) on v_base.id = src.id
266266
when not matched then insert (id, v) values (src.id, src.v) output inserted.id;
267267
"""));
Lines changed: 100 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using SqlServerSimulator.Storage.Bacpac;
1+
using System.Reflection;
2+
using SqlServerSimulator.Storage.Bacpac;
23

34
namespace SqlServerSimulator;
45

@@ -9,45 +10,111 @@ public class QualityTests
910
[Description("Prevents unintentional expansion of the public API.")]
1011
public void PublicApiWhitelist()
1112
{
12-
var simulation = typeof(Simulation);
13-
14-
var types = simulation
13+
var publicTypes = typeof(Simulation)
1514
.Assembly
1615
.GetTypes()
1716
.Where(type => type.IsPublic)
1817
.ToArray();
1918

20-
HashSet<Type> allowedTypes = [
21-
simulation,
22-
typeof(TableValuedParameterExtensions),
23-
typeof(BacpacImportOptions),
24-
typeof(BacpacImportResult),
25-
typeof(BacpacSkipped),
26-
typeof(SimulatedDbConnection),
27-
typeof(SimulatedError),
28-
typeof(SimulatedErrorCollection),
29-
typeof(SimulatedInfoMessageEventArgs),
30-
];
31-
Assert.HasCount(allowedTypes.Count, types);
32-
foreach (var type in types)
33-
Assert.Contains(type, allowedTypes);
34-
35-
var memberNames = simulation
36-
.GetMembers()
37-
.Where(member => member.DeclaringType == simulation)
38-
.Select(member => member.Name)
39-
.ToHashSet();
19+
// Per-type whitelist of member names declared directly on the type.
20+
// Property / event / operator accessors and compiler-generated members
21+
// (record <Clone>$, the C# 14 extension's <G>$... nested type) are
22+
// filtered before comparison, so the entries below read as the
23+
// human-meaningful API surface.
24+
Dictionary<Type, HashSet<string>> allowedMembers = new()
25+
{
26+
[typeof(Simulation)] = [
27+
".ctor",
28+
nameof(Simulation.CreateDbConnection),
29+
nameof(Simulation.ImportBacpac),
30+
nameof(Simulation.AddRemoteSimulation),
31+
],
32+
[typeof(SimulatedDbConnection)] = [
33+
nameof(SimulatedDbConnection.InfoMessage),
34+
nameof(SimulatedDbConnection.ConnectionString),
35+
nameof(SimulatedDbConnection.Database),
36+
nameof(SimulatedDbConnection.DataSource),
37+
nameof(SimulatedDbConnection.ServerVersion),
38+
nameof(SimulatedDbConnection.State),
39+
nameof(SimulatedDbConnection.ChangeDatabase),
40+
nameof(SimulatedDbConnection.Close),
41+
nameof(SimulatedDbConnection.Open),
42+
],
43+
[typeof(SimulatedError)] = [
44+
nameof(SimulatedError.Class),
45+
nameof(SimulatedError.LineNumber),
46+
nameof(SimulatedError.Message),
47+
nameof(SimulatedError.Number),
48+
nameof(SimulatedError.Procedure),
49+
nameof(SimulatedError.Server),
50+
nameof(SimulatedError.Source),
51+
nameof(SimulatedError.State),
52+
nameof(SimulatedError.ToString),
53+
],
54+
[typeof(SimulatedErrorCollection)] = [
55+
"Item",
56+
nameof(SimulatedErrorCollection.Count),
57+
nameof(SimulatedErrorCollection.CopyTo),
58+
nameof(SimulatedErrorCollection.GetEnumerator),
59+
],
60+
[typeof(SimulatedInfoMessageEventArgs)] = [
61+
nameof(SimulatedInfoMessageEventArgs.Errors),
62+
nameof(SimulatedInfoMessageEventArgs.LineNumber),
63+
nameof(SimulatedInfoMessageEventArgs.Message),
64+
nameof(SimulatedInfoMessageEventArgs.Source),
65+
],
66+
// C# 14 lowers the extension(DbParameter) block to static
67+
// accessor methods on the host class plus a compiler-generated
68+
// <G>$... nested type carrying the cross-assembly metadata. The
69+
// nested type is filtered as compiler-generated; the accessors
70+
// surface as regular (non-special-name) methods here.
71+
[typeof(TableValuedParameterExtensions)] = [
72+
"get_TypeName",
73+
"set_TypeName",
74+
],
75+
[typeof(BacpacImportOptions)] = [
76+
".ctor",
77+
nameof(BacpacImportOptions.DatabaseName),
78+
nameof(BacpacImportOptions.MaxDegreeOfParallelism),
79+
nameof(Equals),
80+
nameof(GetHashCode),
81+
nameof(ToString),
82+
],
83+
[typeof(BacpacImportResult)] = [
84+
".ctor",
85+
nameof(BacpacImportResult.ElementCounts),
86+
nameof(BacpacImportResult.Skipped),
87+
nameof(BacpacImportResult.Warnings),
88+
],
89+
[typeof(BacpacSkipped)] = [
90+
".ctor",
91+
"Deconstruct",
92+
nameof(BacpacSkipped.ElementName),
93+
nameof(BacpacSkipped.ElementType),
94+
nameof(BacpacSkipped.Reason),
95+
nameof(Equals),
96+
nameof(GetHashCode),
97+
nameof(ToString),
98+
],
99+
};
40100

41-
HashSet<string> allowedMemberNames = [
42-
".ctor",
43-
nameof(Simulation.CreateDbConnection),
44-
nameof(Simulation.ImportBacpac),
45-
nameof(Simulation.AddRemoteSimulation),
46-
];
101+
Assert.HasCount(allowedMembers.Count, publicTypes);
102+
foreach (var type in publicTypes)
103+
Assert.Contains(type, allowedMembers.Keys);
47104

48-
Assert.HasCount(allowedMemberNames.Count, memberNames);
105+
foreach (var (type, allowedNames) in allowedMembers)
106+
{
107+
var memberNames = type
108+
.GetMembers()
109+
.Where(member => member.DeclaringType == type)
110+
.Where(member => member.Name[0] != '<')
111+
.Where(member => member is not MethodInfo mi || !mi.IsSpecialName)
112+
.Select(member => member.Name)
113+
.ToHashSet();
49114

50-
foreach (var name in memberNames)
51-
Assert.Contains(name, allowedMemberNames);
115+
Assert.HasCount(allowedNames.Count, memberNames, $"Member count mismatch on {type.FullName}");
116+
foreach (var name in memberNames)
117+
Assert.Contains(name, allowedNames, $"Unexpected public member '{name}' on {type.FullName}");
118+
}
52119
}
53120
}

SqlServerSimulator/Errors/SimulatedSqlException.TypeErrors.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ internal static SimulatedSqlException InvalidHierarchyIdInput(string detail) =>
439439
/// attached to a non-string expression (probe-confirmed wording:
440440
/// <c>"Expression type int is invalid for COLLATE clause."</c>).
441441
/// Real SQL Server raises this at bind time; the simulator raises at
442-
/// runtime because <see cref="Storage.SqlType"/> isn't fully bound
442+
/// runtime because <see cref="SqlType"/> isn't fully bound
443443
/// during the parse pass (column refs without a resolver are typed
444444
/// lazily). Same Msg + same wording; only the firing point differs.
445445
/// </summary>

SqlServerSimulator/LinkedServer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace SqlServerSimulator;
2121
/// <para>
2222
/// Writes through a four-part name (<c>INSERT</c>, <c>UPDATE</c>,
2323
/// <c>DELETE</c>, <c>MERGE</c> targeting <c>linkedserver.db.schema.t</c>)
24-
/// raise <see cref="System.NotSupportedException"/> at parse time. Lock-
24+
/// raise <see cref="NotSupportedException"/> at parse time. Lock-
2525
/// manager and undo-log coordination across <see cref="Simulation"/>
2626
/// boundaries isn't modeled; this mirrors the existing
2727
/// <see cref="System.Data.IsolationLevel"/>-related deferral for

SqlServerSimulator/Schemas/SchemaObject.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace SqlServerSimulator.Schemas;
44

55
/// <summary>
66
/// Common metadata base for every named, schema-resident database object —
7-
/// <see cref="Storage.HeapTable"/>, <see cref="View"/>,
7+
/// <see cref="HeapTable"/>, <see cref="View"/>,
88
/// <see cref="Procedure"/>, <see cref="UserDefinedFunction"/>,
99
/// <see cref="Sequence"/>, <see cref="TableType"/>, <see cref="Trigger"/>.
1010
/// The base unifies the fields every concrete type was previously
@@ -17,13 +17,13 @@ namespace SqlServerSimulator.Schemas;
1717
/// <remarks>
1818
/// <para>
1919
/// <see cref="Trigger.Parent"/> is typed as <c>SchemaObject</c> so a trigger
20-
/// can be attached to a <see cref="Storage.HeapTable"/> or a <see cref="View"/>
20+
/// can be attached to a <see cref="HeapTable"/> or a <see cref="View"/>
2121
/// without falling through <c>object</c>. DML hooks that need to dispatch on
2222
/// the trigger's parent take a <c>SchemaObject</c> parameter directly.
2323
/// </para>
2424
/// <para>
2525
/// <see cref="SchemaId"/> is stored as the integer ID for both the
26-
/// <c>SchemaId</c>-only types (<see cref="Storage.HeapTable"/>) and the
26+
/// <c>SchemaId</c>-only types (<see cref="HeapTable"/>) and the
2727
/// types that also carry a <see cref="Schema"/> reference (every other
2828
/// schema-object kind); those derived types continue to expose their
2929
/// <see cref="Schema"/> field independently — the base just keeps the

SqlServerSimulator/Simulation/Simulation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public void AddRemoteSimulation(string name, Simulation target)
9595
/// lazily seeds <see cref="DefaultDatabaseName"/> on first connection
9696
/// to a Simulation that has no databases (so the all-T-SQL use case
9797
/// keeps working without an explicit import / CREATE DATABASE).
98-
/// <see cref="ImportBacpac(System.IO.Stream, out Storage.Bacpac.BacpacImportResult, Storage.Bacpac.BacpacImportOptions?)"/>
98+
/// <see cref="ImportBacpac(Stream, out Storage.Bacpac.BacpacImportResult, Storage.Bacpac.BacpacImportOptions?)"/>
9999
/// adds further entries; <c>USE &lt;db&gt;</c> switches a session's
100100
/// <see cref="SimulatedDbConnection.CurrentDatabase"/> across entries
101101
/// (Msg 911 on miss). Fresh connections pick the lazy seed when present,

SqlServerSimulator/Storage/Bacpac/BacpacImportOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ namespace SqlServerSimulator.Storage.Bacpac;
22

33
/// <summary>
44
/// Options passed to <see cref="Simulation.ImportBacpac(string, out BacpacImportResult, BacpacImportOptions?)"/>
5-
/// and its <see cref="System.IO.Stream"/> overload. Defaulted-everything: the
5+
/// and its <see cref="Stream"/> overload. Defaulted-everything: the
66
/// parameterless constructor (and a <see langword="null"/>-defaulted
77
/// <c>options</c> argument) produce the simulator's standard import behavior.
88
/// </summary>
@@ -31,7 +31,7 @@ public sealed record class BacpacImportOptions
3131
/// <summary>
3232
/// Worker-thread cap for the per-table parallel data-load phase. <c>-1</c>
3333
/// (the default) uses <see cref="Environment.ProcessorCount"/>, matching
34-
/// the convention on <see cref="System.Threading.Tasks.ParallelOptions"/>.
34+
/// the convention on <see cref="ParallelOptions"/>.
3535
/// Positive values cap at that many workers; the loader still won't spin
3636
/// up more workers than there are work items.
3737
/// </summary>

SqlServerSimulator/Storage/Bacpac/BacpacImportResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ namespace SqlServerSimulator.Storage.Bacpac;
22

33
/// <summary>
44
/// Diagnostics carrier returned from <see cref="Simulation.ImportBacpac(string, out BacpacImportResult, BacpacImportOptions?)"/>
5-
/// and its <see cref="System.IO.Stream"/> overload. Tracks model.xml elements
5+
/// and its <see cref="Stream"/> overload. Tracks model.xml elements
66
/// the loader recognized but couldn't fully apply (e.g. a feature the
77
/// simulator doesn't model end-to-end) so the caller has a feature-gap report
88
/// rather than a single throw.

SqlServerSimulator/Storage/LockResource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ internal enum LockMode
3939
/// (each a <see cref="Hold"/> entry with owner / mode / re-entrance
4040
/// count). Every <see cref="SchemaObject"/> carries one via the inherited
4141
/// <see cref="SchemaObject.SchemaLock"/>; row-level locks live in
42-
/// <see cref="Storage.HeapTable.RowLocks"/>, lazily-interned per
42+
/// <see cref="HeapTable.RowLocks"/>, lazily-interned per
4343
/// <c>(pageIndex, slotIndex)</c>. All mutations to <see cref="Holders"/>
4444
/// happen under <see cref="LockManager"/>'s gate; the class itself has no
4545
/// logic.

0 commit comments

Comments
 (0)