|
| 1 | +namespace SqlServerSimulator; |
| 2 | + |
| 3 | +/// <summary> |
| 4 | +/// Verifies <c>CREATE/ALTER PROCEDURE / FUNCTION / VIEW / TRIGGER / SCHEMA</c> |
| 5 | +/// raises Msg 111 ("'X' must be the first statement in a query batch.") when |
| 6 | +/// it isn't the first statement. Probe-confirmed wording per kind: PROCEDURE |
| 7 | +/// merges CREATE / ALTER into one label; the others use their separate |
| 8 | +/// CREATE / ALTER labels. Inner CommandText-equivalent contexts (procedure, |
| 9 | +/// function, trigger, dynamic-SQL bodies) get a fresh BatchContext so the |
| 10 | +/// check naturally resets — proven by the IFBlock + WHILE body cases below |
| 11 | +/// (which raise Msg 111 because BlockDepth > 0). |
| 12 | +/// </summary> |
| 13 | +[TestClass] |
| 14 | +public class BatchBoundaryTests |
| 15 | +{ |
| 16 | + [TestMethod] |
| 17 | + [DataRow("create schema audit", "create schema staging", "CREATE SCHEMA")] |
| 18 | + [DataRow("create table t (id int)", "create view v as select 1 as x", "CREATE VIEW")] |
| 19 | + [DataRow("create table t (id int)", "create function fn() returns int as begin return 1 end", "CREATE FUNCTION")] |
| 20 | + [DataRow("create table t (id int)", "create proc p as select 1", "CREATE/ALTER PROCEDURE")] |
| 21 | + [DataRow("create table t (id int)", "create trigger tr on t after insert as select 1", "CREATE TRIGGER")] |
| 22 | + public void CreateMustBeFirstStatement_RaisesMsg111(string first, string secondCreate, string expectedLabel) |
| 23 | + => new Simulation().AssertSqlError( |
| 24 | + $"{first}; {secondCreate}", |
| 25 | + 111, |
| 26 | + $"'{expectedLabel}' must be the first statement in a query batch."); |
| 27 | + |
| 28 | + [TestMethod] |
| 29 | + public void AlterProcedureMustBeFirstStatement_RaisesMsg111() |
| 30 | + { |
| 31 | + var simulation = new Simulation(); |
| 32 | + simulation.ExecuteBatches("create proc p as select 1"); |
| 33 | + simulation.AssertSqlError( |
| 34 | + "declare @x int = 1; alter proc p as select 2", |
| 35 | + 111, |
| 36 | + "'CREATE/ALTER PROCEDURE' must be the first statement in a query batch."); |
| 37 | + } |
| 38 | + |
| 39 | + [TestMethod] |
| 40 | + public void AlterTriggerMustBeFirstStatement_RaisesMsg111() |
| 41 | + { |
| 42 | + var simulation = new Simulation(); |
| 43 | + simulation.ExecuteBatches( |
| 44 | + "create table t (id int)", |
| 45 | + "create trigger tr on t after insert as select 1"); |
| 46 | + simulation.AssertSqlError( |
| 47 | + "declare @x int = 1; alter trigger tr on t after insert as select 2", |
| 48 | + 111, |
| 49 | + "'ALTER TRIGGER' must be the first statement in a query batch."); |
| 50 | + } |
| 51 | + |
| 52 | + [TestMethod] |
| 53 | + public void CreateOrAlterProcedure_UsesProcedureLabel() |
| 54 | + => new Simulation().AssertSqlError( |
| 55 | + "declare @x int = 1; create or alter procedure p as select 2", |
| 56 | + 111, |
| 57 | + "'CREATE/ALTER PROCEDURE' must be the first statement in a query batch."); |
| 58 | + |
| 59 | + [TestMethod] |
| 60 | + public void LeadingSemicolons_DontCountAsFirstStatement() |
| 61 | + => new Simulation().ExecuteNonQuery("; create proc p as select 1"); |
| 62 | + |
| 63 | + [TestMethod] |
| 64 | + public void CreateProcInsideIfBlock_RaisesMsg111() |
| 65 | + => new Simulation().AssertSqlError( |
| 66 | + "if 1 = 1 begin create proc p as select 1 end", |
| 67 | + 111, |
| 68 | + "'CREATE/ALTER PROCEDURE' must be the first statement in a query batch."); |
| 69 | + |
| 70 | + [TestMethod] |
| 71 | + public void CreateProcInsideWhileBody_RaisesMsg111() |
| 72 | + => new Simulation().AssertSqlError( |
| 73 | + "while 1 = 0 begin create proc p as select 1 end", |
| 74 | + 111, |
| 75 | + "'CREATE/ALTER PROCEDURE' must be the first statement in a query batch."); |
| 76 | + |
| 77 | + [TestMethod] |
| 78 | + public void CreateProcAsFirstStatement_Works() |
| 79 | + => new Simulation().ExecuteNonQuery("create proc p as select 1"); |
| 80 | + |
| 81 | + [TestMethod] |
| 82 | + public void ProcedureBodyIsItsOwnBatch_AllowsCreateProc() |
| 83 | + { |
| 84 | + // Inside a proc body, the dispatched statements form a new batch with |
| 85 | + // its own BatchContext, so CREATE PROCEDURE may legitimately appear |
| 86 | + // as the first statement of the body. (Real SQL Server actually |
| 87 | + // raises Msg 156 syntax error in this position; the simulator's |
| 88 | + // divergence here is documented and benign — no app emits nested |
| 89 | + // CREATE PROCEDUREs inside a proc body.) |
| 90 | + var simulation = new Simulation(); |
| 91 | + simulation.ExecuteBatches( |
| 92 | + "create proc outer_p as create proc inner_p as select 1", |
| 93 | + "exec outer_p"); // Invoke the outer proc — its body creates inner_p. |
| 94 | + Assert.AreEqual(1, simulation.ExecuteScalar("select count(*) from sys.procedures where name = 'inner_p'")); |
| 95 | + } |
| 96 | +} |
0 commit comments