diff --git a/crates/iridium_core/src/executor/metadata/sys/mod.rs b/crates/iridium_core/src/executor/metadata/sys/mod.rs index e61c2b9..86f6404 100644 --- a/crates/iridium_core/src/executor/metadata/sys/mod.rs +++ b/crates/iridium_core/src/executor/metadata/sys/mod.rs @@ -167,6 +167,8 @@ pub(crate) fn lookup(schema: &str, name: &str) -> Option> Some(Box::new(constraints::SysDefaultConstraints)) } else if name.eq_ignore_ascii_case("server_principals") { Some(Box::new(tables::SysServerPrincipals)) + } else if name.eq_ignore_ascii_case("server_role_members") { + Some(Box::new(tables::SysServerRoleMembers)) } else if name.eq_ignore_ascii_case("availability_replicas") { Some(Box::new(hadr::SysAvailabilityReplicas)) } else if name.eq_ignore_ascii_case("availability_groups") { diff --git a/crates/iridium_core/src/executor/metadata/sys/tables/mod.rs b/crates/iridium_core/src/executor/metadata/sys/tables/mod.rs index eaefc42..1ff7841 100644 --- a/crates/iridium_core/src/executor/metadata/sys/tables/mod.rs +++ b/crates/iridium_core/src/executor/metadata/sys/tables/mod.rs @@ -15,7 +15,7 @@ pub(crate) use identity_columns::SysIdentityColumns; pub(crate) use objects_misc::{ SysAssemblyModules, SysDataSpaces, SysEdgeConstraints, SysExtendedProperties, SysForeignKeyColumns, SysIndexColumns, SysInternalTables, SysPeriods, SysSequences, - SysServerPrincipals, SysSqlExpressionDependencies, SysSqlModules, SysAllSqlModules, SysStats, SysStatsColumns, + SysServerPrincipals, SysServerRoleMembers, SysSqlExpressionDependencies, SysSqlModules, SysAllSqlModules, SysStats, SysStatsColumns, SysSynonyms, SysSystemSqlModules, SysTriggerEvents, SysTriggers, SysXmlIndexes, SysXmlSchemaCollections, }; diff --git a/crates/iridium_core/src/executor/metadata/sys/tables/objects_misc.rs b/crates/iridium_core/src/executor/metadata/sys/tables/objects_misc.rs index 8a35cde..bcf6265 100644 --- a/crates/iridium_core/src/executor/metadata/sys/tables/objects_misc.rs +++ b/crates/iridium_core/src/executor/metadata/sys/tables/objects_misc.rs @@ -21,6 +21,7 @@ pub(crate) struct SysSystemSqlModules; pub(crate) struct SysStats; pub(crate) struct SysStatsColumns; pub(crate) struct SysServerPrincipals; +pub(crate) struct SysServerRoleMembers; pub(crate) struct SysTriggerEvents; impl VirtualTable for SysDataSpaces { @@ -638,17 +639,51 @@ impl VirtualTable for SysServerPrincipals { .and_hms_opt(0, 0, 0) .unwrap(), ); - vec![StoredRow { - values: vec![ - Value::Int(1), - Value::VarChar("sa".to_string()), - Value::Char("S".to_string()), - Value::VarChar("SQL_LOGIN".to_string()), - Value::Bit(false), - created.clone(), - created.clone(), - Value::VarChar("master".to_string()), + vec![ + StoredRow { + values: vec![ + Value::Int(1), + Value::VarChar("sa".to_string()), + Value::Char("S".to_string()), + Value::VarChar("SQL_LOGIN".to_string()), + Value::Bit(false), + created.clone(), + created.clone(), + Value::VarChar("master".to_string()), + ], + deleted: false, + }, + StoredRow { + values: vec![ + Value::Int(3), + Value::VarChar("sysadmin".to_string()), + Value::Char("R".to_string()), + Value::VarChar("SERVER_ROLE".to_string()), + Value::Bit(false), + created.clone(), + created.clone(), + Value::Null, + ], + deleted: false, + }, + ] + } +} + +impl VirtualTable for SysServerRoleMembers { + fn definition(&self) -> crate::catalog::TableDef { + virtual_table_def( + "server_role_members", + vec![ + ("role_principal_id", DataType::Int, false), + ("member_principal_id", DataType::Int, false), ], + ) + } + + fn rows(&self, _catalog: &dyn Catalog, _ctx: &ExecutionContext) -> Vec { + vec![StoredRow { + values: vec![Value::Int(3), Value::Int(1)], deleted: false, }] } diff --git a/crates/iridium_core/src/executor/script/procedural/system_procedures.rs b/crates/iridium_core/src/executor/script/procedural/system_procedures.rs index 95ea468..bfdc2f6 100644 --- a/crates/iridium_core/src/executor/script/procedural/system_procedures.rs +++ b/crates/iridium_core/src/executor/script/procedural/system_procedures.rs @@ -23,6 +23,13 @@ const SYSTEM_PROCEDURES: &[&str] = &[ "sp_helpdb", "sp_server_info", "sp_monitor", + "sp_helpuser", + "sp_helprole", + "sp_helprolemember", + "sp_helpsrvrole", + "sp_helpsrvrolemember", + "sp_helpfile", + "sp_helpfilegroup", ]; pub(crate) fn is_system_procedure(name: &str) -> bool { @@ -65,6 +72,20 @@ pub(crate) fn execute_system_procedure( execute_sp_server_info()? } else if name.eq_ignore_ascii_case("sp_monitor") { execute_sp_monitor(exec)? + } else if name.eq_ignore_ascii_case("sp_helpuser") { + execute_sp_helpuser(exec, ctx)? + } else if name.eq_ignore_ascii_case("sp_helprole") { + execute_sp_helprole(exec, ctx)? + } else if name.eq_ignore_ascii_case("sp_helprolemember") { + execute_sp_helprolemember(exec, ctx)? + } else if name.eq_ignore_ascii_case("sp_helpsrvrole") { + execute_sp_helpsrvrole(exec, ctx)? + } else if name.eq_ignore_ascii_case("sp_helpsrvrolemember") { + execute_sp_helpsrvrolemember(exec, ctx)? + } else if name.eq_ignore_ascii_case("sp_helpfile") { + execute_sp_helpfile(exec, ctx)? + } else if name.eq_ignore_ascii_case("sp_helpfilegroup") { + execute_sp_helpfilegroup(exec, ctx)? } else if name.eq_ignore_ascii_case("xp_instance_regread") { // Stub for registry reads. If it has an output parameter, set it to a default. for arg in &stmt.args { @@ -806,3 +827,87 @@ fn execute_sp_tables(exec: &mut ScriptExecutor<'_>) -> Result, + ctx: &mut ExecutionContext<'_>, +) -> Result { + let sql = "SELECT p.name AS UserName, p.type_desc AS RoleName, '' AS LoginName, '' AS DefDBName, '' AS DefSchemaName, p.principal_id AS UserId, p.principal_id AS SID FROM sys.database_principals p"; + let batch = crate::parser::parse_batch(sql)?; + match exec.execute_batch(&batch, ctx)? { + crate::error::StmtOutcome::Ok(Some(res)) => Ok(res), + _ => Err(DbError::Execution("Failed to execute sp_helpuser query".into())), + } +} + +fn execute_sp_helprole( + exec: &mut ScriptExecutor<'_>, + ctx: &mut ExecutionContext<'_>, +) -> Result { + let sql = "SELECT name AS RoleName, principal_id AS RoleId, 0 AS IsAppRole FROM sys.database_principals WHERE type = 'R'"; + let batch = crate::parser::parse_batch(sql)?; + match exec.execute_batch(&batch, ctx)? { + crate::error::StmtOutcome::Ok(Some(res)) => Ok(res), + _ => Err(DbError::Execution("Failed to execute sp_helprole query".into())), + } +} + +fn execute_sp_helprolemember( + exec: &mut ScriptExecutor<'_>, + ctx: &mut ExecutionContext<'_>, +) -> Result { + let sql = "SELECT r.name AS DbRole, m.name AS MemberName, m.principal_id AS MemberSID FROM sys.database_role_members rm JOIN sys.database_principals r ON rm.role_principal_id = r.principal_id JOIN sys.database_principals m ON rm.member_principal_id = m.principal_id"; + let batch = crate::parser::parse_batch(sql)?; + match exec.execute_batch(&batch, ctx)? { + crate::error::StmtOutcome::Ok(Some(res)) => Ok(res), + _ => Err(DbError::Execution("Failed to execute sp_helprolemember query".into())), + } +} + +fn execute_sp_helpsrvrole( + exec: &mut ScriptExecutor<'_>, + ctx: &mut ExecutionContext<'_>, +) -> Result { + let sql = "SELECT name AS ServerRole, principal_id AS RoleId FROM sys.server_principals WHERE type = 'R'"; + let batch = crate::parser::parse_batch(sql)?; + match exec.execute_batch(&batch, ctx)? { + crate::error::StmtOutcome::Ok(Some(res)) => Ok(res), + _ => Err(DbError::Execution("Failed to execute sp_helpsrvrole query".into())), + } +} + +fn execute_sp_helpsrvrolemember( + exec: &mut ScriptExecutor<'_>, + ctx: &mut ExecutionContext<'_>, +) -> Result { + let sql = "SELECT r.name AS ServerRole, m.name AS MemberName, m.principal_id AS MemberSID FROM sys.server_role_members srm JOIN sys.server_principals r ON srm.role_principal_id = r.principal_id JOIN sys.server_principals m ON srm.member_principal_id = m.principal_id"; + let batch = crate::parser::parse_batch(sql)?; + match exec.execute_batch(&batch, ctx)? { + crate::error::StmtOutcome::Ok(Some(res)) => Ok(res), + _ => Err(DbError::Execution("Failed to execute sp_helpsrvrolemember query".into())), + } +} + +fn execute_sp_helpfile( + exec: &mut ScriptExecutor<'_>, + ctx: &mut ExecutionContext<'_>, +) -> Result { + let sql = "SELECT name, file_id, physical_name, type_desc AS usage, size FROM sys.database_files"; + let batch = crate::parser::parse_batch(sql)?; + match exec.execute_batch(&batch, ctx)? { + crate::error::StmtOutcome::Ok(Some(res)) => Ok(res), + _ => Err(DbError::Execution("Failed to execute sp_helpfile query".into())), + } +} + +fn execute_sp_helpfilegroup( + exec: &mut ScriptExecutor<'_>, + ctx: &mut ExecutionContext<'_>, +) -> Result { + let sql = "SELECT name, data_space_id AS groupid, type_desc AS groupname FROM sys.filegroups"; + let batch = crate::parser::parse_batch(sql)?; + match exec.execute_batch(&batch, ctx)? { + crate::error::StmtOutcome::Ok(Some(res)) => Ok(res), + _ => Err(DbError::Execution("Failed to execute sp_helpfilegroup query".into())), + } +} diff --git a/crates/iridium_core/tests/sql_server_2025_parity.rs b/crates/iridium_core/tests/sql_server_2025_parity.rs index 4f19393..e916581 100644 --- a/crates/iridium_core/tests/sql_server_2025_parity.rs +++ b/crates/iridium_core/tests/sql_server_2025_parity.rs @@ -99,3 +99,42 @@ fn test_like_escape_parity() { assert_eq!(res.rows.len(), 1); assert_eq!(res.rows[0][0].to_string_value(), "10%"); } + +#[test] +fn test_new_system_procedures_2025_extended() { + let engine = Engine::new(); + + // sp_helpuser + let res = engine.query("EXEC sp_helpuser").unwrap(); + assert_eq!(res.columns[0], "UserName"); + assert!(res.rows.iter().any(|r| r[0].to_string_value() == "dbo")); + + // sp_helprole + let res = engine.query("EXEC sp_helprole").unwrap(); + assert_eq!(res.columns[0], "RoleName"); + // Currently sys.database_principals has no roles by default, but let's check columns + + // sp_helprolemember + let res = engine.query("EXEC sp_helprolemember").unwrap(); + assert_eq!(res.columns[0], "DbRole"); + + // sp_helpsrvrole + let res = engine.query("EXEC sp_helpsrvrole").unwrap(); + assert_eq!(res.columns[0], "ServerRole"); + assert!(res.rows.iter().any(|r| r[0].to_string_value() == "sysadmin")); + + // sp_helpsrvrolemember + let res = engine.query("EXEC sp_helpsrvrolemember").unwrap(); + assert_eq!(res.columns[0], "ServerRole"); + assert!(res.rows.iter().any(|r| r[0].to_string_value() == "sysadmin" && r[1].to_string_value() == "sa")); + + // sp_helpfile + let res = engine.query("EXEC sp_helpfile").unwrap(); + assert_eq!(res.columns[0], "name"); + assert!(res.rows.iter().any(|r| r[0].to_string_value() == "iridium_sql")); + + // sp_helpfilegroup + let res = engine.query("EXEC sp_helpfilegroup").unwrap(); + assert_eq!(res.columns[0], "name"); + assert!(res.rows.iter().any(|r| r[0].to_string_value() == "PRIMARY")); +} diff --git a/docs/sql-server-2025-implementation-status.md b/docs/sql-server-2025-implementation-status.md index 04deddb..c812414 100644 --- a/docs/sql-server-2025-implementation-status.md +++ b/docs/sql-server-2025-implementation-status.md @@ -364,8 +364,8 @@ This document tracks the implementation status of SQL Server 2025 features in Ir | sp_helpdevice | ❌ Pending | | sp_helpdownloadlist | ❌ Pending | | sp_helpextendedproc | ❌ Pending | -| sp_helpfile | ❌ Pending | -| sp_helpfilegroup | ❌ Pending | +| sp_helpfile | ✅ Implemented | +| sp_helpfilegroup | ✅ Implemented | | sp_helpgroup | ❌ Pending | | sp_helpindex | ✅ Implemented | | sp_helplinkedsrvlogin | ❌ Pending | @@ -373,10 +373,10 @@ This document tracks the implementation status of SQL Server 2025 features in Ir | sp_helpremotelogin | ❌ Pending | | sp_helpremotelogin_90 | ❌ Pending | | sp_helpserver | ❌ Pending | -| sp_helpsrvrole | ❌ Pending | -| sp_helpsrvrolemember | ❌ Pending | +| sp_helpsrvrole | ✅ Implemented | +| sp_helpsrvrolemember | ✅ Implemented | | sp_helptext_jobstep | ❌ Pending | -| sp_helpuser | ❌ Pending | +| sp_helpuser | ✅ Implemented | | sp_indexes | ❌ Pending | | sp_kill_filestream_non_transacted_handles | ❌ Pending | | sp_link_publication | ❌ Pending |