diff --git a/SQL Scripts/COM-AX-001.sql b/SQL Scripts/COM-AX-001.sql index f6a34eb..d805e4c 100644 --- a/SQL Scripts/COM-AX-001.sql +++ b/SQL Scripts/COM-AX-001.sql @@ -5,6 +5,9 @@ -- Titel: Dynamics AX 3.0 er uden support fra Microsoft -- Alvor: Medium -- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- Kører kun i Dynamics AX-databaser (den fælles AX-genkendelse). Axapta 3.0: verificeret mod en kundes +-- skema; AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. -- -- Hvad tjekkes, og hvorfor: -- Dynamics AX 3.0 er uden support fra Microsoft. Microsoft Dynamics AX 3.0 er for længst ude af Microsofts support, @@ -14,8 +17,10 @@ -- -- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): -- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. --- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), --- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede +-- databaser (CustomQueryCollector.DatabasesSql), som AX-genkendelsen (AxDatabaseDetector) genkender som Dynamics AX; +-- er der ingen, er reglen "ikke relevant". Fund går kun til SQLInsight Hub for databaser med AX-tilvalget. +-- Kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke -- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "{database} -- er en Dynamics AX 3.0-database, og AX 3.0 er uden support" ({kolonne} udfyldes fra rækken). -- @@ -34,15 +39,20 @@ -- CustomQueryCollector.BuildRuleSql(database, query) -- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) -- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql -- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: -- rules/community/dynamics-ax.json (id COM-AX-001, indlejret i regelpakken) -- --- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data, --- indstillinger eller objekter. +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen +-- via [db].sys.sp_executesql (kun metadata, ingen AX-tabeldata). Intet ændrer data, indstillinger eller +-- objekter. -- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch -- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet. --- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den --- database, du vil se. +-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database. -- =================================================================================================================== -- ------------------------------------------------------------------------------------------------------------------- @@ -65,10 +75,42 @@ GO SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name GO +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke af databaserne er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: reglens forespørgsel i hver genkendt Dynamics AX-database +-- ------------------------------------------------------------------------------------------------------------------- + -- Variant: SQL Server 2005 og nyere -- Kilde: CustomQueryCollector.BuildRuleSql -- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) --- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. -USE [msdb]; -SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) AS [sql_version] WHERE EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SQLDICTIONARY') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'DATAAREA') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SYSCONFIG') AND COLUMNPROPERTY((SELECT TOP 1 object_id FROM sys.tables WHERE name = N'SQLDICTIONARY'), N'RECID', 'Precision') = 10 +-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database. +USE [AXDB]; +SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) AS [sql_version] WHERE COLUMNPROPERTY((SELECT TOP 1 object_id FROM sys.tables WHERE name = N'SQLDICTIONARY'), N'RECID', 'Precision') = 10 GO diff --git a/SQL Scripts/COM-AX-002.sql b/SQL Scripts/COM-AX-002.sql index cdec105..3702ea2 100644 --- a/SQL Scripts/COM-AX-002.sql +++ b/SQL Scripts/COM-AX-002.sql @@ -5,6 +5,9 @@ -- Titel: Dynamics AX 3.0 kører på en SQL Server-version, der ikke var understøttet til AX 3.0 -- Alvor: Medium -- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- Kører kun i Dynamics AX-databaser (den fælles AX-genkendelse). Axapta 3.0: verificeret mod en kundes +-- skema; AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. -- -- Hvad tjekkes, og hvorfor: -- Dynamics AX 3.0 kører på en SQL Server-version, der ikke var understøttet til AX 3.0. AX 3.0 blev kun frigivet @@ -15,8 +18,10 @@ -- -- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): -- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. --- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), --- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede +-- databaser (CustomQueryCollector.DatabasesSql), som AX-genkendelsen (AxDatabaseDetector) genkender som Dynamics AX; +-- er der ingen, er reglen "ikke relevant". Fund går kun til SQLInsight Hub for databaser med AX-tilvalget. +-- Kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke -- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "AX -- 3.0-databasen {database} ligger på SQL Server {sql_version}" ({kolonne} udfyldes fra rækken). -- @@ -35,15 +40,20 @@ -- CustomQueryCollector.BuildRuleSql(database, query) -- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) -- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql -- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: -- rules/community/dynamics-ax.json (id COM-AX-002, indlejret i regelpakken) -- --- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data, --- indstillinger eller objekter. +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen +-- via [db].sys.sp_executesql (kun metadata, ingen AX-tabeldata). Intet ændrer data, indstillinger eller +-- objekter. -- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch -- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet. --- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den --- database, du vil se. +-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database. -- =================================================================================================================== -- ------------------------------------------------------------------------------------------------------------------- @@ -66,10 +76,42 @@ GO SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name GO +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke af databaserne er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: reglens forespørgsel i hver genkendt Dynamics AX-database +-- ------------------------------------------------------------------------------------------------------------------- + -- Variant: SQL Server 2005 og nyere -- Kilde: CustomQueryCollector.BuildRuleSql -- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) --- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. -USE [msdb]; -SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) AS [sql_version], d.compatibility_level AS [compatibility_level] FROM sys.databases d WHERE d.database_id = DB_ID() AND CAST(PARSENAME(CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), 4) AS int) > 9 AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SQLDICTIONARY') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'DATAAREA') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SYSCONFIG') AND COLUMNPROPERTY((SELECT TOP 1 object_id FROM sys.tables WHERE name = N'SQLDICTIONARY'), N'RECID', 'Precision') = 10 +-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database. +USE [AXDB]; +SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) AS [sql_version], d.compatibility_level AS [compatibility_level] FROM sys.databases d WHERE d.database_id = DB_ID() AND CAST(PARSENAME(CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), 4) AS int) > 9 AND COLUMNPROPERTY((SELECT TOP 1 object_id FROM sys.tables WHERE name = N'SQLDICTIONARY'), N'RECID', 'Precision') = 10 GO diff --git a/SQL Scripts/COM-AX-003.sql b/SQL Scripts/COM-AX-003.sql index 0221f5a..e97865f 100644 --- a/SQL Scripts/COM-AX-003.sql +++ b/SQL Scripts/COM-AX-003.sql @@ -5,6 +5,9 @@ -- Titel: Dynamics AX-database på en instans, hvor MAXDOP ikke er 1 (SQL Server 2005-2014) -- Alvor: Medium -- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- Kører kun i Dynamics AX-databaser (den fælles AX-genkendelse). Axapta 3.0: verificeret mod en kundes +-- skema; AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. -- -- Hvad tjekkes, og hvorfor: -- Dynamics AX-database på en instans, hvor MAXDOP ikke er 1 (SQL Server 2005-2014). Dynamics AX sender mange små, @@ -14,8 +17,10 @@ -- -- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): -- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. --- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), --- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede +-- databaser (CustomQueryCollector.DatabasesSql), som AX-genkendelsen (AxDatabaseDetector) genkender som Dynamics AX; +-- er der ingen, er reglen "ikke relevant". Fund går kun til SQLInsight Hub for databaser med AX-tilvalget. +-- Kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke -- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "MAXDOP er -- {maxdop} på instansen med Dynamics AX-databasen {database}" ({kolonne} udfyldes fra rækken). -- @@ -34,15 +39,20 @@ -- CustomQueryCollector.BuildRuleSql(database, query) -- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) -- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql -- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: -- rules/community/dynamics-ax.json (id COM-AX-003, indlejret i regelpakken) -- --- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data, --- indstillinger eller objekter. +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen +-- via [db].sys.sp_executesql (kun metadata, ingen AX-tabeldata). Intet ændrer data, indstillinger eller +-- objekter. -- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch -- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet. --- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den --- database, du vil se. +-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database. -- =================================================================================================================== -- ------------------------------------------------------------------------------------------------------------------- @@ -65,10 +75,42 @@ GO SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name GO +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke af databaserne er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: reglens forespørgsel i hver genkendt Dynamics AX-database +-- ------------------------------------------------------------------------------------------------------------------- + -- Variant: SQL Server 2005 og nyere -- Kilde: CustomQueryCollector.BuildRuleSql -- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) --- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. -USE [msdb]; -SELECT CAST(c.value_in_use AS int) AS [maxdop] FROM sys.configurations c WHERE c.name = 'max degree of parallelism' AND CAST(c.value_in_use AS int) <> 1 AND CAST(PARSENAME(CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), 4) AS int) < 13 AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SQLDICTIONARY') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'DATAAREA') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SYSCONFIG') +-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database. +USE [AXDB]; +SELECT CAST(c.value_in_use AS int) AS [maxdop] FROM sys.configurations c WHERE c.name = 'max degree of parallelism' AND CAST(c.value_in_use AS int) <> 1 AND CAST(PARSENAME(CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), 4) AS int) < 13 GO diff --git a/SQL Scripts/COM-AX-004.sql b/SQL Scripts/COM-AX-004.sql index e44631c..bad0f12 100644 --- a/SQL Scripts/COM-AX-004.sql +++ b/SQL Scripts/COM-AX-004.sql @@ -5,6 +5,9 @@ -- Titel: Dynamics AX-database, hvor MAXDOP ikke er 1 (SQL Server 2016 og nyere) -- Alvor: Medium -- Versioner: SQL Server 2016 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- Kører kun i Dynamics AX-databaser (den fælles AX-genkendelse). Axapta 3.0: verificeret mod en kundes +-- skema; AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. -- -- Hvad tjekkes, og hvorfor: -- Dynamics AX-database, hvor MAXDOP ikke er 1 (SQL Server 2016 og nyere). Dynamics AX sender mange små, hyppige @@ -14,8 +17,10 @@ -- -- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): -- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. --- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), --- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede +-- databaser (CustomQueryCollector.DatabasesSql), som AX-genkendelsen (AxDatabaseDetector) genkender som Dynamics AX; +-- er der ingen, er reglen "ikke relevant". Fund går kun til SQLInsight Hub for databaser med AX-tilvalget. +-- Kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke -- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "Dynamics -- AX-databasen {database} kører med MAXDOP større end 1" ({kolonne} udfyldes fra rækken). -- @@ -34,15 +39,20 @@ -- CustomQueryCollector.BuildRuleSql(database, query) -- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) -- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql -- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: -- rules/community/dynamics-ax.json (id COM-AX-004, indlejret i regelpakken) -- --- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data, --- indstillinger eller objekter. +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen +-- via [db].sys.sp_executesql (kun metadata, ingen AX-tabeldata). Intet ændrer data, indstillinger eller +-- objekter. -- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch -- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet. --- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den --- database, du vil se. +-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database. -- =================================================================================================================== -- ------------------------------------------------------------------------------------------------------------------- @@ -65,10 +75,42 @@ GO SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name GO +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke af databaserne er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: reglens forespørgsel i hver genkendt Dynamics AX-database +-- ------------------------------------------------------------------------------------------------------------------- + -- Variant: SQL Server 2016 og nyere -- Kilde: CustomQueryCollector.BuildRuleSql -- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) --- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. -USE [msdb]; -SELECT CAST(c.value_in_use AS int) AS [server_maxdop], CAST(s.value AS int) AS [database_maxdop] FROM sys.configurations c CROSS JOIN sys.database_scoped_configurations s WHERE c.name = 'max degree of parallelism' AND s.name = 'MAXDOP' AND (CASE WHEN CAST(s.value AS int) = 0 THEN CAST(c.value_in_use AS int) ELSE CAST(s.value AS int) END) <> 1 AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SQLDICTIONARY') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'DATAAREA') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SYSCONFIG') +-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database. +USE [AXDB]; +SELECT CAST(c.value_in_use AS int) AS [server_maxdop], CAST(s.value AS int) AS [database_maxdop] FROM sys.configurations c CROSS JOIN sys.database_scoped_configurations s WHERE c.name = 'max degree of parallelism' AND s.name = 'MAXDOP' AND (CASE WHEN CAST(s.value AS int) = 0 THEN CAST(c.value_in_use AS int) ELSE CAST(s.value AS int) END) <> 1 GO diff --git a/SQL Scripts/COM-AX-005.sql b/SQL Scripts/COM-AX-005.sql index 821be8c..fc0b249 100644 --- a/SQL Scripts/COM-AX-005.sql +++ b/SQL Scripts/COM-AX-005.sql @@ -5,6 +5,9 @@ -- Titel: Dynamics AX-tabeller uden unikt RecId-indeks -- Alvor: Medium -- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- Kører kun i Dynamics AX-databaser (den fælles AX-genkendelse). Axapta 3.0: verificeret mod en kundes +-- skema; AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. -- -- Hvad tjekkes, og hvorfor: -- Dynamics AX-tabeller uden unikt RecId-indeks. AX identificerer hver post med RecId og opretter normalt et unikt @@ -14,8 +17,10 @@ -- -- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): -- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. --- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), --- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede +-- databaser (CustomQueryCollector.DatabasesSql), som AX-genkendelsen (AxDatabaseDetector) genkender som Dynamics AX; +-- er der ingen, er reglen "ikke relevant". Fund går kun til SQLInsight Hub for databaser med AX-tilvalget. +-- Kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke -- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "{tables} -- AX-tabeller i {database} har intet unikt indeks på RECID" ({kolonne} udfyldes fra rækken). -- @@ -34,15 +39,20 @@ -- CustomQueryCollector.BuildRuleSql(database, query) -- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) -- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql -- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: -- rules/community/dynamics-ax.json (id COM-AX-005, indlejret i regelpakken) -- --- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data, --- indstillinger eller objekter. +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen +-- via [db].sys.sp_executesql (kun metadata, ingen AX-tabeldata). Intet ændrer data, indstillinger eller +-- objekter. -- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch -- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet. --- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den --- database, du vil se. +-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database. -- =================================================================================================================== -- ------------------------------------------------------------------------------------------------------------------- @@ -65,10 +75,42 @@ GO SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name GO +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke af databaserne er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: reglens forespørgsel i hver genkendt Dynamics AX-database +-- ------------------------------------------------------------------------------------------------------------------- + -- Variant: SQL Server 2005 og nyere -- Kilde: CustomQueryCollector.BuildRuleSql -- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) --- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. -USE [msdb]; -SELECT COUNT(*) AS [tables], LEFT(STUFF((SELECT TOP 10 N', ' + t2.name FROM sys.tables t2 WHERE COLUMNPROPERTY(t2.object_id, N'RECID', 'ColumnId') IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys.indexes i JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id WHERE i.object_id = t2.object_id AND i.is_unique = 1 AND c.name = N'RECID') ORDER BY t2.name FOR XML PATH('')), 1, 2, ''), 400) AS [examples] FROM sys.tables t WHERE COLUMNPROPERTY(t.object_id, N'RECID', 'ColumnId') IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys.indexes i JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id WHERE i.object_id = t.object_id AND i.is_unique = 1 AND c.name = N'RECID') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SQLDICTIONARY') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'DATAAREA') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SYSCONFIG') HAVING COUNT(*) > 0 +-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database. +USE [AXDB]; +SELECT COUNT(*) AS [tables], LEFT(STUFF((SELECT TOP 10 N', ' + t2.name FROM sys.tables t2 WHERE COLUMNPROPERTY(t2.object_id, N'RECID', 'ColumnId') IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys.indexes i JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id WHERE i.object_id = t2.object_id AND i.is_unique = 1 AND c.name = N'RECID') ORDER BY t2.name FOR XML PATH('')), 1, 2, ''), 400) AS [examples] FROM sys.tables t WHERE COLUMNPROPERTY(t.object_id, N'RECID', 'ColumnId') IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys.indexes i JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id WHERE i.object_id = t.object_id AND i.is_unique = 1 AND c.name = N'RECID') HAVING COUNT(*) > 0 GO diff --git a/SQL Scripts/COM-AX-006.sql b/SQL Scripts/COM-AX-006.sql index 3f78a38..d080592 100644 --- a/SQL Scripts/COM-AX-006.sql +++ b/SQL Scripts/COM-AX-006.sql @@ -5,6 +5,9 @@ -- Titel: Store Dynamics AX-tabeller uden clustered index (heaps) -- Alvor: Medium -- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- Kører kun i Dynamics AX-databaser (den fælles AX-genkendelse). Axapta 3.0: verificeret mod en kundes +-- skema; AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. -- -- Hvad tjekkes, og hvorfor: -- Store Dynamics AX-tabeller uden clustered index (heaps). En tabel uden clustered index (en heap) får spredte data @@ -14,8 +17,10 @@ -- -- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): -- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. --- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), --- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede +-- databaser (CustomQueryCollector.DatabasesSql), som AX-genkendelsen (AxDatabaseDetector) genkender som Dynamics AX; +-- er der ingen, er reglen "ikke relevant". Fund går kun til SQLInsight Hub for databaser med AX-tilvalget. +-- Kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke -- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "{tables} -- store AX-tabeller i {database} har intet clustered index" ({kolonne} udfyldes fra rækken). -- @@ -34,15 +39,20 @@ -- CustomQueryCollector.BuildRuleSql(database, query) -- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) -- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql -- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: -- rules/community/dynamics-ax.json (id COM-AX-006, indlejret i regelpakken) -- --- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data, --- indstillinger eller objekter. +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen +-- via [db].sys.sp_executesql (kun metadata, ingen AX-tabeldata). Intet ændrer data, indstillinger eller +-- objekter. -- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch -- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet. --- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den --- database, du vil se. +-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database. -- =================================================================================================================== -- ------------------------------------------------------------------------------------------------------------------- @@ -65,10 +75,42 @@ GO SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name GO +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke af databaserne er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: reglens forespørgsel i hver genkendt Dynamics AX-database +-- ------------------------------------------------------------------------------------------------------------------- + -- Variant: SQL Server 2005 og nyere -- Kilde: CustomQueryCollector.BuildRuleSql -- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) --- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. -USE [msdb]; -SELECT COUNT(*) AS [tables], SUM(h.row_count) AS [rows], LEFT(STUFF((SELECT TOP 10 N', ' + OBJECT_NAME(h2.object_id) + N' (' + CAST(h2.row_count AS nvarchar(20)) + N')' FROM (SELECT p.object_id, SUM(p.rows) AS row_count FROM sys.partitions p JOIN sys.tables t ON t.object_id = p.object_id WHERE p.index_id = 0 GROUP BY p.object_id) h2 WHERE h2.row_count >= 100000 ORDER BY h2.row_count DESC FOR XML PATH('')), 1, 2, ''), 400) AS [largest] FROM (SELECT p.object_id, SUM(p.rows) AS row_count FROM sys.partitions p JOIN sys.tables t ON t.object_id = p.object_id WHERE p.index_id = 0 GROUP BY p.object_id) h WHERE h.row_count >= 100000 AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SQLDICTIONARY') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'DATAAREA') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SYSCONFIG') HAVING COUNT(*) > 0 +-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database. +USE [AXDB]; +SELECT COUNT(*) AS [tables], SUM(h.row_count) AS [rows], LEFT(STUFF((SELECT TOP 10 N', ' + OBJECT_NAME(h2.object_id) + N' (' + CAST(h2.row_count AS nvarchar(20)) + N')' FROM (SELECT p.object_id, SUM(p.rows) AS row_count FROM sys.partitions p JOIN sys.tables t ON t.object_id = p.object_id WHERE p.index_id = 0 GROUP BY p.object_id) h2 WHERE h2.row_count >= 100000 ORDER BY h2.row_count DESC FOR XML PATH('')), 1, 2, ''), 400) AS [largest] FROM (SELECT p.object_id, SUM(p.rows) AS row_count FROM sys.partitions p JOIN sys.tables t ON t.object_id = p.object_id WHERE p.index_id = 0 GROUP BY p.object_id) h WHERE h.row_count >= 100000 HAVING COUNT(*) > 0 GO diff --git a/SQL Scripts/COM-AX-007.sql b/SQL Scripts/COM-AX-007.sql index 88c0b80..ac54768 100644 --- a/SQL Scripts/COM-AX-007.sql +++ b/SQL Scripts/COM-AX-007.sql @@ -5,6 +5,9 @@ -- Titel: Automatiske statistikker er slået fra i en Dynamics AX-database -- Alvor: High -- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- Kører kun i Dynamics AX-databaser (den fælles AX-genkendelse). Axapta 3.0: verificeret mod en kundes +-- skema; AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. -- -- Hvad tjekkes, og hvorfor: -- Automatiske statistikker er slået fra i en Dynamics AX-database. AX bygger sine forespørgsler dynamisk ud fra @@ -15,8 +18,10 @@ -- -- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): -- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. --- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), --- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede +-- databaser (CustomQueryCollector.DatabasesSql), som AX-genkendelsen (AxDatabaseDetector) genkender som Dynamics AX; +-- er der ingen, er reglen "ikke relevant". Fund går kun til SQLInsight Hub for databaser med AX-tilvalget. +-- Kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke -- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "Automatiske -- statistikker er slået fra i AX-databasen {database}" ({kolonne} udfyldes fra rækken). -- @@ -35,15 +40,20 @@ -- CustomQueryCollector.BuildRuleSql(database, query) -- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) -- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql -- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: -- rules/community/dynamics-ax.json (id COM-AX-007, indlejret i regelpakken) -- --- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger. Intet ændrer data, --- indstillinger eller objekter. +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen +-- via [db].sys.sp_executesql (kun metadata, ingen AX-tabeldata). Intet ændrer data, indstillinger eller +-- objekter. -- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch -- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet. --- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den --- database, du vil se. +-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database. -- =================================================================================================================== -- ------------------------------------------------------------------------------------------------------------------- @@ -66,10 +76,42 @@ GO SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name GO +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke af databaserne er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: reglens forespørgsel i hver genkendt Dynamics AX-database +-- ------------------------------------------------------------------------------------------------------------------- + -- Variant: SQL Server 2005 og nyere -- Kilde: CustomQueryCollector.BuildRuleSql -- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) --- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. -USE [msdb]; -SELECT CASE WHEN d.is_auto_create_stats_on = 1 THEN 'til' ELSE 'fra' END AS [auto_create_statistics], CASE WHEN d.is_auto_update_stats_on = 1 THEN 'til' ELSE 'fra' END AS [auto_update_statistics] FROM sys.databases d WHERE d.database_id = DB_ID() AND (d.is_auto_create_stats_on = 0 OR d.is_auto_update_stats_on = 0) AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SQLDICTIONARY') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'DATAAREA') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SYSCONFIG') +-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database. +USE [AXDB]; +SELECT CASE WHEN d.is_auto_create_stats_on = 1 THEN 'til' ELSE 'fra' END AS [auto_create_statistics], CASE WHEN d.is_auto_update_stats_on = 1 THEN 'til' ELSE 'fra' END AS [auto_update_statistics] FROM sys.databases d WHERE d.database_id = DB_ID() AND (d.is_auto_create_stats_on = 0 OR d.is_auto_update_stats_on = 0) GO diff --git a/SQL Scripts/README.md b/SQL Scripts/README.md index d3528b6..cc86769 100644 --- a/SQL Scripts/README.md +++ b/SQL Scripts/README.md @@ -14,6 +14,7 @@ identisk med koden (se [Hold mappen i sync](#hold-mappen-i-sync)). | `.sql` | Én fil pr. regel, navngivet med regel-id'et præcis som i systemet: `SQLI-…` for de indbyggede regler (C#), `COM-…` for community-regler (JSON i `rules/community/`). | | `_collectors/.sql` | Alle forespørgsler fra én collector. Flere regler deler ofte samme collector; regelfilerne gentager SQL'en, så hver fil kan køres for sig. | | `_collectors/InstanceProbe.sql` | Registreringen af instansen (version, udgave, Always On) og de sessionsindstillinger, agenten sætter på hver forbindelse. | +| `_collectors/AxDatabaseDetector.sql`, `_collectors/AxBatchCollector.sql`, `_collectors/AxSequenceCollector.sql` | Dynamics AX (fra 1.4): den fælles AX-genkendelse og læsningen af batchjob, nummerserier og RecId i AX-databasen. AX-forespørgslerne er vist for hver AX-version, SQLInsight har et skema for (Axapta 3.0, AX 4.0, AX 2009 og AX 2012), og for databasen `AXDB`. | | `_collectors/DatabaseDiscovery.sql`, `_collectors/PermissionCheck.sql` | Søgningen efter databaser (med Dynamics AX-genkendelse) og forbindelsestesten med rettighedstjek under *Servere og databaser* (fra 1.3). De køres kun, når der bedes om dem i webbrugerfladen, og søgningen desuden hver time. | ## Opbygning af en regelfil @@ -61,7 +62,9 @@ Bemærk: - **Read-only.** Alle scripts læser kun: SELECT mod katalog-views og DMV'er, `DBCC TRACESTATUS`, `DBCC SQLPERF(WAITSTATS)` (kun SQL Server 2000), `xp_readerrorlog`, `xp_msver`, `sys.fn_trace_gettable`, lokale variabler og `USE`. - Ingen undtagelser: heller ikke midlertidige tabeller i tempdb. + Ingen undtagelser: heller ikke midlertidige tabeller i tempdb. Dynamics AX-læsningerne (fra 1.4) kører som + `EXEC [AXDB].sys.sp_executesql N'…'`, så sessionen aldrig skifter database; batchen i strengen er selv kun SELECT med + `TOP` (READ COMMITTED med `SET LOCK_TIMEOUT 2000`, eller SNAPSHOT, aldrig NOLOCK), og testen tjekker den på samme måde. - **Per-database-forespørgsler** (indexes, statistikker, Query Store, database-scoped community-regler) kører agenten i hver online brugerdatabase. Her er de vist for `msdb`, som findes på alle instanser, så scriptet kan køres uændret. Ret `[msdb]` / `N'msdb'` til den database, du vil se. @@ -84,14 +87,19 @@ Bemærk: - `Every_collector_query_is_registered_and_in_a_collector_script`: hver SQL-konstant og hver `Build…Sql`-metode i collectorerne er registreret, og hver registreret forespørgsel står i et `_collectors/`-script. - `Community_rule_scripts_contain_the_rule_query_verbatim`: community-reglens `query` fra JSON står ordret i filen. -- `Scripts_are_read_only`: ingen skrivende sætninger, heller ikke `SELECT … INTO #temp`. +- `Scripts_are_read_only`: ingen skrivende sætninger, heller ikke `SELECT … INTO #temp` - heller ikke i batchen i en + `EXEC [db].sys.sp_executesql N'…'`. +- `tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs` (Dynamics AX): hver AX-regel og AX-collector viser SQL'en for alle + AX-versioner, og headeren siger, hvad der ikke er verificeret mod kundedata. + en collectors SQL ændret, kan blokkene skrives om fra koden: kør testene med `SQLINSIGHT_UPDATE_SQL_SCRIPTS=1` +(kommentarlinjerne bevares). Blokke med værdier, agenten beregner ved kørsel (⟪…⟫), udfyldes kun, når de er nye; værdien så sættes ind i hånden. En ny regel skal derfor have sin `SQL Scripts/.sql` (se `CONTRIBUTING.md`). Ændres en collectors SQL, peger testen på de filer, der skal opdateres. ## Regler -98 regler: 82 indbyggede (C#) og 16 community-regler (JSON). +108 regler: 92 indbyggede (C#) og 16 community-regler (JSON). | Regel-id | Fil | Titel | Collector(s) | Alvor | |---|---|---|---|---| @@ -99,6 +107,16 @@ testen på de filer, der skal opdateres. | SQLI-AG-002 | [SQLI-AG-002.sql](SQLI-AG-002.sql) | Sekundær replika er langt bagud | AvailabilityGroupCollector | Medium | | SQLI-AG-003 | [SQLI-AG-003.sql](SQLI-AG-003.sql) | En node eller et vidne i clusteret er nede | AvailabilityGroupCollector | High | | SQLI-ANOM-001 | [SQLI-ANOM-001.sql](SQLI-ANOM-001.sql) | Usædvanlig belastning i forhold til det normale | CpuCollector, PerformanceCounterCollector | CPU: Medium. Batch requests: Low ved stigning, Medium ved fald. Page life expectancy: Medium. | +| SQLI-AX-001 | [SQLI-AX-001.sql](SQLI-AX-001.sql) | Dynamics AX-batchjob er endt med fejl | AxDatabaseDetector, AxBatchCollector | High | +| SQLI-AX-002 | [SQLI-AX-002.sql](SQLI-AX-002.sql) | Dynamics AX-batchjob kører længere end normalt | AxDatabaseDetector, AxBatchCollector | Medium; High, når kørslen er dobbelt så lang som grænsen. | +| SQLI-AX-003 | [SQLI-AX-003.sql](SQLI-AX-003.sql) | Dynamics AX-batchjob er ikke startet til tiden | AxDatabaseDetector, AxBatchCollector | Medium; High, når jobbet i praksis aldrig er startet. | +| SQLI-AX-004 | [SQLI-AX-004.sql](SQLI-AX-004.sql) | Dynamics AX-batchjob hænger i Udfører | AxDatabaseDetector, AxBatchCollector | High | +| SQLI-AX-005 | [SQLI-AX-005.sql](SQLI-AX-005.sql) | Dynamics AX-nummerserie er ved at løbe tør | AxDatabaseDetector, AxSequenceCollector | Medium; High over den kritiske procent eller inden for de kritiske dage; Critical, når serien er opbrugt. | +| SQLI-AX-006 | [SQLI-AX-006.sql](SQLI-AX-006.sql) | Dynamics AX-nummerseriens format er for kort til Højeste | AxDatabaseDetector, AxSequenceCollector | Medium; High, når NEXTREC allerede er større end formatets kapacitet. | +| SQLI-AX-007 | [SQLI-AX-007.sql](SQLI-AX-007.sql) | Spærret Dynamics AX-nummerserie bruges stadig | AxDatabaseDetector, AxSequenceCollector | Medium | +| SQLI-AX-008 | [SQLI-AX-008.sql](SQLI-AX-008.sql) | Kontinuerlig Dynamics AX-nummerserie er ikke ryddet op | AxDatabaseDetector, AxSequenceCollector | Low | +| SQLI-AX-009 | [SQLI-AX-009.sql](SQLI-AX-009.sql) | Dynamics AX RecId er ved at løbe tør | AxDatabaseDetector, AxSequenceCollector | Axapta 3.0: High/Critical efter grænserne; 64-bit: High; Critical, når RecId er opbrugt. | +| SQLI-AX-010 | [SQLI-AX-010.sql](SQLI-AX-010.sql) | AX-bruger blokerer andre | AxDatabaseDetector, BlockingCollector | Medium; High ved ventetid over 5 minutter eller mindst 10 blokerede sessioner. | | SQLI-BLK-001 | [SQLI-BLK-001.sql](SQLI-BLK-001.sql) | Langvarig blocking | BlockingCollector | Medium; High når længste ventetid er mindst 5 minutter eller mindst 10 sessioner er blokeret. | | SQLI-CFG-001 | [SQLI-CFG-001.sql](SQLI-CFG-001.sql) | max server memory er ikke sat | ConfigurationCollector, ServerCollector | High | | SQLI-CFG-002 | [SQLI-CFG-002.sql](SQLI-CFG-002.sql) | max server memory efterlader for lidt til Windows | ConfigurationCollector, ServerCollector | Medium | @@ -217,7 +235,7 @@ testen på de filer, der skal opdateres. | [ReplicationCollector](_collectors/ReplicationCollector.sql) | `src/SQLInsight.Collectors/Instance/ReplicationCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:replication) | SQL Server 2005 og nyere | 2 regler | | [PerformanceCounterCollector](_collectors/PerformanceCounterCollector.sql) | `src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:performance-counters) | Alle (SQL Server 2000 og nyere) | 2 regler | | [CpuCollector](_collectors/CpuCollector.sql) | `src/SQLInsight.Collectors/Performance/CpuCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:cpu) | SQL Server 2005 og nyere | 2 regler | -| [BlockingCollector](_collectors/BlockingCollector.sql) | `src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs` | 1 min som standard (SQLInsight:CollectorIntervals:blocking) | Alle (SQL Server 2000 og nyere) | 1 regel | +| [BlockingCollector](_collectors/BlockingCollector.sql) | `src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs` | 1 min som standard (SQLInsight:CollectorIntervals:blocking) | Alle (SQL Server 2000 og nyere) | 2 regler | | [ActivityCollector](_collectors/ActivityCollector.sql) | `src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs` | 1 min som standard (SQLInsight:CollectorIntervals:activity) | SQL Server 2005 og nyere | 3 regler | | [DeadlockCollector](_collectors/DeadlockCollector.sql) | `src/SQLInsight.Collectors/Concurrency/DeadlockCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:deadlocks) | SQL Server 2008 og nyere (filer fra 2012, ellers ring buffer) | 1 regel | | [QueryStoreCollector](_collectors/QueryStoreCollector.sql) | `src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs` | 30 min som standard (SQLInsight:CollectorIntervals:query-store) | SQL Server 2016 og nyere | 3 regler | @@ -226,3 +244,6 @@ testen på de filer, der skal opdateres. | [AvailabilityGroupCollector](_collectors/AvailabilityGroupCollector.sql) | `src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs` | 1 min som standard (SQLInsight:CollectorIntervals:availability-groups) | SQL Server 2012 og nyere med Always On slået til | 3 regler | | [AgentJobCollector](_collectors/AgentJobCollector.sql) | `src/SQLInsight.Collectors/Instance/AgentJobCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:agent-jobs) | SQL Server 2005 og nyere | 2 regler | | [CustomQueryCollector](_collectors/CustomQueryCollector.sql) | `src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) | SQL Server 2005 og nyere (pr. regel: minimumVersion) | 16 regler | +| [AxDatabaseDetector](_collectors/AxDatabaseDetector.sql) | `src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs` | 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax); uden AX-læseadgang hvert 5. minut | SQL Server 2005 og nyere | 17 regler (alle AX-regler) | +| [AxBatchCollector](_collectors/AxBatchCollector.sql) | `src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:dynamics-ax-batch) | SQL Server 2005 og nyere; kræver valgfri AX-læseadgang | 4 regler | +| [AxSequenceCollector](_collectors/AxSequenceCollector.sql) | `src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs` | 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax-sequences) | SQL Server 2005 og nyere; kræver valgfri AX-læseadgang | 5 regler | diff --git a/SQL Scripts/SQLI-AX-001.sql b/SQL Scripts/SQLI-AX-001.sql index 3bb0ee4..c7f9344 100644 --- a/SQL Scripts/SQLI-AX-001.sql +++ b/SQL Scripts/SQLI-AX-001.sql @@ -1,152 +1,159 @@ -/* - Regel: SQLI-AX-001 – Dynamics AX-batchjob er endt med fejl - Alvorlighed: Høj (High) +-- =================================================================================================================== +-- SQLI-AX-001: Dynamics AX-batchjob er endt med fejl +-- =================================================================================================================== +-- Regel-id: SQLI-AX-001 +-- Titel: Dynamics AX-batchjob er endt med fejl +-- Alvor: High +-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/ +-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. +-- +-- Hvad tjekkes, og hvorfor: +-- Et batchjob i Dynamics AX er stoppet med fejl. Det, jobbet skulle gøre (bogføring, lagerafslutning, integration +-- ...), er ikke gjort eller kun delvist, og gentagne job fortsætter ikke nødvendigvis af sig selv. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Et fund pr. job, når jobbet nu har status Fejl og sluttede (eller var planlagt) inden for grænsen, eller når jobbet +-- venter på næste gentagelse, men dets seneste kørsel i SQLInsights egen historik (insight.AxBatchRuns) endte med +-- fejl inden for grænsen. +-- STATUS oversættes af versionens adapter: AX 2009+ 0 Tilbageholdt, 1 Venter, 2 Udfører, 3 Fejl, 4 Afsluttet, +-- 5 Klar, 6 Ikke kørt, 7 Annullerer, 8 Annulleret; Axapta 3.0/4.0 (antaget, ikke verificeret mod kundedata) +-- 0 Venter, 1 Udfører, 2 Fejl, 3 Afsluttet, 4 Tilbageholdt. Højst 25 fund pr. database. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- SQLInsight:DynamicsAx:BatchFailedLookbackHours = 24: fejl inden for så mange timer rapporteres. +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql): +-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger): +-- Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO [bruger]; +-- AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO [bruger]; +-- AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO [bruger]; +-- GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO [bruger]; +-- Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen (også under Dynamics AX → AX-databaser +-- og GET /api/v1/dynamics-ax/grant-script). Uden GRANT ændres intet. +-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en +-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql +-- AxBatchCollector +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs +-- Medlemmer: AxSchemaAdapter.BatchJobs(database), AxSchemaAdapter.BatchHistory(database, afterRecId), AxSql.InDatabase +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:dynamics-ax-batch) +-- Delt script: SQL Scripts/_collectors/AxBatchCollector.sql +-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxBatchFailedRule +-- +-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via +-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000 +-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger +-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til +-- overvågning. +-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch +-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet. +-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database. +-- =================================================================================================================== - Hvad og hvorfor - Et batchjob i Dynamics AX er stoppet med fejl. Det, jobbet skulle gøre (bogføring, lagerafslutning, integration …), - er ikke gjort eller kun delvist, og gentagne job fortsætter ikke nødvendigvis af sig selv. +-- ------------------------------------------------------------------------------------------------------------------- +-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse +-- ------------------------------------------------------------------------------------------------------------------- - Tærskel (agentens konfiguration SQLInsight:DynamicsAx) - BatchFailedLookbackHours = 24 (standard): fejl inden for så mange timer rapporteres. +-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner +-- Kilde: SqlConnectionFactory.SessionSettings +-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings +SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +GO - Vurdering (C#: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs – AxBatchFailedRule) - Et fund pr. job, når jobbet nu har status Fejl og sluttede (eller var planlagt) inden for tærsklen, eller når jobbet - venter på næste gentagelse, men dets seneste kørsel i SQLInsights egen historik (insight.AxBatchRuns) endte med fejl - inden for tærsklen. STATUS oversættes af versionens adapter: AX 2009+ 0 Tilbageholdt, 1 Venter, 2 Udfører, 3 Fejl, - 4 Afsluttet, 5 Klar, 6 Ikke kørt, 7 Annullerer, 8 Annulleret; Axapta 3.0/4.0 (antaget) 0 Venter, 1 Udfører, 2 Fejl, - 3 Afsluttet, 4 Tilbageholdt. Højst 25 fund pr. database. +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- - Collector: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs – AxBatchCollector.CollectAsync (hvert 5. minut). - SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – BatchJobs og BatchHistory. - Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql): - Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO []; - AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO []; - AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO []; - GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO []; - Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen (også under Dynamics AX → - AX-databaser og GET /api/v1/dynamics-ax/grant-script). Genkendelsen kræver kun grant-least-privilege.sql. - Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/ - schema-ax30.tsv); STATUS-værdierne er dog ikke verificeret mod kundedata. AX 4.0, AX 2009, AX 2012 og F&O: - baseret på Microsofts dokumenterede skema – ikke verificeret mod kundedata. - Læsning: Kun SELECT med TOP, READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen tillader det), aldrig - NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og valgt til overvågning. -*/ --- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden. --- Variant: Axapta 3.0 (skema bmssa): batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[BATCH] b ORDER BY b.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCH] b ORDER BY b.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- ------------------------------------------------------------------------------------------------------------------- +-- AxBatchCollector: Batchjob og kørselshistorik i AX-databasen (valgfri AX-læseadgang) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(3.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[BATCH] b ORDER BY b.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(4.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCH] b ORDER BY b.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(2009) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger fra seneste RECID minus 2000 -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @FraRecId bigint; -SET @FraRecId = 0; -- <== SQLInsight bruger den højeste RECID, den har læst, minus 2000 -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 999999999 ORDER BY h.[RECID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -SET @Sql = REPLACE(@Sql, N'> 999999999', N'> ' + CAST(@FraRecId AS nvarchar(20))); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste) +-- Kilde: AxSchemaAdapter.BatchHistory(2009, første) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet) +-- Kilde: AxSchemaAdapter.BatchHistory(2009, næste) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase +-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(2012) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger fra seneste RECID minus 2000 -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @FraRecId bigint; -SET @FraRecId = 0; -- <== SQLInsight bruger den højeste RECID, den har læst, minus 2000 -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 999999999 ORDER BY h.[RECID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -SET @Sql = REPLACE(@Sql, N'> 999999999', N'> ' + CAST(@FraRecId AS nvarchar(20))); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste) +-- Kilde: AxSchemaAdapter.BatchHistory(2012, første) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- <<< slut på genereret SQL + +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet) +-- Kilde: AxSchemaAdapter.BatchHistory(2012, næste) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase +-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + diff --git a/SQL Scripts/SQLI-AX-002.sql b/SQL Scripts/SQLI-AX-002.sql index 0d18f60..e9ed39b 100644 --- a/SQL Scripts/SQLI-AX-002.sql +++ b/SQL Scripts/SQLI-AX-002.sql @@ -1,152 +1,163 @@ -/* - Regel: SQLI-AX-002 – Dynamics AX-batchjob kører længere end normalt - Alvorlighed: Medium; Høj (High), når kørslen er dobbelt så lang som grænsen +-- =================================================================================================================== +-- SQLI-AX-002: Dynamics AX-batchjob kører længere end normalt +-- =================================================================================================================== +-- Regel-id: SQLI-AX-002 +-- Titel: Dynamics AX-batchjob kører længere end normalt +-- Alvor: Medium; High, når kørslen er dobbelt så lang som grænsen. +-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/ +-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. +-- +-- Hvad tjekkes, og hvorfor: +-- Et kørende batchjob har kørt meget længere end jobbets egne seneste kørsler. Det kan skyldes blokering, en ændret +-- datamængde, en dårlig plan, eller at jobbet hænger - og imens kan det holde låse, som brugerne venter på. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- For job med status Udfører: kørt = nu - faktisk start (AX 2009+: BATCHJOB.STARTDATETIME; Axapta 3.0/4.0 gemmer +-- ikke den faktiske start, så det er første gang, SQLInsight så jobbet køre). Grænse = max(faktor x 90 %-fraktil af +-- jobbets seneste op til 20 vellykkede kørsler, minimum). Historikken er SQLInsights egen (insight.AxBatchRuns): fra +-- BATCHJOBHISTORY i AX 2009+, ellers de kørsler, SQLInsight selv ser blive afsluttet. Hænger jobbet (SQLI-AX-004), +-- gives kun det fund. +-- STATUS oversættes af versionens adapter: AX 2009+ 0 Tilbageholdt, 1 Venter, 2 Udfører, 3 Fejl, 4 Afsluttet, +-- 5 Klar, 6 Ikke kørt, 7 Annullerer, 8 Annulleret; Axapta 3.0/4.0 (antaget, ikke verificeret mod kundedata) +-- 0 Venter, 1 Udfører, 2 Fejl, 3 Afsluttet, 4 Tilbageholdt. Højst 25 fund pr. database. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- SQLInsight:DynamicsAx:BatchLongRunningFactor = 2.0: kørt mere end så mange gange jobbets 90 %-fraktil +-- SQLInsight:DynamicsAx:BatchLongRunningMinimumMinutes = 10: men aldrig under så mange minutter +-- SQLInsight:DynamicsAx:BatchHistoryMinimumRuns = 5: så mange tidligere kørsler skal der være +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql): +-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger): +-- Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO [bruger]; +-- AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO [bruger]; +-- AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO [bruger]; +-- GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO [bruger]; +-- Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen (også under Dynamics AX → AX-databaser +-- og GET /api/v1/dynamics-ax/grant-script). Uden GRANT ændres intet. +-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en +-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql +-- AxBatchCollector +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs +-- Medlemmer: AxSchemaAdapter.BatchJobs(database), AxSchemaAdapter.BatchHistory(database, afterRecId), AxSql.InDatabase +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:dynamics-ax-batch) +-- Delt script: SQL Scripts/_collectors/AxBatchCollector.sql +-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxBatchLongRunningRule +-- +-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via +-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000 +-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger +-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til +-- overvågning. +-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch +-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet. +-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database. +-- =================================================================================================================== - Hvad og hvorfor - Et kørende batchjob har kørt meget længere end jobbets egne seneste kørsler. Det kan skyldes blokering, en ændret - datamængde, en dårlig plan, eller at jobbet hænger – og imens kan det holde låse, som brugerne venter på. +-- ------------------------------------------------------------------------------------------------------------------- +-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse +-- ------------------------------------------------------------------------------------------------------------------- - Tærskler (agentens konfiguration SQLInsight:DynamicsAx) - BatchLongRunningFactor = 2.0 kørt mere end så mange gange jobbets 90 %-fraktil - BatchLongRunningMinimumMinutes = 10 men aldrig under så mange minutter - BatchHistoryMinimumRuns = 5 så mange tidligere kørsler skal der være +-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner +-- Kilde: SqlConnectionFactory.SessionSettings +-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings +SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +GO - Vurdering (C#: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs – AxBatchLongRunningRule) - For job med status Udfører: kørt = nu − faktisk start (AX 2009+: BATCHJOB.STARTDATETIME; Axapta 3.0/4.0 gemmer ikke - den faktiske start, så det er første gang SQLInsight så jobbet køre). Grænse = max(faktor × 90 %-fraktil af jobbets - seneste op til 20 vellykkede kørsler, minimum). Historikken er SQLInsights egen (insight.AxBatchRuns): fra - BATCHJOBHISTORY i AX 2009+, ellers de kørsler SQLInsight selv ser blive afsluttet. Højst 25 fund pr. database. +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- - Collector: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs – AxBatchCollector.CollectAsync (hvert 5. minut). - SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – BatchJobs og BatchHistory. - Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql): - Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO []; - AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO []; - AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO []; - GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO []; - Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. - Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema; STATUS-værdierne er dog - ikke verificeret mod kundedata. AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede - skema – ikke verificeret mod kundedata. - Læsning: Kun SELECT med TOP, READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen tillader det), aldrig - NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og valgt til overvågning. -*/ --- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden. --- Variant: Axapta 3.0 (skema bmssa): batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[BATCH] b ORDER BY b.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCH] b ORDER BY b.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- ------------------------------------------------------------------------------------------------------------------- +-- AxBatchCollector: Batchjob og kørselshistorik i AX-databasen (valgfri AX-læseadgang) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(3.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[BATCH] b ORDER BY b.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(4.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCH] b ORDER BY b.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(2009) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger fra seneste RECID minus 2000 -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @FraRecId bigint; -SET @FraRecId = 0; -- <== SQLInsight bruger den højeste RECID, den har læst, minus 2000 -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 999999999 ORDER BY h.[RECID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -SET @Sql = REPLACE(@Sql, N'> 999999999', N'> ' + CAST(@FraRecId AS nvarchar(20))); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste) +-- Kilde: AxSchemaAdapter.BatchHistory(2009, første) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet) +-- Kilde: AxSchemaAdapter.BatchHistory(2009, næste) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase +-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(2012) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger fra seneste RECID minus 2000 -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @FraRecId bigint; -SET @FraRecId = 0; -- <== SQLInsight bruger den højeste RECID, den har læst, minus 2000 -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 999999999 ORDER BY h.[RECID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -SET @Sql = REPLACE(@Sql, N'> 999999999', N'> ' + CAST(@FraRecId AS nvarchar(20))); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste) +-- Kilde: AxSchemaAdapter.BatchHistory(2012, første) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- <<< slut på genereret SQL + +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet) +-- Kilde: AxSchemaAdapter.BatchHistory(2012, næste) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase +-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + diff --git a/SQL Scripts/SQLI-AX-003.sql b/SQL Scripts/SQLI-AX-003.sql index ee0c679..2d1d696 100644 --- a/SQL Scripts/SQLI-AX-003.sql +++ b/SQL Scripts/SQLI-AX-003.sql @@ -1,150 +1,162 @@ -/* - Regel: SQLI-AX-003 – Dynamics AX-batchjob er ikke startet til tiden - Alvorlighed: Medium; Høj (High), når jobbet i praksis aldrig er startet +-- =================================================================================================================== +-- SQLI-AX-003: Dynamics AX-batchjob er ikke startet til tiden +-- =================================================================================================================== +-- Regel-id: SQLI-AX-003 +-- Titel: Dynamics AX-batchjob er ikke startet til tiden +-- Alvor: Medium; High, når jobbet i praksis aldrig er startet. +-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/ +-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. +-- +-- Hvad tjekkes, og hvorfor: +-- Et ventende batchjob, hvis planlagte start er passeret: batchserveren (AOS med batch, eller batch-klienten i +-- Axapta 3.0) tager det ikke. Mange forsinkede job på én gang betyder typisk, at batchserveren slet ikke kører. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- For job med status Venter: forsinkelse = nu - planlagt start. Planlagt start er i AX 2009+ BATCHJOB.ORIGSTARTDATETIME +-- (UTC). I Axapta 3.0/4.0 er den BATCH.STARTDATE (lokal dato) + STARTTIME (sekunder efter midnat, lokal tid); den +-- regnes om til UTC med AX-tidszonen (SQLInsight:DynamicsAx:LocalTimeZone, ellers agentens tidszone) og de +-- sommertidsregler, der gjaldt på datoen. AX' tomme dato (1900-01-01) er "ingen". Job med status Tilbageholdt er +-- ikke forsinkede. De mest forsinkede først. +-- STATUS oversættes af versionens adapter: AX 2009+ 0 Tilbageholdt, 1 Venter, 2 Udfører, 3 Fejl, 4 Afsluttet, +-- 5 Klar, 6 Ikke kørt, 7 Annullerer, 8 Annulleret; Axapta 3.0/4.0 (antaget, ikke verificeret mod kundedata) +-- 0 Venter, 1 Udfører, 2 Fejl, 3 Afsluttet, 4 Tilbageholdt. Højst 25 fund pr. database. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- SQLInsight:DynamicsAx:BatchLateMinutes = 30: forsinket mere end så mange minutter: Medium +-- SQLInsight:DynamicsAx:BatchNeverStartedHours = 24: forsinket mere end så mange timer: High, "aldrig startet" +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql): +-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger): +-- Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO [bruger]; +-- AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO [bruger]; +-- AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO [bruger]; +-- GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO [bruger]; +-- Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen (også under Dynamics AX → AX-databaser +-- og GET /api/v1/dynamics-ax/grant-script). Uden GRANT ændres intet. +-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en +-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql +-- AxBatchCollector +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs +-- Medlemmer: AxSchemaAdapter.BatchJobs(database), AxSchemaAdapter.BatchHistory(database, afterRecId), AxSql.InDatabase +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:dynamics-ax-batch) +-- Delt script: SQL Scripts/_collectors/AxBatchCollector.sql +-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxBatchLateRule +-- +-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via +-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000 +-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger +-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til +-- overvågning. +-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch +-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet. +-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database. +-- =================================================================================================================== - Hvad og hvorfor - Et ventende batchjob, hvis planlagte start er passeret: batchserveren (AOS med batch, eller batch-klienten i - Axapta 3.0) tager det ikke. Mange forsinkede job på én gang betyder typisk, at batchserveren slet ikke kører. +-- ------------------------------------------------------------------------------------------------------------------- +-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse +-- ------------------------------------------------------------------------------------------------------------------- - Tærskler (agentens konfiguration SQLInsight:DynamicsAx) - BatchLateMinutes = 30 forsinket mere end så mange minutter: Medium - BatchNeverStartedHours = 24 forsinket mere end så mange timer: Høj, "aldrig startet" +-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner +-- Kilde: SqlConnectionFactory.SessionSettings +-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings +SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +GO - Vurdering (C#: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs – AxBatchLateRule) - For job med status Venter: forsinkelse = nu − planlagt start. Planlagt start er i AX 2009+ BATCHJOB.ORIGSTARTDATETIME - (UTC). I Axapta 3.0/4.0 er den BATCH.STARTDATE (lokal dato) + STARTTIME (sekunder efter midnat, lokal tid); den regnes - om til UTC med SQL Servers forskel mellem GETDATE() og GETUTCDATE() på læsetidspunktet. AX' tomme dato (1900-01-01) - er "ingen". Job med status Tilbageholdt er ikke forsinkede. Højst 25 fund pr. database, de mest forsinkede først. +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- - Collector: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs – AxBatchCollector.CollectAsync (hvert 5. minut). - SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – BatchJobs. - Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql): - Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO []; - AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO []; - AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO []; - Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. - Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema; STATUS-værdierne er dog - ikke verificeret mod kundedata. AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede - skema – ikke verificeret mod kundedata. - Læsning: Kun SELECT med TOP, READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen tillader det), aldrig - NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og valgt til overvågning. -*/ --- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden. --- Variant: Axapta 3.0 (skema bmssa): batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[BATCH] b ORDER BY b.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCH] b ORDER BY b.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- ------------------------------------------------------------------------------------------------------------------- +-- AxBatchCollector: Batchjob og kørselshistorik i AX-databasen (valgfri AX-læseadgang) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(3.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[BATCH] b ORDER BY b.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(4.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCH] b ORDER BY b.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(2009) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger fra seneste RECID minus 2000 -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @FraRecId bigint; -SET @FraRecId = 0; -- <== SQLInsight bruger den højeste RECID, den har læst, minus 2000 -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 999999999 ORDER BY h.[RECID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -SET @Sql = REPLACE(@Sql, N'> 999999999', N'> ' + CAST(@FraRecId AS nvarchar(20))); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste) +-- Kilde: AxSchemaAdapter.BatchHistory(2009, første) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet) +-- Kilde: AxSchemaAdapter.BatchHistory(2009, næste) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase +-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(2012) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger fra seneste RECID minus 2000 -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @FraRecId bigint; -SET @FraRecId = 0; -- <== SQLInsight bruger den højeste RECID, den har læst, minus 2000 -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 999999999 ORDER BY h.[RECID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -SET @Sql = REPLACE(@Sql, N'> 999999999', N'> ' + CAST(@FraRecId AS nvarchar(20))); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste) +-- Kilde: AxSchemaAdapter.BatchHistory(2012, første) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- <<< slut på genereret SQL + +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet) +-- Kilde: AxSchemaAdapter.BatchHistory(2012, næste) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase +-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + diff --git a/SQL Scripts/SQLI-AX-004.sql b/SQL Scripts/SQLI-AX-004.sql index 831b024..46d793d 100644 --- a/SQL Scripts/SQLI-AX-004.sql +++ b/SQL Scripts/SQLI-AX-004.sql @@ -1,150 +1,163 @@ -/* - Regel: SQLI-AX-004 – Dynamics AX-batchjob hænger i Udfører - Alvorlighed: Høj (High) +-- =================================================================================================================== +-- SQLI-AX-004: Dynamics AX-batchjob hænger i Udfører +-- =================================================================================================================== +-- Regel-id: SQLI-AX-004 +-- Titel: Dynamics AX-batchjob hænger i Udfører +-- Alvor: High +-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/ +-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. +-- +-- Hvad tjekkes, og hvorfor: +-- Et batchjob har haft status Udfører i flere timer. Typisk er AOS'en eller batch-klienten genstartet eller gået ned +-- midt i kørslen, så jobbet aldrig blev afsluttet: det står som kørende, intet sker, og gentagne job planlægges ikke +-- igen. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- For job med status Udfører: kørt = nu - faktisk start (AX 2009+: BATCHJOB.STARTDATETIME; Axapta 3.0/4.0: første +-- gang, SQLInsight så jobbet køre, så det kan have kørt længere). Fund, når kørt >= BatchStuckHours, medmindre jobbet +-- efter sin egen historik (insight.AxBatchRuns, mindst BatchHistoryMinimumRuns kørsler) normalt kører så længe +-- (kørt <= faktor x 90 %-fraktil). +-- STATUS oversættes af versionens adapter: AX 2009+ 0 Tilbageholdt, 1 Venter, 2 Udfører, 3 Fejl, 4 Afsluttet, +-- 5 Klar, 6 Ikke kørt, 7 Annullerer, 8 Annulleret; Axapta 3.0/4.0 (antaget, ikke verificeret mod kundedata) +-- 0 Venter, 1 Udfører, 2 Fejl, 3 Afsluttet, 4 Tilbageholdt. Højst 25 fund pr. database. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- SQLInsight:DynamicsAx:BatchStuckHours = 6: kørt mindst så mange timer +-- SQLInsight:DynamicsAx:BatchLongRunningFactor = 2.0: ...og mere end så mange gange jobbets 90 %-fraktil, når der er +-- historik +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql): +-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger): +-- Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO [bruger]; +-- AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO [bruger]; +-- AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO [bruger]; +-- GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO [bruger]; +-- Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen (også under Dynamics AX → AX-databaser +-- og GET /api/v1/dynamics-ax/grant-script). Uden GRANT ændres intet. +-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en +-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql +-- AxBatchCollector +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs +-- Medlemmer: AxSchemaAdapter.BatchJobs(database), AxSchemaAdapter.BatchHistory(database, afterRecId), AxSql.InDatabase +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:dynamics-ax-batch) +-- Delt script: SQL Scripts/_collectors/AxBatchCollector.sql +-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxBatchStuckRule +-- +-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via +-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000 +-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger +-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til +-- overvågning. +-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch +-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet. +-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database. +-- =================================================================================================================== - Hvad og hvorfor - Et batchjob har haft status Udfører i flere timer. Typisk er AOS'en eller batch-klienten genstartet eller gået ned midt - i kørslen, så jobbet aldrig blev afsluttet: det står som kørende, intet sker, og gentagne job planlægges ikke igen. +-- ------------------------------------------------------------------------------------------------------------------- +-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse +-- ------------------------------------------------------------------------------------------------------------------- - Tærskler (agentens konfiguration SQLInsight:DynamicsAx) - BatchStuckHours = 6 kørt mindst så mange timer - BatchLongRunningFactor = 2.0 …og mere end så mange gange jobbets 90 %-fraktil, når der er historik +-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner +-- Kilde: SqlConnectionFactory.SessionSettings +-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings +SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +GO - Vurdering (C#: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs – AxBatchStuckRule) - For job med status Udfører: kørt = nu − faktisk start (AX 2009+: BATCHJOB.STARTDATETIME; Axapta 3.0/4.0: første gang - SQLInsight så jobbet køre, så det kan have kørt længere). Fund, når kørt ≥ BatchStuckHours, medmindre jobbet efter - sin egen historik (insight.AxBatchRuns) normalt kører så længe (kørt ≤ faktor × 90 %-fraktil). +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- - Collector: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs – AxBatchCollector.CollectAsync (hvert 5. minut). - SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – BatchJobs og BatchHistory. - Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql): - Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO []; - AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO []; - AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO []; - GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO []; - Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. - Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema; STATUS-værdierne er dog - ikke verificeret mod kundedata. AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede - skema – ikke verificeret mod kundedata. - Læsning: Kun SELECT med TOP, READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen tillader det), aldrig - NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og valgt til overvågning. -*/ --- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden. --- Variant: Axapta 3.0 (skema bmssa): batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[BATCH] b ORDER BY b.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCH] b ORDER BY b.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- ------------------------------------------------------------------------------------------------------------------- +-- AxBatchCollector: Batchjob og kørselshistorik i AX-databasen (valgfri AX-læseadgang) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(3.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[BATCH] b ORDER BY b.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(4.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCH] b ORDER BY b.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(2009) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger fra seneste RECID minus 2000 -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @FraRecId bigint; -SET @FraRecId = 0; -- <== SQLInsight bruger den højeste RECID, den har læst, minus 2000 -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 999999999 ORDER BY h.[RECID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -SET @Sql = REPLACE(@Sql, N'> 999999999', N'> ' + CAST(@FraRecId AS nvarchar(20))); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste) +-- Kilde: AxSchemaAdapter.BatchHistory(2009, første) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet) +-- Kilde: AxSchemaAdapter.BatchHistory(2009, næste) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase +-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(2012) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger fra seneste RECID minus 2000 -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @FraRecId bigint; -SET @FraRecId = 0; -- <== SQLInsight bruger den højeste RECID, den har læst, minus 2000 -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 999999999 ORDER BY h.[RECID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -SET @Sql = REPLACE(@Sql, N'> 999999999', N'> ' + CAST(@FraRecId AS nvarchar(20))); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste) +-- Kilde: AxSchemaAdapter.BatchHistory(2012, første) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- <<< slut på genereret SQL + +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet) +-- Kilde: AxSchemaAdapter.BatchHistory(2012, næste) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase +-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + diff --git a/SQL Scripts/SQLI-AX-005.sql b/SQL Scripts/SQLI-AX-005.sql index c1c2523..ae18846 100644 --- a/SQL Scripts/SQLI-AX-005.sql +++ b/SQL Scripts/SQLI-AX-005.sql @@ -1,96 +1,131 @@ -/* - Regel: SQLI-AX-005 – Dynamics AX-nummerserie er ved at løbe tør - Alvorlighed: Medium; Høj (High) over den kritiske procent eller inden for de kritiske dage; Kritisk (Critical), når serien er opbrugt +-- =================================================================================================================== +-- SQLI-AX-005: Dynamics AX-nummerserie er ved at løbe tør +-- =================================================================================================================== +-- Regel-id: SQLI-AX-005 +-- Titel: Dynamics AX-nummerserie er ved at løbe tør +-- Alvor: Medium; High over den kritiske procent eller inden for de kritiske dage; Critical, når serien er opbrugt. +-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/ +-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. +-- +-- Hvad tjekkes, og hvorfor: +-- Når en nummerserie er brugt op, kan AX ikke tildele flere numre: oprettelse eller bogføring af fx ordrer, fakturaer +-- eller posteringer, der bruger serien, fejler for alle brugere. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Grænse = HIGHEST, eller formatets kapacitet (10^antal # - 1), hvis den er lavere. Brugt % = (NEXTREC - LOWEST) / +-- (grænse - LOWEST + 1). Tilbage = grænse - NEXTREC + 1. Prognose: forbrug pr. dag = (NEXTREC nu - NEXTREC i den +-- ældste daglige måling i vinduet) / dage imellem; dage tilbage = (grænse - NEXTREC) / forbrug pr. dag. Målingerne er +-- SQLInsights egne (insight.AxSequenceSamples, én pr. serie pr. dag). Manuelle, cykliske og spærrede serier springes +-- over. Højst 25 fund pr. database. Tal: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : Interpret, AxForecast.From. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- SQLInsight:DynamicsAx:NumberSequenceWarnPercent = 80: brugt mindst så mange procent: Medium +-- SQLInsight:DynamicsAx:NumberSequenceCriticalPercent = 95: High +-- SQLInsight:DynamicsAx:ForecastWarnDays = 90: løber tør inden for så mange dage efter prognosen: Medium +-- SQLInsight:DynamicsAx:ForecastCriticalDays = 30: High +-- SQLInsight:DynamicsAx:ForecastBasisDays = 30: prognosen bruger den ældste daglige måling inden for så mange dage +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql): +-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger): +-- Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO [bruger]; +-- GRANT SELECT ON [bmssa].[NUMBERSEQUENCEREFERENCE] TO [bruger]; -- valgfri: om serien bruges +-- AX 4.0/2009: de samme tabeller i [dbo] +-- AX 2012/F&O: desuden GRANT SELECT ON [dbo].[NUMBERSEQUENCESCOPE] TO [bruger]; -- valgfri: firma +-- Mangler NUMBERSEQUENCETABLE, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. Uden GRANT ændres intet. +-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en +-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql +-- AxSequenceCollector +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs +-- Medlemmer: AxSchemaAdapter.NumberSequences(database), AxSchemaAdapter.RecIds(database), AxSql.InDatabase +-- Interval: 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax-sequences) +-- Delt script: SQL Scripts/_collectors/AxSequenceCollector.sql +-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxNumberSequenceExhaustionRule +-- +-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via +-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000 +-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger +-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til +-- overvågning. +-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch +-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet. +-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database. +-- =================================================================================================================== - Hvad og hvorfor - Når en nummerserie er brugt op, kan AX ikke tildele flere numre: oprettelse eller bogføring af fx ordrer, fakturaer - eller posteringer, der bruger serien, fejler for alle brugere. +-- ------------------------------------------------------------------------------------------------------------------- +-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse +-- ------------------------------------------------------------------------------------------------------------------- - Tærskler (agentens konfiguration SQLInsight:DynamicsAx) - NumberSequenceWarnPercent = 80 brugt mindst så mange procent: Medium - NumberSequenceCriticalPercent = 95 …: Høj - ForecastWarnDays = 90 løber tør inden for så mange dage efter prognosen: Medium - ForecastCriticalDays = 30 …: Høj - ForecastBasisDays = 30 prognosen bruger den ældste daglige måling inden for så mange dage (mindst 1 dag gammel) +-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner +-- Kilde: SqlConnectionFactory.SessionSettings +-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings +SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +GO - Vurdering (C#: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs – AxNumberSequenceExhaustionRule; tal i - src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – Interpret og AxForecast.From) - Grænse = HIGHEST, eller formatets kapacitet (10^antal # − 1), hvis den er lavere. Brugt % = (NEXTREC − LOWEST) / - (grænse − LOWEST + 1). Tilbage = grænse − NEXTREC + 1. Prognose: forbrug pr. dag = (NEXTREC nu − NEXTREC i den ældste - daglige måling i vinduet) / dage imellem; dage tilbage = (grænse − NEXTREC) / forbrug pr. dag. Målingerne er SQLInsights - egne (insight.AxSequenceSamples, én pr. serie pr. dag, gemt i HourlyDays = 400 dage). Manuelle, cykliske og spærrede - serier springes over. Højst 25 fund pr. database. +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- - Collector: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs – AxSequenceCollector.CollectAsync (hver time). - SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – NumberSequences. - Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql): - Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO []; - GRANT SELECT ON [bmssa].[NUMBERSEQUENCEREFERENCE] TO []; (valgfri) - AX 4.0/2009: samme tabeller i [dbo] - AX 2012/F&O: GRANT SELECT ON [dbo].[NUMBERSEQUENCETABLE] TO []; - GRANT SELECT ON [dbo].[NUMBERSEQUENCESCOPE] TO []; (valgfri: firma) - GRANT SELECT ON [dbo].[NUMBERSEQUENCEREFERENCE] TO []; (valgfri) - Mangler NUMBERSEQUENCETABLE, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. - Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (schema-ax30.tsv). - AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema – ikke verificeret mod kundedata. - Læsning: Kun SELECT med TOP (de mest brugte først), READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen - tillader det), aldrig NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og - valgt til overvågning. -*/ --- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden. --- Variant: Axapta 3.0 (skema bmssa): nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- ------------------------------------------------------------------------------------------------------------------- +-- AxSequenceCollector: Nummerserier i AX-databasen (valgfri AX-læseadgang) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(3.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(4.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(2009) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- <<< slut på genereret SQL + +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(2012) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + diff --git a/SQL Scripts/SQLI-AX-006.sql b/SQL Scripts/SQLI-AX-006.sql index b205fa2..6677cb6 100644 --- a/SQL Scripts/SQLI-AX-006.sql +++ b/SQL Scripts/SQLI-AX-006.sql @@ -1,83 +1,124 @@ -/* - Regel: SQLI-AX-006 – Dynamics AX-nummerseriens format er for kort til Højeste - Alvorlighed: Medium; Høj (High), når NEXTREC allerede er større end formatets kapacitet +-- =================================================================================================================== +-- SQLI-AX-006: Dynamics AX-nummerseriens format er for kort til Højeste +-- =================================================================================================================== +-- Regel-id: SQLI-AX-006 +-- Titel: Dynamics AX-nummerseriens format er for kort til Højeste +-- Alvor: Medium; High, når NEXTREC allerede er større end formatets kapacitet. +-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/ +-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. +-- +-- Hvad tjekkes, og hvorfor: +-- Formatet (fx "FA-####") har færre cifre, end HIGHEST kræver. Når serien når forbi formatets kapacitet, fejler +-- tildelingen af numre, selv om serien ikke er brugt op. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Kapacitet = 10^(antal # i FORMAT) - 1. Fund, når HIGHEST > kapacitet (ikke for manuelle eller spærrede serier). High, +-- når også NEXTREC > kapacitet. Højst 25 fund pr. database. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: ren sammenligning. +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql): +-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger): +-- Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO [bruger]; +-- GRANT SELECT ON [bmssa].[NUMBERSEQUENCEREFERENCE] TO [bruger]; -- valgfri: om serien bruges +-- AX 4.0/2009: de samme tabeller i [dbo] +-- AX 2012/F&O: desuden GRANT SELECT ON [dbo].[NUMBERSEQUENCESCOPE] TO [bruger]; -- valgfri: firma +-- Mangler NUMBERSEQUENCETABLE, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. Uden GRANT ændres intet. +-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en +-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql +-- AxSequenceCollector +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs +-- Medlemmer: AxSchemaAdapter.NumberSequences(database), AxSchemaAdapter.RecIds(database), AxSql.InDatabase +-- Interval: 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax-sequences) +-- Delt script: SQL Scripts/_collectors/AxSequenceCollector.sql +-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxNumberSequenceFormatRule +-- +-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via +-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000 +-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger +-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til +-- overvågning. +-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch +-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet. +-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database. +-- =================================================================================================================== - Hvad og hvorfor - Formatet (fx "FA-####") har færre cifre, end HIGHEST kræver. Når serien når forbi formatets kapacitet, fejler - tildelingen af numre, selv om serien ikke er brugt op. +-- ------------------------------------------------------------------------------------------------------------------- +-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse +-- ------------------------------------------------------------------------------------------------------------------- - Tærskler: ingen (ren sammenligning). +-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner +-- Kilde: SqlConnectionFactory.SessionSettings +-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings +SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +GO - Vurdering (C#: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs – AxNumberSequenceFormatRule; tal i - src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – Interpret) - Kapacitet = 10^(antal # i FORMAT) − 1. Fund, når HIGHEST > kapacitet (ikke for manuelle serier). Høj, når også - NEXTREC > kapacitet. Højst 25 fund pr. database. +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- - Collector: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs – AxSequenceCollector.CollectAsync (hver time). - SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – NumberSequences. - Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql): - Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO []; - AX 4.0/2009/2012/F&O: GRANT SELECT ON [dbo].[NUMBERSEQUENCETABLE] TO []; - Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. - Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema. AX 4.0, AX 2009, AX 2012 - og F&O: baseret på Microsofts dokumenterede skema – ikke verificeret mod kundedata. - Læsning: Kun SELECT med TOP, READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen tillader det), aldrig - NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og valgt til overvågning. -*/ --- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden. --- Variant: Axapta 3.0 (skema bmssa): nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- ------------------------------------------------------------------------------------------------------------------- +-- AxSequenceCollector: Nummerserier i AX-databasen (valgfri AX-læseadgang) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(3.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(4.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(2009) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- <<< slut på genereret SQL + +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(2012) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + diff --git a/SQL Scripts/SQLI-AX-007.sql b/SQL Scripts/SQLI-AX-007.sql index 9de61ca..1ded933 100644 --- a/SQL Scripts/SQLI-AX-007.sql +++ b/SQL Scripts/SQLI-AX-007.sql @@ -1,84 +1,125 @@ -/* - Regel: SQLI-AX-007 – Spærret Dynamics AX-nummerserie bruges stadig - Alvorlighed: Medium +-- =================================================================================================================== +-- SQLI-AX-007: Spærret Dynamics AX-nummerserie bruges stadig +-- =================================================================================================================== +-- Regel-id: SQLI-AX-007 +-- Titel: Spærret Dynamics AX-nummerserie bruges stadig +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/ +-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. +-- +-- Hvad tjekkes, og hvorfor: +-- Serien er spærret (BLOCKED = 1), men en nummerseriereference peger stadig på den. Alt i AX, der skal have et nummer +-- fra den reference, fejler med en fejl om, at nummerserien er spærret. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund, når BLOCKED = 1 og der findes en NUMBERSEQUENCEREFERENCE til serien (Axapta 3.0/4.0/2009: samme NUMBERSEQUENCE +-- og DATAAREAID; AX 2012/F&O: NUMBERSEQUENCEID = seriens RECID). Kan referencetabellen ikke læses, vides det ikke, og +-- der gives intet fund. Højst 25 fund pr. database. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle. +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql): +-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger): +-- Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO [bruger]; +-- GRANT SELECT ON [bmssa].[NUMBERSEQUENCEREFERENCE] TO [bruger]; -- valgfri: om serien bruges +-- AX 4.0/2009: de samme tabeller i [dbo] +-- AX 2012/F&O: desuden GRANT SELECT ON [dbo].[NUMBERSEQUENCESCOPE] TO [bruger]; -- valgfri: firma +-- Mangler NUMBERSEQUENCETABLE, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. Uden GRANT ændres intet. +-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en +-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql +-- AxSequenceCollector +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs +-- Medlemmer: AxSchemaAdapter.NumberSequences(database), AxSchemaAdapter.RecIds(database), AxSql.InDatabase +-- Interval: 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax-sequences) +-- Delt script: SQL Scripts/_collectors/AxSequenceCollector.sql +-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxNumberSequenceBlockedRule +-- +-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via +-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000 +-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger +-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til +-- overvågning. +-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch +-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet. +-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database. +-- =================================================================================================================== - Hvad og hvorfor - Serien er spærret (BLOCKED = 1), men en nummerseriereference peger stadig på den. Alt i AX, der skal have et nummer - fra den reference, fejler med en fejl om, at nummerserien er spærret. +-- ------------------------------------------------------------------------------------------------------------------- +-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse +-- ------------------------------------------------------------------------------------------------------------------- - Tærskler: ingen. +-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner +-- Kilde: SqlConnectionFactory.SessionSettings +-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings +SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +GO - Vurdering (C#: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs – AxNumberSequenceBlockedRule) - Fund, når BLOCKED = 1 og der findes en NUMBERSEQUENCEREFERENCE til serien (Axapta 3.0/4.0/2009: samme NUMBERSEQUENCE - og DATAAREAID; AX 2012/F&O: NUMBERSEQUENCEID = serieens RECID). Kan referencetabellen ikke læses, vides det ikke, og - der gives intet fund (datanoten under Dynamics AX → Nummerserier siger det). Højst 25 fund pr. database. +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- - Collector: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs – AxSequenceCollector.CollectAsync (hver time). - SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – NumberSequences. - Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql): - Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO []; - GRANT SELECT ON [bmssa].[NUMBERSEQUENCEREFERENCE] TO []; - AX 4.0/2009/2012/F&O: de samme tabeller i [dbo] - Mangler NUMBERSEQUENCETABLE, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. - Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema. AX 4.0, AX 2009, AX 2012 - og F&O: baseret på Microsofts dokumenterede skema – ikke verificeret mod kundedata. - Læsning: Kun SELECT med TOP, READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen tillader det), aldrig - NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og valgt til overvågning. -*/ --- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden. --- Variant: Axapta 3.0 (skema bmssa): nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- ------------------------------------------------------------------------------------------------------------------- +-- AxSequenceCollector: Nummerserier i AX-databasen (valgfri AX-læseadgang) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(3.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(4.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(2009) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- <<< slut på genereret SQL + +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(2012) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + diff --git a/SQL Scripts/SQLI-AX-008.sql b/SQL Scripts/SQLI-AX-008.sql index f2ce828..f6104da 100644 --- a/SQL Scripts/SQLI-AX-008.sql +++ b/SQL Scripts/SQLI-AX-008.sql @@ -1,86 +1,127 @@ -/* - Regel: SQLI-AX-008 – Kontinuerlig Dynamics AX-nummerserie er ikke ryddet op - Alvorlighed: Lav (Low) +-- =================================================================================================================== +-- SQLI-AX-008: Kontinuerlig Dynamics AX-nummerserie er ikke ryddet op +-- =================================================================================================================== +-- Regel-id: SQLI-AX-008 +-- Titel: Kontinuerlig Dynamics AX-nummerserie er ikke ryddet op +-- Alvor: Low +-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/ +-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. +-- +-- Hvad tjekkes, og hvorfor: +-- En kontinuerlig serie må ikke have huller. Numre reserveret af afbrudte sessioner bliver liggende som optagede, til +-- serien ryddes op; uden oprydning opstår huller og fejl, og serien bliver en flaskehals, fordi hver tildeling låser +-- rækken i NUMBERSEQUENCETABLE, til transaktionen er færdig. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- For kontinuerlige (CONTINUOUS = 1), ikke spærrede, ikke manuelle serier, der er taget i brug (NEXTREC > LOWEST): +-- fund, når seneste oprydning er ældre end grænsen eller aldrig er sket (1900-01-01). Seneste oprydning er +-- LATESTCLEANDATETIME (UTC) i AX 2009+, og LATESTCLEANDATE + LATESTCLEANTIME (lokal tid, sekunder efter midnat) i +-- Axapta 3.0. Mangler kolonnen, gives intet fund. Højst 25 fund pr. database. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 7 dage (fast, AxNumberSequenceContinuousRule.MinimumDays), eller 2 x seriens eget CLEANINTERVAL (timer), hvis længere. +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql): +-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger): +-- Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO [bruger]; +-- GRANT SELECT ON [bmssa].[NUMBERSEQUENCEREFERENCE] TO [bruger]; -- valgfri: om serien bruges +-- AX 4.0/2009: de samme tabeller i [dbo] +-- AX 2012/F&O: desuden GRANT SELECT ON [dbo].[NUMBERSEQUENCESCOPE] TO [bruger]; -- valgfri: firma +-- Mangler NUMBERSEQUENCETABLE, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. Uden GRANT ændres intet. +-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en +-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql +-- AxSequenceCollector +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs +-- Medlemmer: AxSchemaAdapter.NumberSequences(database), AxSchemaAdapter.RecIds(database), AxSql.InDatabase +-- Interval: 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax-sequences) +-- Delt script: SQL Scripts/_collectors/AxSequenceCollector.sql +-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxNumberSequenceContinuousRule +-- +-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via +-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000 +-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger +-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til +-- overvågning. +-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch +-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet. +-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database. +-- =================================================================================================================== - Hvad og hvorfor - En kontinuerlig serie må ikke have huller. Numre reserveret af afbrudte sessioner bliver liggende som optagede, til - serien ryddes op; uden oprydning opstår huller og fejl, og serien bliver en flaskehals, fordi hver tildeling låser - rækken i NUMBERSEQUENCETABLE, til transaktionen er færdig. +-- ------------------------------------------------------------------------------------------------------------------- +-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse +-- ------------------------------------------------------------------------------------------------------------------- - Tærskler - 7 dage (fast, AxNumberSequenceContinuousRule.MinimumDays), eller 2 × seriens eget CLEANINTERVAL (timer), hvis længere. +-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner +-- Kilde: SqlConnectionFactory.SessionSettings +-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings +SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +GO - Vurdering (C#: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs – AxNumberSequenceContinuousRule) - For kontinuerlige (CONTINUOUS = 1), ikke spærrede, ikke manuelle serier, der er taget i brug (NEXTREC > LOWEST): fund, - når seneste oprydning er ældre end tærsklen eller aldrig er sket (1900-01-01). Seneste oprydning er - LATESTCLEANDATETIME (UTC) i AX 2009+, og LATESTCLEANDATE + LATESTCLEANTIME (lokal tid, sekunder efter midnat) i - Axapta 3.0. Mangler kolonnen, gives intet fund. Højst 25 fund pr. database. +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- - Collector: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs – AxSequenceCollector.CollectAsync (hver time). - SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – NumberSequences. - Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql): - Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO []; - AX 4.0/2009/2012/F&O: GRANT SELECT ON [dbo].[NUMBERSEQUENCETABLE] TO []; - Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. - Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema. AX 4.0, AX 2009, AX 2012 - og F&O: baseret på Microsofts dokumenterede skema – ikke verificeret mod kundedata. - Læsning: Kun SELECT med TOP, READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen tillader det), aldrig - NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og valgt til overvågning. -*/ --- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden. --- Variant: Axapta 3.0 (skema bmssa): nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- ------------------------------------------------------------------------------------------------------------------- +-- AxSequenceCollector: Nummerserier i AX-databasen (valgfri AX-læseadgang) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(3.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(4.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(2009) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- <<< slut på genereret SQL + +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(2012) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + diff --git a/SQL Scripts/SQLI-AX-009.sql b/SQL Scripts/SQLI-AX-009.sql index 75dbdec..4d33b86 100644 --- a/SQL Scripts/SQLI-AX-009.sql +++ b/SQL Scripts/SQLI-AX-009.sql @@ -1,94 +1,130 @@ -/* - Regel: SQLI-AX-009 – Dynamics AX RecId er ved at løbe tør - Alvorlighed: Axapta 3.0 (32-bit): Høj (High) fra advarselsgrænsen, Kritisk (Critical) fra den kritiske grænse; - 64-bit: Høj (High); altid Kritisk, når RecId er opbrugt +-- =================================================================================================================== +-- SQLI-AX-009: Dynamics AX RecId er ved at løbe tør +-- =================================================================================================================== +-- Regel-id: SQLI-AX-009 +-- Titel: Dynamics AX RecId er ved at løbe tør +-- Alvor: Axapta 3.0 (32-bit): High fra advarselsgrænsen, Critical fra den kritiske grænse; 64-bit: High; altid Critical, når RecId er opbrugt. +-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/ +-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. +-- +-- Hvad tjekkes, og hvorfor: +-- Når en tabels RecId-serie er brugt op, kan AX ikke oprette flere poster. Axapta 3.0 har 32-bit RecId (int, højst +-- 2.147.483.647 pr. tabel og firma), og det løses kun med et stort projekt - derfor advares langt tidligere for 3.0. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Grænse = typens maksimum (int, når SYSTEMSEQUENCES.NEXTVAL er int som i Axapta 3.0; ellers bigint), eller MAXVAL, +-- hvis den er sat lavere. Brugt % = NEXTVAL / grænse. Prognose ud fra SQLInsights egne daglige målinger af NEXTVAL +-- (insight.AxSequenceSamples). Højst 25 fund pr. database, de mest brugte først. Tal: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : Interpret(AxRecIdRow). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- SQLInsight:DynamicsAx:RecId32WarnPercent = 50: Axapta 3.0: High fra så mange procent af int-intervallet +-- SQLInsight:DynamicsAx:RecId32CriticalPercent = 75: Axapta 3.0: Critical +-- SQLInsight:DynamicsAx:RecId32ForecastDays = 730: Axapta 3.0: High, hvis den løber tør inden for så mange dage; +-- Critical inden for halvdelen +-- SQLInsight:DynamicsAx:RecId64WarnPercent = 80: 64-bit (eller sænket MAXVAL): High +-- SQLInsight:DynamicsAx:ForecastWarnDays = 90: 64-bit: High, hvis den løber tør inden for så mange dage +-- SQLInsight:DynamicsAx:ForecastBasisDays = 30: prognosen bruger den ældste daglige måling inden for så mange dage +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql): +-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger): +-- Axapta 3.0: GRANT SELECT ON [bmssa].[SYSTEMSEQUENCES] TO [bruger]; +-- GRANT SELECT ON [bmssa].[SQLDICTIONARY] TO [bruger]; -- valgfri: tabelnavne +-- AX 4.0/2009/2012/F&O: de samme tabeller i [dbo] +-- Mangler SYSTEMSEQUENCES, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. Uden GRANT ændres intet. +-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en +-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql +-- AxSequenceCollector +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs +-- Medlemmer: AxSchemaAdapter.NumberSequences(database), AxSchemaAdapter.RecIds(database), AxSql.InDatabase +-- Interval: 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax-sequences) +-- Delt script: SQL Scripts/_collectors/AxSequenceCollector.sql +-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxRecIdExhaustionRule +-- +-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via +-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000 +-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger +-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til +-- overvågning. +-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch +-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet. +-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database. +-- =================================================================================================================== - Hvad og hvorfor - Når en tabels RecId-serie er brugt op, kan AX ikke oprette flere poster. Axapta 3.0 har 32-bit RecId (int, højst - 2.147.483.647 pr. tabel og firma), og det løses kun med et stort projekt – derfor advares langt tidligere for 3.0. +-- ------------------------------------------------------------------------------------------------------------------- +-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse +-- ------------------------------------------------------------------------------------------------------------------- - Tærskler (agentens konfiguration SQLInsight:DynamicsAx) - RecId32WarnPercent = 50 Axapta 3.0: Høj fra så mange procent af int-intervallet - RecId32CriticalPercent = 75 Axapta 3.0: Kritisk - RecId32ForecastDays = 730 Axapta 3.0: Høj, hvis den løber tør inden for så mange dage; Kritisk inden for halvdelen - RecId64WarnPercent = 80 64-bit (eller sænket MAXVAL): Høj - ForecastWarnDays = 90 64-bit: Høj, hvis den løber tør inden for så mange dage - ForecastBasisDays = 30 prognosen bruger den ældste daglige måling inden for så mange dage +-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner +-- Kilde: SqlConnectionFactory.SessionSettings +-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings +SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +GO - Vurdering (C#: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs – AxRecIdExhaustionRule.Assess; tal i - src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – Interpret(AxRecIdRow)) - Grænse = typens maksimum (int, når SYSTEMSEQUENCES.NEXTVAL er int som i Axapta 3.0; ellers bigint), eller MAXVAL, hvis - den er sat lavere. Brugt % = NEXTVAL / grænse. Prognose ud fra SQLInsights egne daglige målinger af NEXTVAL - (insight.AxSequenceSamples). Højst 25 fund pr. database, de mest brugte først. +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- - Collector: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs – AxSequenceCollector.CollectAsync (hver time). - SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – RecIds. - Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql): - Axapta 3.0: GRANT SELECT ON [bmssa].[SYSTEMSEQUENCES] TO []; - GRANT SELECT ON [bmssa].[SQLDICTIONARY] TO []; (valgfri: tabelnavne) - AX 4.0/2009/2012/F&O: de samme tabeller i [dbo] - Mangler SYSTEMSEQUENCES, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. - Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (SYSTEMSEQUENCES med - NEXTVAL/MAXVAL pr. TABID og DATAAREAID). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede - skema – ikke verificeret mod kundedata. - Læsning: Kun SELECT med TOP (de højeste først), READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen - tillader det), aldrig NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og - valgt til overvågning. -*/ --- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden. --- Variant: Axapta 3.0 (skema bmssa): RecId pr. tabel (AxSchemaAdapter.RecIds) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [bmssa].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [bmssa].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; -SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql +SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on +FROM sys.databases +WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 +ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: RecId pr. tabel (AxSchemaAdapter.RecIds) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- ------------------------------------------------------------------------------------------------------------------- +-- AxSequenceCollector: RecId pr. tabel og firma i AX-databasen (valgfri AX-læseadgang) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): RecId pr. tabel (SYSTEMSEQUENCES) +-- Kilde: AxSchemaAdapter.RecIds(3.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [bmssa].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [bmssa].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (AxSchemaAdapter.RecIds) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: RecId pr. tabel (SYSTEMSEQUENCES) +-- Kilde: AxSchemaAdapter.RecIds(4.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (AxSchemaAdapter.RecIds) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (SYSTEMSEQUENCES) +-- Kilde: AxSchemaAdapter.RecIds(2009) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- <<< slut på genereret SQL + +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (SYSTEMSEQUENCES) +-- Kilde: AxSchemaAdapter.RecIds(2012) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +GO + diff --git a/SQL Scripts/SQLI-AX-010.sql b/SQL Scripts/SQLI-AX-010.sql index 2b64304..7c965b4 100644 --- a/SQL Scripts/SQLI-AX-010.sql +++ b/SQL Scripts/SQLI-AX-010.sql @@ -1,50 +1,89 @@ -/* - Regel: SQLI-AX-010 – AX-bruger blokerer andre - Alvorlighed: Medium; Høj (High) ved ventetid over 5 minutter eller mindst 10 blokerede sessioner +-- =================================================================================================================== +-- SQLI-AX-010: AX-bruger blokerer andre +-- =================================================================================================================== +-- Regel-id: SQLI-AX-010 +-- Titel: AX-bruger blokerer andre +-- Alvor: Medium; High ved ventetid over 5 minutter eller mindst 10 blokerede sessioner. +-- Versioner: Blokering: SQL Server 2000 og nyere; AX-genkendelsen og dermed reglen: SQL Server 2005 og nyere. +-- Axapta 3.0/4.0: AX-brugeren kan ikke ses. AX 2009/2012/F&O: baseret på Microsofts dokumentation - ikke +-- verificeret mod kundedata. +-- +-- Hvad tjekkes, og hvorfor: +-- En session i en Dynamics AX-database holder låse, som andre AX-brugere eller batchjob venter på. Alle AX-brugere går +-- gennem AOS'ens login, så SQL Server-login'et siger ikke, hvem det er. Reglen viser AX-brugeren, når SQL Server kan +-- se den. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- For hver head blocker, hvis egen database eller en blokeret sessions database er genkendt som AX: fund, når den +-- længste ventetid i kæden er over grænsen. AX 2009/2012/F&O: AX-brugeren og AX-sessionen læses fra CONTEXT_INFO, +-- som AOS'en sætter, når "connectioncontext" er slået til i AOS-konfigurationen (formatet er dokumenteret af +-- Microsoft for AX 2012 - ikke verificeret mod kundedata). Axapta 3.0 og AX 4.0 sætter aldrig CONTEXT_INFO: +-- brugeren kan ikke ses fra SQL Server, og fundet siger det ærligt. AX-bruger, vært, program og CONTEXT_INFO er +-- følsomme målinger (sendes ikke til SQLInsight Hub uden SendSensitiveEvidence). Brugeren findes af +-- src/SQLInsight.Core/DynamicsAx/AxSessionMapper.cs. AX-tabeller læses ikke (og aldrig med NOLOCK). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- SQLInsight:DynamicsAx:BlockingSeconds = 30: den længste ventetid bag sessionen skal være mindst så mange sekunder. +-- High fra 300.000 ms eller 10 blokerede (fast i koden, som SQLI-BLK-001). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql): +-- Ingen AX-læseadgang: kun grant-least-privilege.sql. +-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019 (sessionerne) +-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ +-- GRANT VIEW ANY DEFINITION TO [agent]; -- AX-genkendelsen (metadata) +-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; før 2014 en databasebruger i AX-databasen +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AxDatabaseDetector (den fælles AX-genkendelse) +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database) +-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors +-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql +-- BlockingCollector +-- Fil: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs +-- Medlemmer: BlockingCollector.BuildSql2005(version), BlockingCollector.Sql2000 +-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:blocking) +-- Delt script: SQL Scripts/_collectors/BlockingCollector.sql +-- Regelkode: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs : AxBlockingUserRule +-- +-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via +-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000 +-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger +-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til +-- overvågning. +-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch +-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet. +-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database. +-- =================================================================================================================== - Hvad og hvorfor - En session fra Dynamics AX holder låse, som andre AX-brugere eller batchjob venter på. Alle AX-brugere går gennem - AOS'ens login, så SQL Server-login'et siger ikke, hvem det er. Reglen viser AX-brugeren, når SQL Server kan se den. +-- ------------------------------------------------------------------------------------------------------------------- +-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse +-- ------------------------------------------------------------------------------------------------------------------- - Tærskel (agentens konfiguration SQLInsight:DynamicsAx) - BlockingSeconds = 30 den længste ventetid bag sessionen skal være mindst så mange sekunder +-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner +-- Kilde: SqlConnectionFactory.SessionSettings +-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings +SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +GO - Vurdering (C#: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs – AxBlockingUserRule; brugeren findes af - src/SQLInsight.Core/DynamicsAx/AxSessionMapper.cs) - For hver head blocker, hvis egen database eller en blokeret sessions database er genkendt som AX: fund, når den - længste ventetid i kæden er over tærsklen. AX 2009/2012/F&O: AX-brugeren og AX-sessionen læses fra CONTEXT_INFO, som - AOS'en sætter, når "connectioncontext" er slået til i AOS-konfigurationen (første ord med bogstaver = bruger, første - tal = AX-session; formatet er dokumenteret af Microsoft for AX 2012 – ikke verificeret mod kundedata). Axapta 3.0 og - AX 4.0 sætter aldrig CONTEXT_INFO: brugeren kan ikke ses fra SQL Server, og fundet siger det ærligt og viser vært - (AOS'en ved 3-lags, brugerens pc ved 2-lags-klient) og program. AX-brugeren er følsom (sendes ikke til hubben uden - tilladelse). +-- ------------------------------------------------------------------------------------------------------------------- +-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata) +-- ------------------------------------------------------------------------------------------------------------------- - Collector: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs – AxDatabaseDetector.GetAsync (via DynamicsAxCollector, - hver time) og src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs – BuildSql2005/Sql2000 (hvert minut). - Rettigheder: Ingen ud over grant-least-privilege.sql (VIEW SERVER STATE til sessionerne, VIEW ANY DEFINITION til - genkendelsen). Ingen AX-læseadgang nødvendig. - Versioner: Blokering: SQL Server 2000 og nyere; AX-genkendelsen og dermed reglen: SQL Server 2005 og nyere. - Axapta 3.0/4.0: AX-brugeren kan ikke ses. AX 2009/2012/F&O: baseret på Microsofts dokumentation – ikke - verificeret mod kundedata. - Læsning: Kun DMV'er og metadata. Genkendelsen kører i hver overvåget database via [db].sys.sp_executesql. -*/ --- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden. --- Variant: Alle versioner: online brugerdatabaser og deres snapshot-indstillinger (AxDatabaseDetector.DatabasesSql) +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name GO --- Variant: Alle versioner: agentens login, til GRANT-teksten -SELECT SUSER_SNAME() -GO - --- Variant: SQL Server 2005 og nyere: AX-genkendelse i hver overvåget database (AxDetection.MetadataSql via AxSql.InDatabase) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til databasen -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) FROM sys.tables t @@ -52,78 +91,16 @@ JOIN sys.schemas s ON s.schema_id = t.schema_id JOIN sys.columns c ON c.object_id = t.object_id WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') ORDER BY s.name, t.name, c.column_id'; -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: SQL Server 2000 (kun opdagelse under Servere og databaser, AxDetection.MetadataSql2000) -SELECT u.name, UPPER(o.name), UPPER(c.name), TYPE_NAME(c.xtype), c.xprec, c.length, 0, NULL -FROM [AXDB].dbo.sysobjects o -JOIN [AXDB].dbo.sysusers u ON u.uid = o.uid -JOIN [AXDB].dbo.syscolumns c ON c.id = o.id -WHERE o.xtype = 'U' AND UPPER(o.name) IN (N'SQLDICTIONARY', N'DATAAREA', N'SYSCONFIG', N'BATCH', N'BATCHJOB', N'BATCHJOBHISTORY', N'BATCHGROUP', N'NUMBERSEQUENCETABLE', N'NUMBERSEQUENCESCOPE', N'NUMBERSEQUENCEREFERENCE', N'SYSTEMSEQUENCES', N'MODELELEMENT') -ORDER BY u.name, o.name, c.colid -GO +-- ------------------------------------------------------------------------------------------------------------------- +-- BlockingCollector: Blokerede sessioner og deres blokerere lige nu, med sætning, åbne transaktioner og CONTEXT_INFO. +-- ------------------------------------------------------------------------------------------------------------------- --- Variant: Axapta 3.0 (skema bmssa): firmaer (AxSchemaAdapter.Companies) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [bmssa].[DATAAREA] d ORDER BY d.[ID]'; -SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; -SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; -GO - --- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: firmaer (AxSchemaAdapter.Companies) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [dbo].[DATAAREA] d ORDER BY d.[ID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; -SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; -GO - --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: firmaer (AxSchemaAdapter.Companies) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [dbo].[DATAAREA] d ORDER BY d.[ID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; -SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; -GO - --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: firmaer (AxSchemaAdapter.Companies) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [dbo].[DATAAREA] d ORDER BY d.[ID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; -SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; -GO - --- Variant: SQL Server 2012 og nyere (BlockingCollector.BuildSql2005) +-- Variant: SQL Server 2012 og nyere +-- Kilde: BlockingCollector.BuildSql2005(2012+) +-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version) SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource, DB_NAME(COALESCE(r.database_id, s.database_id)), s.login_name, s.host_name, s.program_name, s.open_transaction_count, LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL @@ -140,7 +117,9 @@ WHERE r.blocking_session_id > 0 OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0) GO --- Variant: SQL Server 2008 og 2008 R2 +-- Variant: SQL Server 2008 – 2008 R2 +-- Kilde: BlockingCollector.BuildSql2005(2008) +-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version) SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource, DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, (SELECT COUNT(*) FROM sys.dm_tran_session_transactions st WHERE st.session_id = s.session_id), LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL @@ -157,7 +136,9 @@ WHERE r.blocking_session_id > 0 OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0) GO --- Variant: SQL Server 2005 +-- Variant: SQL Server 2005 (CONTEXT_INFO fra sys.sysprocesses) +-- Kilde: BlockingCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version) SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource, DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, (SELECT COUNT(*) FROM sys.dm_tran_session_transactions st WHERE st.session_id = s.session_id), LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL @@ -174,7 +155,9 @@ WHERE r.blocking_session_id > 0 OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0) GO --- Variant: SQL Server 2000 (BlockingCollector.Sql2000) +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: BlockingCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.Sql2000 SELECT p.spid, NULLIF(CASE WHEN p.blocked = p.spid THEN 0 ELSE p.blocked END, 0), RTRIM(p.status), RTRIM(p.lastwaittype), p.waittime, RTRIM(p.waitresource), DB_NAME(p.dbid), RTRIM(p.loginame), RTRIM(p.hostname), RTRIM(p.program_name), p.open_tran, CAST(NULL AS nvarchar(4000)), p.last_batch, p.context_info @@ -182,4 +165,4 @@ FROM master.dbo.sysprocesses p WHERE (p.blocked > 0 AND p.blocked <> p.spid) OR p.spid IN (SELECT blocked FROM master.dbo.sysprocesses WHERE blocked > 0 AND blocked <> spid) GO --- <<< slut på genereret SQL + diff --git a/SQL Scripts/SQLI-BLK-001.sql b/SQL Scripts/SQLI-BLK-001.sql index 36271fe..1e66c8b 100644 --- a/SQL Scripts/SQLI-BLK-001.sql +++ b/SQL Scripts/SQLI-BLK-001.sql @@ -62,7 +62,7 @@ SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.stat (CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END - r.statement_start_offset) / 2 + 1) ELSE t.text END, 4000), - s.last_request_end_time + s.last_request_end_time, s.context_info FROM sys.dm_exec_sessions s LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id @@ -71,7 +71,26 @@ WHERE r.blocking_session_id > 0 OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0) GO --- Variant: SQL Server 2005 – 2008 R2 +-- Variant: SQL Server 2008 – 2008 R2 +-- Kilde: BlockingCollector.BuildSql2005(2008) +-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version) +SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource, + DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, (SELECT COUNT(*) FROM sys.dm_tran_session_transactions st WHERE st.session_id = s.session_id), + LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL + THEN SUBSTRING(t.text, r.statement_start_offset / 2 + 1, + (CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END + - r.statement_start_offset) / 2 + 1) + ELSE t.text END, 4000), + s.last_request_end_time, s.context_info +FROM sys.dm_exec_sessions s +LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id +LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id +OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) t +WHERE r.blocking_session_id > 0 + OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0) +GO + +-- Variant: SQL Server 2005 (CONTEXT_INFO fra sys.sysprocesses) -- Kilde: BlockingCollector.BuildSql2005(2005) -- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version) SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource, @@ -81,7 +100,7 @@ SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.stat (CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END - r.statement_start_offset) / 2 + 1) ELSE t.text END, 4000), - s.last_request_end_time + s.last_request_end_time, (SELECT TOP 1 p.context_info FROM sys.sysprocesses p WHERE p.spid = s.session_id AND p.ecid = 0) FROM sys.dm_exec_sessions s LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id @@ -95,7 +114,7 @@ GO -- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.Sql2000 SELECT p.spid, NULLIF(CASE WHEN p.blocked = p.spid THEN 0 ELSE p.blocked END, 0), RTRIM(p.status), RTRIM(p.lastwaittype), p.waittime, RTRIM(p.waitresource), DB_NAME(p.dbid), RTRIM(p.loginame), RTRIM(p.hostname), RTRIM(p.program_name), - p.open_tran, CAST(NULL AS nvarchar(4000)), p.last_batch + p.open_tran, CAST(NULL AS nvarchar(4000)), p.last_batch, p.context_info FROM master.dbo.sysprocesses p WHERE (p.blocked > 0 AND p.blocked <> p.spid) OR p.spid IN (SELECT blocked FROM master.dbo.sysprocesses WHERE blocked > 0 AND blocked <> spid) diff --git a/SQL Scripts/_collectors/AxBatchCollector.sql b/SQL Scripts/_collectors/AxBatchCollector.sql index c1076ce..1994288 100644 --- a/SQL Scripts/_collectors/AxBatchCollector.sql +++ b/SQL Scripts/_collectors/AxBatchCollector.sql @@ -1,137 +1,98 @@ -/* - Delt collector-forespørgsel: Dynamics AX-batchjob (SQLI-AX-001, -002, -003, -004 og siden Dynamics AX → Batchjob) +-- =================================================================================================================== +-- Collector: AxBatchCollector ("dynamics-ax-batch") +-- =================================================================================================================== +-- Hvad: Hvert 5. minut, i hver genkendt og overvåget AX-database: batchjob (BATCH i Axapta 3.0/4.0, +-- BATCHJOB fra AX 2009) og - fra AX 2009 - de afsluttede kørsler i BATCHJOBHISTORY efter seneste læste +-- RECID (minus et overlap, fordi AOS'er får RecId i blokke). Axapta 3.0/4.0 har ingen historiktabel; der +-- gemmer SQLInsight de kørsler, den selv ser blive afsluttet. Kørslerne gemmes i SQLInsights repository +-- (insight.AxBatchRuns) - aldrig i AX. +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:dynamics-ax-batch) +-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/ +-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql): +-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger): +-- Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO [bruger]; +-- AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO [bruger]; +-- AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO [bruger]; +-- GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO [bruger]; +-- Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen (også under Dynamics AX → AX-databaser +-- og GET /api/v1/dynamics-ax/grant-script). Uden GRANT ændres intet. +-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en +-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];). +-- +-- Bruges af: SQLI-AX-001, SQLI-AX-002, SQLI-AX-003, SQLI-AX-004 og Dynamics AX → Batchjob +-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via +-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000 +-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger +-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til +-- overvågning. +-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch +-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet. +-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database. +-- =================================================================================================================== - Hvad - Hvert 5. minut, i hver genkendt og overvåget AX-database: de nyeste 1000 batchjob (højeste RECID først) og – i AX - 2009 og senere – de afsluttede kørsler i BATCHJOBHISTORY efter seneste læste RECID (minus 2000, fordi AOS'er får - RecId i blokke). Axapta 3.0/4.0 har ingen historiktabel; der gemmer SQLInsight de kørsler, den selv ser blive - afsluttet. Kørslerne gemmes i SQLInsights repository (insight.AxBatchRuns, 90 dage) – aldrig i AX. +-- ------------------------------------------------------------------------------------------------------------------- +-- AxBatchCollector: Batchjob og kørselshistorik i AX-databasen (valgfri AX-læseadgang) +-- ------------------------------------------------------------------------------------------------------------------- - Collector: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs – AxBatchCollector.CollectAsync. - SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – BatchJobs og BatchHistory. - Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql): - Axapta 3.0: [bmssa].[BATCH]; AX 4.0: [dbo].[BATCH]; AX 2009/2012/F&O: [dbo].[BATCHJOB], [dbo].[BATCHJOBHISTORY]. - Versioner: SQL Server 2005 og nyere. Axapta 3.0: verificeret mod en kundes skema (STATUS-værdierne dog ikke). AX 4.0, - AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema – ikke verificeret mod kundedata. - Læsning: Kun SELECT med TOP, READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen tillader det), aldrig - NOLOCK, via [db].sys.sp_executesql. En låst tabel giver "ikke målt denne gang", ikke en fejl. -*/ --- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden. --- Variant: Axapta 3.0 (skema bmssa): batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[BATCH] b ORDER BY b.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(3.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[BATCH] b ORDER BY b.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCH] b ORDER BY b.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(4.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(b.[RECID] AS bigint), b.[CLASSNUM], CAST(NULL AS nvarchar(60)), b.[COMPANY], b.[GROUPID], b.[STATUS], b.[STARTDATE], b.[STARTTIME], CAST(NULL AS datetime), CAST(NULL AS int), b.[ENDDATE], b.[ENDTIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCH] b ORDER BY b.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(2009) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste) +-- Kilde: AxSchemaAdapter.BatchHistory(2009, første) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger fra seneste RECID minus 2000 -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @FraRecId bigint; -SET @FraRecId = 0; -- <== SQLInsight bruger den højeste RECID, den har læst, minus 2000 -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 999999999 ORDER BY h.[RECID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -SET @Sql = REPLACE(@Sql, N'> 999999999', N'> ' + CAST(@FraRecId AS nvarchar(20))); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet) +-- Kilde: AxSchemaAdapter.BatchHistory(2009, næste) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase +-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob +-- Kilde: AxSchemaAdapter.BatchJobs(2012) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(j.[RECID] AS bigint), CAST(NULL AS int), j.[CAPTION], j.[COMPANY], CAST(NULL AS nvarchar(20)), j.[STATUS], j.[ORIGSTARTDATETIME], CAST(NULL AS int), j.[STARTDATETIME], CAST(NULL AS int), j.[ENDDATETIME], CAST(NULL AS int), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOB] j ORDER BY j.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste) +-- Kilde: AxSchemaAdapter.BatchHistory(2012, første) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h ORDER BY h.[RECID] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger fra seneste RECID minus 2000 -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @FraRecId bigint; -SET @FraRecId = 0; -- <== SQLInsight bruger den højeste RECID, den har læst, minus 2000 -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 999999999 ORDER BY h.[RECID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -SET @Sql = REPLACE(@Sql, N'> 999999999', N'> ' + CAST(@FraRecId AS nvarchar(20))); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus overlappet) +-- Kilde: AxSchemaAdapter.BatchHistory(2012, næste) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase +-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (1000) CAST(h.[RECID] AS bigint), CAST(h.[BATCHJOBID] AS bigint), h.[CAPTION], h.[COMPANY], h.[STATUS], h.[STARTDATETIME], h.[ENDDATETIME], DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[BATCHJOBHISTORY] h WHERE h.[RECID] > 0 ORDER BY h.[RECID]'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- <<< slut på genereret SQL + diff --git a/SQL Scripts/_collectors/AxDatabaseDetector.sql b/SQL Scripts/_collectors/AxDatabaseDetector.sql index 8e36592..0246372 100644 --- a/SQL Scripts/_collectors/AxDatabaseDetector.sql +++ b/SQL Scripts/_collectors/AxDatabaseDetector.sql @@ -1,45 +1,54 @@ -/* - Delt collector-forespørgsel: Dynamics AX-genkendelsen (bruges af alle SQLI-AX- og COM-AX-regler, Servere og databaser - og Dynamics AX-siderne i SQLInsight Web) +-- =================================================================================================================== +-- Collector: AxDatabaseDetector ("dynamics-ax", og før hver AX-collector og AX-community-regel) +-- =================================================================================================================== +-- Hvad: Finder de overvågede databaser, der er Dynamics AX: SQLDICTIONARY og DATAAREA i samme skema. +-- Versionen afgøres af skemaet (src/SQLInsight.Core/DynamicsAx/AxDetection.cs : Recognise): RECID int +-- -> Axapta 2.5/3.0 (typisk skema bmssa); bigint uden BATCHJOB -> AX 4.0; BATCHJOB uden +-- NUMBERSEQUENCESCOPE -> AX 2009; NUMBERSEQUENCESCOPE uden PARTITION -> AX 2012 RTM/FP; PARTITION og +-- _model -> AX 2012 R2/R3; PARTITION uden modellager -> Dynamics 365 F&O (formodet). Læser kun +-- metadata (sys.tables, sys.columns, HAS_PERMS_BY_NAME) og - hvis agenten må - firmalisten i DATAAREA. +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs (collector: src/SQLInsight.Collectors/DynamicsAx/DynamicsAxCollector.cs) +-- Interval: 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax); en AX-database uden AX-læseadgang hvert 5. minut +-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/ +-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql): +-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en +-- databasebruger). Firmalisten: valgfri AX-læseadgang, GRANT SELECT ON [bmssa].[DATAAREA] TO [bruger]; +-- ([dbo] efter Axapta 3.0). +-- +-- Bruges af: SQLI-AX-001..010, COM-AX-001..007, AxBatchCollector, AxSequenceCollector og siderne under Dynamics AX +-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via +-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000 +-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger +-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til +-- overvågning. +-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch +-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet. +-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database. +-- =================================================================================================================== - Hvad - Finder de overvågede databaser, der er Dynamics AX: SQLDICTIONARY og DATAAREA i samme skema. Versionen afgøres af - skemaet (src/SQLInsight.Core/DynamicsAx/AxDetection.cs – Recognise): - RECID på SQLDICTIONARY er int (præcision 10) → Axapta 2.5/3.0 (typisk skema bmssa) - bigint, BATCHJOB findes ikke → AX 4.0 - BATCHJOB findes, NUMBERSEQUENCESCOPE gør ikke → AX 2009 - NUMBERSEQUENCESCOPE, ingen PARTITION-kolonne → AX 2012 RTM/FP - PARTITION og modeldatabasen _model på instansen → AX 2012 R2/R3 - PARTITION uden noget modellager → Dynamics 365 F&O on-premises (formodet) - Læser kun metadata (sys.tables, sys.columns, HAS_PERMS_BY_NAME) og – hvis agenten må – firmalisten i DATAAREA. - Resultatet gemmes i hukommelsen i en time og som snapshot "dynamics-ax" i repository'et. - - Collector: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs – AxDatabaseDetector.GetAsync (via - DynamicsAxCollector, hver time; også under Servere og databaser: src/SQLInsight.Collectors/Discovery/ - DatabaseDiscovery.cs – DynamicsAxSql). - Rettigheder: Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION, CONNECT ANY DATABASE). Firmalisten: - valgfri AX-læseadgang, GRANT SELECT ON [bmssa].[DATAAREA] TO []; (dbo efter AX 3.0). - Versioner: SQL Server 2005 og nyere; under Servere og databaser også SQL Server 2000 (sysobjects, uden rettigheds- - oplysninger). Axapta 3.0 er verificeret mod en kundes skema. AX 4.0, AX 2009, AX 2012 og F&O: baseret på - Microsofts dokumenterede skema – ikke verificeret mod kundedata. -*/ --- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden. --- Variant: Alle versioner: online brugerdatabaser og deres snapshot-indstillinger (AxDatabaseDetector.DatabasesSql) +-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger +-- Kilde: AxDatabaseDetector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name GO --- Variant: Alle versioner: agentens login, til GRANT-teksten +-- Variant: Agentens login, til GRANT-teksten, når AX-læseadgangen mangler +-- Kilde: AxDatabaseDetector.LoginSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.LoginSql SELECT SUSER_SNAME() GO --- Variant: SQL Server 2005 og nyere: AX-genkendelse i hver overvåget database (AxDetection.MetadataSql via AxSql.InDatabase) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til databasen -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, +-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB) +-- Kilde: AxDatabaseDetector.BuildDetectionSql +-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql) +-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke. +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) FROM sys.tables t @@ -47,74 +56,34 @@ JOIN sys.schemas s ON s.schema_id = t.schema_id JOIN sys.columns c ON c.object_id = t.object_id WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') ORDER BY s.name, t.name, c.column_id'; -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: SQL Server 2000 (kun opdagelse under Servere og databaser, AxDetection.MetadataSql2000) -SELECT u.name, UPPER(o.name), UPPER(c.name), TYPE_NAME(c.xtype), c.xprec, c.length, 0, NULL -FROM [AXDB].dbo.sysobjects o -JOIN [AXDB].dbo.sysusers u ON u.uid = o.uid -JOIN [AXDB].dbo.syscolumns c ON c.id = o.id -WHERE o.xtype = 'U' AND UPPER(o.name) IN (N'SQLDICTIONARY', N'DATAAREA', N'SYSCONFIG', N'BATCH', N'BATCHJOB', N'BATCHJOBHISTORY', N'BATCHGROUP', N'NUMBERSEQUENCETABLE', N'NUMBERSEQUENCESCOPE', N'NUMBERSEQUENCEREFERENCE', N'SYSTEMSEQUENCES', N'MODELELEMENT') -ORDER BY u.name, o.name, c.colid -GO - --- Variant: Axapta 3.0 (skema bmssa): firmaer (AxSchemaAdapter.Companies) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [bmssa].[DATAAREA] d ORDER BY d.[ID]'; -SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): firmaerne (DATAAREA), kun med AX-læseadgang +-- Kilde: AxSchemaAdapter.Companies(3.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.Companies(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [bmssa].[DATAAREA] d ORDER BY d.[ID]'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: firmaer (AxSchemaAdapter.Companies) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [dbo].[DATAAREA] d ORDER BY d.[ID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: firmaerne (DATAAREA), kun med AX-læseadgang +-- Kilde: AxSchemaAdapter.Companies(4.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.Companies(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [dbo].[DATAAREA] d ORDER BY d.[ID]'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: firmaer (AxSchemaAdapter.Companies) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [dbo].[DATAAREA] d ORDER BY d.[ID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: firmaerne (DATAAREA), kun med AX-læseadgang +-- Kilde: AxSchemaAdapter.Companies(2009) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.Companies(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [dbo].[DATAAREA] d ORDER BY d.[ID]'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: firmaer (AxSchemaAdapter.Companies) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [dbo].[DATAAREA] d ORDER BY d.[ID]'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: firmaerne (DATAAREA), kun med AX-læseadgang +-- Kilde: AxSchemaAdapter.Companies(2012) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.Companies(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [dbo].[DATAAREA] d ORDER BY d.[ID]'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- <<< slut på genereret SQL + diff --git a/SQL Scripts/_collectors/AxSequenceCollector.sql b/SQL Scripts/_collectors/AxSequenceCollector.sql index 6230e48..9f955f5 100644 --- a/SQL Scripts/_collectors/AxSequenceCollector.sql +++ b/SQL Scripts/_collectors/AxSequenceCollector.sql @@ -1,133 +1,105 @@ -/* - Delt collector-forespørgsel: Dynamics AX-nummerserier og RecId (SQLI-AX-005, -006, -007, -008, -009 og siderne - Dynamics AX → Nummerserier og RecId) +-- =================================================================================================================== +-- Collector: AxSequenceCollector ("dynamics-ax-sequences") +-- =================================================================================================================== +-- Hvad: Hver time, i hver genkendt og overvåget AX-database: de mest brugte nummerserier +-- (NUMBERSEQUENCETABLE, med firma og om en reference bruger serien) og de højeste RecId-serier +-- (SYSTEMSEQUENCES). SQLInsight gemmer én måling pr. serie pr. dag i sit eget repository +-- (insight.AxSequenceSamples) og laver prognoserne ud fra den - aldrig i AX. +-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs; SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs +-- Interval: 1 time som standard (SQLInsight:CollectorIntervals:dynamics-ax-sequences) +-- Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (docs/dynamics-ax/ +-- schema-ax30.tsv). AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema - ikke +-- verificeret mod kundedata. +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql): +-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger): +-- Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO [bruger]; +-- GRANT SELECT ON [bmssa].[NUMBERSEQUENCEREFERENCE] TO [bruger]; -- valgfri: om serien bruges +-- AX 4.0/2009: de samme tabeller i [dbo] +-- AX 2012/F&O: desuden GRANT SELECT ON [dbo].[NUMBERSEQUENCESCOPE] TO [bruger]; -- valgfri: firma +-- Mangler NUMBERSEQUENCETABLE, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. Uden GRANT ændres intet. +-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en +-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];). +-- Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql; [bruger] = agentens databasebruger): +-- Axapta 3.0: GRANT SELECT ON [bmssa].[SYSTEMSEQUENCES] TO [bruger]; +-- GRANT SELECT ON [bmssa].[SQLDICTIONARY] TO [bruger]; -- valgfri: tabelnavne +-- AX 4.0/2009/2012/F&O: de samme tabeller i [dbo] +-- Mangler SYSTEMSEQUENCES, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. Uden GRANT ændres intet. +-- Genkendelsen: kun grant-least-privilege.sql (VIEW ANY DEFINITION; CONNECT ANY DATABASE fra 2014, ellers en +-- databasebruger: CREATE USER [agent] FOR LOGIN [agent];). +-- +-- Bruges af: SQLI-AX-005..009 og Dynamics AX → Nummerserier og RecId +-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via +-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000 +-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger +-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til +-- overvågning. +-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch +-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet. +-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database. +-- =================================================================================================================== - Hvad - Hver time, i hver genkendt og overvåget AX-database: de 5000 mest brugte nummerserier (NUMBERSEQUENCETABLE, med - firma og om en reference bruger serien) og de 500 højeste RecId-serier (SYSTEMSEQUENCES med NAME = 'SEQNO'). - SQLInsight gemmer én måling pr. serie pr. dag af NEXTREC/NEXTVAL i sit eget repository (insight.AxSequenceSamples, - 400 dage) og laver prognoserne ud fra den – aldrig i AX. +-- ------------------------------------------------------------------------------------------------------------------- +-- AxSequenceCollector: Nummerserier i AX-databasen (valgfri AX-læseadgang) +-- ------------------------------------------------------------------------------------------------------------------- - Collector: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs – AxSequenceCollector.CollectAsync. - SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – NumberSequences og RecIds. - Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql): NUMBERSEQUENCETABLE, - NUMBERSEQUENCEREFERENCE, NUMBERSEQUENCESCOPE (AX 2012/F&O), SYSTEMSEQUENCES og SQLDICTIONARY (tabelnavne), - i [bmssa] i Axapta 3.0 og [dbo] senere. - Versioner: SQL Server 2005 og nyere. Axapta 3.0: verificeret mod en kundes skema. AX 4.0, AX 2009, AX 2012 og F&O: - baseret på Microsofts dokumenterede skema – ikke verificeret mod kundedata. - Læsning: Kun SELECT med TOP, READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen tillader det), aldrig - NOLOCK, via [db].sys.sp_executesql. -*/ --- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden. --- Variant: Axapta 3.0 (skema bmssa): nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(3.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(4.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(2009) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier +-- Kilde: AxSchemaAdapter.NumberSequences(2012) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: Axapta 3.0 (skema bmssa): RecId pr. tabel (AxSchemaAdapter.RecIds) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [bmssa].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [bmssa].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; -SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- ------------------------------------------------------------------------------------------------------------------- +-- AxSequenceCollector: RecId pr. tabel og firma i AX-databasen (valgfri AX-læseadgang) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): RecId pr. tabel (SYSTEMSEQUENCES) +-- Kilde: AxSchemaAdapter.RecIds(3.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [bmssa].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [bmssa].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: RecId pr. tabel (AxSchemaAdapter.RecIds) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: RecId pr. tabel (SYSTEMSEQUENCES) +-- Kilde: AxSchemaAdapter.RecIds(4.0) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (AxSchemaAdapter.RecIds) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (SYSTEMSEQUENCES) +-- Kilde: AxSchemaAdapter.RecIds(2009) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (AxSchemaAdapter.RecIds) -DECLARE @Database sysname; -SET @Database = N'AXDB'; -- <== ret til AX-databasen -DECLARE @Schema sysname; -SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo -DECLARE @Sql nvarchar(max); -SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; -SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); -DECLARE @Procedure nvarchar(300); -SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; -EXEC @Procedure @Sql; +-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (SYSTEMSEQUENCES) +-- Kilde: AxSchemaAdapter.RecIds(2012) +-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase +EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) s.[TABID], s.[DATAAREAID], CAST(s.[NEXTVAL] AS bigint), CAST(s.[MAXVAL] AS bigint), (SELECT TOP 1 d.[NAME] FROM [dbo].[SQLDICTIONARY] d WHERE d.[TABLEID] = s.[TABID] AND d.[FIELDID] = 0) FROM [dbo].[SYSTEMSEQUENCES] s WHERE s.[NAME] = N''SEQNO'' ORDER BY s.[NEXTVAL] DESC'; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO --- <<< slut på genereret SQL + diff --git a/SQL Scripts/_collectors/BlockingCollector.sql b/SQL Scripts/_collectors/BlockingCollector.sql index caaf170..18dd9f4 100644 --- a/SQL Scripts/_collectors/BlockingCollector.sql +++ b/SQL Scripts/_collectors/BlockingCollector.sql @@ -1,7 +1,8 @@ -- =================================================================================================================== -- Collector: BlockingCollector ("blocking") -- =================================================================================================================== --- Hvad: Blokerede sessioner og deres blokerere lige nu, med sætning og åbne transaktioner. +-- Hvad: Blokerede sessioner og deres blokerere lige nu, med sætning, åbne transaktioner og CONTEXT_INFO (fra 1.4: +-- Dynamics AX 2009/2012 AOS kan lægge AX-brugeren dér). -- Fil: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs -- Interval: 1 min som standard (SQLInsight:CollectorIntervals:blocking) -- Versioner: Alle (SQL Server 2000 og nyere) @@ -12,7 +13,7 @@ -- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+ -- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). -- --- Bruges af: SQLI-BLK-001 +-- Bruges af: SQLI-BLK-001, SQLI-AX-010 -- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter. -- Kørsel: Kør i SSMS som agentens login. Hver variant er sin egen batch (GO); en variant for en anden SQL Server-version -- end din kan fejle - det er forventet. Agenten kører altid med sessionsindstillingerne i _collectors/InstanceProbe.sql. @@ -28,7 +29,7 @@ SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.stat (CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END - r.statement_start_offset) / 2 + 1) ELSE t.text END, 4000), - s.last_request_end_time + s.last_request_end_time, s.context_info FROM sys.dm_exec_sessions s LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id @@ -37,7 +38,26 @@ WHERE r.blocking_session_id > 0 OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0) GO --- Variant: SQL Server 2005 – 2008 R2 +-- Variant: SQL Server 2008 – 2008 R2 +-- Kilde: BlockingCollector.BuildSql2005(2008) +-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version) +SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource, + DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, (SELECT COUNT(*) FROM sys.dm_tran_session_transactions st WHERE st.session_id = s.session_id), + LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL + THEN SUBSTRING(t.text, r.statement_start_offset / 2 + 1, + (CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END + - r.statement_start_offset) / 2 + 1) + ELSE t.text END, 4000), + s.last_request_end_time, s.context_info +FROM sys.dm_exec_sessions s +LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id +LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id +OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) t +WHERE r.blocking_session_id > 0 + OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0) +GO + +-- Variant: SQL Server 2005 (CONTEXT_INFO fra sys.sysprocesses) -- Kilde: BlockingCollector.BuildSql2005(2005) -- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version) SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource, @@ -47,7 +67,7 @@ SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.stat (CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END - r.statement_start_offset) / 2 + 1) ELSE t.text END, 4000), - s.last_request_end_time + s.last_request_end_time, (SELECT TOP 1 p.context_info FROM sys.sysprocesses p WHERE p.spid = s.session_id AND p.ecid = 0) FROM sys.dm_exec_sessions s LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id @@ -61,7 +81,7 @@ GO -- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.Sql2000 SELECT p.spid, NULLIF(CASE WHEN p.blocked = p.spid THEN 0 ELSE p.blocked END, 0), RTRIM(p.status), RTRIM(p.lastwaittype), p.waittime, RTRIM(p.waitresource), DB_NAME(p.dbid), RTRIM(p.loginame), RTRIM(p.hostname), RTRIM(p.program_name), - p.open_tran, CAST(NULL AS nvarchar(4000)), p.last_batch + p.open_tran, CAST(NULL AS nvarchar(4000)), p.last_batch, p.context_info FROM master.dbo.sysprocesses p WHERE (p.blocked > 0 AND p.blocked <> p.spid) OR p.spid IN (SELECT blocked FROM master.dbo.sysprocesses WHERE blocked > 0 AND blocked <> spid) diff --git a/SQL Scripts/_collectors/DatabaseDiscovery.sql b/SQL Scripts/_collectors/DatabaseDiscovery.sql index 36d7178..71b232c 100644 --- a/SQL Scripts/_collectors/DatabaseDiscovery.sql +++ b/SQL Scripts/_collectors/DatabaseDiscovery.sql @@ -19,8 +19,10 @@ -- -- Bruges af: Ingen regel direkte. Styrer, hvilke databaser de databasespecifikke regler vurderes i, og hvilke -- AX-databaser der må sende AX-fund (COM-AX-*) til SQLInsight Hub. --- Read-only: Ja: kun SELECT på metadata, via tre-delte navne. Sessionen skifter aldrig database, og intet ændrer --- data, indstillinger eller objekter. +-- Read-only: Ja: kun SELECT på metadata, via tre-delte navne og (fra 1.4, AX-genkendelsen på SQL Server 2005 og nyere) +-- [db].sys.sp_executesql under READ COMMITTED med SET LOCK_TIMEOUT 2000, aldrig NOLOCK. Sessionen skifter +-- aldrig database, og intet ændrer data, indstillinger eller objekter. AX-genkendelsen er den samme som i +-- _collectors/AxDatabaseDetector.sql (src/SQLInsight.Core/DynamicsAx/AxDetection.cs). -- Kørsel: Kør i SSMS som agentens login. Hver variant er sin egen batch (GO); en variant for en anden SQL Server-version -- end din kan fejle - det er forventet. Agenten kører altid med sessionsindstillingerne i _collectors/InstanceProbe.sql. -- =================================================================================================================== @@ -54,18 +56,25 @@ GO -- Variant: SQL Server 2005 og nyere: Dynamics AX-genkendelse, én gang pr. online brugerdatabase, agenten kan åbne (vist for msdb) -- Kilde: DatabaseDiscovery.DynamicsAxSql(2005) -- Kode: src/SQLInsight.Collectors/Discovery/DatabaseDiscovery.cs : DatabaseDiscovery.DynamicsAxSql(database, version) -SELECT COUNT(DISTINCT t.name), MAX(CASE WHEN t.name = N'SQLDICTIONARY' THEN c.precision END) -FROM [msdb].sys.tables t -LEFT JOIN [msdb].sys.columns c ON c.object_id = t.object_id AND c.name = N'RECID' -WHERE t.name IN (N'SQLDICTIONARY', N'DATAAREA', N'SYSCONFIG') +EXEC [msdb].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length, + ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0), + (SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID()) +FROM sys.tables t +JOIN sys.schemas s ON s.schema_id = t.schema_id +JOIN sys.columns c ON c.object_id = t.object_id +WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'') +ORDER BY s.name, t.name, c.column_id'; +SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO -- Variant: SQL Server 2000 (legacy ODBC-sti): Dynamics AX-genkendelse, én gang pr. online brugerdatabase (vist for msdb) -- Kilde: DatabaseDiscovery.DynamicsAxSql(2000) -- Kode: src/SQLInsight.Collectors/Discovery/DatabaseDiscovery.cs : DatabaseDiscovery.DynamicsAxSql(database, version) -SELECT COUNT(DISTINCT o.name), MAX(CASE WHEN o.name = N'SQLDICTIONARY' THEN c.xprec END) +SELECT u.name, UPPER(o.name), UPPER(c.name), TYPE_NAME(c.xtype), c.xprec, c.length, 0, NULL FROM [msdb].dbo.sysobjects o -LEFT JOIN [msdb].dbo.syscolumns c ON c.id = o.id AND c.name = N'RECID' -WHERE o.xtype = 'U' AND o.name IN (N'SQLDICTIONARY', N'DATAAREA', N'SYSCONFIG') +JOIN [msdb].dbo.sysusers u ON u.uid = o.uid +JOIN [msdb].dbo.syscolumns c ON c.id = o.id +WHERE o.xtype = 'U' AND UPPER(o.name) IN (N'SQLDICTIONARY', N'DATAAREA', N'SYSCONFIG', N'BATCH', N'BATCHJOB', N'BATCHJOBHISTORY', N'BATCHGROUP', N'NUMBERSEQUENCETABLE', N'NUMBERSEQUENCESCOPE', N'NUMBERSEQUENCEREFERENCE', N'SYSTEMSEQUENCES', N'MODELELEMENT') +ORDER BY u.name, o.name, c.colid GO diff --git a/src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs b/src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs index dfb604b..8d5847e 100644 --- a/src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs +++ b/src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs @@ -31,6 +31,12 @@ public sealed class AxDatabaseDetector ORDER BY name """; + /// The agent's login, for the GRANT text shown when AX read access is missing. + internal const string LoginSql = "SELECT SUSER_SNAME()"; + + /// The detection, run in each monitored database through [db].sys.sp_executesql (metadata only). + internal static string BuildDetectionSql(string database) => AxSql.InDatabase(database, AxDetection.MetadataSql); + /// The monitored AX databases, from the cache when younger than (unless ). public async Task GetAsync(CollectorContext context, bool force = false) { @@ -38,7 +44,7 @@ public sealed class AxDatabaseDetector var connection = context.Connection; var ct = context.CancellationToken; var databases = await Db.QueryAsync(connection, DatabasesSql, r => (Name: r.String(0), Snapshot: r.Int(1), Rcsi: r.Bool(2)), ct).ConfigureAwait(false); - _login ??= (await Db.QueryAsync(connection, "SELECT SUSER_SNAME()", r => r.NullableString(0), ct).ConfigureAwait(false)).FirstOrDefault(); + _login ??= (await Db.QueryAsync(connection, LoginSql, r => r.NullableString(0), ct).ConfigureAwait(false)).FirstOrDefault(); var names = databases.Select(d => d.Name).ToList(); var found = new List(); var problems = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -82,7 +88,7 @@ public sealed class AxDatabaseDetector { try { - var rows = await Db.QueryAsync(context.Connection, AxSql.InDatabase(database, AxDetection.MetadataSql), + var rows = await Db.QueryAsync(context.Connection, BuildDetectionSql(database), r => new AxDetectionRow(r.String(0), r.String(1), r.String(2), r.NullableString(3) ?? "?", r.NullableInt(4) ?? 0, r.NullableInt(5) ?? 0, r.Bool(6), r.NullableString(7)), AxSql.CommandTimeoutSeconds, context.CancellationToken).ConfigureAwait(false); if (AxDetection.Recognise(database, rows, names) is not { } ax) diff --git a/tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs b/tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs index 9ed3d5b..4f288ee 100644 --- a/tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs +++ b/tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs @@ -1,214 +1,111 @@ -using System.Text; -using System.Text.Json; -using SQLInsight.Collectors.Concurrency; -using SQLInsight.Collectors.DynamicsAx; -using SQLInsight.Core.DynamicsAx; -using SQLInsight.Core.Instances; +using SQLInsight.Collectors.Tests.SqlScripts; namespace SQLInsight.Collectors.Tests; /// -/// "SQL Scripts/<regel-id>.sql": a reviewable script per rule. The header is written by hand; the SQL below the -/// marker is exactly what SQLInsight sends, generated from the code here, so it can never drift. Run the tests with -/// SQLINSIGHT_UPDATE_SQL_SCRIPTS=1 to rewrite the generated part after changing a query. +/// The Dynamics AX part of "SQL Scripts" (1.4): every AX rule script and AX collector script shows the SQL its rule or +/// collector sends, for every AX version SQLInsight has a schema for. The SQL itself is compared with the code by +/// (the -- Kilde: blocks, registered in ); +/// rewrite out-of-date blocks with SQLINSIGHT_UPDATE_SQL_SCRIPTS=1. /// public sealed class SqlScriptTests { - public const string Begin = "-- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden."; - public const string End = "-- <<< slut på genereret SQL"; + private static readonly string[] AxVersions = ["3.0", "4.0", "2009", "2012"]; - private static string DynamicsAxDirectory => Path.Combine(AppContext.BaseDirectory, "DynamicsAx"); + private static readonly string[] Detection = ["AxDatabaseDetector.DatabasesSql", "AxDatabaseDetector.BuildDetectionSql"]; - public static TheoryData Files() => [.. Generated().Keys]; + private static string[] Batch => + [ + .. Detection, .. AxVersions.Select(v => $"AxSchemaAdapter.BatchJobs({v})"), + "AxSchemaAdapter.BatchHistory(2009, første)", "AxSchemaAdapter.BatchHistory(2009, næste)", + "AxSchemaAdapter.BatchHistory(2012, første)", "AxSchemaAdapter.BatchHistory(2012, næste)", + ]; + + private static string[] NumberSequences => [.. Detection, .. AxVersions.Select(v => $"AxSchemaAdapter.NumberSequences({v})")]; + + /// File → the sources (keys) whose SQL it must show. + internal static Dictionary Expected() + { + var files = new Dictionary(StringComparer.Ordinal) + { + ["_collectors/AxDatabaseDetector.sql"] = [.. Detection, "AxDatabaseDetector.LoginSql", .. AxVersions.Select(v => $"AxSchemaAdapter.Companies({v})")], + ["_collectors/AxBatchCollector.sql"] = Batch[Detection.Length..], + ["_collectors/AxSequenceCollector.sql"] = [.. AxVersions.Select(v => $"AxSchemaAdapter.NumberSequences({v})"), .. AxVersions.Select(v => $"AxSchemaAdapter.RecIds({v})")], + ["SQLI-AX-001.sql"] = Batch, + ["SQLI-AX-002.sql"] = Batch, + ["SQLI-AX-003.sql"] = Batch, + ["SQLI-AX-004.sql"] = Batch, + ["SQLI-AX-005.sql"] = NumberSequences, + ["SQLI-AX-006.sql"] = NumberSequences, + ["SQLI-AX-007.sql"] = NumberSequences, + ["SQLI-AX-008.sql"] = NumberSequences, + ["SQLI-AX-009.sql"] = [.. Detection, .. AxVersions.Select(v => $"AxSchemaAdapter.RecIds({v})")], + ["SQLI-AX-010.sql"] = + [ + .. Detection, "BlockingCollector.BuildSql2005(2012+)", "BlockingCollector.BuildSql2005(2008)", "BlockingCollector.BuildSql2005(2005)", "BlockingCollector.Sql2000", + ], + }; + + // The AX community rules run only in the monitored databases the shared detection recognises. + for (var i = 1; i <= 7; i++) + { + files[$"COM-AX-00{i}.sql"] = ["CustomQueryCollector.DatabasesSql", .. Detection, "CustomQueryCollector.BuildRuleSql"]; + } + + return files; + } + + public static TheoryData Files() => [.. Expected().Keys]; [Theory] [MemberData(nameof(Files))] - public void Script_holds_exactly_the_sql_sqlinsight_runs(string file) + public void Script_holds_the_sql_sqlinsight_runs_for_every_ax_version(string file) { - var path = Path.Combine(RepositoryRoot(), "SQL Scripts", file); + var path = Path.Combine(Folder, file); Assert.True(File.Exists(path), $"SQL Scripts/{file} mangler."); - var text = File.ReadAllText(path); - var begin = text.IndexOf(Begin, StringComparison.Ordinal); - var end = text.IndexOf(End, StringComparison.Ordinal); - Assert.True(begin > 0 && end > begin, $"SQL Scripts/{file}: markeringerne for den genererede SQL mangler."); - - var expected = Generated()[file]; - var actual = text[(begin + Begin.Length)..end].Replace("\r\n", "\n", StringComparison.Ordinal).Trim('\n'); - if (actual != expected.Replace("\r\n", "\n", StringComparison.Ordinal).Trim('\n') && Environment.GetEnvironmentVariable("SQLINSIGHT_UPDATE_SQL_SCRIPTS") == "1") - { - var updated = text[..(begin + Begin.Length)] + "\r\n" + expected.Replace("\r\n", "\n", StringComparison.Ordinal).Trim('\n').Replace("\n", "\r\n", StringComparison.Ordinal) - + "\r\n" + text[end..]; - File.WriteAllText(path, updated, new UTF8Encoding(false)); - return; - } - - Assert.Equal(expected.Replace("\r\n", "\n", StringComparison.Ordinal).Trim('\n'), actual); + var keys = File.ReadAllLines(path).Where(l => l.StartsWith("-- Kilde: ", StringComparison.Ordinal)).Select(l => l["-- Kilde: ".Length..].Trim()).ToHashSet(StringComparer.Ordinal); + var missing = Expected()[file].Where(k => !keys.Contains(k)).ToList(); + Assert.True(missing.Count == 0, $"SQL Scripts/{file} mangler blokkene: " + string.Join(", ", missing)); + Assert.All(Expected()[file], k => Assert.True(SqlScriptSources.ByKey.ContainsKey(k), $"'{k}' er ikke registreret i SqlScriptSources")); } [Theory] [MemberData(nameof(Files))] public void Header_says_what_the_reader_needs(string file) { - var header = File.ReadAllText(Path.Combine(RepositoryRoot(), "SQL Scripts", file)).Split(Begin)[0]; + var text = File.ReadAllText(Path.Combine(Folder, file)); + // The header as running text: comment lines wrapped at 120 characters are joined again. + var header = System.Text.RegularExpressions.Regex.Replace(text[..text.IndexOf("-- Variant:", StringComparison.Ordinal)], @"\r?\n--\s+", " "); + Assert.Contains("Versioner:", header, StringComparison.Ordinal); + Assert.Contains("Read-only:", header, StringComparison.Ordinal); + Assert.Contains("ikke verificeret mod kundedata", header, StringComparison.Ordinal); + Assert.Contains("sp_executesql", header, StringComparison.Ordinal); + if (!file.StartsWith("COM-", StringComparison.Ordinal)) + { + // The AX table reads (the community rules read metadata only). + Assert.Contains("aldrig NOLOCK", header, StringComparison.Ordinal); + } + + Assert.DoesNotContain("PXI_", text, StringComparison.OrdinalIgnoreCase); if (!file.StartsWith('_')) { - Assert.Contains("Regel:", header, StringComparison.Ordinal); - Assert.Contains("Alvorlighed:", header, StringComparison.Ordinal); + Assert.Contains("Alvor:", header, StringComparison.Ordinal); + Assert.Contains("Rettigheder", header, StringComparison.Ordinal); } - - Assert.Contains("Rettigheder:", header, StringComparison.Ordinal); - Assert.Contains("Versioner:", header, StringComparison.Ordinal); - Assert.Contains("Collector:", header, StringComparison.Ordinal); - Assert.Contains("ikke verificeret mod kundedata", header, StringComparison.Ordinal); } - /// File name → the generated SQL. - internal static Dictionary Generated() + private static string Folder { - var ax30 = Database("schema-ax30"); - var ax40 = AxDetection.Recognise("AXDB", AxSchemaFile.Find(DynamicsAxDirectory, "schema-ax30")!.DetectionRows() - .Select(r => r with { Schema = "dbo", Precision = r.Column == "RECID" ? 19 : r.Precision, Type = r.Column == "RECID" ? "bigint" : r.Type }).ToList(), ["AXDB"])!; - var ax2009 = Database("schema-ax2009"); - var ax2012 = Database("schema-ax2012"); - Assert.Equal((AxVersion.Axapta30, AxVersion.Ax40, AxVersion.Ax2009, AxVersion.Ax2012R2), (ax30.Version, ax40.Version, ax2009.Version, ax2012.Version)); - - var versions = new (string Title, AxDatabase Database)[] + get { - ("Axapta 3.0 (skema bmssa)", ax30), - ("AX 4.0 (skema dbo) – ikke verificeret mod kundedata", ax40), - ("AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata", ax2009), - ("AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata", ax2012), - }; - - string Batch() => Join(versions.SelectMany(v => - { - var adapter = AxSchemaAdapter.For(v.Database.Version)!; - var list = new List { Ax($"{v.Title}: batchjob (AxSchemaAdapter.BatchJobs)", v.Database, adapter.BatchJobs(v.Database)) }; - if (adapter.BatchHistory(v.Database, null) is { } first) + var directory = new DirectoryInfo(AppContext.BaseDirectory); + while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "SQLInsight.slnx"))) { - list.Add(Ax($"{v.Title}: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory)", v.Database, first)); - list.Add(Ax($"{v.Title}: kørselshistorik, de næste læsninger fra seneste RECID minus {AxSchemaAdapter.HistoryOverlap}", v.Database, - adapter.BatchHistory(v.Database, Watermark + AxSchemaAdapter.HistoryOverlap)!, watermark: true)); + directory = directory.Parent; } - return list; - })); - - string Numbers() => Join(versions.Select(v => Ax($"{v.Title}: nummerserier (AxSchemaAdapter.NumberSequences)", v.Database, - AxSchemaAdapter.For(v.Database.Version)!.NumberSequences(v.Database)))); - - string RecIds() => Join(versions.Select(v => Ax($"{v.Title}: RecId pr. tabel (AxSchemaAdapter.RecIds)", v.Database, - AxSchemaAdapter.For(v.Database.Version)!.RecIds(v.Database)))); - - var detection = Join( - [ - Plain("Alle versioner: online brugerdatabaser og deres snapshot-indstillinger (AxDatabaseDetector.DatabasesSql)", AxDatabaseDetector.DatabasesSql), - Plain("Alle versioner: agentens login, til GRANT-teksten", "SELECT SUSER_SNAME()"), - Detection(), - Plain("SQL Server 2000 (kun opdagelse under Servere og databaser, AxDetection.MetadataSql2000)", AxDetection.MetadataSql2000("AXDB")), - .. versions.Select(v => Ax($"{v.Title}: firmaer (AxSchemaAdapter.Companies)", v.Database, AxSchemaAdapter.Companies(v.Database))), - ]); - var blocking = Join( - [ - Plain("SQL Server 2012 og nyere (BlockingCollector.BuildSql2005)", BlockingCollector.BuildSql2005(SqlServerVersion.Parse("11.0.7001.0"))), - Plain("SQL Server 2008 og 2008 R2", BlockingCollector.BuildSql2005(SqlServerVersion.Parse("10.50.6000.34"))), - Plain("SQL Server 2005", BlockingCollector.BuildSql2005(SqlServerVersion.Parse("9.0.5000.0"))), - Plain("SQL Server 2000 (BlockingCollector.Sql2000)", BlockingCollector.Sql2000), - ]); - - var files = new Dictionary(StringComparer.Ordinal) - { - ["_collectors/AxDatabaseDetector.sql"] = detection, - ["_collectors/AxBatchCollector.sql"] = Batch(), - ["_collectors/AxSequenceCollector.sql"] = Join([Numbers(), RecIds()]), - ["SQLI-AX-001.sql"] = Batch(), - ["SQLI-AX-002.sql"] = Batch(), - ["SQLI-AX-003.sql"] = Batch(), - ["SQLI-AX-004.sql"] = Batch(), - ["SQLI-AX-005.sql"] = Numbers(), - ["SQLI-AX-006.sql"] = Numbers(), - ["SQLI-AX-007.sql"] = Numbers(), - ["SQLI-AX-008.sql"] = Numbers(), - ["SQLI-AX-009.sql"] = RecIds(), - ["SQLI-AX-010.sql"] = Join([detection, blocking]), - }; - - foreach (var (id, minimum, query) in CommunityAxRules()) - { - files[id + ".sql"] = Join( - [ - Detection(), - $"-- Variant: SQL Server {Release(minimum)} og nyere – i hver overvåget database, som AX-genkendelsen herover har genkendt (CustomQueryCollector)\r\n" - + "USE [AXDB]; -- <== ret til AX-databasen\r\n" + query + "\r\nGO", - ]); + return Path.Combine(directory?.FullName ?? throw new InvalidOperationException("Repository-roden blev ikke fundet."), "SQL Scripts"); } - - return files; - } - - private const long Watermark = 999_999_999; - - private static string Release(string minimumVersion) => minimumVersion switch { "9.0" => "2005", "10.0" => "2008", "11.0" => "2012", "12.0" => "2014", "13.0" => "2016", _ => minimumVersion }; - - private static AxDatabase Database(string schemaFile) => - AxDetection.Recognise("AXDB", AxSchemaFile.Find(DynamicsAxDirectory, schemaFile)!.DetectionRows(), ["AXDB", "AXDB_model"])!; - - private static string Detection() => Plain("SQL Server 2005 og nyere: AX-genkendelse i hver overvåget database (AxDetection.MetadataSql via AxSql.InDatabase)", - "DECLARE @Database sysname;\r\nSET @Database = N'AXDB'; -- <== ret til databasen\r\nDECLARE @Sql nvarchar(max);\r\nSET @Sql = " - + AxSql.Literal(AxSql.Batch(AxDetection.MetadataSql)) + ";\r\nDECLARE @Procedure nvarchar(300);\r\nSET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql';\r\n" - + "EXEC @Procedure @Sql;\r\n" + AxSql.Reset); - - private static string Plain(string title, string sql) => $"-- Variant: {title}\r\n{sql.Trim()}\r\nGO"; - - /// - /// An AX read as the agent sends it: the batch below runs inside the AX database through [db].sys.sp_executesql - /// (read committed, 2 s lock timeout; snapshot instead when the database allows it), so the session never changes database. - /// - private static string Ax(string title, AxDatabase database, AxQuery? query, bool watermark = false) - { - Assert.NotNull(query); - Assert.True(query.IsMeasurable, query.NotMeasurable); - var sql = new StringBuilder() - .Append("-- Variant: ").Append(title).Append("\r\n") - .Append("DECLARE @Database sysname;\r\nSET @Database = N'AXDB'; -- <== ret til AX-databasen\r\n") - .Append("DECLARE @Schema sysname;\r\nSET @Schema = N'").Append(database.Schema).Append("'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo\r\n"); - if (watermark) - { - sql.Append("DECLARE @FraRecId bigint;\r\nSET @FraRecId = 0; -- <== SQLInsight bruger den højeste RECID, den har læst, minus ") - .Append(AxSchemaAdapter.HistoryOverlap).Append("\r\n"); - } - - sql.Append("DECLARE @Sql nvarchar(max);\r\nSET @Sql = ").Append(AxSql.Literal(AxSql.Batch(query.Sql!))).Append(";\r\n") - .Append("SET @Sql = REPLACE(@Sql, N'[").Append(database.Schema).Append("].', QUOTENAME(@Schema) + N'.');\r\n"); - if (watermark) - { - sql.Append("SET @Sql = REPLACE(@Sql, N'> ").Append(Watermark).Append("', N'> ' + CAST(@FraRecId AS nvarchar(20)));\r\n"); - } - - return sql.Append("DECLARE @Procedure nvarchar(300);\r\nSET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql';\r\n") - .Append("EXEC @Procedure @Sql;\r\n").Append(AxSql.Reset).Append("\r\nGO").ToString(); - } - - private static string Join(IEnumerable parts) => string.Join("\r\n\r\n", parts); - - private static List<(string Id, string Minimum, string Query)> CommunityAxRules() - { - using var document = JsonDocument.Parse(File.ReadAllText(Path.Combine(RepositoryRoot(), "rules", "community", "dynamics-ax.json")), - new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true }); - return document.RootElement.EnumerateArray() - .Select(r => (r.GetProperty("id").GetString()!, r.TryGetProperty("minimumVersion", out var v) ? v.GetString()! : "9.0", r.GetProperty("query").GetString()!)) - .ToList(); - } - - private static string RepositoryRoot() - { - var directory = new DirectoryInfo(AppContext.BaseDirectory); - while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "SQLInsight.slnx"))) - { - directory = directory.Parent; - } - - return directory?.FullName ?? throw new InvalidOperationException("Repository-roden blev ikke fundet."); } } diff --git a/tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptSources.cs b/tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptSources.cs index 69362d6..caf9aa0 100644 --- a/tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptSources.cs +++ b/tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptSources.cs @@ -257,8 +257,10 @@ internal static class SqlScriptSources // Concurrency. new("BlockingCollector.BuildSql2005(2012+)", "BlockingCollector", "SQL Server 2012 og nyere", C + "Concurrency/BlockingCollector.cs", "BlockingCollector.BuildSql2005(version)", () => BlockingCollector.BuildSql2005(V2012)), - new("BlockingCollector.BuildSql2005(2005)", "BlockingCollector", "SQL Server 2005 – 2008 R2", + new("BlockingCollector.BuildSql2005(2008)", "BlockingCollector", "SQL Server 2008 – 2008 R2", C + "Concurrency/BlockingCollector.cs", "BlockingCollector.BuildSql2005(version)", () => BlockingCollector.BuildSql2005(V2008R2Sp1)), + new("BlockingCollector.BuildSql2005(2005)", "BlockingCollector", "SQL Server 2005 (CONTEXT_INFO fra sys.sysprocesses)", + C + "Concurrency/BlockingCollector.cs", "BlockingCollector.BuildSql2005(version)", () => BlockingCollector.BuildSql2005(V2005)), new("BlockingCollector.Sql2000", "BlockingCollector", "SQL Server 2000 (legacy ODBC-sti)", C + "Concurrency/BlockingCollector.cs", "BlockingCollector.Sql2000", () => BlockingCollector.Sql2000), new("ActivityCollector.BuildTransactionsSql", "ActivityCollector", "SQL Server 2005 og nyere (vist med standardgrænsen 1024 MB log)", @@ -343,9 +345,88 @@ internal static class SqlScriptSources C + "Discovery/PermissionCheck.cs", "PermissionCheck.DatabasesWithoutAccessSql", () => PermissionCheck.DatabasesWithoutAccessSql), }; + sources.AddRange(DynamicsAx()); return sources; } + /// The AX database the Dynamics AX scripts are shown for: there is no AX database on every instance, so the reader changes it. + public const string AxExampleDatabase = "AXDB"; + + /// A RecId the history watermark is rendered with, replaced by the token ⟪fra⟫ (the agent uses the highest RecId it has read). + private const long AxWatermark = 1_000_000_000; + + private const string Ax = "src/SQLInsight.Core/DynamicsAx/"; + + private static string AxDirectory => Path.Combine(AppContext.BaseDirectory, "DynamicsAx"); + + /// + /// The AX versions SQLInsight has a schema for (docs/dynamics-ax/*.tsv): Axapta 3.0 from a real customer database, + /// AX 4.0 as 3.0 with bigint RecId in dbo, AX 2009 and 2012 R2/R3 from Microsoft's documentation. + /// + private static IReadOnlyList<(string Key, string Title, Core.DynamicsAx.AxDatabase Database)> AxVersions() + { + static Core.DynamicsAx.AxDatabase Load(string file) => + Core.DynamicsAx.AxDetection.Recognise(AxExampleDatabase, Core.DynamicsAx.AxSchemaFile.Find(AxDirectory, file)!.DetectionRows(), [AxExampleDatabase, AxExampleDatabase + "_model"])!; + var ax40 = Core.DynamicsAx.AxDetection.Recognise(AxExampleDatabase, Core.DynamicsAx.AxSchemaFile.Find(AxDirectory, "schema-ax30")!.DetectionRows() + .Select(r => r with { Schema = "dbo", Precision = r.Column == "RECID" ? 19 : r.Precision, Type = r.Column == "RECID" ? "bigint" : r.Type }).ToList(), [AxExampleDatabase])!; + return + [ + ("3.0", "Axapta 3.0 (skema bmssa, verificeret mod kundedata)", Load("schema-ax30")), + ("4.0", "AX 4.0 (skema dbo) - ikke verificeret mod kundedata", ax40), + ("2009", "AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata", Load("schema-ax2009")), + ("2012", "AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata", Load("schema-ax2012")), + ]; + } + + private static string AxRead(Core.DynamicsAx.AxDatabase database, Core.DynamicsAx.AxQuery? query) => + Core.DynamicsAx.AxSql.InDatabase(database.Database, query?.Sql ?? throw new InvalidOperationException(query?.NotMeasurable ?? "Ingen forespørgsel."), database.Isolation); + + private static IEnumerable DynamicsAx() + { + // The shared detection: which monitored databases are AX (collector dynamics-ax, and before every AX check). + yield return new("AxDatabaseDetector.DatabasesSql", "AxDatabaseDetector", "Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger", + C + "DynamicsAx/AxDatabaseDetector.cs", "AxDatabaseDetector.DatabasesSql", () => Collectors.DynamicsAx.AxDatabaseDetector.DatabasesSql); + yield return new("AxDatabaseDetector.LoginSql", "AxDatabaseDetector", "Agentens login, til GRANT-teksten, når AX-læseadgangen mangler", + C + "DynamicsAx/AxDatabaseDetector.cs", "AxDatabaseDetector.LoginSql", () => Collectors.DynamicsAx.AxDatabaseDetector.LoginSql); + yield return new("AxDatabaseDetector.BuildDetectionSql", "AxDatabaseDetector", + "SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for " + AxExampleDatabase + ")", + C + "DynamicsAx/AxDatabaseDetector.cs", "AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)", + () => Collectors.DynamicsAx.AxDatabaseDetector.BuildDetectionSql(AxExampleDatabase)); + foreach (var (key, title, database) in AxVersions()) + { + yield return new($"AxSchemaAdapter.Companies({key})", "AxDatabaseDetector", title + ": firmaerne (DATAAREA), kun med AX-læseadgang", + Ax + "AxSchemaAdapter.cs", "AxSchemaAdapter.Companies(database) via AxSql.InDatabase", () => AxRead(database, Core.DynamicsAx.AxSchemaAdapter.Companies(database))); + } + + // Batch jobs (collector dynamics-ax-batch). + foreach (var (key, title, database) in AxVersions()) + { + var adapter = Core.DynamicsAx.AxSchemaAdapter.For(database.Version)!; + yield return new($"AxSchemaAdapter.BatchJobs({key})", "AxBatchCollector", title + ": batchjob", + Ax + "AxSchemaAdapter.cs", "AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase", () => AxRead(database, adapter.BatchJobs(database))); + if (adapter.BatchHistory(database, null) is not null) + { + yield return new($"AxSchemaAdapter.BatchHistory({key}, første)", "AxBatchCollector", title + ": kørselshistorik, første læsning (de nyeste)", + Ax + "AxSchemaAdapter.cs", "AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase", () => AxRead(database, adapter.BatchHistory(database, null))); + yield return new($"AxSchemaAdapter.BatchHistory({key}, næste)", "AxBatchCollector", + title + $": kørselshistorik, de næste læsninger (fra den højeste RECID, SQLInsight har læst, minus {Core.DynamicsAx.AxSchemaAdapter.HistoryOverlap})", + Ax + "AxSchemaAdapter.cs", "AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase", + () => AxRead(database, adapter.BatchHistory(database, AxWatermark + Core.DynamicsAx.AxSchemaAdapter.HistoryOverlap)) + .Replace("> " + AxWatermark.ToString(System.Globalization.CultureInfo.InvariantCulture), "> ⟪fra⟫", StringComparison.Ordinal)); + } + } + + // Number sequences and RecId (collector dynamics-ax-sequences). + foreach (var (key, title, database) in AxVersions()) + { + var adapter = Core.DynamicsAx.AxSchemaAdapter.For(database.Version)!; + yield return new($"AxSchemaAdapter.NumberSequences({key})", "AxSequenceCollector", title + ": nummerserier", + Ax + "AxSchemaAdapter.cs", "AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase", () => AxRead(database, adapter.NumberSequences(database))); + yield return new($"AxSchemaAdapter.RecIds({key})", "AxSequenceCollector", title + ": RecId pr. tabel (SYSTEMSEQUENCES)", + Ax + "AxSchemaAdapter.cs", "AxSchemaAdapter.RecIds(database) via AxSql.InDatabase", () => AxRead(database, adapter.RecIds(database))); + } + } + private static Source StartTime(string collector, SqlServerVersion version, string variant) => new( $"{collector}.StartTimeSql({(version.Major >= 10 ? "2008+" : version.Major == 9 ? "2005" : "2000")})", collector, variant, C + "Db.cs", "Db.StartTimeSql(version)", () => Db.StartTimeSql(version)); diff --git a/tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptTests.cs b/tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptTests.cs index 56429dc..feb719c 100644 --- a/tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptTests.cs +++ b/tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptTests.cs @@ -60,6 +60,11 @@ public sealed partial class SqlScriptTests foreach (var file in AllScripts()) { var name = Path.GetRelativePath(Folder, file); + if (Environment.GetEnvironmentVariable("SQLINSIGHT_UPDATE_SQL_SCRIPTS") == "1") + { + Update(file); + } + var blocks = Blocks(Read(file)).ToList(); if (blocks.Count == 0) { @@ -139,10 +144,7 @@ public sealed partial class SqlScriptTests var problems = new List(); foreach (var file in AllScripts()) { - var text = Read(file); - var code = CommentsAndStrings().Replace(text, " "); - code = AllowedStatements().Replace(code, " "); - problems.AddRange(Writes().Matches(code).Select(m => $"{Path.GetRelativePath(Folder, file)}: {m.Value.ToUpperInvariant()}").Distinct()); + problems.AddRange(WritesIn(Read(file)).Select(w => $"{Path.GetRelativePath(Folder, file)}: {w}").Distinct()); } Assert.True(problems.Count == 0, "Skrivende sætninger i \"SQL Scripts\": " + string.Join("; ", problems)); @@ -157,8 +159,66 @@ public sealed partial class SqlScriptTests [InlineData("EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til", false)] [InlineData("DBCC TRACESTATUS(-1) WITH NO_INFOMSGS", false)] [InlineData("SELECT N'DELETE' AS [UPDATE]", false)] - public void The_read_only_check_itself_recognises_writes(string sql, bool writes) => - Assert.Equal(writes, Writes().IsMatch(AllowedStatements().Replace(CommentsAndStrings().Replace(sql, " "), " "))); + [InlineData("EXEC [AXDB].sys.sp_executesql N'SET LOCK_TIMEOUT 2000; SELECT TOP (10) a FROM [dbo].[BATCH] WHERE b = N''x''';", false)] + [InlineData("EXEC [AXDB].sys.sp_executesql N'DELETE FROM [dbo].[BATCH]';", true)] + [InlineData("EXEC [AXDB].sys.sp_executesql N'SELECT N''x'' AS a INTO #t';", true)] + [InlineData("EXEC [AXDB].sys.sp_executesql @sql;", true)] + public void The_read_only_check_itself_recognises_writes(string sql, bool writes) => Assert.Equal(writes, WritesIn(sql).Any()); + + /// + /// The writing keywords in , outside comments and strings. The batch of an + /// EXEC [db].sys.sp_executesql N'…' (how the Dynamics AX reads run in the AX database) is checked the same way: + /// the call itself only runs that batch, so it is read-only exactly when the batch is. + /// + private static IEnumerable WritesIn(string sql) + { + var batches = new List(); + sql = DynamicSql().Replace(sql, m => + { + batches.Add(m.Groups["sql"].Value.Replace("''", "'", StringComparison.Ordinal)); + return " "; + }); + var code = AllowedStatements().Replace(CommentsAndStrings().Replace(sql, " "), " "); + return Writes().Matches(code).Select(m => m.Value.ToUpperInvariant()).Concat(batches.SelectMany(WritesIn)).ToList(); + } + + /// + /// SQLINSIGHT_UPDATE_SQL_SCRIPTS=1: a block whose SQL is no longer what the code sends gets the current SQL (its + /// comment lines are kept). A block with run-time values (⟪tokens⟫) is only filled while it has no SQL yet. + /// + private static void Update(string file) + { + var lines = Read(file).Split('\n').ToList(); + var output = new List(); + for (var i = 0; i < lines.Count; i++) + { + output.Add(lines[i]); + if (!lines[i].StartsWith("-- Kilde: ", StringComparison.Ordinal) + || !SqlScriptSources.ByKey.TryGetValue(lines[i]["-- Kilde: ".Length..].Trim(), out var source)) + { + continue; + } + + var end = lines.FindIndex(i + 1, l => l.Trim() == "GO"); + if (end < 0 || SqlScriptSources.Pattern(source.Sql).IsMatch(string.Join("\n", lines[(i + 1)..end]))) + { + continue; + } + + // A block with run-time values is only filled when it is new (no SQL yet); the ⟪tokens⟫ must then be replaced by hand. + var comments = lines[(i + 1)..end].TakeWhile(l => l.StartsWith("--", StringComparison.Ordinal)).ToList(); + if (source.Sql.Contains('⟪', StringComparison.Ordinal) && comments.Count < end - i - 1) + { + continue; + } + + output.AddRange(comments); + output.AddRange(source.Sql.TrimEnd('\n').Split('\n')); + i = end - 1; + } + + File.WriteAllText(file, string.Join("\r\n", output), new System.Text.UTF8Encoding(false)); + } /// The -- Kilde: key blocks: everything after the line up to the next GO. private static IEnumerable<(string Key, string Body)> Blocks(string text) @@ -210,4 +270,8 @@ public sealed partial class SqlScriptTests [GeneratedRegex(@"\b(INSERT|UPDATE|DELETE|MERGE|DROP|ALTER|CREATE|TRUNCATE|GRANT|REVOKE|DENY|BACKUP|RESTORE|RECONFIGURE|KILL|SHUTDOWN|BULK|OPENROWSET|OPENQUERY|OPENDATASOURCE|WAITFOR|INTO|EXEC|EXECUTE|DBCC|SETUSER)\b", RegexOptions.IgnoreCase)] private static partial Regex Writes(); + + /// A call of sp_executesql in a named database with a literal batch (not a variable, which cannot be checked). + [GeneratedRegex(@"\bEXEC\s+\[[^\]]+\]\.sys\.sp_executesql\s+N'(?(?:[^']|'')*)'", RegexOptions.IgnoreCase)] + private static partial Regex DynamicSql(); }