2010-05-31 6 views
6

Para Data Explorer Me gustaría agregar soporte para un separador de lotes.Cómo manejo con precisión un separador de lotes para SQL de C#

Está claro que necesito algún tipo de analizador aquí, mi esperanza es que este es un problema resuelto y yo sólo puede conectarlo. (Escribir un analizador de T-SQL completa es algo que no me gustaría hacer)

¿Qué componente/código de demostración podría lograr dividir este lote en sus 3 partes?

+0

Por Dios ... éste cuesta 12k http: //www.temporal-wave .com/index.php? option = com_content & view = article & id = 48: sqlparser & catid = 35: products & Itemid = 53 –

+0

nota: Microsoft envía un TSql100Parser pero ... por mi vida no puedo encontrar la forma de proporcionarle un lote identificador ... Estoy tendiendo a hacer una solución trivial aunque muy restringida. –

Respuesta

2

No es frecuente que digo esto, pero este es un caso en el que sin duda me defiendo la flexión de la entrada del usuario para ajustarse a las normas de ordenador en lugar de tratar de resolver el problema de tener un ordenador entiende masivamente variada de usuario de entrada

imponer una regla simple de: La palabra "go" debe aparecer en su propia línea con el fin de ser interpretado como una orden para proceder

Si los usuarios no pueden cumplir con una regla como esa, ¿deberían participar realmente en la tarea mucho más compleja de escribir consultas SQL?

+0

yerp esto es lo que hice –

+0

de https://docs.microsoft.com/en-us/sql/t -sql/language-elements/sql-server-utilities-statements-go, "Una instrucción Transact-SQL no puede ocupar la misma línea que un comando GO. Sin embargo, la línea puede contener comentarios". Entonces esto ya es (casi) verdad. El caso opuesto es lo que me preocupa: '' 'seleccionar '\ n ir \ n';' '' (no se pueden poner nuevas líneas en los comentarios afaik). –

0

No conozco una solución existente para esto (aunque estoy de acuerdo que probablemente haya uno por ahí). Solo quiero señalar que probablemente no necesite escribir un analizador T-SQL completo: todo lo que necesita encontrar es la palabra "ir" fuera de las comillas. Es decir, busque <word boundary>GO<word boundary> y realice un seguimiento de las cotizaciones de apertura y cierre en el camino. Si encuentra una coincidencia y no es después de una cita de apertura (antes de su cierre de coincidencia), entonces es el separador de lotes. Debería ser bastante fácil hacer esto sin escribir nada que llamarías un "analizador" apropiado.

+0

yerp esto podría funcionar, pero se pone complicado, por ejemplo: seleccione 1 como ir es válido sql it estudio mgmt –

0

En su caso anterior, ¿no puede simplemente dividir en líneas nuevas, probar cada línea si comienza con la palabra "ir", y luego dividir la secuencia de comandos en eso?

Después de volver a leer esto un par de veces, este es un problema realmente desagradable. Mirando la primera línea en su secuencia de comandos, en realidad hay sin delimitadores de comando (punto y coma o líneas nuevas). No creo que tengas más remedio que analizar todo el asunto.

Pero, en algún lugar de la línea esto tiene que ser analizado de todos modos, ¿no? Quizás haya algo que pueda hacer dentro o usar el analizador existente para esto. Dependiendo de la cantidad de acceso que tiene a ella, usted podría:

  • Cambiar el código para el analizador existente para entender el comando "ir" a firmar y devolver lo que tiene, a continuación, ejecute de nuevo.

  • Tome una copia del código de análisis existente, adáptelo para entender el comando "ir", quite la parte del intérprete y luego úselo para dividir los bloques y alimentar al analizador real.

2

Estaba buscando una solución para el mismo problema, pero no encontré ninguna adecuada (el uso de SMO no era aceptable en mi caso). Entonces, tuve que escribir mi propio analizador.Aquí está:

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(); 
} 

Maneja correctamente "IR" dentro de cadenas, identificadores y comentarios. Quizás no sea el ideal, pero se probó con éxito en cientos de varios scripts .sql.

Y entonces, por ejemplo:

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(); 
} 

espero que ayude a alguien.

+0

¿Esto maneja los identificadores entre comillas/cadenas de comillas dobles? –

+0

@ArinTaylor, para cadenas de comillas dobles - sí (avíseme si no se trata de algo que sea sintácticamente correcto), para identificadores entre comillas - Creo que no. No manejará casos como 'create table 'GO" '. Siempre uso identificadores de estilo '[name]' y nunca '' name "' -style, y el código no está diseñado para eso. Pero también se puede revisar fácilmente para admitir identificadores entrecomillados. Simplemente agregue '" 'a través del código de manera similar a como se hace con' '' usado para cadenas. –

0

Puede cambiar la base de datos actual para un SqlConnection abierto con mucha facilidad:

connection.ChangeDatabase("YourDB"); 

Un ejemplo:

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); 
    } 
} 
Cuestiones relacionadas