If the select statement is within a try/catch or if/else this code will not find the select, it will be written out as a single statement in the else part of the code.
It's difficult to see the error in your ways without the complete code but you will find using a visitor instead of navigating the DOM directly is much easier and robust. This will find statements of interest regardless of where they appear in the code, including nested begin/end constructs. See the Fragment Visitors section of this article for an introduction to script DOM visitors.
Here's a complete console app example that uses a visitor.
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.SqlServer.TransactSql.ScriptDom;
internal class Program
{
static void Main(string[] args)
{
var SQLProcedureCode = @"
CREATE PROC dbo.Example
AS
BEGIN
BEGIN TRY
SELECT 1;
/*
some comment
*/
--another comment
SELECT 2;
END TRY
BEGIN CATCH
SELECT 3;
END CATCH;
END;
";
var visitor = new InstrumentProcVisitor();
var instrumentedProc = visitor.getInstrumentedProc(SQLProcedureCode);
Console.WriteLine(instrumentedProc);
}
class InstrumentProcVisitor : TSqlConcreteFragmentVisitor
{
StringWriter instrumentedProcCode = new StringWriter();
List<SelectStatement> selectStatements = new List<SelectStatement>();
public override void Visit(SelectStatement node)
{
selectStatements.Add(node);
}
public string getInstrumentedProc(string procCreateScript)
{
var TheTextReader = new StringReader(procCreateScript);
var SQLParser = new TSql150Parser(true);
IList<ParseError> ParseErrors = new List<ParseError>();
var procFragments = SQLParser.Parse(TheTextReader, out ParseErrors);
if (ParseErrors.Count > 0)
{
throw new InvalidDataException($"{ParseErrors.Count} parse errors in script");
}
//this will visit all SELECT statements in the proc and add to selectStatements list
procFragments.Accept(this);
var scriptTokenIndex = 0;
foreach (var selectStatement in selectStatements)
{
//copy stuff before this select statement
for (scriptTokenIndex = scriptTokenIndex; scriptTokenIndex < selectStatement.FirstTokenIndex; ++scriptTokenIndex)
{
instrumentedProcCode.Write(procFragments.ScriptTokenStream[scriptTokenIndex].Text);
}
//add instrumentation code before select
instrumentedProcCode.WriteLine("\r\nPRINT 'Executing SELECT statement';");
//copy select statement
for (int tokenIndex = selectStatement.FirstTokenIndex; tokenIndex <= selectStatement.LastTokenIndex; ++tokenIndex)
{
instrumentedProcCode.Write(selectStatement.ScriptTokenStream[tokenIndex].Text);
}
//add instrumentation code after select
instrumentedProcCode.WriteLine("\r\nPRINT 'SELECT statement completed';");
//set token index to next token after select
scriptTokenIndex = selectStatement.LastTokenIndex + 1;
}
//copy stuff after last select statement
for (scriptTokenIndex = scriptTokenIndex; scriptTokenIndex < procFragments.ScriptTokenStream.Count; ++scriptTokenIndex)
{
instrumentedProcCode.Write(procFragments.ScriptTokenStream[scriptTokenIndex].Text);
}
return instrumentedProcCode.ToString();
}
}
}
Below is the resultant instrumented code:
CREATE PROC dbo.Example
AS
BEGIN
BEGIN TRY
PRINT 'Executing SELECT statement';
SELECT 1;
PRINT 'SELECT statement completed';
/*
some comment
*/
--another comment
PRINT 'Executing SELECT statement';
SELECT 2;
PRINT 'SELECT statement completed';
END TRY
BEGIN CATCH
PRINT 'Executing SELECT statement';
SELECT 3;
PRINT 'SELECT statement completed';
END CATCH;
END;