left-icon

ScriptDOM Succinctly®
by Joseph D. Booth

Previous
Chapter

of
A
A
A

CHAPTER 6

Variables


In this chapter, we will use ScriptDOM to get a list of variables and parameters from a stored procedure. Let’s consider our newbie script from Chapter 1. Code Listing 18 shows that procedure.

Code Listing 18: Newbie procedure

CREATE procedure [FindUsersByLanguage](@languagecode varchar)

as

begin

     set nocount on

     declare @numPeopleFound int

     select * from [Person]

     where LanguageID = null or LanguageID <> @languagecode

end

Some problems include:

·     No size specified on the language code parameters

·     Unused variables

·     No schema specified on the create procedure

In this chapter, we will use ScriptDOM to find each of these issues.

Variable declarations

The DeclareVariableStatement exists for each declare statement and the list of variables. In addition to the standard statement properties defined in Chapter 4, this statement also has a Declarations property, which is a list of all declaration variable elements found in the script.

The key properties of the DeclareVariableElement class are shown in Table 7.

Table 7: DeclarationVariableElement key properties

Property

ScriptDOM class

Description

DataType

SQL data type reference

SQLDataType option that contains the data type name (date, int, etc.). See SQL Data Type Enum to see the data types.

Value

Function call

If value is set from a function, function details can be derived from the FunctionCall class

(Integer, float, etc.) Literal

The nested Value property will contain the value of the literal data type

VariableName

Identifier

This class contains the name of the variable

Visitor method

The visitor method call to get all Declare statements is shown in Code Listing 19.

Code Listing 19: Visit method for declared variables

        public override void ExplicitVisit(DeclareVariableStatement node)

        {

            base.ExplicitVisit(node);

        }

Within the method code, we can process the Declarations list. We will extract the variable name and the line and row number. We also have a string indicating a variable (V) or parameter (P). Also, we add an empty slot to hold the number of times the variable is referenced. Listing 20 shows the code to load the declarations into a dictionary object.

Code Listing 20: Visitor class

namespace Chap6

{

        public class Visitor : TSqlFragmentVisitor

        {

            private Dictionary<string, varInfo> variables =

                               new Dictionary<string, varInfo>();

            public struct varInfo

            {

                public int LineNumber;

                public int ColumnNumber;

                public int TimesVariableUsed;

                public string VarType;  // V=variable, P=parameter

                public string DataType;

                public int DataSize;

            }

            // Class properties

            public Dictionary<string, varInfo> Variables

            { get => variables; set => variables = value; }

            public string ProcedureName;

            public string SchemaName;

            public Visitor()

            {

            }

            // Visitor statements

            /// <summary>

            /// Get any variable declarations

            /// </summary>

            /// <param name="node"></param>

            public override void ExplicitVisit(DeclareVariableStatement node)

            {

                foreach (DeclareVariableElement varObj in node.Declarations)

                {

                    SqlDataTypeReference dt = (SqlDataTypeReference)varObj.DataType;

                    string datatype = dt.Name.BaseIdentifier.Value;

                    int varSize = 0;

                    if (dt.Parameters.Count>0)

                    {

                        var pm = dt.Parameters[0];

                        if (pm is IntegerLiteral) {

                            varSize = int.Parse(pm.Value);

                        }

                        if (pm is MaxLiteral) {

                            varSize = 1024;

                        }

                    }

                    varInfo v = new varInfo

                    {

                        LineNumber = varObj.StartLine,

                        ColumnNumber = varObj.StartColumn,

                        TimesVariableUsed = 0,

                        VarType = "V",

                        DataType = datatype,

                        DataSize = varSize,

                    };

                    Variables.Add(varObj.VariableName.Value.ToString(), v);

                }

                base.ExplicitVisit(node);

            }

We create a dictionary and structure to hold information extracted from the DeclareVariableElement. Each time a declaration is found, it will be added to the dictionary.

Parameter definitions

The parameters passed to the procedure will be part of the CreateProcedureStatement, so we can create a Visit reference to that as well. The code is similar to the declaration code, except we are using a different source and specifying the var type as a P for parameter. Listing 21 shows the Explicit class override for the CreateProcedureStatement.

Code Listing 21: Create procedure explicit visit

       /// <summary>

       /// Extract procedure name and any parameters

       /// </summary>

       /// <param name="node"></param>

       public override void ExplicitVisit(CreateProcedureStatement node)

       {

            ProcedureName = node.ProcedureReference.Name.BaseIdentifier.Value;

            if (node.ProcedureReference.Name.SchemaIdentifier is not null)

            {

                SchemaName = node.ProcedureReference.Name.SchemaIdentifier.Value;

            }

            if (node.Parameters.Count > 0)

                {

                    foreach (ProcedureParameter p in node.Parameters)

                    {

                        SqlDataTypeReference dt = (SqlDataTypeReference)p.DataType;

                        string datatype = dt.Name.BaseIdentifier.Value;

                        int varSize = 0;

                        if (dt.Parameters.Count > 0)

                        {

                            var pm = dt.Parameters[0];

                            if (pm is IntegerLiteral)

                            {

                                varSize = int.Parse(pm.Value);

                            }

                            if (pm is MaxLiteral)

                            {

                                varSize = 1024;

                            }

                        }

                        varInfo v = new varInfo

                        {

                            LineNumber = p.StartLine,

                            ColumnNumber = p.StartColumn,

                            TimesVariableUsed = 0,

                            VarType = "P",

                            DataType = datatype,

                            DataSize = varSize,

                        };

                        Variables.Add(p.VariableName.Value.ToString(), v);

                    }

                }

                base.ExplicitVisit(node);

            }

In this method, we are adding the parameters to our variable collection and also grabbing the procedure and schema name. If a schema is not specified, the schema name string will be empty.

Variable usage

We can create another Visit to be called each time a variable is referenced. The code is shown in Code Listing 22.

Code Listing 22: Visit method for declared variables

public override void ExplicitVisit(VariableReference node)

