@@ -888,6 +888,54 @@ void Iso(string name, HeapColumn[] columns, Func<Parser.BatchContext, Database,
888888 new ( "member_principal_id" , SqlType . Int32 , null , false ) ,
889889 ] , EnumerateSysDatabaseRoleMembers ) ;
890890
891+ // sys.server_principals: probe-confirmed 14-col shape against SQL
892+ // Server 2025 (2026-07-15), projected over the per-Simulation login
893+ // registry (Simulation.Logins) plus two synthetic fixed rows: sa
894+ // (principal_id 1) and public (principal_id 2). Columns the simulator
895+ // doesn't track (credential_id, disabled flag) surface as their real
896+ // low-privilege defaults.
897+ Sys ( "server_principals" ,
898+ [
899+ new ( "name" , SqlType . SystemName , 128 , false ) ,
900+ new ( "principal_id" , SqlType . Int32 , null , false ) ,
901+ new ( "sid" , SqlType . Varbinary , 85 , true ) ,
902+ new ( "type" , charOne , 1 , false ) ,
903+ new ( "type_desc" , nvarchar60Catalog , 60 , true ) ,
904+ new ( "is_disabled" , SqlType . Bit , null , false ) ,
905+ new ( "create_date" , SqlType . DateTime , null , false ) ,
906+ new ( "modify_date" , SqlType . DateTime , null , false ) ,
907+ new ( "default_database_name" , SqlType . SystemName , 128 , true ) ,
908+ new ( "default_language_name" , SqlType . SystemName , 128 , true ) ,
909+ new ( "credential_id" , SqlType . Int32 , null , true ) ,
910+ new ( "owning_principal_id" , SqlType . Int32 , null , true ) ,
911+ new ( "is_fixed_role" , SqlType . Bit , null , false ) ,
912+ new ( "tenant_id" , SqlType . UniqueIdentifier , null , true ) ,
913+ ] , EnumerateSysServerPrincipals ) ;
914+
915+ // sys.sql_logins: probe-confirmed 14-col shape against SQL Server 2025
916+ // (2026-07-15). Same leading 10 columns as sys.server_principals,
917+ // filtered to type='S' (SQL logins) — sa plus the registry logins,
918+ // never the public server role. password_hash surfaces NULL: the
919+ // simulator deliberately doesn't expose its stored PWDCOMPARE hash,
920+ // matching what a low-privilege reader sees on the reference instance.
921+ Sys ( "sql_logins" ,
922+ [
923+ new ( "name" , SqlType . SystemName , 128 , false ) ,
924+ new ( "principal_id" , SqlType . Int32 , null , false ) ,
925+ new ( "sid" , SqlType . Varbinary , 85 , true ) ,
926+ new ( "type" , charOne , 1 , false ) ,
927+ new ( "type_desc" , nvarchar60Catalog , 60 , true ) ,
928+ new ( "is_disabled" , SqlType . Bit , null , false ) ,
929+ new ( "create_date" , SqlType . DateTime , null , false ) ,
930+ new ( "modify_date" , SqlType . DateTime , null , false ) ,
931+ new ( "default_database_name" , SqlType . SystemName , 128 , true ) ,
932+ new ( "default_language_name" , SqlType . SystemName , 128 , true ) ,
933+ new ( "credential_id" , SqlType . Int32 , null , true ) ,
934+ new ( "is_policy_checked" , SqlType . Bit , null , true ) ,
935+ new ( "is_expiration_checked" , SqlType . Bit , null , true ) ,
936+ new ( "password_hash" , SqlType . Varbinary , 256 , true ) ,
937+ ] , EnumerateSysSqlLogins ) ;
938+
891939 // sys.fulltext_catalogs: per-database full-text catalog metadata.
892940 // Column subset matches Microsoft Learn's documented surface for
893941 // SQL Server 2022+ (the reference instance doesn't have full-text
@@ -1897,6 +1945,166 @@ private static IEnumerable<SqlValue[]> EnumerateSysDatabaseRoleMembers(Parser.Ba
18971945 }
18981946 }
18991947
1948+ /// <summary>
1949+ /// Derives a deterministic 16-byte synthetic <c>sid</c> from a login name.
1950+ /// Real SQL logins carry a 16-byte random GUID sid; the simulator fills the
1951+ /// four 32-bit quadrants with a per-quadrant-salted FNV-1a hash so the same
1952+ /// name always maps to the same bytes without persisting a GUID.
1953+ /// </summary>
1954+ private static byte [ ] DeriveLoginSid ( string name )
1955+ {
1956+ var sid = new byte [ 16 ] ;
1957+ for ( var quadrant = 0 ; quadrant < 4 ; quadrant ++ )
1958+ {
1959+ var hash = Simulation . Fnv1a32 . Initial ;
1960+ hash . Mix ( name ) ;
1961+ hash . Mix ( ( byte ) quadrant ) ;
1962+ var value = hash . Value ;
1963+ var offset = quadrant * 4 ;
1964+ sid [ offset ] = ( byte ) value ;
1965+ sid [ offset + 1 ] = ( byte ) ( value >> 8 ) ;
1966+ sid [ offset + 2 ] = ( byte ) ( value >> 16 ) ;
1967+ sid [ offset + 3 ] = ( byte ) ( value >> 24 ) ;
1968+ }
1969+ return sid ;
1970+ }
1971+
1972+ /// <summary>
1973+ /// Projects <c>sys.server_principals</c> over the per-Simulation login
1974+ /// registry plus the two synthetic fixed rows (<c>sa</c> = principal_id 1,
1975+ /// <c>public</c> = principal_id 2). Rows emit in principal_id order.
1976+ /// </summary>
1977+ private static IEnumerable < SqlValue [ ] > EnumerateSysServerPrincipals ( Parser . BatchContext batch , Database database )
1978+ {
1979+ var simulation = batch . Connection . Simulation ;
1980+ var charOne = SqlType . GetChar ( 1 ) ;
1981+ var falseBit = SqlValue . FromBoolean ( false ) ;
1982+ var sqlLogin = SqlValue . FromNVarchar ( "SQL_LOGIN" ) ;
1983+ var loginType = SqlValue . FromChar ( charOne , "S" ) ;
1984+ var nullCredentialId = SqlValue . Null ( SqlType . Int32 ) ;
1985+ var nullOwningId = SqlValue . Null ( SqlType . Int32 ) ;
1986+ var master = SqlValue . FromSystemName ( "master" ) ;
1987+ var usEnglish = SqlValue . FromSystemName ( "us_english" ) ;
1988+ var nullTenant = SqlValue . Null ( SqlType . UniqueIdentifier ) ;
1989+ var zeroTenant = SqlValue . FromGuid ( Guid . Empty ) ;
1990+ var seedDate = SqlValue . FromDateTime ( simulation . SeedDate ) ;
1991+
1992+ // sa: the fixed SQL-authentication login, principal_id 1.
1993+ yield return [
1994+ SqlValue . FromSystemName ( "sa" ) ,
1995+ SqlValue . FromInt32 ( 1 ) ,
1996+ SqlValue . FromVarbinary ( [ 0x01 ] ) ,
1997+ loginType ,
1998+ sqlLogin ,
1999+ falseBit ,
2000+ seedDate ,
2001+ seedDate ,
2002+ master ,
2003+ usEnglish ,
2004+ nullCredentialId ,
2005+ nullOwningId ,
2006+ falseBit ,
2007+ nullTenant ,
2008+ ] ;
2009+
2010+ // public: the fixed server role, principal_id 2. owning_principal_id
2011+ // points at sa (1); is_fixed_role is 0 (probe-confirmed).
2012+ yield return [
2013+ SqlValue . FromSystemName ( "public" ) ,
2014+ SqlValue . FromInt32 ( 2 ) ,
2015+ SqlValue . FromVarbinary ( [ 0x02 ] ) ,
2016+ SqlValue . FromChar ( charOne , "R" ) ,
2017+ SqlValue . FromNVarchar ( "SERVER_ROLE" ) ,
2018+ falseBit ,
2019+ seedDate ,
2020+ seedDate ,
2021+ SqlValue . Null ( SqlType . SystemName ) ,
2022+ SqlValue . Null ( SqlType . SystemName ) ,
2023+ nullCredentialId ,
2024+ SqlValue . FromInt32 ( 1 ) ,
2025+ falseBit ,
2026+ nullTenant ,
2027+ ] ;
2028+
2029+ foreach ( var login in simulation . Logins . Values . OrderBy ( l => l . PrincipalId ) )
2030+ {
2031+ yield return [
2032+ SqlValue . FromSystemName ( login . Name ) ,
2033+ SqlValue . FromInt32 ( login . PrincipalId ) ,
2034+ SqlValue . FromVarbinary ( DeriveLoginSid ( login . Name ) ) ,
2035+ loginType ,
2036+ sqlLogin ,
2037+ falseBit ,
2038+ SqlValue . FromDateTime ( login . CreateDate ) ,
2039+ SqlValue . FromDateTime ( login . PasswordLastSetTime ) ,
2040+ master ,
2041+ usEnglish ,
2042+ nullCredentialId ,
2043+ nullOwningId ,
2044+ falseBit ,
2045+ zeroTenant ,
2046+ ] ;
2047+ }
2048+ }
2049+
2050+ /// <summary>
2051+ /// Projects <c>sys.sql_logins</c>: the type='S' subset of
2052+ /// <c>sys.server_principals</c> (<c>sa</c> plus the registry logins, never
2053+ /// the <c>public</c> server role), with the policy / expiration / hash
2054+ /// tail. Rows emit in principal_id order.
2055+ /// </summary>
2056+ private static IEnumerable < SqlValue [ ] > EnumerateSysSqlLogins ( Parser . BatchContext batch , Database database )
2057+ {
2058+ var simulation = batch . Connection . Simulation ;
2059+ var charOne = SqlType . GetChar ( 1 ) ;
2060+ var trueBit = SqlValue . FromBoolean ( true ) ;
2061+ var falseBit = SqlValue . FromBoolean ( false ) ;
2062+ var sqlLogin = SqlValue . FromNVarchar ( "SQL_LOGIN" ) ;
2063+ var loginType = SqlValue . FromChar ( charOne , "S" ) ;
2064+ var nullCredentialId = SqlValue . Null ( SqlType . Int32 ) ;
2065+ var master = SqlValue . FromSystemName ( "master" ) ;
2066+ var usEnglish = SqlValue . FromSystemName ( "us_english" ) ;
2067+ var nullPasswordHash = SqlValue . Null ( SqlType . Varbinary ) ;
2068+ var seedDate = SqlValue . FromDateTime ( simulation . SeedDate ) ;
2069+
2070+ yield return [
2071+ SqlValue . FromSystemName ( "sa" ) ,
2072+ SqlValue . FromInt32 ( 1 ) ,
2073+ SqlValue . FromVarbinary ( [ 0x01 ] ) ,
2074+ loginType ,
2075+ sqlLogin ,
2076+ falseBit ,
2077+ seedDate ,
2078+ seedDate ,
2079+ master ,
2080+ usEnglish ,
2081+ nullCredentialId ,
2082+ trueBit ,
2083+ falseBit ,
2084+ nullPasswordHash ,
2085+ ] ;
2086+
2087+ foreach ( var login in simulation . Logins . Values . OrderBy ( l => l . PrincipalId ) )
2088+ {
2089+ yield return [
2090+ SqlValue . FromSystemName ( login . Name ) ,
2091+ SqlValue . FromInt32 ( login . PrincipalId ) ,
2092+ SqlValue . FromVarbinary ( DeriveLoginSid ( login . Name ) ) ,
2093+ loginType ,
2094+ sqlLogin ,
2095+ falseBit ,
2096+ SqlValue . FromDateTime ( login . CreateDate ) ,
2097+ SqlValue . FromDateTime ( login . PasswordLastSetTime ) ,
2098+ master ,
2099+ usEnglish ,
2100+ nullCredentialId ,
2101+ trueBit ,
2102+ falseBit ,
2103+ nullPasswordHash ,
2104+ ] ;
2105+ }
2106+ }
2107+
19002108 /// <summary>
19012109 /// Rows for <c>sys.fulltext_catalogs</c>. One row per
19022110 /// <see cref="FullTextCatalog"/> in <see cref="Database.FullTextCatalogs"/>.
0 commit comments