Как мне точно обработать пакетный разделитель для SQL из C # - PullRequest
6 голосов
/ 31 мая 2010

Для Обозреватель данных Я хотел бы добавить поддержку разделителя пакетов.

Так, например, если пользователи вводят:

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

Я хотел бы вернуть три набора результатов.

Понятно, что здесь нужен какой-то парсер, я надеюсь, что это решенная проблема, и я могу просто подключить ее. (Написание полноценного парсера T-SQL я бы не хотел)

Какой компонент / демонстрационный код может разделить этот пакет на 3 части?

Ответы [ 5 ]

2 голосов
/ 26 июня 2013

Я искал решение той же проблемы, но не нашел подходящего (использование 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();
}

Надеюсь, это кому-нибудь поможет.

2 голосов
/ 09 июня 2010

Я не часто говорю это, но это тот случай, когда я определенно рекомендую сгибать пользовательский ввод, чтобы он соответствовал компьютерным правилам, а не пытаться решить проблему, когда компьютер понимает сильно различающиеся пользовательские данные

наложить простое правило: Слово «идти» должно появляться в отдельной строке, чтобы быть интерпретированным как команда для продолжения

Если ваши пользователи не могут придерживаться такого правила, должны ли они действительно заниматься гораздо более сложной задачей написания SQL-запросов?

0 голосов
/ 11 мая 2014

Вы можете легко изменить текущую базу данных для открытого 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);
    }
}
0 голосов
/ 31 мая 2010

В приведенном выше случае, вы не можете просто разбить на новые строки, проверить каждую строку, если она начинается со слова "go", а затем разбить скрипт на это?

Перечитав это пару раз, это действительно ужасная проблема. Глядя на первую строку в вашем скрипте, на самом деле нет разделителей команд (точки с запятой или переводы строки). Я не думаю, что у вас есть большой выбор, кроме как на самом деле разобрать все это.

Но где-то вдоль линии это все равно нужно анализировать, верно? Возможно, вы можете что-то сделать внутри или использовать для этого существующий парсер. В зависимости от того, сколько у вас есть к нему доступа, вы можете:

  • Измените код для существующего синтаксического анализатора, чтобы понять команду "go", которую нужно выполнить, и вернуть то, что у нее есть, а затем снова запустить.

  • Взять копию существующего кода синтаксического анализа, адаптировать ее для понимания команды "go", вырезать часть интерпретатора, а затем просто использовать ее для разбиения блоков и передачи в настоящий анализатор?

0 голосов
/ 31 мая 2010

Я не знаю о существующем решении этого (хотя я согласен, что, вероятно, есть одно там). Я просто хочу отметить, что вам, вероятно, не нужно писать полный синтаксический анализатор T-SQL: все, что вам действительно нужно найти, - это слово «идти» вне кавычек. То есть ищите <word boundary>GO<word boundary> и следите за открытием и закрытием кавычек по пути. Если вы найдете совпадение, и оно не после открывающей кавычки (до ее совпадения закрывающей), то это разделитель пакетов. Это должно быть довольно легко сделать без написания того, что вы бы назвали правильным «парсером».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...