// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System.Collections.Generic; using System.Text; using Microsoft.Data.SqlClient; using Microsoft.SqlServer.TransactSql.ScriptDom; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; namespace Microsoft.SqlTools.ServiceLayer.AutoParameterizaition { /// /// Entry point for SqlParameterization, this class is responsible for visiting the parse tree and identifying the scalar expressions to be parameterized /// internal class TsqlMultiVisitor : TSqlFragmentVisitor { private readonly ScalarExpressionTransformer ScalarExpressionTransformer; private readonly bool IsCodeSenseRequest; private Dictionary _executionParameters = null; public List Parameters { get; private set; } public List CodeSenseMessages { get; private set; } public List CodeSenseErrors { get; private set; } public Dictionary ExecutionParameters { get { _executionParameters ??= new Dictionary(); return _executionParameters; } } public TsqlMultiVisitor(bool isCodeSenseRequest) { Parameters = new List(); IsCodeSenseRequest = isCodeSenseRequest; CodeSenseMessages = new List(); CodeSenseErrors = new List(); ScalarExpressionTransformer = new ScalarExpressionTransformer(isCodeSenseRequest, CodeSenseErrors); } public override void ExplicitVisit(DeclareVariableStatement node) { if (node == null || node.Declarations == null) { return; } StringBuilder codeSenseMessageStringBuilder = new StringBuilder(); int endLine = -1; int endCol = -1; foreach (DeclareVariableElement declareVariableElement in node.Declarations) { if (declareVariableElement.DataType is SqlDataTypeReference dataTypeReference) { SqlDataTypeOption sqlDataTypeOption = dataTypeReference.SqlDataTypeOption; if (ShouldParamterize(sqlDataTypeOption)) { IList sqlDataTypeParameters = dataTypeReference.Parameters; ScalarExpressionTransformer.SqlDataTypeOption = sqlDataTypeOption; ScalarExpressionTransformer.SqlDataTypeParameters = sqlDataTypeParameters; ScalarExpressionTransformer.VariableName = declareVariableElement.VariableName.Value; ScalarExpression declareVariableElementValue = declareVariableElement.Value; ScalarExpressionTransformer.ExplicitVisit(declareVariableElementValue); declareVariableElement.Value = ScalarExpressionTransformer.GetTransformedExpression(); IList sqlParameters = ScalarExpressionTransformer.Parameters; if (sqlParameters.Count == 1 && declareVariableElementValue != null) { codeSenseMessageStringBuilder.Append(SR.ParameterizationDetails(declareVariableElement.VariableName.Value, sqlParameters[0].SqlDbType.ToString(), sqlParameters[0].Size, sqlParameters[0].Precision, sqlParameters[0].Scale, sqlParameters[0].SqlValue.ToString())); endLine = declareVariableElementValue.StartLine; endCol = declareVariableElementValue.StartColumn + declareVariableElementValue.FragmentLength; if (!IsCodeSenseRequest) { string sqlParameterKey = sqlParameters[0].SqlDbType.ToString(); ExecutionParameters.TryGetValue(sqlParameterKey, out int currentCount); ExecutionParameters[sqlParameterKey] = currentCount + 1; } } Parameters.AddRange(sqlParameters); ScalarExpressionTransformer.Reset(); } } } if (codeSenseMessageStringBuilder.Length > 0) { CodeSenseMessages.Add(new ScriptFileMarker { Level = ScriptFileMarkerLevel.Information, Message = codeSenseMessageStringBuilder.ToString(), ScriptRegion = new ScriptRegion { StartLineNumber = node.StartLine, StartColumnNumber = node.StartColumn, EndLineNumber = endLine == -1 ? node.StartLine : endLine, EndColumnNumber = endCol == -1 ? node.StartColumn + node.LastTokenIndex - node.FirstTokenIndex : endCol } }); } node.AcceptChildren(this); base.ExplicitVisit(node); // let the base class finish up } private bool ShouldParamterize(SqlDataTypeOption sqlDataTypeOption) { switch (sqlDataTypeOption) { case SqlDataTypeOption.BigInt: case SqlDataTypeOption.Int: case SqlDataTypeOption.SmallInt: case SqlDataTypeOption.TinyInt: case SqlDataTypeOption.Bit: case SqlDataTypeOption.Decimal: case SqlDataTypeOption.Numeric: case SqlDataTypeOption.Money: case SqlDataTypeOption.SmallMoney: case SqlDataTypeOption.Float: case SqlDataTypeOption.Real: case SqlDataTypeOption.DateTime: case SqlDataTypeOption.SmallDateTime: case SqlDataTypeOption.Char: case SqlDataTypeOption.VarChar: case SqlDataTypeOption.NChar: case SqlDataTypeOption.NVarChar: case SqlDataTypeOption.Binary: case SqlDataTypeOption.VarBinary: case SqlDataTypeOption.UniqueIdentifier: case SqlDataTypeOption.Date: case SqlDataTypeOption.Time: case SqlDataTypeOption.DateTime2: case SqlDataTypeOption.DateTimeOffset: return true; default: return false; } } public void Reset() { Parameters.Clear(); CodeSenseMessages.Clear(); CodeSenseErrors.Clear(); ExecutionParameters.Clear(); } } }