Skip to content

Commit d43c0d0

Browse files
committed
The sql_variant migration is completed across the remaining inner-type-substitution sites: DATABASEPROPERTYEX, OBJECTPROPERTYEX, sys.sequences and sys.identity_columns value columns wrapping their declared types, sys.partition_range_values.value, the security-view thumbprint/algid columns, and sys.parameters.default_value.
1 parent c1ffa69 commit d43c0d0

15 files changed

Lines changed: 291 additions & 170 deletions

SqlServerSimulator.Tests/CollationMetadataTests.cs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,15 +221,43 @@ public void DatabasePropertyEx_Version_ReturnsZeroAsInt()
221221
"SELECT DATABASEPROPERTYEX('simulated', 'Version')"));
222222

223223
[TestMethod]
224-
public void DatabasePropertyEx_Version_SurfacesAsIntType()
224+
public void DatabasePropertyEx_Version_SurfacesAsSqlVariant()
225225
{
226+
// Like real SQL Server, DATABASEPROPERTYEX projects sql_variant; the
227+
// Version cell carries an int inner base type, which SqlClient unwraps.
226228
using var reader = new Simulation().ExecuteReader(
227229
"SELECT DATABASEPROPERTYEX('simulated', 'Version')");
228230
IsTrue(reader.Read());
229-
AreEqual("int", reader.GetDataTypeName(0));
231+
AreEqual("sql_variant", reader.GetDataTypeName(0));
230232
_ = Assert.IsInstanceOfType<int>(reader.GetValue(0));
231233
}
232234

235+
[TestMethod]
236+
public void DatabasePropertyEx_Version_InnerBaseTypeIsInt()
237+
=> AreEqual("int", new Simulation().ExecuteScalar(
238+
"SELECT SQL_VARIANT_PROPERTY(DATABASEPROPERTYEX('simulated', 'Version'), 'BaseType')"));
239+
240+
[TestMethod]
241+
public void DatabasePropertyEx_SqlSortOrder_InnerBaseTypeIsTinyInt()
242+
=> AreEqual("tinyint", new Simulation().ExecuteScalar(
243+
"SELECT SQL_VARIANT_PROPERTY(DATABASEPROPERTYEX('simulated', 'SQLSortOrder'), 'BaseType')"));
244+
245+
[TestMethod]
246+
public void DatabasePropertyEx_Collation_InnerBaseTypeIsNVarchar()
247+
=> AreEqual("nvarchar", new Simulation().ExecuteScalar(
248+
"SELECT SQL_VARIANT_PROPERTY(DATABASEPROPERTYEX('simulated', 'Collation'), 'BaseType')"));
249+
250+
/// <summary>
251+
/// DBCC CHECKDB isn't modeled, so the property is a NULL sql_variant;
252+
/// SMO's CAST(ISNULL(prop, 0) AS datetime) must resolve to 1900-01-01
253+
/// (ISNULL fixes to sql_variant wrapping the int 0, CAST to datetime
254+
/// epoch) — probe-confirmed against SQL Server 2025.
255+
/// </summary>
256+
[TestMethod]
257+
public void DatabasePropertyEx_LastGoodCheckDbTime_SmoIsNullCastShape_ResolvesToEpoch()
258+
=> AreEqual(new DateTime(1900, 1, 1), new Simulation().ExecuteScalar(
259+
"SELECT CAST(ISNULL(DATABASEPROPERTYEX('simulated', 'LastGoodCheckDbTime'), 0) AS datetime)"));
260+
233261
[TestMethod]
234262
public void DatabasePropertyEx_ComparisonStyle_ReturnsInt()
235263
=> AreEqual(196609, new Simulation().ExecuteScalar(
@@ -246,10 +274,13 @@ public void DatabasePropertyEx_SqlSortOrder_ReturnsByte52OnDefaultCollation()
246274
"SELECT DATABASEPROPERTYEX('simulated', 'SQLSortOrder')"));
247275

