Vai al contenuto

WDO MySQL - Utilizzo del pool

Una volta configurato il pool in config.json, usarlo negli handler è molto semplice.

Template base di handler

FUNCTION ProductosList()

   LOCAL oConn := WDO_Get( "mysql" )   // acquisisce uno slot dal 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 di hash
      oStmt:Free()
   CATCH oError
      oConn:Close()
      RETURN USendError( 500, oError:description )
   END

   oConn:Close()   // restituisce lo slot al pool (NON chiude il socket)

RETURN USendJson( aRows )

Chiama sempre Close()

oConn:Close() restituisce lo slot al pool affinché un altro thread possa usarlo. Se lo dimentichi, lo slot rimane bloccato finché il dispatcher di HIX non lo recupera al termine della richiesta (con un WARN nel log).

API del driver

Query di lettura

// Query semplice → oggetto resultset
oStmt := oConn:Query( "SELECT id, name FROM users WHERE active = 1" )

// Ottenere tutte le righe come array di hash (campo => valore)
aRows := oStmt:FetchAll( .T. )

// O come array di array (più veloce, accesso per indice)
aRows := oStmt:FetchAll( .F. )

// Riga per riga
DO WHILE ( hRow := oStmt:Fetch_Assoc() ) != NIL
   ? hRow[ "name" ]
ENDDO

oStmt:Free()   // liberare sempre il resultset

Query di scrittura

// Exec - per INSERT / UPDATE / DELETE senza resultset
oConn:Exec( "UPDATE users SET active = 0 WHERE id = " + hb_NToS( nId ) )

// Righe interessate
? oConn:Affected_Rows()

// Ultimo ID autogenerato (dopo INSERT con AUTO_INCREMENT)
nNewId := oConn:Last_Insert_Id()

Escaping manuale

Usa i Prepared Statements

Per valori esterni (form, JSON, query string), usa sempre i Prepared Statements. L'escaping manuale è solo per SQL costruito interamente in codice senza input esterno.

// Per query senza prepared statements con stringhe esterne
cSeguro := oConn:Escape( cValoreEsterno )
oConn:Exec( "INSERT INTO log (msg) VALUES ('" + cSeguro + "')" )

Informazioni sul server

? oConn:mysql_get_server_info()    // "8.0.33"
? oConn:mysql_get_client_info()    // "6.1.6" (versione della DLL client)
? oConn:VersionName()              // "MySQL 8.0.33"
? oConn:DllPath()                  // "c:/myapp/libmysql64.dll"
? oConn:DllSource()                // "exedir" / "override" / ecc.

Bilanciamento HIX ↔ Pool ↔ MySQL

I tre livelli devono essere dimensionati a cascata:

MySQL max_connections  >  HIX pool_size  ≥  WDO pool_size
Livello Parametro Raccomandazione
HIX workers hix.ini → server.pool_size 2× il pool WDO
WDO pool config.json → pool_size QPS_picco × latenza_media_sec
MySQL my.cnf → max_connections WDO pool + 30 (margine admin)

Formula di Little: se la tua app fa 500 req/s e ogni query impiega in media 10 ms, hai bisogno di 500 × 0.010 = 5 connessioni attive simultanee. Un pool di 10 dà un margine di 2×.

Verificare in produzione

oSrv:AddRouteGet( "health.db", "/health/db", {||
   USendJson( WDO_PoolStats( "mysql" ) )
   // restituisce: { "size" => 5, "busy" => 2, "free" => 3, "closed" => false }
} )

Se free rimane a 0 in modo sostenuto → aumenta pool_size. Se busy non supera mai 2 con un pool di 10 → sei sovradimensionato.

Pattern frequenti

Leggere un record per ID

FUNCTION UserGet()

   LOCAL nId   := Val( UParam( "id", "0" ) )
   LOCAL oConn, oStmt, hRow

   IF nId <= 0
      RETURN USendError( 400, "id non valido" )
   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, "Utente non trovato" )
   ENDIF

RETURN USendJson( hRow )

Paginazione

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

   // Per valori esterni, usa Prepare (vedi sezione 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 - liberazione garantita

Se l'handler è complesso e può uscire per più percorsi:

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()
      // ... elaborazione ...
   FINALLY
      IF oStmt != NIL ; oStmt:Free() ; ENDIF
      oConn:Close()   // viene sempre eseguito, successo o errore
   END

RETURN NIL