代码之家  ›  专栏  ›  技术社区  ›  Sam Saffron James Allen

如何准确处理C中的SQL批处理分隔符#

  •  6
  • Sam Saffron James Allen  · 技术社区  · 14 年前

    为了 Data Explorer 我想添加对批处理分隔符的支持。

    例如,如果用户键入:

     
    select 'GO' go select 1 as go 
    Go 
    select 100
    

    我想返回三个结果集。

    很明显,我需要某种类型的解析器,我希望这是一个已解决的问题,我可以直接插入它。(我不想写完整的T-SQL解析器)

    什么组件/演示代码可以将这批代码分成3个部分?

    5 回复  |  直到 10 年前
        1
  •  2
  •   matt    14 年前

    我不是经常这么说,但我绝对主张弯曲用户输入以符合计算机规则,而不是试图解决让计算机理解大量变化的用户输入的问题。

    实施一个简单的规则: “go”一词必须出现在它自己的行中,以便被解释为继续执行的命令。

    如果您的用户不能遵守这样的规则,那么他们真的应该参与编写SQL查询这项更复杂的任务吗?

        2
  •  2
  •   i-one    11 年前

    我在寻找同样问题的解决方案,但没有找到合适的解决方案(在我的案例中,使用SMO是不可接受的)。所以,我必须编写自己的解析器。这里是:

    static IEnumerable<string> ParseSqlBatch(Stream s)
    {
        if (s == null)
            throw new ArgumentNullException();
    
        StringBuilder sbSqlStatement = new StringBuilder();
        Stack<string> state = new Stack<string>();
        StreamReader sr = new StreamReader(s);
    
        //initially search for "GO" or open tag of strings ('), comments (--, /*) or identifiers ([)
        string pattern = @"(?>(?<=^\s*)go(?=\s*(--.*)?$)|''(?!')|(?<!')'|(?<!\[)\[|--(?=.*)?|/\*)";
        //if open tag found search for close tag, then continue search
        string patternCloseString = @"(?>''|'(?!'))";
        string patternCloseIdentifier = @"(?>\]\]|\](?!\]))";
        string patternComments = @"(?>\*/|/\*)";
    
        Regex rx = new Regex(pattern, RegexOptions.IgnoreCase);
    
        while (!sr.EndOfStream)
        {
            string line = sr.ReadLine();
    
            int ix = 0;
            bool bBreak = false;
            while (ix < line.Length && !bBreak)
            {
                Match m = rx.Match(line, ix);
    
                if (!m.Success)
                {
                    sbSqlStatement.Append(line.Substring(ix));
                    break;
                }
    
                int ix2 = m.Index;
                string word = m.Value;
    
                sbSqlStatement.Append(line.Substring(ix, ix2 - ix));
    
                if (state.Count == 0)
                {
                    if (string.Compare(word, "GO", true) == 0)
                    {
                        if (sbSqlStatement.Length > 0)
                        {
                            yield return sbSqlStatement.ToString();
                            sbSqlStatement = new StringBuilder();
                            break;
                        }
                    }
                    else
                    {
                        switch (word)
                        {
                            case "'":
                                rx = new Regex(patternCloseString);
                                break;
                            case "[":
                                rx = new Regex(patternCloseIdentifier);
                                break;
                            case "/*":
                                rx = new Regex(patternComments);
                                break;
                            case "--":
                                sbSqlStatement.Append(line.Substring(ix2));
                                bBreak = true;
                                continue;
                        }
    
                        if (word != "''")
                            state.Push(word);
                    }
                }
                else
                {
                    string st = state.Peek();
    
                    switch (st)
                    {
                        case "'":
                            if (st == word)
                                state.Pop();
                            break;
                        case "[":
                            if (word == "]")
                                state.Pop();
                            break;
                        case "/*":
                            if (word == "*/")
                                state.Pop();
                            else if (word == "/*")
                                state.Push(word);
                            break;
                    }
    
                    if (state.Count == 0)
                        rx = new Regex(pattern, RegexOptions.IgnoreCase);
                }
    
                ix = ix2 + word.Length;
                sbSqlStatement.Append(word);
            }
    
            sbSqlStatement.AppendLine();
        }
    
        if (sbSqlStatement.Length > 0)
            yield return sbSqlStatement.ToString();
    }
    

    它正确地处理字符串、标识符和注释中的“go”。也许不太理想,但是已经成功地测试了数百个不同的.sql脚本。

    然后,例如:

    using (FileStream fs = new FileStream("SampleBatch.sql", FileMode.Open, FileAccess.Read))
    {
        foreach (string statement in ParseSqlBatch(fs))
        {
            //execute statement here, or do something with it
        }
    
        fs.Close();
    }
    

    我希望它能帮助别人。

        3
  •  0
  •   EMP    14 年前

    我不知道现有的解决方案(尽管我同意可能有一个)。我只是想指出,您可能不需要编写完整的T-SQL解析器:您真正需要找到的只是引号之外的单词“go”。也就是说,寻找 <word boundary>GO<word boundary> 一路跟踪开盘价和收盘价。如果你找到了一个匹配项,并且它不在开盘价之后(在它匹配的收盘价之前),那么它就是批次分隔符。在不编写任何称为适当的“解析器”的东西的情况下,这样做应该相当容易。

        4
  •  0
  •   lc.    14 年前

    在上面的例子中,您不能只在换行符上拆分,测试每一行是否以“go”开头,然后在上面拆分脚本吗?

    在反复读了几遍之后,这是一个非常难看的问题。看看脚本的第一行,实际上 没有命令分隔符 (分号或换行符)。我认为你别无选择,只能真正分析整个过程。

    但是,在这条线上的某个地方,无论如何,这必须被分析,对吗?也许你可以在里面做点什么或者用 现有的 解析器。根据您对它的访问量,您可以:

    • 更改现有解析器的代码,以了解要执行的“go”命令并返回它拥有的内容,然后再次运行。

    • 取一份现有解析代码的副本,调整它以理解“go”命令,去掉解释器部分,然后只使用它来拆分块并将其提供给真正的解析器?

        5
  •  0
  •   J_hajian_nzd    10 年前

    您可以很容易地为打开的sqlconnection更改当前数据库:

     connection.ChangeDatabase("YourDB");
    

    一个例子:

    private static void ConctDatabase(string connectionString)
    {
        using (SqlConnection conn = new SqlConnection(connectionString))
        {
            conn.Open();
            MessageBox.Show("Database: {0}", conn.Database);
            conn.ChangeDatabase("Northwind");
            MessageBox.Show("Database: {0}", conn.Database);
        }
    }