- VERSION 5.09.18

- feat(manager): Implementa recarga granular (Hot-Swap).
- Actualiza manager.html para solicitar la DB Key a recargar (ej: DB2).
- Se modifica Manager.bas para leer este parámetro y ejecutar el Hot-Swap de forma atómica solo en el pool de conexión especificado, lo cual mejora la eficiencia y la disponibilidad del servicio.
This commit is contained in:
2025-09-27 14:14:15 -06:00
parent 820fe9fc2b
commit 616013f0fb
9 changed files with 923 additions and 210 deletions

View File

@@ -105,17 +105,34 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
con = Connector.GetConnection(dbKey) ' ¡La conexión a la BD se obtiene aquí del pool de conexiones!
' <<<< ¡BUSY_CONNECTIONS YA SE CAPTURABA BIEN! >>>>
' Este bloque captura el número de conexiones actualmente ocupadas en el pool
' *después* de que esta petición ha obtenido la suya.
If Connector.IsInitialized Then
Dim poolStats As Map = Connector.GetPoolStats
If poolStats.ContainsKey("BusyConnections") Then
' <<<< ¡CORRECCIÓN CLAVE: Aseguramos que el valor sea Int! >>>>
poolBusyConnectionsForLog = poolStats.Get("BusyConnections").As(Int) ' Capturamos el valor.
Log($">>>>>>>>>> ${poolStats.Get("BusyConnections")} "$)
End If
End If
' <<<< ¡FIN DE CAPTURA! >>>>
Dim cachedStatsB4X As Map = Main.LatestPoolStats.Get(dbKey).As(Map)
If cachedStatsB4X.IsInitialized Then
' 1. Actualizar Busy Connections y Active Requests
cachedStatsB4X.Put("BusyConnections", poolBusyConnectionsForLog)
cachedStatsB4X.Put("HandlerActiveRequests", requestsBeforeDecrement)
' 2. Capturar TotalConnections y IdleConnections (ya disponibles en poolStats)
If poolStats.ContainsKey("TotalConnections") Then
cachedStatsB4X.Put("TotalConnections", poolStats.Get("TotalConnections"))
End If
If poolStats.ContainsKey("IdleConnections") Then
cachedStatsB4X.Put("IdleConnections", poolStats.Get("IdleConnections"))
End If
' 3. Re-escribir el mapa en el cache global (es Thread-Safe)
Main.LatestPoolStats.Put(dbKey, cachedStatsB4X)
End If
' Log("Metodo: " & method) ' Log de depuración para identificar el método de la petición.
@@ -167,9 +184,16 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
End If
Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL ---
Log(LastException) ' Registra la excepción completa en el log.
Main.LogServerError("ERROR", "DBHandlerB4X.Handle", LastException.Message, dbKey, q, req.RemoteAddress) ' <-- Nuevo Log
SendPlainTextError(resp, 500, LastException.Message) ' Envía un error 500 al cliente.
Dim errorMessage As String = LastException.Message
If errorMessage.Contains("ORA-01002") Or errorMessage.Contains("recuperación fuera de secuencia") Then
errorMessage = "SE USA EXECUTEQUERY EN LUGAR DE EXECUTECOMMAND: " & errorMessage
else If errorMessage.Contains("ORA-17003") Or errorMessage.Contains("Índice de columnas no válido") Then
errorMessage = "NUMERO DE PARAMETROS EQUIVOCADO: " & errorMessage
End If
Log(errorMessage) ' Registra la excepción completa en el log.
Main.LogServerError("ERROR", "DBHandlerB4X.Handle", errorMessage, dbKey, q, req.RemoteAddress) ' <-- Nuevo Log
SendPlainTextError(resp, 500, errorMessage) ' Envía un error 500 al cliente.
q = "error_in_b4x_handler" ' Aseguramos un valor para 'q' en caso de excepción.
End Try ' --- FIN: Bloque Try principal ---
@@ -344,11 +368,12 @@ Private Sub ExecuteBatch2(DB As String, con As SQL, in As InputStream, resp As S
Dim m As Map = ser.ConvertBytesToObject(Bit.InputStreamToBytes(in))
' Obtiene la lista de objetos DBCommand.
Dim commands As List = m.Get("commands")
Dim totalAffectedRows As Int = 0 ' Contador para acumular el total de filas afectadas.
' Prepara un objeto DBResult para la respuesta (aunque para batch no devuelve datos, solo confirmación).
Dim res As DBResult
res.Initialize
res.columns = CreateMap("AffectedRows (N/A)": 0) ' Columna simbólica.
res.columns = CreateMap("AffectedRows": 0) ' Columna simbólica.
res.Rows.Initialize
res.Tag = Null
@@ -390,10 +415,14 @@ Private Sub ExecuteBatch2(DB As String, con As SQL, in As InputStream, resp As S
End If
con.ExecNonQuery2(sqlCommand, validationResult.ParamsToExecute) ' Ejecuta el comando con la lista de parámetros validada.
totalAffectedRows = totalAffectedRows + 1 ' Acumulamos 1 por cada comando ejecutado sin error.
' <<< FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA DENTRO DEL BATCH >>>
Next
res.Rows.Add(Array As Object(0)) ' Añade una fila simbólica al resultado para indicar éxito.
res.Rows.Add(Array As Object(totalAffectedRows)) ' Añade una fila simbólica al resultado para indicar éxito.
con.TransactionSuccessful ' Si todos los comandos se ejecutaron sin error, confirma la transacción.
Catch
' Si cualquier comando falla, se captura el error.
@@ -427,7 +456,7 @@ End Sub
' Ejecuta un lote de comandos usando el protocolo V1.
Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String
Log($"ExecuteBatch ${DB}"$)
' Log($"ExecuteBatch ${DB}"$)
' Lee y descarta la versión del cliente.
Dim clientVersion As Float = ReadObject(in) 'ignore
' Lee cuántos comandos vienen en el lote.
@@ -441,18 +470,18 @@ Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As Se
Try
con.BeginTransaction
' Itera para procesar cada comando del lote.
Log(numberOfStatements)
' Log(numberOfStatements)
For i = 0 To numberOfStatements - 1
Log($"i: ${i}"$)
' Log($"i: ${i}"$)
' Lee el nombre del comando y la lista de parámetros usando el deserializador V1.
Dim queryName As String = ReadObject(in)
Dim params As List = ReadList(in)
Log(params)
' Log(params)
If numberOfStatements = 1 Then
singleQueryName = queryName 'Capturamos el nombre del query.
End If
Dim sqlCommand As String = Connector.GetCommand(DB, queryName)
Log(sqlCommand)
' Log(sqlCommand)
' <<< INICIO NUEVA VALIDACIÓN: VERIFICAR SI EL COMANDO EXISTE (V1) >>>
If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then
con.Rollback ' Deshace la transacción si un comando es inválido.
@@ -473,7 +502,7 @@ Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As Se
Return "error" ' Salida temprana si la validación falla.
End If
Log(validationResult.ParamsToExecute)
' Log(validationResult.ParamsToExecute)
Dim affectedCount As Int = 1 ' Asumimos éxito (1) ya que la llamada directa es la única que ejecuta el SQL sin fallar en runtime.
@@ -486,14 +515,14 @@ Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As Se
con.TransactionSuccessful ' Confirma la transacción.
Log("Transaction succesfull")
' Log("Transaction succesfull")
Dim out As OutputStream = cs.WrapOutputStream(resp.OutputStream, "gzip") ' Comprime la salida antes de enviarla.
' Escribe la respuesta usando el serializador V1.
WriteObject(Main.VERSION, out)
WriteObject("batch", out)
WriteInt(res.Length, out)
Log(affectedCounts.Size)
' Log(affectedCounts.Size)
For Each r As Int In affectedCounts
WriteInt(r, out)
Next