package services import "strings" // DividirSentenciasSQL parte un script en sentencias por punto y coma, // respetando dónde ese punto y coma NO es un separador. // // Partir con strings.Split(texto, ";") parece que funciona hasta que el script // trae un punto y coma dentro de un comentario o de una cadena — ahí corta una // sentencia al medio y produce SQL corrupto que igual se manda a ejecutar. // // Las sentencias se devuelven con sus comentarios: MySQL los acepta, y sirven // para que el usuario reconozca en los resultados qué se ejecutó. func DividirSentenciasSQL(texto string) []string { var sentencias []string var actual strings.Builder r := []rune(texto) agregar := func() { if s := strings.TrimSpace(actual.String()); TieneSQLReal(s) { sentencias = append(sentencias, s) } actual.Reset() } for i := 0; i < len(r); i++ { c := r[i] var sig rune if i+1 < len(r) { sig = r[i+1] } switch { // Comentario de línea: -- … o # … case (c == '-' && sig == '-') || c == '#': for i < len(r) && r[i] != '\n' { actual.WriteRune(r[i]) i++ } if i < len(r) { actual.WriteRune('\n') } // Comentario de bloque: /* … */ case c == '/' && sig == '*': actual.WriteString("/*") i += 2 for i < len(r) && !(r[i] == '*' && i+1 < len(r) && r[i+1] == '/') { actual.WriteRune(r[i]) i++ } actual.WriteString("*/") i++ // Cadenas y nombres citados: ' " ` case c == '\'' || c == '"' || c == '`': comilla := c actual.WriteRune(c) i++ for i < len(r) { if r[i] == '\\' && i+1 < len(r) { // Escape con barra: se copia el par entero. actual.WriteRune(r[i]) actual.WriteRune(r[i+1]) i += 2 continue } if r[i] == comilla { actual.WriteRune(r[i]) // Comilla duplicada ('') es una comilla literal, no el cierre. if i+1 < len(r) && r[i+1] == comilla { actual.WriteRune(r[i+1]) i += 2 continue } break } actual.WriteRune(r[i]) i++ } case c == ';': agregar() default: actual.WriteRune(c) } } agregar() return sentencias } // TieneSQLReal dice si al trozo le queda algo ejecutable una vez sacados los // comentarios. Es lo que distingue "esta sentencia viene precedida por un // encabezado en comentarios" de "esto es solo un comentario suelto". // // El filtro viejo descartaba todo trozo que EMPEZARA con "--", y como en un // script bien documentado casi todas las sentencias vienen debajo de su // comentario, se saltaba la mayoría del archivo sin decir nada. func TieneSQLReal(s string) bool { limpio := quitarComentarios(s) return strings.TrimSpace(limpio) != "" } func quitarComentarios(s string) string { var out strings.Builder r := []rune(s) for i := 0; i < len(r); i++ { c := r[i] var sig rune if i+1 < len(r) { sig = r[i+1] } switch { case (c == '-' && sig == '-') || c == '#': for i < len(r) && r[i] != '\n' { i++ } out.WriteRune('\n') case c == '/' && sig == '*': i += 2 for i < len(r) && !(r[i] == '*' && i+1 < len(r) && r[i+1] == '/') { i++ } i++ out.WriteRune(' ') case c == '\'' || c == '"' || c == '`': // Una cadena cuenta como contenido real; se copia entera para no // confundir un "--" que viva adentro con un comentario. comilla := c out.WriteRune(c) i++ for i < len(r) { if r[i] == '\\' && i+1 < len(r) { out.WriteRune(r[i]) out.WriteRune(r[i+1]) i += 2 continue } if r[i] == comilla { out.WriteRune(r[i]) break } out.WriteRune(r[i]) i++ } default: out.WriteRune(c) } } return out.String() }