        {

            base.ExplicitVisit(node);

        }

The VariableReference class has a property called Name, which is the variable being referenced. Code Listing 23 shows the Visit updated to increment the TimeVariableUsed count for the variable.

Code Listing 23: Count references to variable

        public override void ExplicitVisit(VariableReference node)

        {

            if (variables.ContainsKey(node.Name))

            {

                varInfo v = variables[node.Name];

                v.TimesVariableUsed++;

                variables[node.Name] = v;

            }

            base.ExplicitVisit(node);

        }

When the program is finished, the Variables property will contain all declared variables and the number of times the variable was used. We can also add a property to the Visitor class to return a list of unused variables, as shown in Listing 24.

Code Listing 24: Get list of unused variables

  public List<string> UnusedVariables

        {

            get

            {

                List<string> result = new List<string>();

                result = variables.

Where(v => v.Value.TimesUsed<1).Select(v=>v.Key).ToList();

                return result;

            }

        }

Main code

With the Visitor class completed, we can define our code to display the variables and any errors found. Listing 25 is the main program.

Code Listing 25: Main variable checking program

private static void Main(string[] args)

{

     Console.WriteLine("ScriptDOM Parser - Newbie procedure");

     Console.WriteLine("");

     var parser = new TSql160Parser(true, SqlEngineType.All);

     IList<ParseError>? errors = null;

     StringBuilder sb = new();

     sb.AppendLine("--  Chap 6 Newbie procedure");

     sb.AppendLine("");

     sb.AppendLine("CREATE procedure [FindUsersByLanguage](@languagecode varchar)");

     sb.AppendLine("AS ");

     sb.AppendLine("BEGIN");

     sb.AppendLine("     set nocount on");

     sb.AppendLine("     declare @numPeopleFound int");

     sb.AppendLine("     select * from [person]");

     sb.AppendLine("     where LanguageID is null or LanguageID <> @languagecode");

     sb.AppendLine("END");

     byte[] byteArray = Encoding.ASCII.GetBytes(sb.ToString());

     MemoryStream stream = new(byteArray);

     StreamReader rdr = new(stream);

     tSqlScript tree = (tSqlScript)parser.Parse(rdr, out errors);

     Visitor treedata = new Visitor();

     tree.Accept(treedata);

     if (errors.Count > 0)

     {

         foreach (ParseError err in errors)

         {

              Console.WriteLine(err.Message);

         }

     }

     else

     {

        // Show the parameters and variables found

        Console.WriteLine("Procedure: " + treedata.ProcedureName);

        if (string.IsNullOrEmpty(treedata.SchemaName))

           {

             Console.WriteLine("ERROR: Missing schema name");

           }

           Console.WriteLine("Parameters");

           Console.WriteLine("");

           string msg = "";

           foreach (var item in treedata.Variables.

                    Where((v) => v.Value.VarType == "P"))

           {

              msg = "";

              Visitor.varInfo v = item.Value;

              if (v.DataType.ToLower().Contains("char") && v.DataSize ==0)

              {

                  msg =  "ERROR: No size specified (defaults to 1)";

              }

              if (v.TimesUsed<1)

              {

                  msg =  "ERROR: "+item.Key+" is never used...";

              }

              Console.WriteLine(item.Key + " [" + v.DataType + "] " + msg);

           }

           Console.WriteLine("");

           Console.WriteLine("Variables");

           Console.WriteLine("");

           foreach (var item in treedata.Variables.

                  Where((v) => v.Value.VarType == "V"))

           {

               msg = "";

               Visitor.varInfo v = item.Value;

               if (v.DataType.ToLower().Contains("char") && v.DataSize == 0)

               {

                  msg = "ERROR: No size specified (defaults to 1)";

               }

               if (v.TimesUsed < 1)

               {

                   msg = "ERROR: " + item.Key + " is never used...";

               }

               Console.WriteLine("Line " + v.LineNumber.ToString() + ": " +

                      item.Key + " [" + v.DataType + "] " + msg);

           }

           Console.WriteLine("");

        }

        Console.ReadKey();

        Console.WriteLine("");

        Console.WriteLine("End of demo");

     }

When this is run on the newbie’s code, the console output appears as shown in Figure 9.

Newbie’s errors

Figure 9: Newbie’s errors

Summary

We hope this whets your appetite for the type of analysis ScriptDOM is capable of. You can loop through all stored procedures in a database and report the errors from this Visitor class. The source code for all chapters is available at GitHub.

Scroll To Top
Disclaimer

DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.