248276
[TestMethod]
249-
public void DatabasePropertyEx_NonConstantProperty_FallsBackToNVarchar()
277+
public void DatabasePropertyEx_NonConstantProperty_StillProjectsSqlVariant()
250278
{
279+
// The projection type is always sql_variant (no compile-time-constant
280+
// dependency), so a non-constant property name resolves its inner base
281+
// type at runtime identically — Version's int inner unwraps to 0.
251282
var sim = new Simulation();
252-
AreEqual("0", sim.ExecuteScalar(
283+
AreEqual(0, sim.ExecuteScalar(
253284
"declare @p nvarchar(30) = 'Version'; SELECT DATABASEPROPERTYEX('simulated', @p)"));
254285
}
255286

SqlServerSimulator.Tests/PropertyFunctionsTests.cs

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,13 @@ public void TypeProperty_CaseInsensitiveProperty()
318318

319319
// === OBJECTPROPERTYEX ===
320320

321+
/// <summary>
322+
/// OBJECTPROPERTYEX projects sql_variant; the boolean Is-X properties
323+
/// carry an int inner base type, which SqlClient unwraps to an int.
324+
/// </summary>
321325
[TestMethod]
322326
public void ObjectPropertyEx_IsTable_OnTable_Returns1()
323-
=> AreEqual("1", new Simulation().ExecuteScalar(
327+
=> AreEqual(1, new Simulation().ExecuteScalar(
324328
"create table t (id int); " +
325329
"select objectpropertyex(object_id('t'), 'IsTable')"));
326330

@@ -346,88 +350,128 @@ public void ObjectPropertyEx_BaseType_Procedure_Returns_P()
346350
AreEqual("P ", sim.ExecuteScalar("select objectpropertyex(object_id('p'), 'BaseType')"));
347351
}
348352

353+
/// <summary>
354+
/// SchemaId's inner base type is int.
355+
/// </summary>
349356
[TestMethod]
350357
public void ObjectPropertyEx_SchemaId_Dbo_Returns1()
351-
=> AreEqual("1", new Simulation().ExecuteScalar(
358+
=> AreEqual(1, new Simulation().ExecuteScalar(
352359
"create table t (id int); " +
353360
"select objectpropertyex(object_id('t'), 'SchemaId')"));
354361

362+
/// <summary>
363+
/// Cardinality's inner base type is bigint, which unwraps to a long.
364+
/// </summary>
355365
[TestMethod]
356366
public void ObjectPropertyEx_Cardinality_EmptyTable_Returns0()
357-
=> AreEqual("0", new Simulation().ExecuteScalar(
367+
=> AreEqual(0L, new Simulation().ExecuteScalar(
358368
"create table t (id int); " +
359369
"select objectpropertyex(object_id('t'), 'Cardinality')"));
360370

361371
[TestMethod]
362372
public void ObjectPropertyEx_Cardinality_ThreeRows_Returns3()
363-
=> AreEqual("3", new Simulation().ExecuteScalar(
373+
=> AreEqual(3L, new Simulation().ExecuteScalar(
364374
"create table t (id int); " +
365375
"insert t values (1), (2), (3); " +
366376
"select objectpropertyex(object_id('t'), 'Cardinality')"));
367377

368378
[TestMethod]
369379
public void ObjectPropertyEx_TableHasIdentity_WithIdentity_Returns1()
370-
=> AreEqual("1", new Simulation().ExecuteScalar(
380+
=> AreEqual(1, new Simulation().ExecuteScalar(
371381
"create table t (id int identity(1,1) primary key); " +
372382
"select objectpropertyex(object_id('t'), 'TableHasIdentity')"));
373383

374384
[TestMethod]
375385
public void ObjectPropertyEx_TableHasIdentity_NoIdentity_Returns0()
376-
=> AreEqual("0", new Simulation().ExecuteScalar(
386+
=> AreEqual(0, new Simulation().ExecuteScalar(
377387
"create table t (id int); " +
378388
"select objectpropertyex(object_id('t'), 'TableHasIdentity')"));
379389

380390
[TestMethod]
381391
public void ObjectPropertyEx_TableHasPrimaryKey_WithPK_Returns1()
382-
=> AreEqual("1", new Simulation().ExecuteScalar(
392+
=> AreEqual(1, new Simulation().ExecuteScalar(
383393
"create table t (id int primary key); " +
384394
"select objectpropertyex(object_id('t'), 'TableHasPrimaryKey')"));
385395

386396
[TestMethod]
387397
public void ObjectPropertyEx_TableHasPrimaryKey_NoPK_Returns0()
388-
=> AreEqual("0", new Simulation().ExecuteScalar(
398+
=> AreEqual(0, new Simulation().ExecuteScalar(
389399
"create table t (id int); " +
390400
"select objectpropertyex(object_id('t'), 'TableHasPrimaryKey')"));
391401

392402
[TestMethod]
393403
public void ObjectPropertyEx_TableHasUniqueCnst_WithUnique_Returns1()
394-
=> AreEqual("1", new Simulation().ExecuteScalar(
404+
=> AreEqual(1, new Simulation().ExecuteScalar(
395405
"create table t (id int, name varchar(50), constraint uq unique (name)); " +
396406
"select objectpropertyex(object_id('t'), 'TableHasUniqueCnst')"));
397407

398408
[TestMethod]
399409
public void ObjectPropertyEx_TableHasCheckCnst_WithCheck_Returns1()
400-
=> AreEqual("1", new Simulation().ExecuteScalar(
410+
=> AreEqual(1, new Simulation().ExecuteScalar(
401411
"create table t (id int, check (id > 0)); " +
402412
"select objectpropertyex(object_id('t'), 'TableHasCheckCnst')"));
403413

404414
[TestMethod]
405415
public void ObjectPropertyEx_TableHasForeignKey_WithFK_Returns1()
406-
=> AreEqual("1", new Simulation().ExecuteScalar(
416+
=> AreEqual(1, new Simulation().ExecuteScalar(
407417
"create table p (id int primary key); " +
408418
"create table c (id int primary key, p_id int references p(id)); " +
409419
"select objectpropertyex(object_id('c'), 'TableHasForeignKey')"));
410420

411421
[TestMethod]
412422
public void ObjectPropertyEx_TableHasForeignRef_WithIncoming_Returns1()
413-
=> AreEqual("1", new Simulation().ExecuteScalar(
423+
=> AreEqual(1, new Simulation().ExecuteScalar(
414424
"create table p (id int primary key); " +
415425
"create table c (id int primary key, p_id int references p(id)); " +
416426
"select objectpropertyex(object_id('p'), 'TableHasForeignRef')"));
417427

418428
[TestMethod]
419429
public void ObjectPropertyEx_TableHasIndex_WithIndex_Returns1()
420-
=> AreEqual("1", new Simulation().ExecuteScalar(
430+
=> AreEqual(1, new Simulation().ExecuteScalar(
421431
"create table t (id int, name varchar(50)); " +
422432
"create index ix on t(name); " +
423433
"select objectpropertyex(object_id('t'), 'TableHasIndex')"));
424434

425435
[TestMethod]
426436
public void ObjectPropertyEx_TableHasRowGuidCol_AlwaysZero()
427-
=> AreEqual("0", new Simulation().ExecuteScalar(
437+
=> AreEqual(0, new Simulation().ExecuteScalar(
428438
"create table t (id int); " +
429439
"select objectpropertyex(object_id('t'), 'TableHasRowGuidCol')"));
430440

441+
[TestMethod]
442+
public void ObjectPropertyEx_ProjectsSqlVariant()
443+
{
444+
using var reader = new Simulation().ExecuteReader(
445+
"create table t (id int); " +
446+
"select objectpropertyex(object_id('t'), 'SchemaId')");
447+
IsTrue(reader.Read());
448+
AreEqual("sql_variant", reader.GetDataTypeName(0));
449+
}
450+
451+
[TestMethod]
452+
public void ObjectPropertyEx_BaseType_InnerBaseTypeIsChar()
453+
=> AreEqual("char", new Simulation().ExecuteScalar(
454+
"create table t (id int); " +
455+
"select sql_variant_property(objectpropertyex(object_id('t'), 'BaseType'), 'BaseType')"));
456+
457+
[TestMethod]
458+
public void ObjectPropertyEx_SchemaId_InnerBaseTypeIsInt()
459+
=> AreEqual("int", new Simulation().ExecuteScalar(
460+
"create table t (id int); " +
461+
"select sql_variant_property(objectpropertyex(object_id('t'), 'SchemaId'), 'BaseType')"));
462+
463+
[TestMethod]
464+
public void ObjectPropertyEx_Cardinality_InnerBaseTypeIsBigInt()
465+
=> AreEqual("bigint", new Simulation().ExecuteScalar(
466+
"create table t (id int); " +
467+
"select sql_variant_property(objectpropertyex(object_id('t'), 'Cardinality'), 'BaseType')"));
468+
469+
[TestMethod]
470+
public void ObjectPropertyEx_TableHasIdentity_InnerBaseTypeIsInt()
471+
=> AreEqual("int", new Simulation().ExecuteScalar(
472+
"create table t (id int identity(1,1)); " +
473+
"select sql_variant_property(objectpropertyex(object_id('t'), 'TableHasIdentity'), 'BaseType')"));
474+
431475
[TestMethod]
432476
public void ObjectPropertyEx_UnknownProperty_ReturnsNull()
433477
=> AreEqual(DBNull.Value, new Simulation().ExecuteScalar(

SqlServerSimulator.Tests/SequenceTests.cs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -331,11 +331,15 @@ public void SysSequences_ListsRegisteredSequences()
331331
""").ExecuteReader();
332332
IsTrue(reader.Read());
333333
AreEqual("sysview1", reader.GetString(0));
334-
AreEqual(100L, reader.GetInt64(1));
335-
AreEqual(5L, reader.GetInt64(2));
334+
// start_value / increment / current_value are sql_variant carrying the
335+
// sequence's declared type (int here); SqlClient surfaces the inner int
336+
// via GetValue.
337+
AreEqual("sql_variant", reader.GetDataTypeName(1));
338+
AreEqual(100, reader.GetValue(1));
339+
AreEqual(5, reader.GetValue(2));
336340
IsTrue(reader.GetBoolean(3));
337341
IsFalse(reader.GetBoolean(4));
338-
AreEqual(100L, reader.GetInt64(5));
342+
AreEqual(100, reader.GetValue(5));
339343
}
340344

341345
[TestMethod]
@@ -346,9 +350,31 @@ public void SysSequences_AfterAdvance_CurrentValueUpdates()
346350
_ = simulation.ExecuteScalar("select next value for sysview2");
347351
_ = simulation.ExecuteScalar("select next value for sysview2");
348352
// After two advances starting from 1 with increment 1, current_value
349-
// is the next value to emit: 3.
350-
AreEqual(3L, simulation.ExecuteScalar<long>("select current_value from sys.sequences where name = 'sysview2'"));
351-
}
353+
// is the next value to emit: 3 (int inner base type for an int sequence).
354+
AreEqual(3, simulation.ExecuteScalar<int>("select current_value from sys.sequences where name = 'sysview2'"));
355+
}
356+
357+
// A decimal sequence's inner type reports BaseType 'numeric' — the
358+
// simulator's single decimal family surfaces as numeric (documented quirk),
359+
// diverging from real's 'decimal' for a decimal-declared sequence.
360+
[TestMethod]
361+
[DataRow("bigint", "bigint")]
362+
[DataRow("int", "int")]
363+
[DataRow("smallint", "smallint")]
364+
[DataRow("tinyint", "tinyint")]
365+
[DataRow("decimal(18,0)", "numeric")]
366+
public void SysSequences_StartValue_InnerBaseTypeMatchesDeclaredType(string declared, string expectedBaseType)
367+
=> AreEqual(expectedBaseType, new Simulation().ExecuteScalar($"""
368+
create sequence sv_bt as {declared} start with 5;
369+
select sql_variant_property(start_value, 'BaseType') from sys.sequences where name = 'sv_bt'
370+
"""));
371+
372+
[TestMethod]
373+
public void SysSequences_BigIntSequence_StartValueUnwrapsToLong()
374+
=> AreEqual(5000000000L, new Simulation().ExecuteScalar("""
375+
create sequence sv_big as bigint start with 5000000000;
376+
select start_value from sys.sequences where name = 'sv_big'
377+
"""));
352378

353379
/// <summary>
354380
/// last_used_value is NULL until the first NEXT VALUE FOR (probe-confirmed:

SqlServerSimulator.Tests/SsmsScriptTableCatalogTests.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,11 @@ public void IdentityColumns_ProjectsSeedAndIncrement()
8181
"select name, seed_value, increment_value, cast(is_not_for_replication as int) from sys.identity_columns where object_id = object_id('t')");
8282
IsTrue(reader.Read());
8383
AreEqual("id", reader.GetString(0));
84-
AreEqual(5L, reader.GetInt64(1));
85-
AreEqual(3L, reader.GetInt64(2));
84+
// seed_value / increment_value are sql_variant carrying the identity
85+
// column's declared type (int here); SqlClient surfaces the inner int.
86+
AreEqual("sql_variant", reader.GetDataTypeName(1));
87+
AreEqual(5, reader.GetValue(1));
88+
AreEqual(3, reader.GetValue(2));
8689
AreEqual(0, reader.GetInt32(3));
8790
IsFalse(reader.Read());
8891
}
@@ -94,6 +97,27 @@ public void IdentityColumns_LastValueNullBeforeFirstInsert()
9497
select case when last_value is null then 1 else 0 end from sys.identity_columns where object_id = object_id('t')
9598
"""));
9699

100+
// Decimal identity columns aren't modeled (Msg 2749), so only the integer
101+
// family is exercised here.
102+
[TestMethod]
103+
[DataRow("bigint", "bigint")]
104+
[DataRow("int", "int")]
105+
[DataRow("smallint", "smallint")]
106+
[DataRow("tinyint", "tinyint")]
107+
public void IdentityColumns_SeedValue_InnerBaseTypeMatchesColumnType(string declared, string expectedBaseType)
108+
=> AreEqual(expectedBaseType, new Simulation().ExecuteScalar($"""
109+
create table t (id {declared} identity(5, 3) not null primary key);
110+
select sql_variant_property(seed_value, 'BaseType') from sys.identity_columns where object_id = object_id('t')
111+
"""));
112+
113+
[TestMethod]
114+
public void IdentityColumns_LastValue_InnerBaseTypeIsColumnType_AfterInsert()
115+
=> AreEqual("bigint", new Simulation().ExecuteScalar("""
116+
create table t (id bigint identity(1, 1) not null primary key, x int);
117+
insert t (x) values (1);
118+
select sql_variant_property(last_value, 'BaseType') from sys.identity_columns where object_id = object_id('t')
119+
"""));
120+
97121
// === sys.trigger_events ===
98122

99123
[TestMethod]

0 commit comments

Comments
 (0)