microsoft/healthcare-shared-components

Public

mirrored from https://github.com/microsoft/healthcare-shared-componentsAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.0.0-1.0.79

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

tools/Microsoft.Health.Extensions.BuildTimeCodeGenerator/Sql/SqlVisitor.cs

282lines · modecode

1// -------------------------------------------------------------------------------------------------
2// Copyright (c) Microsoft Corporation. All rights reserved.
3// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4// -------------------------------------------------------------------------------------------------
5
6using System;
7using System.Collections.Generic;
8using System.Data;
9using System.IO;
10using System.Linq;
11using System.Text.RegularExpressions;
12using Microsoft.CodeAnalysis.CSharp;
13using Microsoft.CodeAnalysis.CSharp.Syntax;
14using Microsoft.SqlServer.TransactSql.ScriptDom;
15using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
16
17namespace Microsoft.Health.Extensions.BuildTimeCodeGenerator.Sql
18{
19 /// <summary>
20 /// The base class for SQL visitors
21 /// </summary>
22 public abstract class SqlVisitor : TSqlFragmentVisitor
23 {
24 /// <summary>
25 /// These members will be added to the generated class.
26 /// </summary>
27 public List<MemberDeclarationSyntax> MembersToAdd { get; } = new List<MemberDeclarationSyntax>();
28
29 public abstract int ArtifactSortOder { get; }
30
31 /// <summary>
32 /// Converts a <see cref="DataTypeReference"/> to a <see cref="TypeSyntax"/>
33 /// </summary>
34 /// <param name="dataType">The type</param>
35 /// <param name="nullable">Whether the type is nullable</param>
36 /// <returns>The <see cref="TypeSyntax"/></returns>
37 protected static TypeSyntax DataTypeReferenceToClrType(DataTypeReference dataType, bool nullable)
38 {
39 if (Enum.TryParse<SqlDbType>(dataType.Name.BaseIdentifier.Value, ignoreCase: true, out var sqlDbType))
40 {
41 return SqlDbTypeToClrType(sqlDbType, nullable).ToTypeSyntax(useGlobalAlias: true);
42 }
43
44 // assumed to be a table type
45
46 return TypeExtensions.CreateGenericTypeFromGenericTypeDefinition(
47 typeof(IEnumerable<>).ToTypeSyntax(true),
48 IdentifierName(GetRowStructNameForTableType(dataType.Name)));
49 }
50
51 /// <summary>
52 /// Converts a <see cref="SqlDbType"/> to a <see cref="Type"/>.
53 /// </summary>
54 /// <param name="sqlDbType">The type</param>
55 /// <param name="nullable">Whether the type is nullable</param>
56 /// <returns>The <see cref="Type"/></returns>
57 protected static Type SqlDbTypeToClrType(SqlDbType sqlDbType, bool nullable)
58 {
59 Type type = SqlDbTypeToClrType(sqlDbType);
60 if (nullable && !type.IsClass)
61 {
62 type = typeof(Nullable<>).MakeGenericType(type);
63 }
64
65 return type;
66 }
67
68 /// <summary>
69 /// Converts a <see cref="SqlDbType"/> to a <see cref="Type"/>.
70 /// </summary>
71 /// <param name="sqlDbType">The type</param>
72 /// <returns>The <see cref="Type"/></returns>
73 protected static Type SqlDbTypeToClrType(SqlDbType sqlDbType)
74 {
75 switch (sqlDbType)
76 {
77 case SqlDbType.BigInt:
78 return typeof(long);
79 case SqlDbType.Binary:
80 return typeof(byte[]);
81 case SqlDbType.Bit:
82 return typeof(bool);
83 case SqlDbType.Char:
84 break;
85 case SqlDbType.Date:
86 case SqlDbType.DateTime:
87 case SqlDbType.DateTime2:
88 case SqlDbType.SmallDateTime:
89 case SqlDbType.Time:
90 return typeof(DateTime);
91 case SqlDbType.DateTimeOffset:
92 return typeof(DateTimeOffset);
93 case SqlDbType.Decimal:
94 return typeof(decimal);
95 case SqlDbType.Float:
96 break;
97 case SqlDbType.Image:
98 break;
99 case SqlDbType.Int:
100 return typeof(int);
101 case SqlDbType.Money:
102 break;
103 case SqlDbType.NChar:
104 case SqlDbType.NText:
105 case SqlDbType.VarChar:
106 case SqlDbType.NVarChar:
107 case SqlDbType.Text:
108 return typeof(string);
109 case SqlDbType.Real:
110 return typeof(double);
111 case SqlDbType.SmallInt:
112 return typeof(short);
113 case SqlDbType.SmallMoney:
114 break;
115 case SqlDbType.Structured:
116 return typeof(object);
117 case SqlDbType.Timestamp:
118 return typeof(byte[]);
119 case SqlDbType.TinyInt:
120 return typeof(byte);
121 case SqlDbType.Udt:
122 break;
123 case SqlDbType.UniqueIdentifier:
124 return typeof(Guid);
125 case SqlDbType.VarBinary:
126 return typeof(Stream);
127 case SqlDbType.Variant:
128 break;
129 case SqlDbType.Xml:
130 break;
131 }
132
133 throw new NotSupportedException(sqlDbType.ToString());
134 }
135
136 /// <summary>
137 /// Determines whether the given <see cref="ColumnDefinition"/> is nullable
138 /// </summary>
139 /// <param name="column">The column</param>
140 /// <returns>Whether the column is nullable</returns>
141 protected static bool IsColumnNullable(ColumnDefinition column)
142 {
143 return column.Constraints.Any(c => c is NullableConstraintDefinition nc && nc.Nullable);
144 }
145
146 /// <summary>
147 /// Creates an internal static readonly field for an instance of a class that has a parameterless constructor
148 /// </summary>
149 /// <param name="className">The class name</param>
150 /// <param name="fieldName">The field name</param>
151 /// <returns>The field.</returns>
152 protected static FieldDeclarationSyntax CreateStaticFieldForClass(string className, string fieldName)
153 {
154 return FieldDeclaration(
155 VariableDeclaration(IdentifierName(className))
156 .AddVariables(VariableDeclarator(fieldName)
157 .WithInitializer(
158 EqualsValueClause(
159 ObjectCreationExpression(
160 IdentifierName(className)).AddArgumentListArguments()))))
161 .AddModifiers(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.ReadOnlyKeyword), Token(SyntaxKind.StaticKeyword));
162 }
163
164 /// <summary>
165 /// Gets the arguments for SQL database types, like the length, precision, and scale.
166 /// </summary>
167 /// <param name="dataType">The column data type</param>
168 /// <param name="collation">The column collation, if any</param>
169 /// <returns>The type arguments</returns>
170 protected static IEnumerable<ArgumentSyntax> GetDataTypeSpecificConstructorArguments(DataTypeReference dataType, Identifier collation)
171 {
172 if (dataType is ParameterizedDataTypeReference parameterizedDataType)
173 {
174 foreach (var parameter in parameterizedDataType.Parameters)
175 {
176 yield return Argument(
177 LiteralExpression(
178 SyntaxKind.NumericLiteralExpression,
179 Literal(parameter.LiteralType == LiteralType.Max ? -1 : int.Parse(parameter.Value))));
180 }
181 }
182
183 if (collation != null)
184 {
185 yield return Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(collation.Value)));
186 }
187 }
188
189 /// <summary>
190 /// Gets the name of the class that derives from TableValuedParameterDefiniition for a table type.
191 /// </summary>
192 /// <param name="objectName">The name of the table type</param>
193 /// <returns>The class name</returns>
194 protected static string GetClassNameForTableValuedParameterDefinition(SchemaObjectName objectName)
195 {
196 return $"{GetTableTypeMemberName(objectName)}TableValuedParameterDefinition";
197 }
198
199 /// <summary>
200 /// Gets the name of the row struct for a table type.
201 /// </summary>
202 /// <param name="objectName">The name of the table type</param>
203 /// <returns>The struct name</returns>
204 protected static string GetRowStructNameForTableType(SchemaObjectName objectName)
205 {
206 return $"{GetTableTypeMemberName(objectName)}Row";
207 }
208
209 /// <summary>
210 /// Strips away the underscore in a in a suffix for a table type name and replaces it with "V"
211 /// For example, MyTableType_1 will return MyTableTypeV1
212 /// </summary>
213 /// <param name="objectName">The table type name</param>
214 /// <returns>The name</returns>
215 private static string GetTableTypeMemberName(SchemaObjectName objectName)
216 {
217 return Regex.Replace(objectName.BaseIdentifier.Value, @"_(\d+)$", "V$1");
218 }
219
220 /// <summary>
221 /// Strips away the version suffix for a member name.
222 /// For example, "MyProcedure_1" will return "MyProcedure"
223 /// </summary>
224 /// <param name="objectName">The object name</param>
225 /// <returns>The name</returns>
226 protected static string GetMemberNameWithoutVersionSuffix(SchemaObjectName objectName)
227 {
228 return Regex.Replace(objectName.BaseIdentifier.Value, @"_\d+$", string.Empty);
229 }
230
231 protected MemberDeclarationSyntax CreatePropertyForColumn(ColumnDefinition column)
232 {
233 string normalizedSqlDbType = null;
234
235 // Handle computed columns
236 if (column.DataType == null && column.ComputedColumnExpression != null)
237 {
238 return FieldDeclaration(
239 VariableDeclaration(SyntaxFactory.ParseTypeName("string"))
240 .AddVariables(VariableDeclarator($"{column.ColumnIdentifier.Value}")
241 .WithInitializer(
242 EqualsValueClause(
243 LiteralExpression(
244 SyntaxKind.StringLiteralExpression,
245 Literal(column.ColumnIdentifier.Value))))))
246 .AddModifiers(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.ConstKeyword));
247 }
248
249 var sqlDbTypeToNormalize = column.DataType.Name.BaseIdentifier.Value;
250 if (Enum.TryParse(sqlDbTypeToNormalize, true, out SqlDbType sqlDbType))
251 {
252 normalizedSqlDbType = sqlDbType.ToString();
253 }
254 else
255 {
256 if (string.Compare(sqlDbTypeToNormalize, "RowVersion", StringComparison.InvariantCultureIgnoreCase) == 0)
257 {
258 // The timestamp data type is a synonym for the rowversion data type. Its syntax is now deprecated in SQL.
259 // We are translating rowversion (used in the SQL code) to timestamp here because the SqlDbType enum only supports timestamp.
260 normalizedSqlDbType = SqlDbType.Timestamp.ToString();
261 }
262 }
263
264 IdentifierNameSyntax typeName = IdentifierName($"{(IsColumnNullable(column) ? "Nullable" : string.Empty)}{normalizedSqlDbType}Column");
265
266 return FieldDeclaration(
267 VariableDeclaration(typeName)
268 .AddVariables(VariableDeclarator($"{column.ColumnIdentifier.Value}")
269 .WithInitializer(
270 EqualsValueClause(
271 ObjectCreationExpression(
272 typeName)
273 .AddArgumentListArguments(
274 Argument(
275 LiteralExpression(
276 SyntaxKind.StringLiteralExpression,
277 Literal(column.ColumnIdentifier.Value))))
278 .AddArgumentListArguments(GetDataTypeSpecificConstructorArguments(column.DataType, column.Collation).ToArray())))))
279 .AddModifiers(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.ReadOnlyKeyword));
280 }
281 }
282}
283