Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using the TSqlParser

Tags:

tsql

parsing

I'm attempting to parse SQL using the TSql100Parser provided by microsoft. Right now I'm having a little trouble using it the way it seems to be intended to be used. Also, the lack of documentation doesn't help. (example: http://msdn.microsoft.com/en-us/library/microsoft.data.schema.scriptdom.sql.tsql100parser.aspx )

When I run a simple SELECT statement through the parser it returns a collection of TSqlStatements which contains a SELECT statement. Trouble is, the TSqlSelect statement doesn't contain attributes such as a WHERE clause, even though the clause is implemented as a class. http://msdn.microsoft.com/en-us/library/microsoft.data.schema.scriptdom.sql.whereclause.aspx The parser does recognise the WHERE clause as such, looking at the token stream. So, my question is, am I using the parser correctly? Right now the token stream seems to be the most useful feature of the parser...

My Test project:

public static void Main(string[] args)
{
    var parser = new TSql100Parser(false);

            IList<ParseError> Errors;
            IScriptFragment result = parser.Parse(
                new StringReader("Select col from T1 where 1 = 1 group by 1;" +
                    "select col2 from T2;" +
                    "select col1 from tbl1 where id in (select id from tbl);"),
                    out Errors);

            var Script = result as TSqlScript;

            foreach (var ts in Script.Batches)
            {
                Console.WriteLine("new batch");

                foreach (var st in ts.Statements)
                {
                    IterateStatement(st);
                }
            }
}

static void IterateStatement(TSqlStatement statement)
{
            Console.WriteLine("New Statement");

            if (statement is SelectStatement)
            {
                PrintStatement(sstmnt);
            }
}
like image 321
Opflash Avatar asked Nov 22 '09 19:11

Opflash


4 Answers

Yes, you are using the parser correctly.

As Damien_The_Unbeliever points out, within the SelectStatement there is a QueryExpression property which will be a QuerySpecification object for your third select statement (with the WHERE clause).

This represents the 'real' SELECT bit of the query (whereas the outer SelectStatement object you are looking at has just got the 'WITH' clause (for CTEs), 'FOR' clause (for XML), 'ORDER BY' and other bits)

The QuerySpecification object is the object with the FromClauses, WhereClause, GroupByClause etc.

So you can get to your WHERE Clause by using:

((QuerySpecification)((SelectStatement)statement).QueryExpression).WhereClause

which has a SearchCondition property etc. etc.

like image 109
doza Avatar answered Sep 22 '22 23:09

doza


Quick glance around would indicate that it contains a QueryExpression, which could be a QuerySpecification, which does have the Where clause attached to it.

like image 33
Damien_The_Unbeliever Avatar answered Sep 22 '22 23:09

Damien_The_Unbeliever


if someone lands here and wants to know how to get the whole elements of a select statement the following code explain that:

QuerySpecification spec = (QuerySpecification)(((SelectStatement)st).QueryExpression);
StringBuilder sb = new StringBuilder();
sb.AppendLine("Select Elements");
foreach (var elm in spec.SelectElements)                            
  sb.Append(((Identifier)((Column)((SelectColumn)elm).Expression).Identifiers[0]).Value);

sb.AppendLine();

sb.AppendLine("From Elements");
foreach (var elm in spec.FromClauses)
  sb.Append(((SchemaObjectTableSource)elm).SchemaObject.BaseIdentifier.Value);
sb.AppendLine();

sb.AppendLine("Where Elements");
BinaryExpression binaryexp = (BinaryExpression)spec.WhereClause.SearchCondition;
sb.Append("operator is " + binaryexp.BinaryExpressionType);
if (binaryexp.FirstExpression is Column)
  sb.Append(" First exp is " + ((Identifier)((Column)binaryexp.FirstExpression).Identifiers[0]).Value);

if (binaryexp.SecondExpression is Literal)
  sb.Append(" Second exp is " + ((Literal)binaryexp.SecondExpression).Value);
like image 5
Mukhtar Avatar answered Sep 22 '22 23:09

Mukhtar


I had to split a SELECT statement into pieces. My goal was to COUNT how many record a query will return. My first solution was to build a sub query such as

SELECT COUNT(*) FROM (select id, name from T where cat='A' order by id) as QUERY

The problem was that in this case the order clause raises the error "The ORDER BY clause is not valid in views, inline functions, derived tables, sub-queries, and common table expressions, unless TOP or FOR XML is also specified"

So I built a parser that split a SELECT statment into fragments using the TSql100Parser class.

using Microsoft.Data.Schema.ScriptDom.Sql;
using Microsoft.Data.Schema.ScriptDom;
using System.IO;
...
public class SelectParser
{
    public string Parse(string sqlSelect, out string fields, out string from, out string groupby, out string where, out string having, out string orderby)
    {
        TSql100Parser parser = new TSql100Parser(false);
        TextReader rd = new StringReader(sqlSelect);
        IList<ParseError> errors;
        var fragments = parser.Parse(rd, out errors);

        fields = string.Empty;
        from = string.Empty;
        groupby = string.Empty;
        where = string.Empty;
        orderby = string.Empty;
        having = string.Empty;

        if (errors.Count > 0)
        {
            var retMessage = string.Empty;
            foreach (var error in errors)
            {
                retMessage += error.Identifier + " - " + error.Message + " - position: " + error.Offset + "; ";
            }

            return retMessage;
        }

        try
        {
            // Extract the query assuming it is a SelectStatement
            var query = ((fragments as TSqlScript).Batches[0].Statements[0] as SelectStatement).QueryExpression;

            // Constructs the From clause with the optional joins
            from = (query as QuerySpecification).FromClauses[0].GetString();

            // Extract the where clause
            where = (query as QuerySpecification).WhereClause.GetString();

            // Get the field list
            var fieldList = new List<string>();
            foreach (var f in (query as QuerySpecification).SelectElements)
                fieldList.Add((f as SelectColumn).GetString());
            fields = string.Join(", ", fieldList.ToArray());

            // Get The group by clause
            groupby = (query as QuerySpecification).GroupByClause.GetString();

            // Get the having clause of the query
            having = (query as QuerySpecification).HavingClause.GetString();

            // Get the order by clause
            orderby = ((fragments as TSqlScript).Batches[0].Statements[0] as SelectStatement).OrderByClause.GetString();
        }
        catch (Exception ex)
        {
            return ex.ToString();
        }

        return string.Empty;
    }
}


public static class Extension
{
    /// <summary>
    /// Get a string representing the SQL source fragment
    /// </summary>
    /// <param name="statement">The SQL Statement to get the string from, can be any derived class</param>
    /// <returns>The SQL that represents the object</returns>
    public static string GetString(this TSqlFragment statement)
    {
        string s = string.Empty;
        if (statement == null) return string.Empty;

        for (int i = statement.FirstTokenIndex; i <= statement.LastTokenIndex; i++)
        {
            s += statement.ScriptTokenStream[i].Text;
        }

        return s;
    }
}

And to use this class simply:

string fields, from, groupby, where, having, orderby;
SelectParser selectParser = new SelectParser(); 
var retMessage = selectParser.Parse("SELECT * FROM T where cat='A' Order by Id desc",
   out fields, out from, out groupby, out where, out having, out orderby);
like image 1
edecadoudal Avatar answered Sep 23 '22 23:09

edecadoudal