open.mp forum
[Plugin] mysql_samp 1.4.0 — notas de versão para SA-MP e open.mp - Printable Version

+ open.mp forum (https://forum.open.mp)
-- Forum: SA-MP (https://forum.open.mp/forumdisplay.php?fid=3)
--- Forum: Releases (https://forum.open.mp/forumdisplay.php?fid=13)
---- Forum: Plugins (https://forum.open.mp/forumdisplay.php?fid=32)
---- Thread: [Plugin] mysql_samp 1.4.0 — notas de versão para SA-MP e open.mp (/showthread.php?tid=6558)



mysql_samp 1.4.0 — notas de versão para SA-MP e open.mp - NullSablex - 2026-10-11

🦀 mysql_samp 1.4.0

MySQL para SA-MP e open.mp — o mesmo binário nos dois
MySQL for SA-MP and open.mp — the same binary on both


Download · GitHub · Documentação · CHANGELOG

Quote:🇧🇷 Plugin MySQL em Rust, 79 natives, sem dependência para instalar. Esta versão, em três linhas:
  • Uma mudança de comportamento: o OnQueryError entregava seus cinco argumentos na ordem invertida, desde a 1.0.0. Se você trata esse forward, confira o handler.
  • Uma correção de estabilidade: uma rajada de queries num único tick podia abortar o servidor e travar a fila de callbacks permanentemente.
  • Uma novidade: MYSQL_SYNC faz uma chamada específica esperar o banco, sem callback.

🇧🇷 Nenhuma native foi removida ou renomeada e nenhuma assinatura mudou: gamemode de 1.3.0 compila como está.

🇬🇧 MySQL plugin in Rust, 79 natives, nothing to install. One behaviour change (OnQueryError delivered its five arguments reversed since 1.0.0 — check your handler), one stability fix (a burst of queries in one tick could abort the server and stall the callback queue for good), one new feature (MYSQL_SYNC makes a single call wait on the database, with no callback). Nothing removed, renamed or re-signed: 1.3.0 gamemodes compile unchanged.



⚠️ Mudança de comportamento: OnQueryError
Behaviour change: OnQueryError

🇧🇷 O forward é declarado assim:
🇬🇧 The forward is declared as:

Code:
forward OnQueryError(errorid, error[], callback[], query[], connId);

🇧🇷 Até a 1.3.0 o gamemode recebia os campos ao contrário: o id da conexão chegava como errorid, o texto da query como error, a mensagem de erro como query e o código do MySQL como connId.

🇧🇷 A causa estava no despacho. O exec_public! empilha os argumentos ao contrário — é a convenção da pilha do AMX — logo a ordem escrita na chamada já é a ordem que o public recebe. O código os listava de trás para frente, como se tivesse de inverter à mão.

🇧🇷 A inversão era consistente, então um handler podia parecer funcionar há bastante tempo lendo o id da conexão como código de erro. Presente desde a 1.0.0 — se você trata esse forward, é o primeiro lugar a olhar depois de atualizar.

🇬🇧 Up to 1.3.0 the fields arrived reversed: connection id as errorid, query text as error, error message as query, MySQL code as connId. exec_public! pushes arguments in reverse (the AMX stack convention), so the order written at the call site is already the order the public receives; the code listed them backwards as if it had to invert them by hand. Consistent enough that a handler could appear to work for a long time. Present since 1.0.0.

🇧🇷 E ao atualizar / 🇬🇧 And when updating
  • 🇧🇷 Troque o binário e os includes. O release traz .so, .dll, mysql_samp.inc e mysql_samp_omp.inc. Sem o include novo não existem MYSQL_SYNC nem as natives novas — e include velho com binário novo é o erro mais comum numa atualização. O MYSQL_SAMP_VERSION do include serve justamente para comparar na subida / 🇬🇧 replace the binary and the includes; MYSQL_SAMP_VERSION exists to catch a stale one
  • 🇧🇷 Binário de 32 bits, como o servidor. SA-MP em plugins/; open.mp nativo em components/, sem mexer no config.json / 🇬🇧 32-bit, like the server
  • 🇧🇷 Fora o handler do OnQueryError, nada mais a fazer / 🇬🇧 apart from the OnQueryError handler, nothing else to do



🐛 Correções
Fixed

🇧🇷 Uma rajada de queries matava o servidor — e podia travar a fila para sempre
🇧🇷 Submeter 2000 queries num tick abortava o processo com memory allocation of 4194304 bytes failed. A causa é específica desta arquitetura: uma thread por query, com a pilha padrão de 8 MiB, dentro de um servidor 32 bits. Algumas centenas de workers vivos esgotam o espaço de endereçamento, e a próxima alocação que falha leva o processo junto. Foi achado por teste de força bruta, não por relato.

🇧🇷 Eram dois problemas, os dois corrigidos:
  • Pilha de 256 KiB por worker — um worker abre conexão, roda um statement e monta um cache; não recursa. O teto medido saiu de cerca de 1700 para mais de 9000 workers
  • Thread recusada pelo sistema não é mais pânico — e esta parte era pior que o crash: a submissão se perdia junto com o número de sequência, de modo que toda callback ordenada atrás dela esperaria para sempre. Agora é um resultado com erro comum: a sequência é contabilizada, o OnQueryError dispara e a native devolve false
🇧🇷 Medido depois da correção: 9000 queries num tick, 9000 callbacks entregues, ordem intacta, nada preso no caminho.
🇬🇧 2000 queries in one tick aborted the process — one thread per query, 8 MiB default stack, 32-bit server. Workers now get a 256 KiB stack (ceiling moved from ~1700 to past 9000), and a thread the OS refuses is now an ordinary failed result instead of a panic that lost the sequence number and stalled every ordered callback behind it forever.

🇧🇷 As natives de submissão passaram a dizer a verdade
🇧🇷 mysql_query, mysql_pquery, mysql_query_file, mysql_stmt_execute, mysql_stmt_pexecute, mysql_transaction_execute e as natives CRUD do ORM devolviam true sempre que o id da conexão era válido. Agora devolvem o que a submissão realmente fez.
🇬🇧 They returned true whenever the connection id was valid; they now return what the submission actually did.

🇧🇷 Pedir TLS em host local rodava em texto claro
🇧🇷 MYSQL_OPT_SSL = 1 contra 127.0.0.1 reportava sucesso enquanto o cipher voltava vazio. O driver tem prefer_socket ligado por padrão e, depois do handshake, move uma conexão de loopback para o socket unix do servidor — onde ela não protege nada. As duas condições se encontravam na configuração local mais comum que existe. Pedir TLS agora desliga essa otimização; sem TLS ela continua, que é onde ajuda. Verificado contra MariaDB 11.8: TLS_AES_256_GCM_SHA384 onde antes havia string vazia.
🇬🇧 MYSQL_OPT_SSL = 1 against 127.0.0.1 reported success with an empty cipher: the driver moves a loopback connection onto the unix socket after the handshake, where it secures nothing. Asking for TLS now switches that off.

🇧🇷 E ainda / 🇬🇧 And also
  • 🇧🇷 Erro do driver chegava ao Pawn com entranha de Rust — tabela faltando vinha como MySqlError { ERROR 1146 (42S02): Table 'db.t' doesn't exist }. Agora passa a mensagem do servidor, sozinha / 🇬🇧 Rust internals no longer wrap the server's own message
  • 🇧🇷 mysql_query_file falhava sem avisar — arquivo ilegível ou sem statements era só uma linha de log, que a forma sem callback (a recomendada para schema) nunca vê. Agora dispara OnQueryError, com o caminho no lugar da query / 🇬🇧 now fires OnQueryError, naming the file
  • 🇧🇷 rustls 0.23.45 (RUSTSEC-2026-0285) — mensagens de handshake TLS 1.3 eram aceitas atravessando limites de nível de criptografia. O transcript segue autenticado, então ninguém na rede altera ou completa um handshake; o efeito prático é um par conseguir mandar em texto claro o que deveria ir cifrado / 🇬🇧 TLS 1.3 handshake messages accepted across encryption level boundaries



🆕 MYSQL_SYNC: bloquear quando a chamada pede
New: MYSQL_SYNC

🇧🇷 Até a 1.3.0 não havia caminho bloqueante na API, de propósito. O problema é que existe trabalho que não é sobre desempenho: criar tabela no OnGameModeInit, ler uma configuração que a linha seguinte precisa, migrar schema. Exigir callback para isso é cerimônia, e a resposta para "como eu leio isto agora?" não podia ser "não leia".

🇬🇧 Up to 1.3.0 there was no blocking path in the API, by design. But some work isn't about performance: creating tables in OnGameModeInit, reading a setting the next line needs, migrating schema.

Code:
mysql_query(g_mysql, "SELECT COUNT(*) AS n FROM contas", MYSQL_SYNC);
new total = cache_get_value_name_int(0, "n");  // o resultado já está aqui

🇧🇷 Funciona em mysql_query, mysql_pquery e mysql_query_file — onde vive o trabalho de schema na subida do servidor.
🇬🇧 Supported on mysql_query, mysql_pquery and mysql_query_file.

Por que é um valor e não uma native nova
🇧🇷 Porque uma native chamada mysql_query_sync acaba dentro do OnPlayerUpdate. Um valor escrito no lugar do callback põe a intenção onde o custo é pago: quem lê a linha vê que ela espera. E não podia ser um argumento novo — o Pawn não aceita parâmetro depois de {Float,_}:..., e colocá-lo antes do callback quebraria toda chamada existente.
🇬🇧 A native called mysql_query_sync ends up inside OnPlayerUpdate. A value at the call site states the intent where the cost is paid. It couldn't be a new argument either: Pawn refuses any parameter after {Float,_}:....

As três regras que mantêm isso honesto
  • 🇧🇷 Recusado dentro de callback — lá "o cache atual" já significa o do próprio callback; um resultado bloqueante ou o encobriria ou ficaria ilegível. A recusa vai para o log, não passa calada / 🇬🇧 refused inside a callback, out loud
  • 🇧🇷 Liberado no tick seguinte — a mesma garantia que o caminho assíncrono ganha do callback retornar. Nada para liberar à mão / 🇬🇧 freed at the next server tick, nothing to free by hand
  • 🇧🇷 Recusado nas natives que não podem honrá-lo — prepared statements, transações, ORM e hash de senha. Aceito em silêncio, o valor passaria por nome de callback que nunca dispara, e a chamada viraria fire-and-forget sem você saber / 🇬🇧 refused on every native that cannot honour it — otherwise the call would silently become fire-and-forget



⚖️ Como usar assíncrono e síncrono
Using async and sync

🇧🇷 A regra em uma frase: se um jogador está esperando, é assíncrono; se o servidor ainda está subindo, pode ser síncrono.
🇬🇧 One sentence: if a player is waiting, async. If the server is still starting up, sync is fine.

1. Assíncrono com callback — o caso normal

Code:
mysql_query(g_mysql, "SELECT id, nome FROM contas WHERE nome = 'x'", "OnContaCarregada", "d", playerid);

forward OnContaCarregada(playerid);
public OnContaCarregada(playerid)
{
    if (!cache_get_row_count()) return 0;  // conta não existe

    new nome[MAX_PLAYER_NAME];
    cache_get_value_name(0, "nome", nome);
    printf("conta de %s carregada", nome);
    return 1;
}

2. Assíncrono sem callback — quando o resultado não interessa
🇧🇷 O callback sempre foi opcional, mas isso estava num parágrafo no pé de uma página enquanto todos os exemplos sugeriam o contrário — daí a reclamação de que o plugin "obriga a escrever callback para tudo". Um UPDATE cujo resultado você não vai ler é uma linha:
🇬🇧 The callback was always optional; the docs buried it. An UPDATE whose result you won't read is one line:

Code:
mysql_query(g_mysql, "UPDATE contas SET online = 0 WHERE id = 7");

3. Síncrono — trabalho de subida

Code:
public OnGameModeInit()
{
    g_mysql = mysql_connect_file();                      // credenciais fora do código

    mysql_query_file(g_mysql, "schema.sql", MYSQL_SYNC); // schema pronto ANTES de seguir

    mysql_query(g_mysql, "SELECT valor FROM config WHERE chave = 'preco'", MYSQL_SYNC);
    g_preco = cache_get_value_name_int(0, "valor");      // já disponível aqui
    return 1;
}

4. FIFO ordena callbacks, não execução — a armadilha
🇧🇷 Isto merece destaque porque a documentação antiga recomendava errado. O mysql_query entrega os callbacks na ordem em que você submeteu, mas cada statement roda na sua própria conexão, ao mesmo tempo. Um CREATE TABLE seguido de um INSERT corre junto, e o insert falha. Quem serializa de verdade é o mysql_query_file (arquivo inteiro, em ordem) e a transação (lote atômico) — ou o MYSQL_SYNC, que simplesmente espera.
🇬🇧 mysql_query dispatches callbacks in submission order, but each statement runs on its own connection, concurrently: a CREATE TABLE followed by an INSERT races. What actually serialises is mysql_query_file, a transaction, or MYSQL_SYNC.

5. E o mysql_pquery?
🇧🇷 Entrega assim que cada uma termina, sem ordem garantida — bom para muitas queries independentes. Com MYSQL_SYNC ele é igual ao mysql_query: uma chamada que espera não tem com o que rodar em paralelo.
🇬🇧 Delivers as each finishes, no ordering. With MYSQL_SYNC it's identical to the FIFO native.



🔐 Outras novidades
Also new in 1.4.0

Diagnóstico de TLS
🇧🇷 mysql_tls_active(connId) e mysql_tls_cipher(connId, dest[], max_len) dizem no que o handshake realmente terminou, não o que o MYSQL_OPT_SSL pediu. Os dois leem um valor capturado quando a conexão abriu, então nenhum custa uma query. Sessão sem criptografia devolve false e cipher vazio — essa é a resposta, e o retorno separa isso de uma conexão que não existe.
🇬🇧 What the handshake actually settled on, rather than what MYSQL_OPT_SSL asked for. Both read a value captured when the connection opened, so neither costs a query.

Code:
if (!mysql_tls_active(g_mysql)) print("[MySQL] atenção: conexão em texto claro");
else
{
    new cipher[64];
    mysql_tls_cipher(g_mysql, cipher, sizeof(cipher));
    printf("[MySQL] TLS: %s", cipher);          // ex.: TLS_AES_256_GCM_SHA384
}

Os tetos de memória deixaram de ser fixos
🇧🇷 mysql_limit_set e mysql_limit_get mexem em três limites antes cravados no código: MYSQL_LIMIT_SAVED_CACHES (1024), MYSQL_LIMIT_RESULT_ROWS (100000) e MYSQL_LIMIT_ORM_STRING_LEN (4096). Quem precisa de mais, aumenta; máquina apertada, diminui. São globais e não por conexão, porque a memória protegida é a do servidor. O valor 0 é recusado em vez de ser lido como "ilimitado" — ilimitado é justamente o estado que esses limites existem para evitar — e abaixar um teto não joga fora o que já está guardado.
🇬🇧 Three caps that used to be hardcoded. Global, not per connection. 0 is refused rather than read as "unlimited", and lowering a cap keeps what is already held.

Code:
mysql_limit_set(MYSQL_LIMIT_RESULT_ROWS, 250000);  // relatório grande
printf("caches guardados: %d", mysql_limit_get(MYSQL_LIMIT_SAVED_CACHES));

${VARIÁVEL} no arquivo de conexão
🇧🇷 O mysql_connect_file já tirava a senha do gamemode; agora o arquivo pode citar o segredo em vez de guardá-lo, e uma cópia roubada não vale nada por si:
🇬🇧 The file can now name the secret instead of holding it:

Code:
host = 127.0.0.1
user = samp
password = ${MYSQL_PASSWORD}
database = meu_banco

🇧🇷 Nome não definido expande para vazio e diz no log qual faltou — mandar o literal ${MYSQL_PASSWORD} para o servidor falharia como "access denied" e mandaria você procurar no lugar errado. Só ${...} é referência, então senha com sinal de dólar continua funcionando.
🇬🇧 An unset name expands to nothing and logs which one was missing. Only ${...} is a reference, so a password with a dollar sign still works.

IPv6
🇧🇷 É suportado, entre colchetes: [::1] é interpretado e validado como endereço. Sem colchetes vira nome de host — normalmente ainda conecta, mas não valida nada.
🇬🇧 Supported, in brackets: [::1] is parsed and validated as an address; without brackets it's taken as a host name.



⚙️ Por dentro: o SDK e o que ele destravou
Under the hood

🇧🇷 O plugin roda sobre o rust-samp, o SDK em Rust para SA-MP e open.mp, que aqui vai para a 3.6.0 e sai de uma tag do git para o crates.io. A mudança já conserta uma esquisitice: a tag v3.5.0 declarava version = "3.4.0" no próprio manifesto, então o lockfile registrava 3.4.0 apontando para a tag v3.5.0.

🇧🇷 O que a 3.6.0 trouxe de útil aqui foi uma rota que faltava. Quando uma query termina numa worker thread, o resultado precisa aterrissar no estado do plugin — e até a 3.5.0 não havia caminho do SDK para isso, limitação que o plugin contornava com canal próprio. O post_with entrega o plugin ao job, e com ele os canais de resultado do plugin deixaram de existir, tanto na query quanto no hash de senha.

🇧🇷 Não é ganho de desempenho, e eu não vou vender como se fosse. O SDK drena essa fila imediatamente antes do on_tick, então o despacho continua acontecendo no on_tick e nada chega um tick depois do que chegava. O ganho é de arquitetura: menos código próprio fazendo o que o SDK passou a fazer, num ponto onde errar custa caro. Os ganhos de robustez que você sente nesta versão — a rajada de 9000 queries — são do plugin, não do SDK.
🇬🇧 It is not a performance win and I won't sell it as one. The SDK drains that queue immediately before on_tick, so dispatch still happens there and nothing arrives a tick later than it used to. The gain is architectural. The robustness wins you feel in this release are the plugin's, not the SDK's.

🇧🇷 Um detalhe de projeto que não é questão de gosto: entregar o resultado e despachar o callback tiveram de ficar em dois passos. Chamar o Pawn de dentro do job deixaria um public reentrar numa native e pegar um segundo empréstimo mutável do mesmo plugin, que é instantaneamente um bug de memória. Então o job só bufferiza, e quem invoca callback é o on_tick, que detém esse empréstimo legitimamente. A ordem FIFO continua sendo nossa, no buffer de sequência, porque os jobs terminam na ordem em que o banco responde, não na que você pediu.
🇬🇧 Handing the result over and dispatching the callback had to be two steps: calling Pawn from inside the job would let a public re-enter a native and take a second mutable borrow of the plugin. The job only buffers; on_tick dispatches.

Gargalo antigo que já não é seu problema
🇧🇷 Vindo de outro plugin MySQL você provavelmente aprendeu a chamar mysql_tick() num timer. Aqui não é necessário desde a 3.0.0 do SDK: a fila é bombeada a cada tick, sozinha. A native continua existindo só por compatibilidade — e ela não libera o resultado do MYSQL_SYNC, que sai no tick seguinte de todo jeito.
🇬🇧 Coming from another MySQL plugin you probably learned to call mysql_tick() on a timer. Not needed here since SDK 3.0.0: the queue is pumped every tick automatically.



📖 A referência da API agora se escreve sozinha
The API reference generates itself

🇧🇷 A referência da API era uma tabela escrita à mão com cada native, seu tipo e uma descrição — exatamente a informação que o include já carrega ao lado de cada declaração, mantida em sincronia a dedo. Agora ela é gerada do include a cada build: natives, o forward, as constantes documentadas e os enums com seus valores, cada um com assinatura, apelido open.mp, parâmetros e retorno. A cópia manual foi apagada, e a URL é a mesma.

🇧🇷 O gerador lê dois formatos de comentário: o JavaDoc (@param, @return), que é o que estes includes usam, e o pawndoc (<summary>, <param name="">, <returns>), que é o que você encontra em includes de terceiros. E tem autoteste: se uma native entrar sem bloco de documentação, o build da doc falha em vez de publicar uma linha vazia.

🇧🇷 Esses mesmos comentários chegam ao hover do seu editor — na extensão PawnPro para VS Code, por exemplo, você passa o mouse na native e lê o que cada parâmetro espera sem abrir o .inc. Nesta versão as três natives que aceitam MYSQL_SYNC carregam o exemplo bloqueante no próprio comentário, então o jeito de fazer uma chamada esperar aparece onde a native é documentada, não só na página de queries.

🇬🇧 The API reference is now generated from the includes on every docs build — natives, forward, documented constants and enums with their values. Two comment formats are read: JavaDoc, used here, and pawndoc. A native without a doc block fails the docs build. The same comments reach your editor's hover — e.g. the PawnPro VS Code extension.



Quote:🇧🇷 Prepared statement continua sendo a via segura. O mysql_format e o ORM interpolam texto e dependem do escaping; o mysql_stmt_* manda os valores pelo protocolo binário e não tem esse risco. Para qualquer coisa que carregue o que o jogador digitou, prefira statement — isso não mudou nesta versão e não vai mudar.
🇬🇧 Prepared statements are still the safe path for anything carrying player input.

📚 Links
  • Download da 1.4.0 — 🇧🇷 .so, .dll e os dois includes / 🇬🇧 binaries and both includes
  • Documentação — 🇧🇷 conexão, queries, cache, ORM, opções e segurança / 🇬🇧 connection, queries, cache, ORM, options, security
  • Referência da API — 🇧🇷 as 79 natives, geradas do include / 🇬🇧 all 79 natives, generated from the include
  • Guia de migração — 🇧🇷 para quem vem de outro plugin MySQL / 🇬🇧 coming from another MySQL plugin
  • CHANGELOG completo — 🇧🇷 com o que não caberia aqui / 🇬🇧 the full list

🇧🇷 Projeto independente, mantido por uma pessoa. Não é afiliado, endossado nem patrocinado pelo SA-MP ou pelo open.mp. "SA-MP", "open.mp" e "MySQL" pertencem a seus respectivos donos e são citados apenas para descrever compatibilidade.
🇬🇧 Independent project, maintained by one person. Not affiliated with or endorsed by SA-MP or open.mp.


🇧🇷 Dúvidas, sugestões e relatos de bug são bem-vindos — aqui no tópico ou nas issues.
🇬🇧 Questions, suggestions and bug reports are welcome — here or on GitHub issues.


⭐ github.com/NullSablex/mysql_samp