27

How can I validate sql scripts before executing them using .net 2.0 and c#?

If the sql is not valid I want to return error rows.

Ryan Gates
  • 4,501
  • 6
  • 50
  • 90
Vlad Omelyanchuk
  • 3,021
  • 7
  • 29
  • 35
  • Related (but without the C# angle) http://stackoverflow.com/questions/3084387/how-can-i-programmatically-check-parse-the-validity-of-a-tsql-statement – Martin Smith Jul 18 '10 at 16:03
  • possible duplicate of [SQL migration tool](http://stackoverflow.com/questions/3272894/sql-migration-tool) – p.campbell Jul 18 '10 at 16:06
  • @p.campbell then mark this one as a duplicate. – Sam Axe Feb 10 '16 at 22:56
  • @p.campbell sorry. Ignore me. I misread the comments. – Sam Axe Feb 15 '16 at 05:23
  • The problem with the SQL Server parsers is they parse code but they do not verify if the code is correct. If you use the parsers you will find that if you paste that SQL Code into a query window in SSMS you will see that code that will parse successfully will have numerous errors in the query window. – Wayne Hamberg Aug 14 '23 at 13:37

4 Answers4

54

If you are creating a tool that allows the user enter some sql code by hand and you want to validate the code entered using C# code before execution on sql server, you can create a method like this:

using Microsoft.Data.Schema.ScriptDom;
using Microsoft.Data.Schema.ScriptDom.Sql;

public class SqlParser
{
        public List<string> Parse(string sql)
        {
            TSql100Parser parser = new TSql100Parser(false);
            IScriptFragment fragment;
            IList<ParseError> errors;
            fragment = parser.Parse(new StringReader(sql), out errors);
            if (errors != null && errors.Count > 0)
            {
                List<string> errorList = new List<string>();
                foreach (var error in errors)
                {
                    errorList.Add(error.Message);
                }
                return errorList;
            }
            return null;
        }
}

As of 2018 and new database versions, this might be newer version:

using Microsoft.SqlServer.TransactSql.ScriptDom;

(download with npm: PM> Install-Package Microsoft.SqlServer.TransactSql.ScriptDom -Version 14.0.3811.1 )

public bool IsSQLQueryValid(string sql, out List<string> errors)
{
    errors = new List<string>();
    TSql140Parser parser = new TSql140Parser(false);
    TSqlFragment fragment;
    IList<ParseError> parseErrors;

    using (TextReader reader = new StringReader(sql))
    {
        fragment = parser.Parse(reader, out parseErrors);
        if (parseErrors != null && parseErrors.Count > 0)
        {
            errors = parseErrors.Select(e => e.Message).ToList();
            return false;
        }
    }
    return true;
}
Mostafa Elmoghazi
  • 2,124
  • 1
  • 21
  • 27
  • Outside of this sounding like something from a terminator movie, +1 since I haven't heard of this either. Here is a direct link to MSDN for future question viewers - https://msdn.microsoft.com/en-us/library/microsoft.data.schema.scriptdom.sql.tsqlparser%28v=vs.100%29.aspx – Tommy Feb 10 '16 at 22:59
  • 4
    There is a nuget package for whom can not find (or not likely to reference local) ScriptDom dll's: https://www.nuget.org/packages/Microsoft.SqlServer.TransactSql.ScriptDom/ – Onur Omer May 11 '16 at 14:41
20

SSMS has a way of doing this.

If you use the SQL Profiler you will see that it executes SET PARSEONLY ON, then the SQL and then SET PARSEONLY OFF and any errors are risen without compiling or executing the query.

SET PARSEONLY ON;
SELECT * FROM Table; --Query To Parse
SET PARSEONLY OFF; 

PARSEONLY

I have never tried this from c# but I see no reason why it should not work, it works from SSMS after all.

As Martin Smith points out in the comments you can use SET NOEXEC ON

MSDN says the following about both commands.

When SET NOEXEC is ON, SQL Server compiles each batch of Transact-SQL statements but does not execute them. When SET NOEXEC is OFF, all batches are executed after compilation.

When SET PARSEONLY is ON, SQL Server only parses the statement. When SET PARSEONLY is OFF, SQL Server compiles and executes the statement.

That indicates that NOEXEC will also compile the query where PARSEONLY will not. So NOEXEC may catch errors that PARSEONLY does not. The usage is the same.

SET NOEXEC ON;
SELECT * FROM Table; --Query To Parse
SET NOEXEC OFF; 

NOEXEC

Chris Diver
  • 19,362
  • 4
  • 47
  • 58
  • I think `NO EXEC` catches a few more things. http://stackoverflow.com/questions/3084387/how-can-i-programmatically-check-parse-the-validity-of-a-tsql-statement – Martin Smith Jul 18 '10 at 16:07
  • 1
    As per the post linked to by Martin Smith, you may also want to try SET FMTONLY ON. This will identify missing tables etc. – cbp Jun 27 '12 at 06:29
8

I know that the question was about .NET 2.0, but it may be interesting for someone. Validation of queries has slightly changed in the latest versions of Microsoft SQL Server. The namespace is Microsoft.SqlServer.TransactSql.ScriptDom instead of Microsoft.Data.Schema.ScriptDom.

Where to find this library?

Path to the library is %programfiles(x86)%\Microsoft SQL Server\120\SDK\Assemblies If you cannot find this library and Microsoft SQL Server is installed, try to change from 120 to 110 or 100 and use the corresponding parser (TSql110Parser or TSql100Parser respectively).

How to use?

I have two extensions: the first extension checks whether the input string is a valid SQL query and the second can be used to get errors from parsing.

using Microsoft.SqlServer.TransactSql.ScriptDom;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public static class SqlStringExtensions
{
    public static bool IsValidSql(this string str)
    {
        return !str.ValidateSql().Any();
    }

    public static IEnumerable<string> ValidateSql(this string str)
    {
        if (string.IsNullOrWhiteSpace(str))
        {
            return new[] { "SQL query should be non empty." };
        }
        var parser = new TSql120Parser(false);
        IList<ParseError> errors;
        using (var reader = new StringReader(str))
        {
            parser.Parse(reader, out errors);
        }
        return errors.Select(err => err.Message);
    }
}

Additionaly, I check that the input SQL query is not null or empty, because the parser thinks that empty string is perfectly valid (and I don't judge it).

How to test?

There are three NUnit tests which show how you can use this extensions.

using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

[TestFixture]
public class SqlStringExtensionsTests
{
    [Test]
    public void ValidateSql_InvalidSql_ReturnsErrorMessages()
    {
        // this example doesn't contain "," between the field names
        string invalidSql = "SELECT /*comment*/ " +
            "CustomerID AS ID CustomerNumber FROM Customers";
        IEnumerable<string> results = invalidSql.ValidateSql();
        Assert.AreNotEqual(0, results.Count());
    }

    [Test]
    public void IsValidSql_ValidSql_ReturnsTrue()
    {
        string validSql = "SELECT /*comment*/ " +
            "CustomerID AS ID, CustomerNumber FROM Customers";
        bool result = validSql.IsValidSql();
        Assert.AreEqual(true, result);
    }

    [Test]
    public void IsValidSql_InvalidSql_ReturnsFalse()
    {
        // this example doesn't contain "," between the field names
        string invalidSql = "SELECT /*comment*/ "+
            " CustomerID AS ID CustomerNumber FROM Customers";
        bool result = invalidSql.IsValidSql();
        Assert.AreEqual(false, result);
    }
}
Sergii Zhevzhyk
  • 4,074
  • 22
  • 28
-3

What does 'valid' SQL mean? The syntax or the results?

The only sure way to validate the syntax is the execute the SQL in SQL Server. Have you considered running the SQL in a Transaction and then do a rollback at the end?

Begin Transaction

--execute your code between the 'Begin Transaction' and the 'rollback' keywords.
...

--example
Insert into mytable(ID)Values(2)

...

Rollback

MSDN Documentation on rollback

Chuck Conway
  • 16,287
  • 11
  • 58
  • 101
  • How can i do it? Can you write example. Thanks. – Vlad Omelyanchuk Jul 18 '10 at 15:15
  • @Tamifist: use a TransactionScope and never call Transaction.Complete. There are plenty examples for TransactionScope here on stackoverflow (but keep in mind that you have to create your TransactionScope first and inside that the SqlConnection. – Jürgen Steinblock Jul 18 '10 at 15:43