Skip to content

Commit bb471ea

Browse files
committed
SSMS Object Explorer's Views and Programmability folders now enumerate: the views catalog gains principal_id / create_date / modify_date / is_ms_shipped / ledger_view_type and the objects family gains principal_id; sequences gain create_date / modify_date / principal_id; sys.types gains max_length / precision / scale; new catalog surface sys.database_files, sys.plan_guides, sys.assembly_types; xp_instance_regread modeled alongside xp_msver.
1 parent 9f71784 commit bb471ea

10 files changed

Lines changed: 587 additions & 6 deletions
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
2+
3+
namespace SqlServerSimulator;
4+
5+
/// <summary>
6+
/// Tests for the catalog surface SSMS Object Explorer's Views and
7+
/// Programmability nodes read via SMO: <c>sys.all_views</c> (create_date /
8+
/// principal_id / is_ms_shipped / ledger_view_type), the <c>principal_id</c>
9+
/// column SMO's function / procedure enumeration reads off
10+
/// <c>sys.all_objects</c>, <c>sys.sequences</c> (create_date / principal_id —
11+
/// the missing columns that left the Sequences node empty),
12+
/// <c>sys.types.max_length</c> / precision / scale (User-Defined Data Types
13+
/// node), <c>sys.assembly_types</c> (the three CLR system types), the empty
14+
/// <c>sys.plan_guides</c> view, and <c>sys.database_files</c> (current-database
15+
/// projection of <c>sys.master_files</c>, cross-database resolvable). Shapes /
16+
/// values probed against SQL Server 2025 (2026-07-15).
17+
/// </summary>
18+
[TestClass]
19+
public sealed class SsmsProgrammabilityNodeCatalogTests
20+
{
21+
[TestMethod]
22+
public void AllViews_ExposesCreateDateNotNull()
23+
{
24+
var sim = new Simulation();
25+
sim.ExecuteBatches("create view v as select 1 as c");
26+
AreEqual(1, sim.ExecuteScalar<int>(
27+
"select count(*) from sys.all_views where name = 'v' and create_date is not null and modify_date is not null"));
28+
}
29+
30+
[TestMethod]
31+
public void AllViews_PrincipalIdIsNull()
32+
{
33+
var sim = new Simulation();
34+
sim.ExecuteBatches("create view v as select 1 as c");
35+
AreEqual(1, sim.ExecuteScalar<int>("select count(*) from sys.all_views where name = 'v' and principal_id is null"));
36+
}
37+
38+
[TestMethod]
39+
public void AllViews_IsMsShippedAndLedgerViewTypeAreZero()
40+
{
41+
var sim = new Simulation();
42+
sim.ExecuteBatches("create view v as select 1 as c");
43+
AreEqual(0, sim.ExecuteScalar<int>("select cast(is_ms_shipped as int) from sys.all_views where name = 'v'"));
44+
AreEqual(0, sim.ExecuteScalar<int>("select cast(ledger_view_type as int) from sys.all_views where name = 'v'"));
45+
}
46+
47+
[TestMethod]
48+
public void AllObjects_ExposesPrincipalId_NullForUserFunction()
49+
{
50+
var sim = new Simulation();
51+
sim.ExecuteBatches("create function dbo.f(@x int) returns int as begin return @x + 1 end");
52+
AreEqual(1, sim.ExecuteScalar<int>(
53+
"select count(*) from sys.all_objects where name = 'f' and type = 'FN' and principal_id is null"));
54+
}
55+
56+
[TestMethod]
57+
public void Objects_ExposesPrincipalId_NullForUserProcedure()
58+
{
59+
var sim = new Simulation();
60+
sim.ExecuteBatches("create procedure dbo.p as select 1");
61+
AreEqual(1, sim.ExecuteScalar<int>(
62+
"select count(*) from sys.objects where name = 'p' and type = 'P' and principal_id is null"));
63+
}
64+
65+
[TestMethod]
66+
public void Sequences_ExposeCreateDateAndPrincipalId()
67+
{
68+
var sim = new Simulation();
69+
_ = sim.ExecuteNonQuery("create sequence dbo.s as int start with 1 increment by 1");
70+
AreEqual(1, sim.ExecuteScalar<int>(
71+
"select count(*) from sys.sequences where name = 's' and create_date is not null and principal_id is null"));
72+
}
73+
74+
[TestMethod]
75+
public void Types_ScalarAliasReportsUnderlyingByteWidth()
76+
{
77+
var sim = new Simulation();
78+
_ = sim.ExecuteNonQuery("create type dbo.MyStr from nvarchar(50)");
79+
// nvarchar(50) byte width is 100; SMO halves it for the display Length.
80+
AreEqual(100, sim.ExecuteScalar<int>("select cast(max_length as int) from sys.types where name = 'MyStr'"));
81+
}
82+
83+
[TestMethod]
84+
public void Types_ScalarAliasReportsPrecisionAndScale()
85+
{
86+
var sim = new Simulation();
87+
_ = sim.ExecuteNonQuery("create type dbo.Amt from decimal(10, 2)");
88+
AreEqual(9, sim.ExecuteScalar<int>("select cast(max_length as int) from sys.types where name = 'Amt'"));
89+
AreEqual(10, sim.ExecuteScalar<int>("select cast(precision as int) from sys.types where name = 'Amt'"));
90+
AreEqual(2, sim.ExecuteScalar<int>("select cast(scale as int) from sys.types where name = 'Amt'"));
91+
}
92+
93+
[TestMethod]
94+
public void Types_SystemTypeMaxLengthMatchesSystypes()
95+
{
96+
var sim = new Simulation();
97+
AreEqual(4, sim.ExecuteScalar<int>("select cast(max_length as int) from sys.types where name = 'int'"));
98+
AreEqual(8000, sim.ExecuteScalar<int>("select cast(max_length as int) from sys.types where name = 'nvarchar'"));
99+
AreEqual(8016, sim.ExecuteScalar<int>("select cast(max_length as int) from sys.types where name = 'sql_variant'"));
100+
}
101+
102+
[TestMethod]
103+
public void AssemblyTypes_ProjectsThreeClrSystemTypes()
104+
=> AreEqual(3, new Simulation().ExecuteScalar<int>("select count(*) from sys.assembly_types"));
105+
106+
[TestMethod]
107+
public void AssemblyTypes_HierarchyidShape()
108+
{
109+
var sim = new Simulation();
110+
AreEqual(892, sim.ExecuteScalar<int>("select cast(max_length as int) from sys.assembly_types where name = 'hierarchyid'"));
111+
AreEqual(240, sim.ExecuteScalar<int>("select cast(system_type_id as int) from sys.assembly_types where name = 'hierarchyid'"));
112+
AreEqual(128, sim.ExecuteScalar<int>("select user_type_id from sys.assembly_types where name = 'hierarchyid'"));
113+
AreEqual(4, sim.ExecuteScalar<int>("select schema_id from sys.assembly_types where name = 'hierarchyid'"));
114+
AreEqual(1, sim.ExecuteScalar<int>("select assembly_id from sys.assembly_types where name = 'hierarchyid'"));
115+
AreEqual(1, sim.ExecuteScalar<int>("select cast(is_assembly_type as int) from sys.assembly_types where name = 'hierarchyid'"));
116+
AreEqual(0, sim.ExecuteScalar<int>("select cast(is_user_defined as int) from sys.assembly_types where name = 'hierarchyid'"));
117+
}
118+
119+
[TestMethod]
120+
public void AssemblyTypes_SpatialTypesReportMaxLengthMinusOne()
121+
{
122+
var sim = new Simulation();
123+
AreEqual(-1, sim.ExecuteScalar<int>("select cast(max_length as int) from sys.assembly_types where name = 'geometry'"));
124+
AreEqual(-1, sim.ExecuteScalar<int>("select cast(max_length as int) from sys.assembly_types where name = 'geography'"));
125+
}
126+
127+
/// <summary>
128+
/// SMO's User-Defined Types node filters is_user_defined = 1; the three
129+
/// system CLR types all report 0, so the node lists nothing (matching a
130+
/// WWI database with no user CLR types).
131+
/// </summary>
132+
[TestMethod]
133+
public void AssemblyTypes_UserDefinedFilterReturnsEmpty()
134+
=> AreEqual(0, new Simulation().ExecuteScalar<int>("select count(*) from sys.assembly_types where is_user_defined = 1"));
135+
136+
[TestMethod]
137+
public void PlanGuides_IsEmpty()
138+
=> AreEqual(0, new Simulation().ExecuteScalar<int>("select count(*) from sys.plan_guides"));
139+
140+
[TestMethod]
141+
public void PlanGuides_ExposesNameAndIsDisabled()
142+
{
143+
using var reader = new Simulation().ExecuteReader("select name, is_disabled from sys.plan_guides");
144+
AreEqual(2, reader.FieldCount);
145+
AreEqual("name", reader.GetName(0));
146+
AreEqual("is_disabled", reader.GetName(1));
147+
IsFalse(reader.Read());
148+
}
149+
150+
[TestMethod]
151+
public void DatabaseFiles_ReturnsDataAndLogRow()
152+
{
153+
var sim = new Simulation();
154+
AreEqual(2, sim.ExecuteScalar<int>("select count(*) from sys.database_files"));
155+
AreEqual(0, sim.ExecuteScalar<int>("select cast(type as int) from sys.database_files where file_id = 1"));
156+
AreEqual(1, sim.ExecuteScalar<int>("select cast(type as int) from sys.database_files where file_id = 2"));
157+
}
158+
159+
[TestMethod]
160+
public void DatabaseFiles_AgreesWithMasterFilesOnName()
161+
{
162+
var sim = new Simulation();
163+
var fromDatabaseFiles = (string?)sim.ExecuteScalar("select name from sys.database_files where file_id = 1");
164+
var fromMasterFiles = (string?)sim.ExecuteScalar(
165+
"select name from sys.master_files where database_id = db_id() and file_id = 1");
166+
AreEqual(fromMasterFiles, fromDatabaseFiles);
167+
IsTrue(fromDatabaseFiles!.EndsWith("_Data", StringComparison.Ordinal));
168+
}
169+
170+
[TestMethod]
171+
public void DatabaseFiles_ResolvesCrossDatabaseThroughMaster()
172+
{
173+
var sim = new Simulation();
174+
AreEqual(2, sim.ExecuteScalar<int>("select count(*) from master.sys.database_files"));
175+
AreEqual("master_Data", (string?)sim.ExecuteScalar("select name from master.sys.database_files where file_id = 1"));
176+
AreEqual("master_Log", (string?)sim.ExecuteScalar("select name from master.sys.database_files where file_id = 2"));
177+
}
178+
179+
[TestMethod]
180+
public void ViewsNode_TrimmedSmoQuery_ReturnsCreatedView()
181+
{
182+
var sim = new Simulation();
183+
sim.ExecuteBatches("create view dbo.v as select 1 as c");
184+
using var reader = sim.ExecuteReader("""
185+
select v.name, schema_name(v.schema_id) as [Schema], v.create_date,
186+
cast(isnull(v.ledger_view_type, 0) as tinyint) as ledger
187+
from sys.all_views as v
188+
left outer join sys.database_principals as sv
189+
on sv.principal_id = isnull(v.principal_id, objectproperty(v.object_id, 'OwnerId'))
190+
where v.type = N'V' and cast(v.is_ms_shipped as bit) = 0
191+
order by [Schema] asc, v.name asc
192+
""");
193+
IsTrue(reader.Read());
194+
AreEqual("v", reader.GetString(0));
195+
AreEqual("dbo", reader.GetString(1));
196+
IsFalse(reader.Read());
197+
}
198+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
2+
3+
namespace SqlServerSimulator;
4+
5+
/// <summary>
6+
/// Tests for the <c>xp_instance_regread</c> system procedure. SSMS reads the
7+
/// instance <c>SQLPath</c> registry value on connect (via
8+
/// <c>master.dbo.xp_instance_regread ... N'SQLPath', @out OUTPUT</c>) to derive
9+
/// the SMO RootDirectory. The simulator returns a synthetic instance path
10+
/// rooted at <c>/var/opt/mssql</c> (consistent with the physical paths surfaced
11+
/// by <c>sys.master_files</c> / <c>sys.database_files</c>); values are
12+
/// machine-specific on a real server. The OUTPUT form writes into the caller's
13+
/// variable and yields no result set; the no-OUTPUT form returns a
14+
/// <c>(Value, Data)</c> result set (probe-confirmed against SQL Server 2025).
15+
/// </summary>
16+
[TestClass]
17+
public sealed class XpInstanceRegreadTests
18+
{
19+
[TestMethod]
20+
public void SqlPath_OutputForm_ReturnsSyntheticInstanceRoot()
21+
=> AreEqual("/var/opt/mssql", new Simulation().ExecuteScalar("""
22+
declare @v nvarchar(512);
23+
exec master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'SQLPath', @v OUTPUT;
24+
select @v
25+
"""));
26+
27+
[TestMethod]
28+
public void UnknownValueName_OutputForm_YieldsNull()
29+
=> AreEqual(1, new Simulation().ExecuteScalar<int>("""
30+
declare @v nvarchar(512);
31+
exec master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'DoesNotExist', @v OUTPUT;
32+
select case when @v is null then 1 else 0 end
33+
"""));
34+
35+
[TestMethod]
36+
public void NoOutputParameter_ReturnsValueDataResultSet()
37+
{
38+
using var reader = new Simulation().ExecuteReader(
39+
@"exec master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'SQLPath'");
40+
AreEqual(2, reader.FieldCount);
41+
AreEqual("Value", reader.GetName(0));
42+
AreEqual("Data", reader.GetName(1));
43+
IsTrue(reader.Read());
44+
AreEqual("SQLPath", reader.GetString(0));
45+
AreEqual("/var/opt/mssql", reader.GetString(1));
46+
IsFalse(reader.Read());
47+
}
48+
49+
[TestMethod]
50+
public void CallableWithoutMasterDboQualifier()
51+
=> AreEqual("/var/opt/mssql", new Simulation().ExecuteScalar("""
52+
declare @v nvarchar(512);
53+
exec xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'SQLPath', @v OUTPUT;
54+
select @v
55+
"""));
56+
}

SqlServerSimulator/BuiltInResources.CoreObjects.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ void Sys(string name, HeapColumn[] columns, Func<Parser.BatchContext, Database,
171171
new("name", SqlType.SystemName, 128, true),
172172
new("schema_id", SqlType.Int32, null, false),
173173
new("parent_object_id", SqlType.Int32, null, false),
174+
// No explicit object owner is modeled (ownership follows the
175+
// schema), so principal_id is always NULL — matching real SQL
176+
// Server for objects without an AUTHORIZATION override. SMO's
177+
// Object-Explorer function / procedure / sequence enumeration
178+
// reads ISNULL(o.principal_id, OBJECTPROPERTY(o.object_id,'OwnerId')).
179+
new("principal_id", SqlType.Int32, null, true),
174180
new("type", charTwo, 2, true),
175181
new("type_desc", nvarchar60Catalog, 60, true),
176182
new("create_date", SqlType.DateTime, null, false),
@@ -189,6 +195,7 @@ void Sys(string name, HeapColumn[] columns, Func<Parser.BatchContext, Database,
189195
new("name", SqlType.SystemName, 128, true),
190196
new("schema_id", SqlType.Int32, null, false),
191197
new("parent_object_id", SqlType.Int32, null, false),
198+
new("principal_id", SqlType.Int32, null, true),
192199
new("type", charTwo, 2, true),
193200
new("type_desc", nvarchar60Catalog, 60, true),
194201
new("create_date", SqlType.DateTime, null, false),
@@ -569,6 +576,7 @@ private static IEnumerable<SqlValue[]> EnumerateObjects(
569576
SqlValue zeroParent, SqlValue notMsShipped)
570577
{
571578
_ = batch;
579+
var nullPrincipal = SqlValue.Null(SqlType.Int32);
572580
foreach (var schema in database.Schemas.Values)
573581
{
574582
// Schema-resident objects in ObjectId order. SchemaObject's
@@ -586,6 +594,7 @@ private static IEnumerable<SqlValue[]> EnumerateObjects(
586594
SqlValue.FromSystemName(obj.Name),
587595
SqlValue.FromInt32(obj.SchemaId),
588596
parent,
597+
nullPrincipal,
589598
SqlValue.FromChar(charTwo, obj.ObjectTypeCode),
590599
SqlValue.FromNVarchar(obj.ObjectTypeDescription),
591600
SqlValue.FromDateTime(obj.CreateDate),
@@ -609,6 +618,7 @@ private static IEnumerable<SqlValue[]> EnumerateObjects(
609618
SqlValue.FromSystemName(key.Name),
610619
schemaIdValue,
611620
tableObjectId,
621+
nullPrincipal,
612622
key.Kind == KeyConstraintKind.PrimaryKey ? pkType : uqType,
613623
key.Kind == KeyConstraintKind.PrimaryKey ? pkTypeDesc : uqTypeDesc,
614624
createDate,
@@ -623,6 +633,7 @@ private static IEnumerable<SqlValue[]> EnumerateObjects(
623633
SqlValue.FromSystemName(chk.Name),
624634
schemaIdValue,
625635
tableObjectId,
636+
nullPrincipal,
626637
checkType,
627638
checkTypeDesc,
628639
createDate,
@@ -637,6 +648,7 @@ private static IEnumerable<SqlValue[]> EnumerateObjects(
637648
SqlValue.FromSystemName(fk.Name),
638649
schemaIdValue,
639650
tableObjectId,
651+
nullPrincipal,
640652
SqlValue.FromChar(charTwo, "F "),
641653
SqlValue.FromNVarchar("FOREIGN_KEY_CONSTRAINT"),
642654
createDate,

0 commit comments

Comments
 (0)