Skip to content

Commit 0c8f991

Browse files
committed
Integrated the official reserved keyword list. Updated how type names are parsed since they're not reserved keywords.
1 parent 451e876 commit 0c8f991

5 files changed

Lines changed: 220 additions & 45 deletions

File tree

SqlServerSimulator.Tests/CreateTableTests.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace SqlServerSimulator;
1+
using System.Data.Common;
2+
3+
namespace SqlServerSimulator;
24

35
[TestClass]
46
public class CreateTableTests
@@ -15,6 +17,19 @@ public void CreateTableMinimal()
1517
Assert.AreEqual(-1, command.ExecuteNonQuery());
1618
}
1719

20+
[TestMethod]
21+
public void InvalidTypeName()
22+
{
23+
var simulation = new Simulation();
24+
25+
using var connection = simulation.CreateDbConnection();
26+
using var command = connection.CreateCommand("create table t ( v intz )");
27+
28+
connection.Open();
29+
var x = Assert.Throws<DbException>(() => command.ExecuteNonQuery());
30+
Assert.AreEqual("Column, parameter, or variable #1: Cannot find data type intz.", x.Message);
31+
}
32+
1833
[TestMethod]
1934
public void CreateTableNull()
2035
{

SqlServerSimulator/DataType.cs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using SqlServerSimulator.Parser;
2-
using SqlServerSimulator.Parser.Tokens;
1+
using SqlServerSimulator.Parser.Tokens;
32
using System.Data;
43
using System.Globalization;
54

@@ -31,17 +30,36 @@ private protected DataType()
3130

3231
public static readonly DataType BuiltInDbSystemName = new DbSystemName();
3332

34-
public static DataType GetByName(Name name)
33+
/// <summary>
34+
/// Looks up the <see cref="DataType"/> for the provided type name.
35+
/// </summary>
36+
/// <param name="name">The name of the type.</param>
37+
/// <param name="index">The 1-based index of the type, used for an error message.</param>
38+
/// <returns>The matching data type.</returns>
39+
/// <exception cref="SimulatedSqlException">Column, parameter, or variable #<paramref name="index"/>: Cannot find data type <paramref name="name"/>.</exception>
40+
public static DataType GetByName(Name name, int index)
3541
{
36-
var keyword = name.Parse();
37-
return keyword switch
42+
Span<char> upper = stackalloc char[name.Value.Length];
43+
return name.Value.ToUpperInvariant(upper) switch
3844
{
39-
Keyword.Bit => BuiltInDbBoolean,
40-
Keyword.TinyInt => BuiltInDbByte,
41-
Keyword.SmallInt => BuiltInDbInt16,
42-
Keyword.Int => BuiltInDbInt32,
43-
_ => throw new NotSupportedException($"Simulated data type parser doesn't recognize {keyword}"),
44-
};
45+
3 => upper switch
46+
{
47+
"BIT" => BuiltInDbBoolean,
48+
"INT" => BuiltInDbInt32,
49+
_ => null
50+
},
51+
7 => upper switch
52+
{
53+
"TINYINT" => BuiltInDbByte,
54+
_ => null
55+
},
56+
8 => upper switch
57+
{
58+
"SMALLINT" => BuiltInDbInt16,
59+
_ => null
60+
},
61+
_ => null,
62+
} ?? throw SimulatedSqlException.CannotFindDataType(name.Value, index);
4563
}
4664

4765
public static DataType GetByDbType(DbType dbType) => dbType switch
Lines changed: 163 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,196 @@
11
namespace SqlServerSimulator.Parser;
22

