using SQLInsight.Core.CustomRules; namespace SQLInsight.Core.Tests; /// A typed query (custom rule, synthetic SQL check) is one read-only SELECT, also when someone tries to hide more. public class ReadOnlyQueryTests { [Theory] // A ]] in an [identifier] is not its end: the ' after it is no string, and the DELETE is code. [InlineData("SELECT 1 AS [x]]'] ; DELETE FROM dbo.t; SELECT 'a'")] [InlineData("SELECT 1 AS [x]]'] DELETE FROM dbo.t SELECT 'a'")] // Block comments nest in T-SQL; an unterminated one is refused. [InlineData("SELECT 1 /* /* */ DELETE FROM dbo.t")] [InlineData("SELECT 1 AS \"a\"\"'\" DELETE FROM dbo.t --'")] // No semicolon is needed between statements. [InlineData("SELECT 1 COMMIT")] [InlineData("SELECT 1 SELECT 2")] [InlineData("SELECT 1 WRITETEXT dbo.t.c @p 'x'")] [InlineData("SELECT 1 UPDATETEXT dbo.t.c @p 0 NULL 'x'")] [InlineData("SELECT 1 DECLARE @x int")] [InlineData("SELECT 1 SET CONTEXT_INFO 0x01")] [InlineData("SELECT 1 RAISERROR('x', 16, 1) WITH LOG")] [InlineData("SELECT 1 CHECKPOINT")] [InlineData("SELECT 1 ROLLBACK")] [InlineData("SELECT 1 BEGIN TRAN")] [InlineData("SELECT 1 ENABLE TRIGGER trg ON dbo.t")] [InlineData("SELECT 1 WHILE 1 = 1 SELECT 2")] [InlineData("WITH q AS (SELECT 1 AS a) SELECT a FROM q SELECT 2")] [InlineData("SELECT 1 x RECEIVE TOP (1) * FROM dbo.q")] [InlineData("SELECT 1 ADD SIGNATURE TO dbo.p BY CERTIFICATE c")] // Side effects inside one SELECT: sequences are not rolled back; update and exclusive locks block production. [InlineData("SELECT NEXT VALUE FOR dbo.InvoiceNumbers")] [InlineData("SELECT * FROM dbo.CustTable WITH (UPDLOCK, HOLDLOCK)")] [InlineData("SELECT * FROM dbo.CustTable WITH (TABLOCKX)")] [InlineData("SELECT * FROM dbo.CustTable WITH (XLOCK)")] [InlineData("SELECT * INTO dbo.Copy FROM dbo.CustTable")] [InlineData("SELECT 1 EXEC sp_who")] [InlineData("SELECT * FROM OPENQUERY(LINKED, 'EXEC x')")] [InlineData("select 1 exec('delete dbo.t')")] [InlineData("SELECT 'unterminated")] [InlineData("SELECT [unterminated")] public void Hidden_or_extra_statements_are_refused(string query) => Assert.NotEmpty(ReadOnlyQuery.Problems(query)); [Theory] [InlineData("SELECT 1")] [InlineData("SELECT COUNT(*) FROM dbo.DATAAREA")] [InlineData("SELECT [update], [into], [x]]y] FROM dbo.audit")] [InlineData("SELECT 'DELETE; EXEC ''x''' AS text /* INSERT /* nested */ COMMIT */ -- DROP")] [InlineData("WITH d AS (SELECT name FROM sys.databases), e (n) AS (SELECT 1) SELECT * FROM d;")] [InlineData("SELECT name FROM sys.databases UNION ALL SELECT name FROM sys.objects EXCEPT SELECT N'x' INTERSECT SELECT N'x'")] [InlineData("SELECT TOP (10) a FROM (SELECT 1 AS a) x WHERE a IN (SELECT 1) ORDER BY a OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY")] [InlineData("SELECT * FROM dbo.CustTable WITH (NOLOCK) OPTION (MAXDOP 1)")] [InlineData("SELECT IIF(1 = 1, GETDATE(), NULL), \"quoted\"\"name\" FROM sys.objects")] public void Read_only_queries_are_accepted(string query) => Assert.Empty(ReadOnlyQuery.Problems(query)); }