WDO MySQL — Uso del pool¶
Una vez configurado el pool en config.json, usarlo en los handlers es muy sencillo.
Plantilla básica de handler¶
FUNCTION ProductosList()
LOCAL oConn := WDO_Get( "mysql" ) // adquiere un slot del pool
LOCAL oStmt, aRows
IF oConn == NIL
RETURN USendError( 503, "DB unavailable" )
ENDIF
TRY
oStmt := oConn:Query( "SELECT id, nombre, precio FROM productos ORDER BY id" )
aRows := oStmt:FetchAll( .T. ) // .T. = array de hashes
oStmt:Free()
CATCH oError
oConn:Close()
RETURN USendError( 500, oError:description )
END
oConn:Close() // devuelve el slot al pool (NO cierra el socket)
RETURN USendJson( aRows )
Siempre llama a Close()
oConn:Close() devuelve el slot al pool para que otro hilo pueda usarlo. Si lo olvidas, el slot queda bloqueado hasta que el dispatcher de HIX lo reclame al final del request (con un WARN en el log).
API del driver¶
Queries de lectura¶
// Query simple → objeto resultset
oStmt := oConn:Query( "SELECT id, name FROM users WHERE active = 1" )
// Obtener todas las filas como array de hashes (campo => valor)
aRows := oStmt:FetchAll( .T. )
// O como array de arrays (más rápido, acceso por índice)
aRows := oStmt:FetchAll( .F. )
// Fila a fila
DO WHILE ( hRow := oStmt:Fetch_Assoc() ) != NIL
? hRow[ "name" ]
ENDDO
oStmt:Free() // siempre liberar el resultset
Queries de escritura¶
// Exec — para INSERT / UPDATE / DELETE sin resultset
oConn:Exec( "UPDATE users SET active = 0 WHERE id = " + hb_NToS( nId ) )
// Filas afectadas
? oConn:Affected_Rows()
// Último ID autogenerado (tras INSERT con AUTO_INCREMENT)
nNewId := oConn:Last_Insert_Id()
Escaping manual¶
Usa Prepared Statements
Para valores externos (form, JSON, query string), usa siempre Prepared Statements. El escaping manual es solo para SQL construido íntegramente en código sin input externo.
// Para queries sin prepared statements con strings externos
cSeguro := oConn:Escape( cValorExterno )
oConn:Exec( "INSERT INTO log (msg) VALUES ('" + cSeguro + "')" )
Información del servidor¶
? oConn:mysql_get_server_info() // "8.0.33"
? oConn:mysql_get_client_info() // "6.1.6" (versión de la DLL cliente)
? oConn:VersionName() // "MySQL 8.0.33"
? oConn:DllPath() // "c:/myapp/libmysql64.dll"
? oConn:DllSource() // "exedir" / "override" / etc.
Balanceo HIX ↔ Pool ↔ MySQL¶
Los tres niveles deben estar dimensionados en cascada:
| Nivel | Parámetro | Recomendación |
|---|---|---|
| HIX workers | hix.ini → server.pool_size |
2× el pool WDO |
| WDO pool | config.json → pool_size |
QPS_pico × latencia_media_seg |
| MySQL | my.cnf → max_connections |
WDO pool + 30 (margen admin) |
Fórmula de Little: si tu app hace 500 req/s y cada query tarda 10 ms de media, necesitas 500 × 0.010 = 5 conexiones activas simultáneas. Un pool de 10 da un margen de 2×.
Verificar en producción¶
oSrv:AddRouteGet( "health.db", "/health/db", {||
USendJson( WDO_PoolStats( "mysql" ) )
// retorna: { "size" => 5, "busy" => 2, "free" => 3, "closed" => false }
} )
Si free se queda a 0 sostenidamente → sube pool_size. Si busy nunca pasa de 2 con pool de 10 → estás sobredimensionado.
Patrones frecuentes¶
Leer un registro por ID¶
FUNCTION UserGet()
LOCAL nId := Val( UParam( "id", "0" ) )
LOCAL oConn, oStmt, hRow
IF nId <= 0
RETURN USendError( 400, "id invalido" )
ENDIF
oConn := WDO_Get( "mysql" )
IF oConn == NIL ; RETURN USendError( 503, "DB unavailable" ) ; ENDIF
oStmt := oConn:Query( "SELECT * FROM users WHERE id = " + hb_NToS( nId ) )
hRow := oStmt:Fetch_Assoc()
oStmt:Free()
oConn:Close()
IF hRow == NIL
RETURN USendError( 404, "Usuario no encontrado" )
ENDIF
RETURN USendJson( hRow )
Paginación¶
FUNCTION UserList()
LOCAL nPage := Max( 1, Val( UGet( "page", "1" ) ) )
LOCAL nLimit := Min( 100, Val( UGet( "limit", "20" ) ) )
LOCAL nOffset := ( nPage - 1 ) * nLimit
LOCAL oConn, oStmt, aRows
oConn := WDO_Get( "mysql" )
IF oConn == NIL ; RETURN USendError( 503, "DB unavailable" ) ; ENDIF
// Para valores externos, usa Prepare (ver sección Prepared Statements)
oStmt := oConn:Query( "SELECT id, name FROM users ORDER BY id " + ;
"LIMIT " + hb_NToS( nLimit ) + ;
" OFFSET " + hb_NToS( nOffset ) )
aRows := oStmt:FetchAll( .T. )
oStmt:Free()
oConn:Close()
RETURN USendJson( { "page" => nPage, "limit" => nLimit, "data" => aRows } )
FINALLY — liberación garantizada¶
Si el handler es complejo y puede salir por varios caminos:
FUNCTION ComplexHandler()
LOCAL oConn := WDO_Get( "mysql" )
LOCAL oStmt := NIL
IF oConn == NIL ; RETURN USendError( 503, "DB unavailable" ) ; ENDIF
TRY
oStmt := oConn:Prepare( "SELECT id FROM users WHERE age > ?" )
oStmt:BindParam( 1, 18, "i" )
oStmt:Execute()
// ... proceso ...
FINALLY
IF oStmt != NIL ; oStmt:Free() ; ENDIF
oConn:Close() // siempre se ejecuta, éxito o error
END
RETURN NIL
Siguiente paso
Para insertar o actualizar con valores externos de forma segura, usa Prepared Statements.