3+
/// <summary>
4+
/// Reserved keywords from https://learn.microsoft.com/en-us/sql/t-sql/language-elements/reserved-keywords-transact-sql
5+
/// </summary>
36
enum Keyword
47
{
58
_ = 0, // "Default" value for the enum, indicating not a keyword.
9+
Add,
10+
All,
11+
Alter,
12+
And,
13+
Any,
614
As,
715
Asc,
8-
BigInt,
9-
Binary,
10-
Bit,
16+
Authorization,
17+
Backup,
18+
Begin,
19+
Between,
20+
Break,
21+
Browse,
22+
Bulk,
1123
By,
12-
Char,
13-
Count,
14-
CountBig,
24+
Cascade,
25+
Case,
26+
Check,
27+
Checkpoint,
28+
Close,
29+
Clustered,
30+
Coalesce,
31+
Collate,
32+
Column,
33+
Commit,
34+
Compute,
35+
Constraint,
36+
Contains,
37+
ContainsTable,
38+
Continue,
39+
Convert,
1540
Create,
16-
Date,
17-
DateTime,
18-
DateTime2,
19-
DateTimeOffset,
20-
Decimal,
41+
Cross,
42+
Current_Date,
43+
Current_Time,
44+
Current_Timestamp,
45+
Current_User,
46+
Current,
47+
Cursor,
48+
Database,
49+
Dbcc,
50+
Deallocate,
51+
Declare,
52+
Default,
53+
Delete,
54+
Deny,
2155
Desc,
56+
Disk,
57+
Distinct,
58+
Distributed,
59+
Double,
60+
Drop,
61+
Dump,
62+
Else,
63+
End,
64+
ErrLvl,
65+
Escape,
66+
Except,
67+
Exec,
68+
Execute,
2269
Exists,
23-
Float,
70+
Exit,
71+
External,
72+
Fetch,
73+
File,
74+
FillFactor,
75+
For,
76+
Foreign,
77+
FreeText,
78+
FreeTextTable,
2479
From,
25-
Implicit_Transactions,
80+
Full,
81+
Function,
82+
Goto,
83+
Grant,
84+
Group,
85+
Having,
86+
HoldLock,
87+
Identity_Insert,
88+
Identity,
89+
IdentityCol,
90+
If,
2691
In,
92+
Index,
2793
Inner,
2894
Insert,
29-
Int,
95+
Intersect,
3096
Into,
97+
Is,
3198
Join,
3299
Key,
100+
Kill,
33101
Left,
34-
Money,
35-
NChar,
36-
NoCount,
102+
Like,
103+
LineNo,
104+
Load,
105+
Merge,
106+
National,
107+
NoCheck,
108+
NonClustered,
37109
Not,
38110
Null,
39-
Numeric,
40-
NVarChar,
111+
NullIf,
112+
Of,
41113
Off,
114+
Offsets,
42115
On,
116+
Open,
117+
OpenDataSource,
118+
OpenQuery,
119+
OpenRowSet,
120+
OpenXml,
121+
Option,
122+
Or,
43123
Order,
44124
Outer,
125+
Over,
126+
Percent,
127+
Pivot,
128+
Plan,
129+
Precision,
45130
Primary,
46-
Real,
131+
Print,
132+
Proc,
133+
Procedure,
134+
Public,
135+
RaisError, // Alternative casing is RaiseRror, which is even weirder.
136+
Read,
137+
ReadText,
138+
Reconfigure,
139+
References,
140+
Replication,
141+
Restore,
142+
Restrict,
143+
Return,
144+
Revert,
145+
Revoke,
47146
Right,
147+
Rollback,
148+
RowCount,
149+
RowGuidCol,
150+
Rule,
151+
Save,
152+
Schema,
153+
SecurityAudit,
48154
Select,
155+
SemanticKeyPhraseTable,
156+
SemanticSimilarityDetailsTable,
157+
SemanticSimilarityTable,
158+
Session_User,
49159
Set,
50-
SmallDateTime,
51-
SmallInt,
52-
SmallMoney,
160+
SetUser,
161+
Shutdown,
162+
Some,
163+
Statistics,
164+
System_user,
53165
Table,
54-
Time,
55-
TinyInt,
56-
UniqueIdentifier,
166+
TableSample,
167+
TextSize,
168+
Then,
169+
To,
170+
Top,
171+
Tran,
172+
Transaction,
173+
Trigger,
174+
Truncate,
175+
Try_Convert,
176+
TSequal,
177+
Union,
178+
Unique,
179+
Unpivot,
180+
Update,
181+
UpdateText,
182+
Use,
183+
User,
57184
Values,
58-
VarBinary,
59-
VarChar,
185+
Varying,
186+
View,
187+
WaitFor,
188+
When,
60189
Where,
61-
Xml,
190+
While,
191+
With,
192+
// "WITHIN GROUP", including the space, is in the list linked above for some unknown reason.
193+
// "WITHIN" alone doesn't seem to be enforced as a reserved keyword in my testing, and "GROUP" is already covered.
194+
// These factors combine to make it functionally irrelevant as a reserved keyword.
195+
WriteText,
62196
}

SqlServerSimulator/SimulatedSqlException.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ private SimulatedSqlException(string? message, params ReadOnlySpan<SimulatedSqlE
8484

8585
public IReadOnlyList<SimulatedSqlError> Errors { get; }
8686

87+
/// <summary>
88+
/// Mimics the SqlException that occurs when an unknown type is referenced.
89+
/// </summary>
90+
/// <param name="name">The name of the type.</param>
91+
/// <param name="index">The 1-based index of the reference.</param>
92+
/// <returns>The exception.</returns>
93+
internal static SimulatedSqlException CannotFindDataType(string name, int index) => new($"Column, parameter, or variable #{index}: Cannot find data type {name}.", 2715, 16, 6);
94+
8795
internal static SimulatedSqlException InvalidColumnName(string name) => new($"Invalid column name '{name}'.", 207, 16, 1);
8896

8997
internal static SimulatedSqlException InvalidColumnName(IEnumerable<string> name) => InvalidColumnName(string.Join('.', name));

SqlServerSimulator/Simulation.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,10 @@ internal IEnumerable<SimulatedStatementOutcome> CreateResultSetsForCommand(Simul
144144
switch (token = tokens.RequireNext())
145145
{
146146
case UnquotedString setTarget:
147-
switch (setTarget.Parse())
147+
switch (setTarget.Value.ToUpperInvariant())
148148
{
149-
case Keyword.Implicit_Transactions:
150-
case Keyword.NoCount:
149+
case "IMPLICIT_TRANSACTIONS":
150+
case "NOCOUNT":
151151
switch (token = tokens.RequireNext())
152152
{
153153
case UnquotedString onOff:
@@ -217,7 +217,7 @@ internal IEnumerable<SimulatedStatementOutcome> CreateResultSetsForCommand(Simul
217217
nullable = true;
218218
}
219219

220-
columns.Add(new Column(columnName.Value, DataType.GetByName(type), nullable));
220+
columns.Add(new(columnName.Value, DataType.GetByName(type, columns.Count + 1), nullable));
221221
} while ((suppressAdvanceToken ? token : token = tokens.RequireNext()) is Comma);
222222

223223
if (token is not CloseParentheses)

0 commit comments

Comments
 (0)