|
| 1 | +using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert; |
| 2 | +using static SqlServerSimulator.TestHelpers; |
| 3 | + |
| 4 | +namespace SqlServerSimulator; |
| 5 | + |
| 6 | +[TestClass] |
| 7 | +public sealed class OptionalSemicolonTests |
| 8 | +{ |
| 9 | + [TestMethod] |
| 10 | + public void DeclareThenSelect_NoSeparator_Works() |
| 11 | + => AreEqual(7, ExecuteScalar<int>("declare @v int = 7 select @v")); |
| 12 | + |
| 13 | + [TestMethod] |
| 14 | + public void SetThenSet_NoSeparator_Works() |
| 15 | + { |
| 16 | + using var conn = new Simulation().CreateOpenConnection(); |
| 17 | + using var cmd = conn.CreateCommand("declare @v int, @w int set @v = 1 set @w = 2 select @v + @w"); |
| 18 | + AreEqual(3, cmd.ExecuteScalar()); |
| 19 | + } |
| 20 | + |
| 21 | + [TestMethod] |
| 22 | + public void SelectThenSelect_NoSeparator_YieldsTwoResultSets() |
| 23 | + { |
| 24 | + using var conn = new Simulation().CreateOpenConnection(); |
| 25 | + using var cmd = conn.CreateCommand("select 1 as a select 2 as b"); |
| 26 | + using var reader = cmd.ExecuteReader(); |
| 27 | + IsTrue(reader.Read()); |
| 28 | + AreEqual(1, reader.GetInt32(0)); |
| 29 | + IsTrue(reader.NextResult()); |
| 30 | + IsTrue(reader.Read()); |
| 31 | + AreEqual(2, reader.GetInt32(0)); |
| 32 | + } |
| 33 | + |
| 34 | + [TestMethod] |
| 35 | + public void InsertThenSelect_NoSeparator_Works() |
| 36 | + { |
| 37 | + using var conn = new Simulation().CreateOpenConnection(); |
| 38 | + _ = conn.CreateCommand("create table t (id int)").ExecuteNonQuery(); |
| 39 | + AreEqual(1, conn.CreateCommand("insert t values (1) select count(*) from t").ExecuteScalar()); |
| 40 | + } |
| 41 | + |
| 42 | + [TestMethod] |
| 43 | + public void BeginTranThenRollback_NoSeparator_Works() |
| 44 | + { |
| 45 | + var sim = new Simulation(); |
| 46 | + _ = sim.ExecuteNonQuery("create table t (id int)"); |
| 47 | + _ = sim.ExecuteNonQuery("begin tran insert t values (1) rollback"); |
| 48 | + AreEqual(0, sim.ExecuteScalar<int>("select count(*) from t")); |
| 49 | + } |
| 50 | + |
| 51 | + [TestMethod] |
| 52 | + public void CreateInsertSelect_NoSeparators_Works() |
| 53 | + { |
| 54 | + using var conn = new Simulation().CreateOpenConnection(); |
| 55 | + AreEqual(1, conn.CreateCommand( |
| 56 | + "create table t (id int) insert t values (1) select count(*) from t").ExecuteScalar()); |
| 57 | + } |
| 58 | + |
| 59 | + [TestMethod] |
| 60 | + public void NewlineBetweenStatements_NoSeparator_Works() |
| 61 | + => AreEqual(7, ExecuteScalar<int>("declare @v int = 7\nselect @v")); |
| 62 | + |
| 63 | + [TestMethod] |
| 64 | + public void DoubleSemicolon_Works() |
| 65 | + { |
| 66 | + using var conn = new Simulation().CreateOpenConnection(); |
| 67 | + using var cmd = conn.CreateCommand("select 1 as a;;select 2 as b"); |
| 68 | + using var reader = cmd.ExecuteReader(); |
| 69 | + IsTrue(reader.Read()); |
| 70 | + AreEqual(1, reader.GetInt32(0)); |
| 71 | + IsTrue(reader.NextResult()); |
| 72 | + IsTrue(reader.Read()); |
| 73 | + AreEqual(2, reader.GetInt32(0)); |
| 74 | + } |
| 75 | + |
| 76 | + [TestMethod] |
| 77 | + public void TrailingSemicolons_Works() |
| 78 | + => AreEqual(1, ExecuteScalar<int>("select 1;;;")); |
| 79 | + |
| 80 | + [TestMethod] |
| 81 | + public void LeadingSemicolons_Works() |
| 82 | + => AreEqual(1, ExecuteScalar<int>(";;select 1")); |
| 83 | + |
| 84 | + [TestMethod] |
| 85 | + public void EmptyBatchOfSemicolons_NoOp() |
| 86 | + { |
| 87 | + // SqlClient's ExecuteNonQuery returns -1 when no DML statement ran; |
| 88 | + // a batch of bare `;`s should produce that sentinel rather than |
| 89 | + // throwing. |
| 90 | + using var conn = new Simulation().CreateOpenConnection(); |
| 91 | + AreEqual(-1, conn.CreateCommand(";;").ExecuteNonQuery()); |
| 92 | + } |
| 93 | + |
| 94 | + [TestMethod] |
| 95 | + public void CteAtBatchStart_NoSemicolonNeeded() |
| 96 | + => AreEqual(1, ExecuteScalar<int>("with cte as (select 1 as x) select x from cte")); |
| 97 | + |
| 98 | + [TestMethod] |
| 99 | + public void CteAfterSemicolon_Works() |
| 100 | + { |
| 101 | + using var conn = new Simulation().CreateOpenConnection(); |
| 102 | + using var cmd = conn.CreateCommand("select 0 as a;with cte as (select 1 as x) select x from cte"); |
| 103 | + using var reader = cmd.ExecuteReader(); |
| 104 | + IsTrue(reader.Read()); |
| 105 | + AreEqual(0, reader.GetInt32(0)); |
| 106 | + IsTrue(reader.NextResult()); |
| 107 | + IsTrue(reader.Read()); |
| 108 | + AreEqual(1, reader.GetInt32(0)); |
| 109 | + } |
| 110 | + |
| 111 | + [TestMethod] |
| 112 | + public void CteWithoutPrecedingSemicolon_RaisesMsg319() |
| 113 | + => AssertSqlError( |
| 114 | + "select 0 as a with cte as (select 1 as x) select x from cte", |
| 115 | + 319, |
| 116 | + "Incorrect syntax near the keyword 'with'. If this statement is a common table expression, an xmlnamespaces clause or a change tracking context clause, the previous statement must be terminated with a semicolon."); |
| 117 | + |
| 118 | + [TestMethod] |
| 119 | + public void TwoCtesWithoutSemicolon_RaisesMsg319() |
| 120 | + { |
| 121 | + // Even when both statements are CTE-prefixed, the second WITH still |
| 122 | + // requires a `;` to separate it from the prior statement. The error |
| 123 | + // surfaces when the dispatch loop advances to the second WITH — |
| 124 | + // which means the test has to drive iteration past the first result |
| 125 | + // set; ExecuteScalar would short-circuit. |
| 126 | + using var conn = new Simulation().CreateOpenConnection(); |
| 127 | + var ex = Throws<System.Data.Common.DbException>(() => |
| 128 | + conn.CreateCommand( |
| 129 | + "with c1 as (select 1 as x) select x from c1 with c2 as (select 2 as y) select y from c2") |
| 130 | + .ExecuteNonQuery()); |
| 131 | + AreEqual("319", ex.Data["HelpLink.EvtID"]); |
| 132 | + } |
| 133 | + |
| 134 | + [TestMethod] |
| 135 | + public void MergeWithTrailingSemicolon_Works() |
| 136 | + { |
| 137 | + var sim = new Simulation(); |
| 138 | + _ = sim.ExecuteNonQuery("create table dst (id int)"); |
| 139 | + _ = sim.ExecuteNonQuery("merge dst using (values (1)) v(x) on 1=0 when not matched then insert (id) values (v.x);"); |
| 140 | + AreEqual(1, sim.ExecuteScalar<int>("select count(*) from dst")); |
| 141 | + } |
| 142 | + |
| 143 | + [TestMethod] |
| 144 | + public void MergeWithoutTrailingSemicolon_RaisesMsg10713() |
| 145 | + { |
| 146 | + var sim = new Simulation(); |
| 147 | + _ = sim.ExecuteNonQuery("create table dst (id int)"); |
| 148 | + sim.AssertSqlError( |
| 149 | + "merge dst using (values (1)) v(x) on 1=0 when not matched then insert (id) values (v.x)", |
| 150 | + 10713, |
| 151 | + "A MERGE statement must be terminated by a semi-colon (;)."); |
| 152 | + } |
| 153 | + |
| 154 | + [TestMethod] |
| 155 | + public void MergeFollowedByAnotherStatementWithoutTerminator_RaisesMsg10713() |
| 156 | + { |
| 157 | + var sim = new Simulation(); |
| 158 | + _ = sim.ExecuteNonQuery("create table dst (id int)"); |
| 159 | + _ = sim.AssertSqlError( |
| 160 | + "merge dst using (values (1)) v(x) on 1=0 when not matched then insert (id) values (v.x) select 1", |
| 161 | + 10713); |
| 162 | + } |
| 163 | + |
| 164 | + [TestMethod] |
| 165 | + public void MergeThenSelectWithSemicolon_Works() |
| 166 | + { |
| 167 | + using var conn = new Simulation().CreateOpenConnection(); |
| 168 | + _ = conn.CreateCommand("create table dst (id int)").ExecuteNonQuery(); |
| 169 | + AreEqual( |
| 170 | + 1, |
| 171 | + conn.CreateCommand( |
| 172 | + "merge dst using (values (1)) v(x) on 1=0 when not matched then insert (id) values (v.x);select count(*) from dst") |
| 173 | + .ExecuteScalar()); |
| 174 | + } |
| 175 | + |
| 176 | + [TestMethod] |
| 177 | + public void DbccFollowedBySelect_NoSeparator_Works() |
| 178 | + { |
| 179 | + // DBCC TRACEON's parser ends on the closing `)` (last consumed) rather |
| 180 | + // than the lookahead. The dispatch loop normalizes by advancing one |
| 181 | + // token when the cursor isn't already at a statement boundary; this |
| 182 | + // test exercises that path. |
| 183 | + AreEqual(1, ExecuteScalar<int>("dbcc traceon(460) select 1")); |
| 184 | + } |
| 185 | + |
| 186 | + [TestMethod] |
| 187 | + public void AlterDatabaseFollowedBySelect_NoSeparator_Works() |
| 188 | + => AreEqual(2, ExecuteScalar<int>( |
| 189 | + "alter database current set compatibility_level = 160 select 2")); |
| 190 | + |
| 191 | + [TestMethod] |
| 192 | + public void DeclareSetSelect_NoSeparators_Works() |
| 193 | + { |
| 194 | + using var conn = new Simulation().CreateOpenConnection(); |
| 195 | + AreEqual(15, conn.CreateCommand( |
| 196 | + "declare @x int = 7 set @x = @x + 8 select @x").ExecuteScalar()); |
| 197 | + } |
| 198 | +} |
0 commit comments