Review 1.4: SQL Scripts for Dynamics AX in the 1.3 structure
The AX scripts written in 1.4 (own header style and a generator) now follow main's "SQL Scripts" structure: the review header, "-- Kilde:" blocks registered in SqlScriptSources and checked word for word against the code. - SqlScriptSources: the shared AX detection (DatabasesSql, LoginSql, BuildDetectionSql), companies, batch jobs, batch history (first read and from the watermark) and number sequences / RecId for Axapta 3.0, AX 4.0, AX 2009 and AX 2012; BlockingCollector split 2008 / 2005, as CONTEXT_INFO comes from sys.dm_exec_sessions from 2008 and from sysprocesses on 2005. - SQLI-AX-001..010 and _collectors/Ax*.sql in main's format; COM-AX-001..007 with the 1.4 queries, the AX detection and USE [AXDB]; BLK-001, BlockingCollector and DatabaseDiscovery updated (CONTEXT_INFO, sp_executesql). - Scripts_are_read_only stays strict (no INTO #temp anywhere) and now also checks the batch inside EXEC [db].sys.sp_executesql N'...'. - SQLINSIGHT_UPDATE_SQL_SCRIPTS=1 (from 1.4) rewrites out-of-date blocks from the code; the 1.4 generator test now checks that every AX script shows its sources for every AX version and says what is unverified. - README: SQLI-AX-001..010, the AX collector scripts, 108 rules. - AxDatabaseDetector: LoginSql and BuildDetectionSql as members, so the registry sees them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
08cd741b52
commit
3a0bbe22f1
28 changed files with 2090 additions and 1557 deletions
|
|
@ -5,6 +5,9 @@
|
||||||
-- Titel: Dynamics AX 3.0 er uden support fra Microsoft
|
-- Titel: Dynamics AX 3.0 er uden support fra Microsoft
|
||||||
-- Alvor: Medium
|
-- Alvor: Medium
|
||||||
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
|
-- 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:
|
-- 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,
|
-- 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):
|
-- 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.
|
-- 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),
|
-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede
|
||||||
-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
|
-- 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}
|
-- 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).
|
-- 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)
|
-- CustomQueryCollector.BuildRuleSql(database, query)
|
||||||
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
|
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
|
||||||
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
|
-- 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:
|
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
|
||||||
-- rules/community/dynamics-ax.json (id COM-AX-001, indlejret i regelpakken)
|
-- 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,
|
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen
|
||||||
-- indstillinger eller objekter.
|
-- 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
|
-- 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.
|
-- (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
|
-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database.
|
||||||
-- database, du vil se.
|
|
||||||
-- ===================================================================================================================
|
-- ===================================================================================================================
|
||||||
|
|
||||||
-- -------------------------------------------------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
@ -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
|
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
|
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
|
-- Variant: SQL Server 2005 og nyere
|
||||||
-- Kilde: CustomQueryCollector.BuildRuleSql
|
-- Kilde: CustomQueryCollector.BuildRuleSql
|
||||||
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
|
-- 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.
|
-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database.
|
||||||
USE [msdb];
|
USE [AXDB];
|
||||||
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
|
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
|
GO
|
||||||
|
|
|
||||||
|
|
@ -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
|
-- Titel: Dynamics AX 3.0 kører på en SQL Server-version, der ikke var understøttet til AX 3.0
|
||||||
-- Alvor: Medium
|
-- Alvor: Medium
|
||||||
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
|
-- 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:
|
-- 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
|
-- 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):
|
-- 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.
|
-- 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),
|
-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede
|
||||||
-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
|
-- 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
|
-- 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).
|
-- 3.0-databasen {database} ligger på SQL Server {sql_version}" ({kolonne} udfyldes fra rækken).
|
||||||
--
|
--
|
||||||
|
|
@ -35,15 +40,20 @@
|
||||||
-- CustomQueryCollector.BuildRuleSql(database, query)
|
-- CustomQueryCollector.BuildRuleSql(database, query)
|
||||||
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
|
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
|
||||||
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
|
-- 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:
|
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
|
||||||
-- rules/community/dynamics-ax.json (id COM-AX-002, indlejret i regelpakken)
|
-- 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,
|
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen
|
||||||
-- indstillinger eller objekter.
|
-- 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
|
-- 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.
|
-- (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
|
-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database.
|
||||||
-- database, du vil se.
|
|
||||||
-- ===================================================================================================================
|
-- ===================================================================================================================
|
||||||
|
|
||||||
-- -------------------------------------------------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
@ -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
|
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
|
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
|
-- Variant: SQL Server 2005 og nyere
|
||||||
-- Kilde: CustomQueryCollector.BuildRuleSql
|
-- Kilde: CustomQueryCollector.BuildRuleSql
|
||||||
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
|
-- 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.
|
-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database.
|
||||||
USE [msdb];
|
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 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
|
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
|
GO
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
-- Titel: Dynamics AX-database på en instans, hvor MAXDOP ikke er 1 (SQL Server 2005-2014)
|
-- Titel: Dynamics AX-database på en instans, hvor MAXDOP ikke er 1 (SQL Server 2005-2014)
|
||||||
-- Alvor: Medium
|
-- Alvor: Medium
|
||||||
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
|
-- 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:
|
-- Hvad tjekkes, og hvorfor:
|
||||||
-- Dynamics AX-database på en instans, hvor MAXDOP ikke er 1 (SQL Server 2005-2014). Dynamics AX sender mange små,
|
-- 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):
|
-- 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.
|
-- 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),
|
-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede
|
||||||
-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
|
-- 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
|
-- 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).
|
-- {maxdop} på instansen med Dynamics AX-databasen {database}" ({kolonne} udfyldes fra rækken).
|
||||||
--
|
--
|
||||||
|
|
@ -34,15 +39,20 @@
|
||||||
-- CustomQueryCollector.BuildRuleSql(database, query)
|
-- CustomQueryCollector.BuildRuleSql(database, query)
|
||||||
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
|
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
|
||||||
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
|
-- 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:
|
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
|
||||||
-- rules/community/dynamics-ax.json (id COM-AX-003, indlejret i regelpakken)
|
-- 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,
|
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen
|
||||||
-- indstillinger eller objekter.
|
-- 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
|
-- 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.
|
-- (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
|
-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database.
|
||||||
-- database, du vil se.
|
|
||||||
-- ===================================================================================================================
|
-- ===================================================================================================================
|
||||||
|
|
||||||
-- -------------------------------------------------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
@ -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
|
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
|
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
|
-- Variant: SQL Server 2005 og nyere
|
||||||
-- Kilde: CustomQueryCollector.BuildRuleSql
|
-- Kilde: CustomQueryCollector.BuildRuleSql
|
||||||
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
|
-- 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.
|
-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database.
|
||||||
USE [msdb];
|
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 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')
|
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
|
GO
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
-- Titel: Dynamics AX-database, hvor MAXDOP ikke er 1 (SQL Server 2016 og nyere)
|
-- Titel: Dynamics AX-database, hvor MAXDOP ikke er 1 (SQL Server 2016 og nyere)
|
||||||
-- Alvor: Medium
|
-- Alvor: Medium
|
||||||
-- Versioner: SQL Server 2016 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
|
-- 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:
|
-- Hvad tjekkes, og hvorfor:
|
||||||
-- Dynamics AX-database, hvor MAXDOP ikke er 1 (SQL Server 2016 og nyere). Dynamics AX sender mange små, hyppige
|
-- 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):
|
-- 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.
|
-- 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),
|
-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede
|
||||||
-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
|
-- 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
|
-- 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).
|
-- AX-databasen {database} kører med MAXDOP større end 1" ({kolonne} udfyldes fra rækken).
|
||||||
--
|
--
|
||||||
|
|
@ -34,15 +39,20 @@
|
||||||
-- CustomQueryCollector.BuildRuleSql(database, query)
|
-- CustomQueryCollector.BuildRuleSql(database, query)
|
||||||
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
|
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
|
||||||
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
|
-- 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:
|
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
|
||||||
-- rules/community/dynamics-ax.json (id COM-AX-004, indlejret i regelpakken)
|
-- 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,
|
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen
|
||||||
-- indstillinger eller objekter.
|
-- 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
|
-- 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.
|
-- (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
|
-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database.
|
||||||
-- database, du vil se.
|
|
||||||
-- ===================================================================================================================
|
-- ===================================================================================================================
|
||||||
|
|
||||||
-- -------------------------------------------------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
@ -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
|
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
|
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
|
-- Variant: SQL Server 2016 og nyere
|
||||||
-- Kilde: CustomQueryCollector.BuildRuleSql
|
-- Kilde: CustomQueryCollector.BuildRuleSql
|
||||||
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
|
-- 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.
|
-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database.
|
||||||
USE [msdb];
|
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 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')
|
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
|
GO
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
-- Titel: Dynamics AX-tabeller uden unikt RecId-indeks
|
-- Titel: Dynamics AX-tabeller uden unikt RecId-indeks
|
||||||
-- Alvor: Medium
|
-- Alvor: Medium
|
||||||
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
|
-- 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:
|
-- Hvad tjekkes, og hvorfor:
|
||||||
-- Dynamics AX-tabeller uden unikt RecId-indeks. AX identificerer hver post med RecId og opretter normalt et unikt
|
-- 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):
|
-- 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.
|
-- 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),
|
-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede
|
||||||
-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
|
-- 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}
|
-- 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).
|
-- AX-tabeller i {database} har intet unikt indeks på RECID" ({kolonne} udfyldes fra rækken).
|
||||||
--
|
--
|
||||||
|
|
@ -34,15 +39,20 @@
|
||||||
-- CustomQueryCollector.BuildRuleSql(database, query)
|
-- CustomQueryCollector.BuildRuleSql(database, query)
|
||||||
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
|
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
|
||||||
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
|
-- 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:
|
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
|
||||||
-- rules/community/dynamics-ax.json (id COM-AX-005, indlejret i regelpakken)
|
-- 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,
|
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen
|
||||||
-- indstillinger eller objekter.
|
-- 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
|
-- 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.
|
-- (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
|
-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database.
|
||||||
-- database, du vil se.
|
|
||||||
-- ===================================================================================================================
|
-- ===================================================================================================================
|
||||||
|
|
||||||
-- -------------------------------------------------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
@ -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
|
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
|
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
|
-- Variant: SQL Server 2005 og nyere
|
||||||
-- Kilde: CustomQueryCollector.BuildRuleSql
|
-- Kilde: CustomQueryCollector.BuildRuleSql
|
||||||
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
|
-- 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.
|
-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database.
|
||||||
USE [msdb];
|
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') 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
|
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
|
GO
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
-- Titel: Store Dynamics AX-tabeller uden clustered index (heaps)
|
-- Titel: Store Dynamics AX-tabeller uden clustered index (heaps)
|
||||||
-- Alvor: Medium
|
-- Alvor: Medium
|
||||||
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
|
-- 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:
|
-- Hvad tjekkes, og hvorfor:
|
||||||
-- Store Dynamics AX-tabeller uden clustered index (heaps). En tabel uden clustered index (en heap) får spredte data
|
-- 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):
|
-- 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.
|
-- 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),
|
-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede
|
||||||
-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
|
-- 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}
|
-- 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).
|
-- store AX-tabeller i {database} har intet clustered index" ({kolonne} udfyldes fra rækken).
|
||||||
--
|
--
|
||||||
|
|
@ -34,15 +39,20 @@
|
||||||
-- CustomQueryCollector.BuildRuleSql(database, query)
|
-- CustomQueryCollector.BuildRuleSql(database, query)
|
||||||
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
|
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
|
||||||
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
|
-- 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:
|
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
|
||||||
-- rules/community/dynamics-ax.json (id COM-AX-006, indlejret i regelpakken)
|
-- 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,
|
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen
|
||||||
-- indstillinger eller objekter.
|
-- 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
|
-- 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.
|
-- (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
|
-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database.
|
||||||
-- database, du vil se.
|
|
||||||
-- ===================================================================================================================
|
-- ===================================================================================================================
|
||||||
|
|
||||||
-- -------------------------------------------------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
@ -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
|
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
|
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
|
-- Variant: SQL Server 2005 og nyere
|
||||||
-- Kilde: CustomQueryCollector.BuildRuleSql
|
-- Kilde: CustomQueryCollector.BuildRuleSql
|
||||||
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
|
-- 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.
|
-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database.
|
||||||
USE [msdb];
|
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 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
|
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
|
GO
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
-- Titel: Automatiske statistikker er slået fra i en Dynamics AX-database
|
-- Titel: Automatiske statistikker er slået fra i en Dynamics AX-database
|
||||||
-- Alvor: High
|
-- Alvor: High
|
||||||
-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule).
|
-- 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:
|
-- Hvad tjekkes, og hvorfor:
|
||||||
-- Automatiske statistikker er slået fra i en Dynamics AX-database. AX bygger sine forespørgsler dynamisk ud fra
|
-- 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):
|
-- 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.
|
-- 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),
|
-- regel). Reglen er markeret "dynamicsAx" (fra 1.4): forespørgslen køres kun i de overvågede
|
||||||
-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke
|
-- 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
|
-- 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).
|
-- statistikker er slået fra i AX-databasen {database}" ({kolonne} udfyldes fra rækken).
|
||||||
--
|
--
|
||||||
|
|
@ -35,15 +40,20 @@
|
||||||
-- CustomQueryCollector.BuildRuleSql(database, query)
|
-- CustomQueryCollector.BuildRuleSql(database, query)
|
||||||
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
|
-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules)
|
||||||
-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql
|
-- 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:
|
-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv:
|
||||||
-- rules/community/dynamics-ax.json (id COM-AX-007, indlejret i regelpakken)
|
-- 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,
|
-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), SET-sessionsindstillinger og AX-genkendelsen
|
||||||
-- indstillinger eller objekter.
|
-- 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
|
-- 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.
|
-- (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
|
-- Forespørgslerne i databasen er vist for AXDB: ret [AXDB] til din Dynamics AX-database.
|
||||||
-- database, du vil se.
|
|
||||||
-- ===================================================================================================================
|
-- ===================================================================================================================
|
||||||
|
|
||||||
-- -------------------------------------------------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
@ -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
|
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
|
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
|
-- Variant: SQL Server 2005 og nyere
|
||||||
-- Kilde: CustomQueryCollector.BuildRuleSql
|
-- Kilde: CustomQueryCollector.BuildRuleSql
|
||||||
-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query)
|
-- 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.
|
-- Agenten kører dette i hver overvåget database, som AX-genkendelsen herover har genkendt. Ret [AXDB] til din AX-database.
|
||||||
USE [msdb];
|
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) 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')
|
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
|
GO
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ identisk med koden (se [Hold mappen i sync](#hold-mappen-i-sync)).
|
||||||
| `<regel-id>.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/`). |
|
| `<regel-id>.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/<Collector>.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/<Collector>.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/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. |
|
| `_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
|
## 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)`
|
- **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`.
|
(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
|
- **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.
|
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.
|
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
|
- `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.
|
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.
|
- `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/<id>.sql` (se `CONTRIBUTING.md`). Ændres en collectors SQL, peger
|
En ny regel skal derfor have sin `SQL Scripts/<id>.sql` (se `CONTRIBUTING.md`). Ændres en collectors SQL, peger
|
||||||
testen på de filer, der skal opdateres.
|
testen på de filer, der skal opdateres.
|
||||||
|
|
||||||
## Regler
|
## 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 |
|
| 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-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-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-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-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-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 |
|
| 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 |
|
| [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 |
|
| [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 |
|
| [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 |
|
| [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 |
|
| [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 |
|
| [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 |
|
| [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 |
|
| [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 |
|
| [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 |
|
||||||
|
|
|
||||||
|
|
@ -1,152 +1,159 @@
|
||||||
/*
|
-- ===================================================================================================================
|
||||||
Regel: SQLI-AX-001 – Dynamics AX-batchjob er endt med fejl
|
-- SQLI-AX-001: Dynamics AX-batchjob er endt med fejl
|
||||||
Alvorlighed: Høj (High)
|
-- ===================================================================================================================
|
||||||
|
-- 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 …),
|
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
|
||||||
er ikke gjort eller kun delvist, og gentagne job fortsætter ikke nødvendigvis af sig selv.
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
Tærskel (agentens konfiguration SQLInsight:DynamicsAx)
|
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
|
||||||
BatchFailedLookbackHours = 24 (standard): fejl inden for så mange timer rapporteres.
|
-- 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
|
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
|
||||||
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.
|
|
||||||
|
|
||||||
Collector: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs – AxBatchCollector.CollectAsync (hvert 5. minut).
|
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
|
||||||
SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – BatchJobs og BatchHistory.
|
-- Kilde: AxDatabaseDetector.DatabasesSql
|
||||||
Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql):
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
|
||||||
Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO [<agentens bruger>];
|
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
|
||||||
AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO [<agentens bruger>];
|
FROM sys.databases
|
||||||
AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO [<agentens bruger>];
|
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
|
||||||
GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO [<agentens bruger>];
|
ORDER BY name
|
||||||
Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen (også under Dynamics AX →
|
GO
|
||||||
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/
|
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
|
||||||
schema-ax30.tsv); STATUS-værdierne er dog ikke verificeret mod kundedata. AX 4.0, AX 2009, AX 2012 og F&O:
|
-- Kilde: AxDatabaseDetector.BuildDetectionSql
|
||||||
baseret på Microsofts dokumenterede skema – ikke verificeret mod kundedata.
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
|
||||||
Læsning: Kun SELECT med TOP, READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen tillader det), aldrig
|
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
|
||||||
NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og valgt til overvågning.
|
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),
|
||||||
-- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden.
|
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
|
||||||
-- Variant: Axapta 3.0 (skema bmssa): batchjob (AxSchemaAdapter.BatchJobs)
|
FROM sys.tables t
|
||||||
DECLARE @Database sysname;
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
JOIN sys.columns c ON c.object_id = t.object_id
|
||||||
DECLARE @Schema sysname;
|
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'')
|
||||||
SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
ORDER BY s.name, t.name, c.column_id';
|
||||||
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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
DECLARE @Database sysname;
|
-- AxBatchCollector: Batchjob og kørselshistorik i AX-databasen (valgfri AX-læseadgang)
|
||||||
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
|
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): batchjob
|
||||||
DECLARE @Sql nvarchar(max);
|
-- Kilde: AxSchemaAdapter.BatchJobs(3.0)
|
||||||
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';
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.');
|
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';
|
||||||
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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(4.0)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory)
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(2009)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2009, første)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- 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)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2009, næste)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0.
|
||||||
SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
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]';
|
||||||
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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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)
|
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(2012)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2012, første)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,152 +1,163 @@
|
||||||
/*
|
-- ===================================================================================================================
|
||||||
Regel: SQLI-AX-002 – Dynamics AX-batchjob kører længere end normalt
|
-- 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
|
-- ===================================================================================================================
|
||||||
|
-- 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
|
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
|
||||||
datamængde, en dårlig plan, eller at jobbet hænger – og imens kan det holde låse, som brugerne venter på.
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
Tærskler (agentens konfiguration SQLInsight:DynamicsAx)
|
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
|
||||||
BatchLongRunningFactor = 2.0 kørt mere end så mange gange jobbets 90 %-fraktil
|
-- Kilde: SqlConnectionFactory.SessionSettings
|
||||||
BatchLongRunningMinimumMinutes = 10 men aldrig under så mange minutter
|
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
|
||||||
BatchHistoryMinimumRuns = 5 så mange tidligere kørsler skal der være
|
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
|
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
|
||||||
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.
|
|
||||||
|
|
||||||
Collector: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs – AxBatchCollector.CollectAsync (hvert 5. minut).
|
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
|
||||||
SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – BatchJobs og BatchHistory.
|
-- Kilde: AxDatabaseDetector.DatabasesSql
|
||||||
Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql):
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
|
||||||
Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO [<agentens bruger>];
|
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
|
||||||
AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO [<agentens bruger>];
|
FROM sys.databases
|
||||||
AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO [<agentens bruger>];
|
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
|
||||||
GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO [<agentens bruger>];
|
ORDER BY name
|
||||||
Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen.
|
GO
|
||||||
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
|
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
|
||||||
skema – ikke verificeret mod kundedata.
|
-- Kilde: AxDatabaseDetector.BuildDetectionSql
|
||||||
Læsning: Kun SELECT med TOP, READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen tillader det), aldrig
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
|
||||||
NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og valgt til overvågning.
|
-- 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,
|
||||||
-- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden.
|
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
|
||||||
-- Variant: Axapta 3.0 (skema bmssa): batchjob (AxSchemaAdapter.BatchJobs)
|
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
|
||||||
DECLARE @Database sysname;
|
FROM sys.tables t
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
DECLARE @Schema sysname;
|
JOIN sys.columns c ON c.object_id = t.object_id
|
||||||
SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
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'')
|
||||||
DECLARE @Sql nvarchar(max);
|
ORDER BY s.name, t.name, c.column_id';
|
||||||
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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
DECLARE @Database sysname;
|
-- AxBatchCollector: Batchjob og kørselshistorik i AX-databasen (valgfri AX-læseadgang)
|
||||||
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
|
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): batchjob
|
||||||
DECLARE @Sql nvarchar(max);
|
-- Kilde: AxSchemaAdapter.BatchJobs(3.0)
|
||||||
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';
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.');
|
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';
|
||||||
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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(4.0)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory)
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(2009)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2009, første)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- 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)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2009, næste)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0.
|
||||||
SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
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]';
|
||||||
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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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)
|
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(2012)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2012, første)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,150 +1,162 @@
|
||||||
/*
|
-- ===================================================================================================================
|
||||||
Regel: SQLI-AX-003 – Dynamics AX-batchjob er ikke startet til tiden
|
-- SQLI-AX-003: Dynamics AX-batchjob er ikke startet til tiden
|
||||||
Alvorlighed: Medium; Høj (High), når jobbet i praksis aldrig er startet
|
-- ===================================================================================================================
|
||||||
|
-- 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
|
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
|
||||||
Axapta 3.0) tager det ikke. Mange forsinkede job på én gang betyder typisk, at batchserveren slet ikke kører.
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
Tærskler (agentens konfiguration SQLInsight:DynamicsAx)
|
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
|
||||||
BatchLateMinutes = 30 forsinket mere end så mange minutter: Medium
|
-- Kilde: SqlConnectionFactory.SessionSettings
|
||||||
BatchNeverStartedHours = 24 forsinket mere end så mange timer: Høj, "aldrig startet"
|
-- 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
|
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
|
||||||
(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.
|
|
||||||
|
|
||||||
Collector: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs – AxBatchCollector.CollectAsync (hvert 5. minut).
|
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
|
||||||
SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – BatchJobs.
|
-- Kilde: AxDatabaseDetector.DatabasesSql
|
||||||
Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql):
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
|
||||||
Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO [<agentens bruger>];
|
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
|
||||||
AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO [<agentens bruger>];
|
FROM sys.databases
|
||||||
AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO [<agentens bruger>];
|
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
|
||||||
Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen.
|
ORDER BY name
|
||||||
Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema; STATUS-værdierne er dog
|
GO
|
||||||
ikke verificeret mod kundedata. AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede
|
|
||||||
skema – ikke verificeret mod kundedata.
|
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
|
||||||
Læsning: Kun SELECT med TOP, READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen tillader det), aldrig
|
-- Kilde: AxDatabaseDetector.BuildDetectionSql
|
||||||
NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og valgt til overvågning.
|
-- 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.
|
||||||
-- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden.
|
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,
|
||||||
-- Variant: Axapta 3.0 (skema bmssa): batchjob (AxSchemaAdapter.BatchJobs)
|
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
|
||||||
DECLARE @Database sysname;
|
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
FROM sys.tables t
|
||||||
DECLARE @Schema sysname;
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
JOIN sys.columns c ON c.object_id = t.object_id
|
||||||
DECLARE @Sql nvarchar(max);
|
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'')
|
||||||
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';
|
ORDER BY s.name, t.name, c.column_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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
DECLARE @Database sysname;
|
-- AxBatchCollector: Batchjob og kørselshistorik i AX-databasen (valgfri AX-læseadgang)
|
||||||
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
|
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): batchjob
|
||||||
DECLARE @Sql nvarchar(max);
|
-- Kilde: AxSchemaAdapter.BatchJobs(3.0)
|
||||||
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';
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.');
|
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';
|
||||||
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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(4.0)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory)
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(2009)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2009, første)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- 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)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2009, næste)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0.
|
||||||
SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
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]';
|
||||||
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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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)
|
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(2012)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2012, første)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,150 +1,163 @@
|
||||||
/*
|
-- ===================================================================================================================
|
||||||
Regel: SQLI-AX-004 – Dynamics AX-batchjob hænger i Udfører
|
-- SQLI-AX-004: Dynamics AX-batchjob hænger i Udfører
|
||||||
Alvorlighed: Høj (High)
|
-- ===================================================================================================================
|
||||||
|
-- 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
|
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
|
||||||
i kørslen, så jobbet aldrig blev afsluttet: det står som kørende, intet sker, og gentagne job planlægges ikke igen.
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
Tærskler (agentens konfiguration SQLInsight:DynamicsAx)
|
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
|
||||||
BatchStuckHours = 6 kørt mindst så mange timer
|
-- Kilde: SqlConnectionFactory.SessionSettings
|
||||||
BatchLongRunningFactor = 2.0 …og mere end så mange gange jobbets 90 %-fraktil, når der er historik
|
-- 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
|
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
|
||||||
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).
|
|
||||||
|
|
||||||
Collector: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs – AxBatchCollector.CollectAsync (hvert 5. minut).
|
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
|
||||||
SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – BatchJobs og BatchHistory.
|
-- Kilde: AxDatabaseDetector.DatabasesSql
|
||||||
Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql):
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
|
||||||
Axapta 3.0: GRANT SELECT ON [bmssa].[BATCH] TO [<agentens bruger>];
|
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
|
||||||
AX 4.0: GRANT SELECT ON [dbo].[BATCH] TO [<agentens bruger>];
|
FROM sys.databases
|
||||||
AX 2009/2012/F&O: GRANT SELECT ON [dbo].[BATCHJOB] TO [<agentens bruger>];
|
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
|
||||||
GRANT SELECT ON [dbo].[BATCHJOBHISTORY] TO [<agentens bruger>];
|
ORDER BY name
|
||||||
Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen.
|
GO
|
||||||
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
|
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
|
||||||
skema – ikke verificeret mod kundedata.
|
-- Kilde: AxDatabaseDetector.BuildDetectionSql
|
||||||
Læsning: Kun SELECT med TOP, READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen tillader det), aldrig
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
|
||||||
NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og valgt til overvågning.
|
-- 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,
|
||||||
-- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden.
|
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
|
||||||
-- Variant: Axapta 3.0 (skema bmssa): batchjob (AxSchemaAdapter.BatchJobs)
|
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
|
||||||
DECLARE @Database sysname;
|
FROM sys.tables t
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
DECLARE @Schema sysname;
|
JOIN sys.columns c ON c.object_id = t.object_id
|
||||||
SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
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'')
|
||||||
DECLARE @Sql nvarchar(max);
|
ORDER BY s.name, t.name, c.column_id';
|
||||||
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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
DECLARE @Database sysname;
|
-- AxBatchCollector: Batchjob og kørselshistorik i AX-databasen (valgfri AX-læseadgang)
|
||||||
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
|
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): batchjob
|
||||||
DECLARE @Sql nvarchar(max);
|
-- Kilde: AxSchemaAdapter.BatchJobs(3.0)
|
||||||
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';
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.');
|
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';
|
||||||
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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(4.0)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory)
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(2009)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2009, første)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- 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)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2009, næste)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0.
|
||||||
SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
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]';
|
||||||
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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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)
|
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(2012)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2012, første)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,96 +1,131 @@
|
||||||
/*
|
-- ===================================================================================================================
|
||||||
Regel: SQLI-AX-005 – Dynamics AX-nummerserie er ved at løbe tør
|
-- 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
|
-- ===================================================================================================================
|
||||||
|
-- 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
|
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
|
||||||
eller posteringer, der bruger serien, fejler for alle brugere.
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
Tærskler (agentens konfiguration SQLInsight:DynamicsAx)
|
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
|
||||||
NumberSequenceWarnPercent = 80 brugt mindst så mange procent: Medium
|
-- Kilde: SqlConnectionFactory.SessionSettings
|
||||||
NumberSequenceCriticalPercent = 95 …: Høj
|
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
|
||||||
ForecastWarnDays = 90 løber tør inden for så mange dage efter prognosen: Medium
|
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
|
||||||
ForecastCriticalDays = 30 …: Høj
|
GO
|
||||||
ForecastBasisDays = 30 prognosen bruger den ældste daglige måling inden for så mange dage (mindst 1 dag gammel)
|
|
||||||
|
|
||||||
Vurdering (C#: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs – AxNumberSequenceExhaustionRule; tal i
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – Interpret og AxForecast.From)
|
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
|
||||||
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.
|
|
||||||
|
|
||||||
Collector: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs – AxSequenceCollector.CollectAsync (hver time).
|
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
|
||||||
SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – NumberSequences.
|
-- Kilde: AxDatabaseDetector.DatabasesSql
|
||||||
Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql):
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
|
||||||
Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO [<agentens bruger>];
|
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
|
||||||
GRANT SELECT ON [bmssa].[NUMBERSEQUENCEREFERENCE] TO [<agentens bruger>]; (valgfri)
|
FROM sys.databases
|
||||||
AX 4.0/2009: samme tabeller i [dbo]
|
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
|
||||||
AX 2012/F&O: GRANT SELECT ON [dbo].[NUMBERSEQUENCETABLE] TO [<agentens bruger>];
|
ORDER BY name
|
||||||
GRANT SELECT ON [dbo].[NUMBERSEQUENCESCOPE] TO [<agentens bruger>]; (valgfri: firma)
|
GO
|
||||||
GRANT SELECT ON [dbo].[NUMBERSEQUENCEREFERENCE] TO [<agentens bruger>]; (valgfri)
|
|
||||||
Mangler NUMBERSEQUENCETABLE, er reglen "ikke målbar" med den nøjagtige GRANT for databasen.
|
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
|
||||||
Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (schema-ax30.tsv).
|
-- Kilde: AxDatabaseDetector.BuildDetectionSql
|
||||||
AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema – ikke verificeret mod kundedata.
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
|
||||||
Læsning: Kun SELECT med TOP (de mest brugte først), READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen
|
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
|
||||||
tillader det), aldrig NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og
|
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,
|
||||||
valgt til overvågning.
|
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())
|
||||||
-- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden.
|
FROM sys.tables t
|
||||||
-- Variant: Axapta 3.0 (skema bmssa): nummerserier (AxSchemaAdapter.NumberSequences)
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
DECLARE @Database sysname;
|
JOIN sys.columns c ON c.object_id = t.object_id
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
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'')
|
||||||
DECLARE @Schema sysname;
|
ORDER BY s.name, t.name, c.column_id';
|
||||||
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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
DECLARE @Database sysname;
|
-- AxSequenceCollector: Nummerserier i AX-databasen (valgfri AX-læseadgang)
|
||||||
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
|
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): nummerserier
|
||||||
DECLARE @Sql nvarchar(max);
|
-- Kilde: AxSchemaAdapter.NumberSequences(3.0)
|
||||||
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';
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.');
|
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';
|
||||||
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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: nummerserier
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.NumberSequences(4.0)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.NumberSequences(2009)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,83 +1,124 @@
|
||||||
/*
|
-- ===================================================================================================================
|
||||||
Regel: SQLI-AX-006 – Dynamics AX-nummerseriens format er for kort til Højeste
|
-- 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
|
-- ===================================================================================================================
|
||||||
|
-- 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
|
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
|
||||||
tildelingen af numre, selv om serien ikke er brugt op.
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
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)
|
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
|
||||||
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.
|
|
||||||
|
|
||||||
Collector: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs – AxSequenceCollector.CollectAsync (hver time).
|
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
|
||||||
SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – NumberSequences.
|
-- Kilde: AxDatabaseDetector.DatabasesSql
|
||||||
Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql):
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
|
||||||
Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO [<agentens bruger>];
|
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
|
||||||
AX 4.0/2009/2012/F&O: GRANT SELECT ON [dbo].[NUMBERSEQUENCETABLE] TO [<agentens bruger>];
|
FROM sys.databases
|
||||||
Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen.
|
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
|
||||||
Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema. AX 4.0, AX 2009, AX 2012
|
ORDER BY name
|
||||||
og F&O: baseret på Microsofts dokumenterede skema – ikke verificeret mod kundedata.
|
GO
|
||||||
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.
|
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
|
||||||
*/
|
-- Kilde: AxDatabaseDetector.BuildDetectionSql
|
||||||
-- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden.
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
|
||||||
-- Variant: Axapta 3.0 (skema bmssa): nummerserier (AxSchemaAdapter.NumberSequences)
|
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
|
||||||
DECLARE @Database sysname;
|
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,
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
|
||||||
DECLARE @Schema sysname;
|
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
|
||||||
SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
FROM sys.tables t
|
||||||
DECLARE @Sql nvarchar(max);
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
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';
|
JOIN sys.columns c ON c.object_id = t.object_id
|
||||||
SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.');
|
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'')
|
||||||
DECLARE @Procedure nvarchar(300);
|
ORDER BY s.name, t.name, c.column_id';
|
||||||
SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql';
|
|
||||||
EXEC @Procedure @Sql;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
DECLARE @Database sysname;
|
-- AxSequenceCollector: Nummerserier i AX-databasen (valgfri AX-læseadgang)
|
||||||
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
|
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): nummerserier
|
||||||
DECLARE @Sql nvarchar(max);
|
-- Kilde: AxSchemaAdapter.NumberSequences(3.0)
|
||||||
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';
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.');
|
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';
|
||||||
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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: nummerserier
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.NumberSequences(4.0)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.NumberSequences(2009)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,84 +1,125 @@
|
||||||
/*
|
-- ===================================================================================================================
|
||||||
Regel: SQLI-AX-007 – Spærret Dynamics AX-nummerserie bruges stadig
|
-- SQLI-AX-007: Spærret Dynamics AX-nummerserie bruges stadig
|
||||||
Alvorlighed: Medium
|
-- ===================================================================================================================
|
||||||
|
-- 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
|
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
|
||||||
fra den reference, fejler med en fejl om, at nummerserien er spærret.
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
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
|
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
|
||||||
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.
|
|
||||||
|
|
||||||
Collector: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs – AxSequenceCollector.CollectAsync (hver time).
|
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
|
||||||
SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – NumberSequences.
|
-- Kilde: AxDatabaseDetector.DatabasesSql
|
||||||
Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql):
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
|
||||||
Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO [<agentens bruger>];
|
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
|
||||||
GRANT SELECT ON [bmssa].[NUMBERSEQUENCEREFERENCE] TO [<agentens bruger>];
|
FROM sys.databases
|
||||||
AX 4.0/2009/2012/F&O: de samme tabeller i [dbo]
|
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
|
||||||
Mangler NUMBERSEQUENCETABLE, er reglen "ikke målbar" med den nøjagtige GRANT for databasen.
|
ORDER BY name
|
||||||
Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema. AX 4.0, AX 2009, AX 2012
|
GO
|
||||||
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
|
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
|
||||||
NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og valgt til overvågning.
|
-- Kilde: AxDatabaseDetector.BuildDetectionSql
|
||||||
*/
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
|
||||||
-- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden.
|
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
|
||||||
-- Variant: Axapta 3.0 (skema bmssa): nummerserier (AxSchemaAdapter.NumberSequences)
|
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,
|
||||||
DECLARE @Database sysname;
|
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
|
||||||
DECLARE @Schema sysname;
|
FROM sys.tables t
|
||||||
SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
DECLARE @Sql nvarchar(max);
|
JOIN sys.columns c ON c.object_id = t.object_id
|
||||||
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';
|
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'')
|
||||||
SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.');
|
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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
DECLARE @Database sysname;
|
-- AxSequenceCollector: Nummerserier i AX-databasen (valgfri AX-læseadgang)
|
||||||
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
|
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): nummerserier
|
||||||
DECLARE @Sql nvarchar(max);
|
-- Kilde: AxSchemaAdapter.NumberSequences(3.0)
|
||||||
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';
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.');
|
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';
|
||||||
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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: nummerserier
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.NumberSequences(4.0)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.NumberSequences(2009)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,86 +1,127 @@
|
||||||
/*
|
-- ===================================================================================================================
|
||||||
Regel: SQLI-AX-008 – Kontinuerlig Dynamics AX-nummerserie er ikke ryddet op
|
-- SQLI-AX-008: Kontinuerlig Dynamics AX-nummerserie er ikke ryddet op
|
||||||
Alvorlighed: Lav (Low)
|
-- ===================================================================================================================
|
||||||
|
-- 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
|
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
|
||||||
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.
|
|
||||||
|
|
||||||
Tærskler
|
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
|
||||||
7 dage (fast, AxNumberSequenceContinuousRule.MinimumDays), eller 2 × seriens eget CLEANINTERVAL (timer), hvis længere.
|
-- 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,
|
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
|
||||||
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.
|
|
||||||
|
|
||||||
Collector: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs – AxSequenceCollector.CollectAsync (hver time).
|
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
|
||||||
SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – NumberSequences.
|
-- Kilde: AxDatabaseDetector.DatabasesSql
|
||||||
Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql):
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
|
||||||
Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO [<agentens bruger>];
|
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
|
||||||
AX 4.0/2009/2012/F&O: GRANT SELECT ON [dbo].[NUMBERSEQUENCETABLE] TO [<agentens bruger>];
|
FROM sys.databases
|
||||||
Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen.
|
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
|
||||||
Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema. AX 4.0, AX 2009, AX 2012
|
ORDER BY name
|
||||||
og F&O: baseret på Microsofts dokumenterede skema – ikke verificeret mod kundedata.
|
GO
|
||||||
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.
|
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
|
||||||
*/
|
-- Kilde: AxDatabaseDetector.BuildDetectionSql
|
||||||
-- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden.
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
|
||||||
-- Variant: Axapta 3.0 (skema bmssa): nummerserier (AxSchemaAdapter.NumberSequences)
|
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
|
||||||
DECLARE @Database sysname;
|
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,
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
|
||||||
DECLARE @Schema sysname;
|
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
|
||||||
SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
FROM sys.tables t
|
||||||
DECLARE @Sql nvarchar(max);
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
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';
|
JOIN sys.columns c ON c.object_id = t.object_id
|
||||||
SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.');
|
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'')
|
||||||
DECLARE @Procedure nvarchar(300);
|
ORDER BY s.name, t.name, c.column_id';
|
||||||
SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql';
|
|
||||||
EXEC @Procedure @Sql;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
DECLARE @Database sysname;
|
-- AxSequenceCollector: Nummerserier i AX-databasen (valgfri AX-læseadgang)
|
||||||
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
|
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): nummerserier
|
||||||
DECLARE @Sql nvarchar(max);
|
-- Kilde: AxSchemaAdapter.NumberSequences(3.0)
|
||||||
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';
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.');
|
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';
|
||||||
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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: nummerserier
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.NumberSequences(4.0)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.NumberSequences(2009)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,94 +1,130 @@
|
||||||
/*
|
-- ===================================================================================================================
|
||||||
Regel: SQLI-AX-009 – Dynamics AX RecId er ved at løbe tør
|
-- 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
|
-- 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
|
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
|
||||||
2.147.483.647 pr. tabel og firma), og det løses kun med et stort projekt – derfor advares langt tidligere for 3.0.
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
Tærskler (agentens konfiguration SQLInsight:DynamicsAx)
|
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
|
||||||
RecId32WarnPercent = 50 Axapta 3.0: Høj fra så mange procent af int-intervallet
|
-- Kilde: SqlConnectionFactory.SessionSettings
|
||||||
RecId32CriticalPercent = 75 Axapta 3.0: Kritisk
|
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
|
||||||
RecId32ForecastDays = 730 Axapta 3.0: Høj, hvis den løber tør inden for så mange dage; Kritisk inden for halvdelen
|
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
|
||||||
RecId64WarnPercent = 80 64-bit (eller sænket MAXVAL): Høj
|
GO
|
||||||
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
|
|
||||||
|
|
||||||
Vurdering (C#: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs – AxRecIdExhaustionRule.Assess; tal i
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – Interpret(AxRecIdRow))
|
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
|
||||||
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.
|
|
||||||
|
|
||||||
Collector: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs – AxSequenceCollector.CollectAsync (hver time).
|
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
|
||||||
SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – RecIds.
|
-- Kilde: AxDatabaseDetector.DatabasesSql
|
||||||
Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql):
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
|
||||||
Axapta 3.0: GRANT SELECT ON [bmssa].[SYSTEMSEQUENCES] TO [<agentens bruger>];
|
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
|
||||||
GRANT SELECT ON [bmssa].[SQLDICTIONARY] TO [<agentens bruger>]; (valgfri: tabelnavne)
|
FROM sys.databases
|
||||||
AX 4.0/2009/2012/F&O: de samme tabeller i [dbo]
|
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
|
||||||
Mangler SYSTEMSEQUENCES, er reglen "ikke målbar" med den nøjagtige GRANT for databasen.
|
ORDER BY name
|
||||||
Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema (SYSTEMSEQUENCES med
|
GO
|
||||||
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.
|
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
|
||||||
Læsning: Kun SELECT med TOP (de højeste først), READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen
|
-- Kilde: AxDatabaseDetector.BuildDetectionSql
|
||||||
tillader det), aldrig NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
|
||||||
valgt til overvågning.
|
-- 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,
|
||||||
-- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden.
|
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
|
||||||
-- Variant: Axapta 3.0 (skema bmssa): RecId pr. tabel (AxSchemaAdapter.RecIds)
|
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
|
||||||
DECLARE @Database sysname;
|
FROM sys.tables t
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
DECLARE @Schema sysname;
|
JOIN sys.columns c ON c.object_id = t.object_id
|
||||||
SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
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'')
|
||||||
DECLARE @Sql nvarchar(max);
|
ORDER BY s.name, t.name, c.column_id';
|
||||||
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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: RecId pr. tabel (AxSchemaAdapter.RecIds)
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
DECLARE @Database sysname;
|
-- AxSequenceCollector: RecId pr. tabel og firma i AX-databasen (valgfri AX-læseadgang)
|
||||||
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
|
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): RecId pr. tabel (SYSTEMSEQUENCES)
|
||||||
DECLARE @Sql nvarchar(max);
|
-- Kilde: AxSchemaAdapter.RecIds(3.0)
|
||||||
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';
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase
|
||||||
SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.');
|
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';
|
||||||
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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (AxSchemaAdapter.RecIds)
|
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: RecId pr. tabel (SYSTEMSEQUENCES)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.RecIds(4.0)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (AxSchemaAdapter.RecIds)
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (SYSTEMSEQUENCES)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.RecIds(2009)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,89 @@
|
||||||
/*
|
-- ===================================================================================================================
|
||||||
Regel: SQLI-AX-010 – AX-bruger blokerer andre
|
-- SQLI-AX-010: AX-bruger blokerer andre
|
||||||
Alvorlighed: Medium; Høj (High) ved ventetid over 5 minutter eller mindst 10 blokerede sessioner
|
-- ===================================================================================================================
|
||||||
|
-- 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
|
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
|
||||||
AOS'ens login, så SQL Server-login'et siger ikke, hvem det er. Reglen viser AX-brugeren, når SQL Server kan se den.
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
Tærskel (agentens konfiguration SQLInsight:DynamicsAx)
|
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
|
||||||
BlockingSeconds = 30 den længste ventetid bag sessionen skal være mindst så mange sekunder
|
-- 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)
|
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
|
||||||
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).
|
|
||||||
|
|
||||||
Collector: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs – AxDatabaseDetector.GetAsync (via DynamicsAxCollector,
|
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
|
||||||
hver time) og src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs – BuildSql2005/Sql2000 (hvert minut).
|
-- Kilde: AxDatabaseDetector.DatabasesSql
|
||||||
Rettigheder: Ingen ud over grant-least-privilege.sql (VIEW SERVER STATE til sessionerne, VIEW ANY DEFINITION til
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
|
||||||
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)
|
|
||||||
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
|
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
|
||||||
FROM sys.databases
|
FROM sys.databases
|
||||||
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
|
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
|
||||||
ORDER BY name
|
ORDER BY name
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: Alle versioner: agentens login, til GRANT-teksten
|
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
|
||||||
SELECT SUSER_SNAME()
|
-- Kilde: AxDatabaseDetector.BuildDetectionSql
|
||||||
GO
|
-- 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.
|
||||||
-- Variant: SQL Server 2005 og nyere: AX-genkendelse i hver overvåget database (AxDetection.MetadataSql via AxSql.InDatabase)
|
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,
|
||||||
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,
|
|
||||||
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
|
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())
|
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
|
||||||
FROM sys.tables t
|
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
|
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'')
|
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';
|
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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
-- BlockingCollector: Blokerede sessioner og deres blokerere lige nu, med sætning, åbne transaktioner og CONTEXT_INFO.
|
||||||
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)
|
-- Variant: SQL Server 2012 og nyere
|
||||||
DECLARE @Database sysname;
|
-- Kilde: BlockingCollector.BuildSql2005(2012+)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version)
|
||||||
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)
|
|
||||||
SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource,
|
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,
|
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
|
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)
|
OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0)
|
||||||
GO
|
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,
|
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),
|
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
|
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)
|
OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0)
|
||||||
GO
|
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,
|
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),
|
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
|
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)
|
OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0)
|
||||||
GO
|
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),
|
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.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
|
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)
|
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)
|
OR p.spid IN (SELECT blocked FROM master.dbo.sysprocesses WHERE blocked > 0 AND blocked <> spid)
|
||||||
GO
|
GO
|
||||||
-- <<< slut på genereret 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
|
(CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END
|
||||||
- r.statement_start_offset) / 2 + 1)
|
- r.statement_start_offset) / 2 + 1)
|
||||||
ELSE t.text END, 4000),
|
ELSE t.text END, 4000),
|
||||||
s.last_request_end_time
|
s.last_request_end_time, s.context_info
|
||||||
FROM sys.dm_exec_sessions s
|
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_requests r ON r.session_id = s.session_id
|
||||||
LEFT JOIN sys.dm_exec_connections c ON c.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)
|
OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0)
|
||||||
GO
|
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)
|
-- Kilde: BlockingCollector.BuildSql2005(2005)
|
||||||
-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version)
|
-- 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,
|
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
|
(CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END
|
||||||
- r.statement_start_offset) / 2 + 1)
|
- r.statement_start_offset) / 2 + 1)
|
||||||
ELSE t.text END, 4000),
|
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
|
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_requests r ON r.session_id = s.session_id
|
||||||
LEFT JOIN sys.dm_exec_connections c ON c.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
|
-- 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),
|
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.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
|
FROM master.dbo.sysprocesses p
|
||||||
WHERE (p.blocked > 0 AND p.blocked <> p.spid)
|
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)
|
OR p.spid IN (SELECT blocked FROM master.dbo.sysprocesses WHERE blocked > 0 AND blocked <> spid)
|
||||||
|
|
|
||||||
|
|
@ -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
|
-- AxBatchCollector: Batchjob og kørselshistorik i AX-databasen (valgfri AX-læseadgang)
|
||||||
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.
|
|
||||||
|
|
||||||
Collector: src/SQLInsight.Collectors/DynamicsAx/AxBatchCollector.cs – AxBatchCollector.CollectAsync.
|
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): batchjob
|
||||||
SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – BatchJobs og BatchHistory.
|
-- Kilde: AxSchemaAdapter.BatchJobs(3.0)
|
||||||
Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql):
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
Axapta 3.0: [bmssa].[BATCH]; AX 4.0: [dbo].[BATCH]; AX 2009/2012/F&O: [dbo].[BATCHJOB], [dbo].[BATCHJOBHISTORY].
|
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';
|
||||||
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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(4.0)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(2009)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory)
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2009, første)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
-- 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)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2009, næste)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0.
|
||||||
SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
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]';
|
||||||
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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob (AxSchemaAdapter.BatchJobs)
|
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: batchjob
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchJobs(2012)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchJobs(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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)
|
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: kørselshistorik, første læsning (de nyeste)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2012, første)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId: null) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
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
|
-- 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)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.BatchHistory(2012, næste)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.BatchHistory(database, afterRecId) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
-- Agenten indsætter den højeste RECID, den har læst, minus overlappet, hvor der her står 0.
|
||||||
SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
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]';
|
||||||
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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
-- <<< slut på genereret SQL
|
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,54 @@
|
||||||
/*
|
-- ===================================================================================================================
|
||||||
Delt collector-forespørgsel: Dynamics AX-genkendelsen (bruges af alle SQLI-AX- og COM-AX-regler, Servere og databaser
|
-- Collector: AxDatabaseDetector ("dynamics-ax", og før hver AX-collector og AX-community-regel)
|
||||||
og Dynamics AX-siderne i SQLInsight Web)
|
-- ===================================================================================================================
|
||||||
|
-- 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
|
||||||
|
-- <db>_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
|
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
|
||||||
Finder de overvågede databaser, der er Dynamics AX: SQLDICTIONARY og DATAAREA i samme skema. Versionen afgøres af
|
-- Kilde: AxDatabaseDetector.DatabasesSql
|
||||||
skemaet (src/SQLInsight.Core/DynamicsAx/AxDetection.cs – Recognise):
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
|
||||||
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 <db>_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 [<agentens bruger>]; (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)
|
|
||||||
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
|
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
|
||||||
FROM sys.databases
|
FROM sys.databases
|
||||||
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
|
WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1
|
||||||
ORDER BY name
|
ORDER BY name
|
||||||
GO
|
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()
|
SELECT SUSER_SNAME()
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: SQL Server 2005 og nyere: AX-genkendelse i hver overvåget database (AxDetection.MetadataSql via AxSql.InDatabase)
|
-- Variant: SQL Server 2005 og nyere: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxDatabaseDetector.BuildDetectionSql
|
||||||
SET @Database = N'AXDB'; -- <== ret til databasen
|
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
|
||||||
DECLARE @Sql nvarchar(max);
|
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
|
||||||
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,
|
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),
|
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())
|
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
|
||||||
FROM sys.tables t
|
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
|
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'')
|
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';
|
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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: SQL Server 2000 (kun opdagelse under Servere og databaser, AxDetection.MetadataSql2000)
|
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): firmaerne (DATAAREA), kun med AX-læseadgang
|
||||||
SELECT u.name, UPPER(o.name), UPPER(c.name), TYPE_NAME(c.xtype), c.xprec, c.length, 0, NULL
|
-- Kilde: AxSchemaAdapter.Companies(3.0)
|
||||||
FROM [AXDB].dbo.sysobjects o
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.Companies(database) via AxSql.InDatabase
|
||||||
JOIN [AXDB].dbo.sysusers u ON u.uid = o.uid
|
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]';
|
||||||
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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: firmaer (AxSchemaAdapter.Companies)
|
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: firmaerne (DATAAREA), kun med AX-læseadgang
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.Companies(4.0)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.Companies(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: firmaer (AxSchemaAdapter.Companies)
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: firmaerne (DATAAREA), kun med AX-læseadgang
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.Companies(2009)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.Companies(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: firmaer (AxSchemaAdapter.Companies)
|
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: firmaerne (DATAAREA), kun med AX-læseadgang
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.Companies(2012)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.Companies(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
-- <<< slut på genereret SQL
|
|
||||||
|
|
|
||||||
|
|
@ -1,133 +1,105 @@
|
||||||
/*
|
-- ===================================================================================================================
|
||||||
Delt collector-forespørgsel: Dynamics AX-nummerserier og RecId (SQLI-AX-005, -006, -007, -008, -009 og siderne
|
-- Collector: AxSequenceCollector ("dynamics-ax-sequences")
|
||||||
Dynamics AX → Nummerserier og RecId)
|
-- ===================================================================================================================
|
||||||
|
-- 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
|
-- AxSequenceCollector: Nummerserier i AX-databasen (valgfri AX-læseadgang)
|
||||||
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.
|
|
||||||
|
|
||||||
Collector: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs – AxSequenceCollector.CollectAsync.
|
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): nummerserier
|
||||||
SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – NumberSequences og RecIds.
|
-- Kilde: AxSchemaAdapter.NumberSequences(3.0)
|
||||||
Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql): NUMBERSEQUENCETABLE,
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
NUMBERSEQUENCEREFERENCE, NUMBERSEQUENCESCOPE (AX 2012/F&O), SYSTEMSEQUENCES og SQLDICTIONARY (tabelnavne),
|
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';
|
||||||
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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: nummerserier
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.NumberSequences(4.0)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.NumberSequences(2009)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences)
|
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.NumberSequences(2012)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.NumberSequences(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: Axapta 3.0 (skema bmssa): RecId pr. tabel (AxSchemaAdapter.RecIds)
|
-- -------------------------------------------------------------------------------------------------------------------
|
||||||
DECLARE @Database sysname;
|
-- AxSequenceCollector: RecId pr. tabel og firma i AX-databasen (valgfri AX-læseadgang)
|
||||||
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
|
-- Variant: Axapta 3.0 (skema bmssa, verificeret mod kundedata): RecId pr. tabel (SYSTEMSEQUENCES)
|
||||||
DECLARE @Sql nvarchar(max);
|
-- Kilde: AxSchemaAdapter.RecIds(3.0)
|
||||||
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';
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase
|
||||||
SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.');
|
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';
|
||||||
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;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: RecId pr. tabel (AxSchemaAdapter.RecIds)
|
-- Variant: AX 4.0 (skema dbo) - ikke verificeret mod kundedata: RecId pr. tabel (SYSTEMSEQUENCES)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.RecIds(4.0)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (AxSchemaAdapter.RecIds)
|
-- Variant: AX 2009 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (SYSTEMSEQUENCES)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.RecIds(2009)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
-- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (AxSchemaAdapter.RecIds)
|
-- Variant: AX 2012 RTM/R2/R3 - baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: RecId pr. tabel (SYSTEMSEQUENCES)
|
||||||
DECLARE @Database sysname;
|
-- Kilde: AxSchemaAdapter.RecIds(2012)
|
||||||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
-- Kode: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs : AxSchemaAdapter.RecIds(database) via AxSql.InDatabase
|
||||||
DECLARE @Schema sysname;
|
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 @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;
|
|
||||||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||||
GO
|
GO
|
||||||
-- <<< slut på genereret SQL
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
-- ===================================================================================================================
|
-- ===================================================================================================================
|
||||||
-- Collector: BlockingCollector ("blocking")
|
-- 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
|
-- Fil: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs
|
||||||
-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:blocking)
|
-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:blocking)
|
||||||
-- Versioner: Alle (SQL Server 2000 og nyere)
|
-- Versioner: Alle (SQL Server 2000 og nyere)
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
|
-- 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).
|
-- 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.
|
-- 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
|
-- 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.
|
-- 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
|
(CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END
|
||||||
- r.statement_start_offset) / 2 + 1)
|
- r.statement_start_offset) / 2 + 1)
|
||||||
ELSE t.text END, 4000),
|
ELSE t.text END, 4000),
|
||||||
s.last_request_end_time
|
s.last_request_end_time, s.context_info
|
||||||
FROM sys.dm_exec_sessions s
|
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_requests r ON r.session_id = s.session_id
|
||||||
LEFT JOIN sys.dm_exec_connections c ON c.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)
|
OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0)
|
||||||
GO
|
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)
|
-- Kilde: BlockingCollector.BuildSql2005(2005)
|
||||||
-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version)
|
-- 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,
|
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
|
(CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END
|
||||||
- r.statement_start_offset) / 2 + 1)
|
- r.statement_start_offset) / 2 + 1)
|
||||||
ELSE t.text END, 4000),
|
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
|
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_requests r ON r.session_id = s.session_id
|
||||||
LEFT JOIN sys.dm_exec_connections c ON c.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
|
-- 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),
|
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.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
|
FROM master.dbo.sysprocesses p
|
||||||
WHERE (p.blocked > 0 AND p.blocked <> p.spid)
|
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)
|
OR p.spid IN (SELECT blocked FROM master.dbo.sysprocesses WHERE blocked > 0 AND blocked <> spid)
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,10 @@
|
||||||
--
|
--
|
||||||
-- Bruges af: Ingen regel direkte. Styrer, hvilke databaser de databasespecifikke regler vurderes i, og hvilke
|
-- 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.
|
-- 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
|
-- Read-only: Ja: kun SELECT på metadata, via tre-delte navne og (fra 1.4, AX-genkendelsen på SQL Server 2005 og nyere)
|
||||||
-- data, indstillinger eller objekter.
|
-- [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
|
-- 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.
|
-- 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)
|
-- Variant: SQL Server 2005 og nyere: Dynamics AX-genkendelse, én gang pr. online brugerdatabase, agenten kan åbne (vist for msdb)
|
||||||
-- Kilde: DatabaseDiscovery.DynamicsAxSql(2005)
|
-- Kilde: DatabaseDiscovery.DynamicsAxSql(2005)
|
||||||
-- Kode: src/SQLInsight.Collectors/Discovery/DatabaseDiscovery.cs : DatabaseDiscovery.DynamicsAxSql(database, version)
|
-- 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)
|
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,
|
||||||
FROM [msdb].sys.tables t
|
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
|
||||||
LEFT JOIN [msdb].sys.columns c ON c.object_id = t.object_id AND c.name = N'RECID'
|
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
|
||||||
WHERE t.name IN (N'SQLDICTIONARY', N'DATAAREA', N'SYSCONFIG')
|
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
|
GO
|
||||||
|
|
||||||
-- Variant: SQL Server 2000 (legacy ODBC-sti): Dynamics AX-genkendelse, én gang pr. online brugerdatabase (vist for msdb)
|
-- Variant: SQL Server 2000 (legacy ODBC-sti): Dynamics AX-genkendelse, én gang pr. online brugerdatabase (vist for msdb)
|
||||||
-- Kilde: DatabaseDiscovery.DynamicsAxSql(2000)
|
-- Kilde: DatabaseDiscovery.DynamicsAxSql(2000)
|
||||||
-- Kode: src/SQLInsight.Collectors/Discovery/DatabaseDiscovery.cs : DatabaseDiscovery.DynamicsAxSql(database, version)
|
-- 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
|
FROM [msdb].dbo.sysobjects o
|
||||||
LEFT JOIN [msdb].dbo.syscolumns c ON c.id = o.id AND c.name = N'RECID'
|
JOIN [msdb].dbo.sysusers u ON u.uid = o.uid
|
||||||
WHERE o.xtype = 'U' AND o.name IN (N'SQLDICTIONARY', N'DATAAREA', N'SYSCONFIG')
|
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
|
GO
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,12 @@ public sealed class AxDatabaseDetector
|
||||||
ORDER BY name
|
ORDER BY name
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
/// <summary>The agent's login, for the GRANT text shown when AX read access is missing.</summary>
|
||||||
|
internal const string LoginSql = "SELECT SUSER_SNAME()";
|
||||||
|
|
||||||
|
/// <summary>The detection, run in each monitored database through [db].sys.sp_executesql (metadata only).</summary>
|
||||||
|
internal static string BuildDetectionSql(string database) => AxSql.InDatabase(database, AxDetection.MetadataSql);
|
||||||
|
|
||||||
/// <summary>The monitored AX databases, from the cache when younger than <see cref="Refresh"/> (unless <paramref name="force"/>).</summary>
|
/// <summary>The monitored AX databases, from the cache when younger than <see cref="Refresh"/> (unless <paramref name="force"/>).</summary>
|
||||||
public async Task<AxDatabaseSnapshot> GetAsync(CollectorContext context, bool force = false)
|
public async Task<AxDatabaseSnapshot> GetAsync(CollectorContext context, bool force = false)
|
||||||
{
|
{
|
||||||
|
|
@ -38,7 +44,7 @@ public sealed class AxDatabaseDetector
|
||||||
var connection = context.Connection;
|
var connection = context.Connection;
|
||||||
var ct = context.CancellationToken;
|
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);
|
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 names = databases.Select(d => d.Name).ToList();
|
||||||
var found = new List<AxDatabase>();
|
var found = new List<AxDatabase>();
|
||||||
var problems = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
var problems = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
@ -82,7 +88,7 @@ public sealed class AxDatabaseDetector
|
||||||
{
|
{
|
||||||
try
|
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)),
|
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);
|
AxSql.CommandTimeoutSeconds, context.CancellationToken).ConfigureAwait(false);
|
||||||
if (AxDetection.Recognise(database, rows, names) is not { } ax)
|
if (AxDetection.Recognise(database, rows, names) is not { } ax)
|
||||||
|
|
|
||||||
|
|
@ -1,214 +1,111 @@
|
||||||
using System.Text;
|
using SQLInsight.Collectors.Tests.SqlScripts;
|
||||||
using System.Text.Json;
|
|
||||||
using SQLInsight.Collectors.Concurrency;
|
|
||||||
using SQLInsight.Collectors.DynamicsAx;
|
|
||||||
using SQLInsight.Core.DynamicsAx;
|
|
||||||
using SQLInsight.Core.Instances;
|
|
||||||
|
|
||||||
namespace SQLInsight.Collectors.Tests;
|
namespace SQLInsight.Collectors.Tests;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "SQL Scripts/<regel-id>.sql": a reviewable script per rule. The header is written by hand; the SQL below the
|
/// The Dynamics AX part of "SQL Scripts" (1.4): every AX rule script and AX collector script shows the SQL its rule or
|
||||||
/// marker is exactly what SQLInsight sends, generated from the code here, so it can never drift. Run the tests with
|
/// collector sends, for every AX version SQLInsight has a schema for. The SQL itself is compared with the code by
|
||||||
/// SQLINSIGHT_UPDATE_SQL_SCRIPTS=1 to rewrite the generated part after changing a query.
|
/// <see cref="SqlScripts.SqlScriptTests"/> (the <c>-- Kilde:</c> blocks, registered in <see cref="SqlScriptSources"/>);
|
||||||
|
/// rewrite out-of-date blocks with SQLINSIGHT_UPDATE_SQL_SCRIPTS=1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SqlScriptTests
|
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.";
|
private static readonly string[] AxVersions = ["3.0", "4.0", "2009", "2012"];
|
||||||
public const string End = "-- <<< slut på genereret SQL";
|
|
||||||
|
|
||||||
private static string DynamicsAxDirectory => Path.Combine(AppContext.BaseDirectory, "DynamicsAx");
|
private static readonly string[] Detection = ["AxDatabaseDetector.DatabasesSql", "AxDatabaseDetector.BuildDetectionSql"];
|
||||||
|
|
||||||
public static TheoryData<string> 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})")];
|
||||||
|
|
||||||
|
/// <summary>File → the sources (keys) whose SQL it must show.</summary>
|
||||||
|
internal static Dictionary<string, string[]> Expected()
|
||||||
|
{
|
||||||
|
var files = new Dictionary<string, string[]>(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<string> Files() => [.. Expected().Keys];
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(Files))]
|
[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.");
|
Assert.True(File.Exists(path), $"SQL Scripts/{file} mangler.");
|
||||||
var text = File.ReadAllText(path);
|
var keys = File.ReadAllLines(path).Where(l => l.StartsWith("-- Kilde: ", StringComparison.Ordinal)).Select(l => l["-- Kilde: ".Length..].Trim()).ToHashSet(StringComparer.Ordinal);
|
||||||
var begin = text.IndexOf(Begin, StringComparison.Ordinal);
|
var missing = Expected()[file].Where(k => !keys.Contains(k)).ToList();
|
||||||
var end = text.IndexOf(End, StringComparison.Ordinal);
|
Assert.True(missing.Count == 0, $"SQL Scripts/{file} mangler blokkene: " + string.Join(", ", missing));
|
||||||
Assert.True(begin > 0 && end > begin, $"SQL Scripts/{file}: markeringerne for den genererede SQL mangler.");
|
Assert.All(Expected()[file], k => Assert.True(SqlScriptSources.ByKey.ContainsKey(k), $"'{k}' er ikke registreret i SqlScriptSources"));
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(Files))]
|
[MemberData(nameof(Files))]
|
||||||
public void Header_says_what_the_reader_needs(string file)
|
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('_'))
|
if (!file.StartsWith('_'))
|
||||||
{
|
{
|
||||||
Assert.Contains("Regel:", header, StringComparison.Ordinal);
|
Assert.Contains("Alvor:", header, StringComparison.Ordinal);
|
||||||
Assert.Contains("Alvorlighed:", 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>File name → the generated SQL.</summary>
|
private static string Folder
|
||||||
internal static Dictionary<string, string> Generated()
|
|
||||||
{
|
{
|
||||||
var ax30 = Database("schema-ax30");
|
get
|
||||||
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)[]
|
|
||||||
{
|
{
|
||||||
("Axapta 3.0 (skema bmssa)", ax30),
|
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||||
("AX 4.0 (skema dbo) – ikke verificeret mod kundedata", ax40),
|
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "SQLInsight.slnx")))
|
||||||
("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<string> { Ax($"{v.Title}: batchjob (AxSchemaAdapter.BatchJobs)", v.Database, adapter.BatchJobs(v.Database)) };
|
|
||||||
if (adapter.BatchHistory(v.Database, null) is { } first)
|
|
||||||
{
|
{
|
||||||
list.Add(Ax($"{v.Title}: kørselshistorik, første læsning (AxSchemaAdapter.BatchHistory)", v.Database, first));
|
directory = directory.Parent;
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return Path.Combine(directory?.FullName ?? throw new InvalidOperationException("Repository-roden blev ikke fundet."), "SQL Scripts");
|
||||||
}));
|
|
||||||
|
|
||||||
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<string, string>(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 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";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
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<string> 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.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -257,8 +257,10 @@ internal static class SqlScriptSources
|
||||||
// Concurrency.
|
// Concurrency.
|
||||||
new("BlockingCollector.BuildSql2005(2012+)", "BlockingCollector", "SQL Server 2012 og nyere",
|
new("BlockingCollector.BuildSql2005(2012+)", "BlockingCollector", "SQL Server 2012 og nyere",
|
||||||
C + "Concurrency/BlockingCollector.cs", "BlockingCollector.BuildSql2005(version)", () => BlockingCollector.BuildSql2005(V2012)),
|
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)),
|
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)",
|
new("BlockingCollector.Sql2000", "BlockingCollector", "SQL Server 2000 (legacy ODBC-sti)",
|
||||||
C + "Concurrency/BlockingCollector.cs", "BlockingCollector.Sql2000", () => BlockingCollector.Sql2000),
|
C + "Concurrency/BlockingCollector.cs", "BlockingCollector.Sql2000", () => BlockingCollector.Sql2000),
|
||||||
new("ActivityCollector.BuildTransactionsSql", "ActivityCollector", "SQL Server 2005 og nyere (vist med standardgrænsen 1024 MB log)",
|
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),
|
C + "Discovery/PermissionCheck.cs", "PermissionCheck.DatabasesWithoutAccessSql", () => PermissionCheck.DatabasesWithoutAccessSql),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sources.AddRange(DynamicsAx());
|
||||||
return sources;
|
return sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>The AX database the Dynamics AX scripts are shown for: there is no AX database on every instance, so the reader changes it.</summary>
|
||||||
|
public const string AxExampleDatabase = "AXDB";
|
||||||
|
|
||||||
|
/// <summary>A RecId the history watermark is rendered with, replaced by the token ⟪fra⟫ (the agent uses the highest RecId it has read).</summary>
|
||||||
|
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");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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<Source> 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(
|
private static Source StartTime(string collector, SqlServerVersion version, string variant) => new(
|
||||||
$"{collector}.StartTimeSql({(version.Major >= 10 ? "2008+" : version.Major == 9 ? "2005" : "2000")})",
|
$"{collector}.StartTimeSql({(version.Major >= 10 ? "2008+" : version.Major == 9 ? "2005" : "2000")})",
|
||||||
collector, variant, C + "Db.cs", "Db.StartTimeSql(version)", () => Db.StartTimeSql(version));
|
collector, variant, C + "Db.cs", "Db.StartTimeSql(version)", () => Db.StartTimeSql(version));
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,11 @@ public sealed partial class SqlScriptTests
|
||||||
foreach (var file in AllScripts())
|
foreach (var file in AllScripts())
|
||||||
{
|
{
|
||||||
var name = Path.GetRelativePath(Folder, file);
|
var name = Path.GetRelativePath(Folder, file);
|
||||||
|
if (Environment.GetEnvironmentVariable("SQLINSIGHT_UPDATE_SQL_SCRIPTS") == "1")
|
||||||
|
{
|
||||||
|
Update(file);
|
||||||
|
}
|
||||||
|
|
||||||
var blocks = Blocks(Read(file)).ToList();
|
var blocks = Blocks(Read(file)).ToList();
|
||||||
if (blocks.Count == 0)
|
if (blocks.Count == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -139,10 +144,7 @@ public sealed partial class SqlScriptTests
|
||||||
var problems = new List<string>();
|
var problems = new List<string>();
|
||||||
foreach (var file in AllScripts())
|
foreach (var file in AllScripts())
|
||||||
{
|
{
|
||||||
var text = Read(file);
|
problems.AddRange(WritesIn(Read(file)).Select(w => $"{Path.GetRelativePath(Folder, file)}: {w}").Distinct());
|
||||||
var code = CommentsAndStrings().Replace(text, " ");
|
|
||||||
code = AllowedStatements().Replace(code, " ");
|
|
||||||
problems.AddRange(Writes().Matches(code).Select(m => $"{Path.GetRelativePath(Folder, file)}: {m.Value.ToUpperInvariant()}").Distinct());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.True(problems.Count == 0, "Skrivende sætninger i \"SQL Scripts\": " + string.Join("; ", problems));
|
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("EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til", false)]
|
||||||
[InlineData("DBCC TRACESTATUS(-1) WITH NO_INFOMSGS", false)]
|
[InlineData("DBCC TRACESTATUS(-1) WITH NO_INFOMSGS", false)]
|
||||||
[InlineData("SELECT N'DELETE' AS [UPDATE]", false)]
|
[InlineData("SELECT N'DELETE' AS [UPDATE]", false)]
|
||||||
public void The_read_only_check_itself_recognises_writes(string sql, bool writes) =>
|
[InlineData("EXEC [AXDB].sys.sp_executesql N'SET LOCK_TIMEOUT 2000; SELECT TOP (10) a FROM [dbo].[BATCH] WHERE b = N''x''';", false)]
|
||||||
Assert.Equal(writes, Writes().IsMatch(AllowedStatements().Replace(CommentsAndStrings().Replace(sql, " "), " ")));
|
[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());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The writing keywords in <paramref name="sql"/>, outside comments and strings. The batch of an
|
||||||
|
/// <c>EXEC [db].sys.sp_executesql N'…'</c> (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.
|
||||||
|
/// </summary>
|
||||||
|
private static IEnumerable<string> WritesIn(string sql)
|
||||||
|
{
|
||||||
|
var batches = new List<string>();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
private static void Update(string file)
|
||||||
|
{
|
||||||
|
var lines = Read(file).Split('\n').ToList();
|
||||||
|
var output = new List<string>();
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>The <c>-- Kilde: key</c> blocks: everything after the line up to the next GO.</summary>
|
/// <summary>The <c>-- Kilde: key</c> blocks: everything after the line up to the next GO.</summary>
|
||||||
private static IEnumerable<(string Key, string Body)> Blocks(string text)
|
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)]
|
[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();
|
private static partial Regex Writes();
|
||||||
|
|
||||||
|
/// <summary>A call of sp_executesql in a named database with a literal batch (not a variable, which cannot be checked).</summary>
|
||||||
|
[GeneratedRegex(@"\bEXEC\s+\[[^\]]+\]\.sys\.sp_executesql\s+N'(?<sql>(?:[^']|'')*)'", RegexOptions.IgnoreCase)]
|
||||||
|
private static partial Regex DynamicSql();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue