fix(query-runner): "conx_db_id inválido" al correr un .sql, y el parser que se comía el script
Dos bugs encadenados. El segundo es mucho peor que el que se veía.
1. EL ERROR VISIBLE. RunBatchQuery leía conx_db_id del formulario y lo guardaba
bien, y después lo pisaba con cero. El struct del body solo tenía etiquetas
json, pero en multipart Fiber mapea por la etiqueta form: no encontraba
conx_db_id, dejaba el campo en cero — y BodyParser NO devuelve error cuando
no mapea nada, así que la rama se ejecutaba igual y sobreescribía el valor
bueno. De ahí el "conx_db_id inválido" con la conexión bien elegida en
pantalla. Reproducido con un test antes de tocar nada.
2. EL QUE NO SE VEÍA. El script se partía con split(';') y se descartaba todo
trozo que empezara con "--". En un script documentado, donde cada sentencia
va debajo de su encabezado en comentarios, eso se saltaba casi todo el
archivo en silencio. Con el .sql que lo destapó: de 10 sentencias reales
pasaban 3 — y una de esas tres era un fragmento corrupto, porque un punto y
coma dentro de un comentario había partido una sentencia al medio. Se
perdían los dos INSERT de alta y el ALTER TABLE, que era el único cambio
estructural del script.
Es decir: arreglar solo el bug 1 habría sido peor que dejarlo. El usuario
habría leído "3 consultas ejecutadas" y se habría ido tranquilo con la
migración a medio aplicar.
DividirSentenciasSQL parte respetando dónde el punto y coma no separa nada:
comentarios de línea (-- y #), de bloque, y cadenas con ' " ` incluyendo
escapes con barra y comillas duplicadas. Y en vez de descartar lo que empieza
con "--", pregunta si al trozo le queda SQL después de sacarle los comentarios
— un "--" adentro de una cadena ya no oculta la sentencia.
Mismo criterio en los dos lados: el front arma las sentencias y el backend
divide los archivos subidos, y las dos implementaciones dan las mismas 10 sobre
el archivo real.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b7c52f174a
commit
93f5fdec7a
@@ -1591,12 +1591,67 @@ document.addEventListener('alpine:init', () => {
|
||||
},
|
||||
|
||||
// ── Batch mode ──────────────────────────────────────────────
|
||||
// Parte el script por punto y coma, respetando dónde ese punto y coma
|
||||
// NO separa nada: dentro de un comentario o de una cadena.
|
||||
//
|
||||
// La versión vieja hacía split(';') y además descartaba todo trozo que
|
||||
// empezara con '--'. En un script documentado, donde cada sentencia va
|
||||
// debajo de su encabezado, eso se saltaba casi todo el archivo sin
|
||||
// decir una palabra — y un punto y coma dentro de un comentario partía
|
||||
// una sentencia al medio y mandaba el pedazo corrupto a ejecutar.
|
||||
extractSQLStatements(text) {
|
||||
// Split by semicolons, trim whitespace, remove empty
|
||||
return text
|
||||
.split(';')
|
||||
.map(s => s.trim())
|
||||
.filter(s => s.length > 0 && !s.toUpperCase().startsWith('--') && !s.startsWith('#'));
|
||||
const out = [];
|
||||
let actual = '';
|
||||
const agregar = () => {
|
||||
const s = actual.trim();
|
||||
if (this.tieneSQLReal(s)) out.push(s);
|
||||
actual = '';
|
||||
};
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const c = text[i], sig = text[i + 1];
|
||||
|
||||
if ((c === '-' && sig === '-') || c === '#') {
|
||||
while (i < text.length && text[i] !== '\n') actual += text[i++];
|
||||
if (i < text.length) actual += '\n';
|
||||
continue;
|
||||
}
|
||||
if (c === '/' && sig === '*') {
|
||||
actual += '/*'; i += 2;
|
||||
while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) actual += text[i++];
|
||||
actual += '*/'; i++;
|
||||
continue;
|
||||
}
|
||||
if (c === "'" || c === '"' || c === '`') {
|
||||
actual += c; i++;
|
||||
while (i < text.length) {
|
||||
if (text[i] === '\\') { actual += text[i] + (text[i + 1] || ''); i += 2; continue; }
|
||||
if (text[i] === c) {
|
||||
actual += text[i];
|
||||
// Comilla duplicada: es literal, no cierra la cadena.
|
||||
if (text[i + 1] === c) { actual += text[i + 1]; i += 2; continue; }
|
||||
break;
|
||||
}
|
||||
actual += text[i++];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (c === ';') { agregar(); continue; }
|
||||
actual += c;
|
||||
}
|
||||
agregar();
|
||||
return out;
|
||||
},
|
||||
|
||||
// ¿Al trozo le queda algo ejecutable una vez sacados los comentarios?
|
||||
// Es lo que distingue "sentencia con encabezado" de "comentario suelto".
|
||||
tieneSQLReal(s) {
|
||||
const sinBloques = s.replace(/\/\*[\s\S]*?\*\//g, ' ');
|
||||
const sinLinea = sinBloques
|
||||
.split('\n')
|
||||
.map(l => l.replace(/^\s*(--|#).*$/, ''))
|
||||
.join('\n');
|
||||
return sinLinea.trim().length > 0;
|
||||
},
|
||||
|
||||
uploadSQLFile(event) {
|
||||
@@ -1628,7 +1683,7 @@ document.addEventListener('alpine:init', () => {
|
||||
this.activeTab = 'results';
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('conx_db_id', this.selectedConxId);
|
||||
formData.append('conx_db_id', parseInt(this.selectedConxId) || 0);
|
||||
formData.append('database', this.selectedDb || '');
|
||||
sqls.forEach((sql, i) => formData.append('sqls[]', sql));
|
||||
const res = await axios.post('/app/query-runner/run-batch', formData, {
|
||||
|
||||
Reference in New Issue
Block a user