Repository navigation
stored procedures
Home > Banco de dados (DBWare) > Stored procedures
Important
Em desenvolvimento (2.0). Ainda não está numa versão lançada e pode mudar.
Delphi e Lazarus · Nível: intermediário
Em uma frase: o TRALDBModule chama as stored procedures do banco pelo nome,
numa rota só (proc/<nome>), acha cada uma no catálogo do próprio banco e responde
no formato que o cliente pedir - para um dataset do RAL ou para qualquer cliente
HTTP, de qualquer linguagem.
Você vai precisar: o DBWare funcionando, um banco com procedures
(Firebird, PostgreSQL ou MySQL; o SQLite não tem) e, no servidor, um storage para a
resposta (RALStoragePackage; o JSON é o padrão).
ProcOptions. |
Padrão | O que é |
|---|---|---|
Enabled |
False |
cria a rota; desligado, ela não existe (nem para os datasets do RAL) |
Route |
proc |
prefixo: /db/proc/<nome>
|
Filter |
vazio | máscaras de nome aceitas, uma por linha (API_*); vazio = todas |
Schemas |
vazio | esquemas aceitos, um por linha; vazio = o padrão da conexão |
CatalogLife |
5 |
minutos que o módulo guarda o que o catálogo disse; 0 = pergunta sempre |
Storage |
vazio | formato da resposta quando o Accept do cliente não pede outro; vazio = JSON |
Nada de procedure fica guardado no módulo nem no .dfm: uma procedure criada ou
alterada no banco aparece sozinha quando o cache vence (RefreshProcedures esquece
na hora). O módulo escolhe a sintaxe pelo banco e pelo tipo da procedure:
| Banco | Procedure | Chamada |
|---|---|---|
| Firebird | selecionável (SUSPEND) |
select * from NOME(...) |
| Firebird | executável |
execute procedure NOME(...); as saídas voltam como parâmetros |
| PostgreSQL | procedure |
call NOME(...); OUT/INOUT voltam como parâmetros |
| PostgreSQL | function |
select * from NOME(...) |
| MySQL | PROCEDURE |
call NOME(...); OUT/INOUT voltam como parâmetros |
| MySQL | FUNCTION |
select NOME(...) |
Warning
Ligar a rota abre todas as procedures que o usuário da conexão pode executar.
O limite de verdade é o banco: use um usuário só para a API, com GRANT EXECUTE
apenas no que ela deve expor. Filter, Schemas e o evento abaixo apertam mais.
Roda antes de cada chamada (pela rota ou por um dataset do RAL), com o que o catálogo disse e os parâmetros já no tipo certo:
procedure TForm1.RALDBModule1ValidateProc(ASender: TObject; ARequest: TRALRequest;
AProc: TRALDBProcInfo; AParams: TParams; var AAllow: Boolean);
begin
// o vendedor vem do token, não do cliente: o valor posto aqui vence
if AParams.FindParam('VENDEDOR') <> nil then
AParams.ParamByName('VENDEDOR').AsString := ARequest.ParamByName('sub').AsString;
// e uma regra qualquer
AAllow := not SameText(AProc.Name, 'API_FECHA_MES') or EhGerente(ARequest);
end;Nos três datasets (TRALDBFDMemTable, TRALDBZMemTable, TRALDBBufDataset):
| Membro | O que faz |
|---|---|
StoredProcName |
a procedure; com ela, Open e ExecProc chamam a procedure em vez de rodar SQL
|
ExecProc |
chama; as saídas voltam em Params, e as linhas, se houver, abrem o dataset |
NextRecordSet |
passa para o próximo result set da última chamada |
ResultSets |
liga o 2º, 3º... result set a outros datasets: outro dataset do RAL ou qualquer TDataSet
|
qrSaldo.StoredProcName := 'API_BAIXA_ESTOQUE';
qrSaldo.Params.CreateParam(ftInteger, 'PRODUTO', ptInput).AsInteger := 10;
qrSaldo.Params.CreateParam(ftInteger, 'QTD', ptInput).AsInteger := 2;
qrSaldo.ExecProc; // levanta exceção se falhar
ShowMessage(qrSaldo.ParamByName('SALDO').AsString); // saída da procedureSem dataset do RAL, direto pela conexão, em quaisquer datasets (pelo storage):
RALDBConnection1.ExecProcRemote('API_PEDIDOS_DO_CLIENTE', vParams,
[cdsPedidos, cdsItens], RALStorageBINLink1);O ProcRoute do TRALDBConnection tem de ser igual ao ProcOptions.Route do
servidor (os dois começam em proc).
Uma procedure do MySQL com dois select, ou do PostgreSQL que devolve dois
refcursor, devolve dois datasets numa chamada. Cada um vai no formato nativo do
componente quando cliente e servidor usam o mesmo (FireDAC com FireDAC, por exemplo)
e pelo storage nos outros casos. Quem lê o servidor é o FireDAC (NextRecordSet); o
SQLDB e o Zeos entregam um result set por chamada.
Duas formas, as duas sem guardar nada no servidor entre pedidos:
-
No servidor,
TRALDBModule.AutoCommit := False: cada pedido é uma transação. Se um comando falha, todos os do pedido são desfeitos e voltam comRolled back: another statement of the same request failed.- o que falhou traz o erro do banco. ComTrue(o padrão), cada comando é gravado sozinho, como sempre. -
No cliente, a fila do
TRALDBConnection: nada sai pela rede até oCommit, que manda tudo num pedido só, gravado inteiro ou nada:
RALDBConnection1.StartTransaction;
mtClientes.ApplyUpdates;
mtPedidos.ExecSQL;
RALDBConnection1.ExecProcRemote('BAIXA_ESTOQUE', vParams, []);
RALDBConnection1.Commit; // um pedido; cada um recebe a sua resposta; levanta exceção se falharRollback descarta a fila (nada foi enviado). Dentro da fila não dá para ler o
resultado de um comando para montar o seguinte, e o Open é recusado: essa lógica
vai numa procedure, que roda inteira no servidor.
A rota é POST /<Domain>/<Route>/<nome>, por exemplo POST /db/proc/API_SALDO.
Pedido. Os parâmetros, pelo nome, num objeto JSON no corpo, ou na query string,
ou como campos de formulário. Texto é convertido para o tipo que o catálogo diz:
números com ponto, datas em ISO 8601, booleanos true/false, binário em base64.
curl -X POST http://localhost:8000/db/proc/API_SALDO \
-H "Content-Type: application/json" -d '{"PRODUTO": 10}'Resposta.
| Caso | Corpo |
|---|---|
| a procedure não devolve nada | 204 No Content |
| um dataset (as saídas, ou as linhas) | o dataset no storage escolhido |
| mais de um |
multipart/mixed, uma parte por dataset |
procedure desconhecida, fora do Filter/Schemas
|
404 |
| nome inválido | 400 |
OnValidateProc recusou |
403 |
| erro do banco |
500, com a mensagem (escondida com HideErrorDetails) |
O storage é o que o Accept pede (application/json, text/csv, application/xml,
application/octet-stream... entre os que o servidor tem), senão ProcOptions.Storage,
senão JSON. As saídas viram um dataset de uma linha, um campo por parâmetro.
No multipart/mixed, as partes vêm nesta ordem: params (as saídas, quando há),
data (o primeiro result set), data2, data3... Cada parte tem o seu
Content-Type e Content-Disposition: inline; name="...":
HTTP/1.1 200 OK
Content-Type: multipart/mixed; boundary=ral3E571E648F401AB31E2C9C7A
--ral3E571E648F401AB31E2C9C7A
Content-Disposition: inline; name="params"
Content-Type: application/json
{ ...o dataset das saídas... }
--ral3E571E648F401AB31E2C9C7A
Content-Disposition: inline; name="data"
Content-Type: application/json
{ ...as linhas... }
--ral3E571E648F401AB31E2C9C7A--
Em JavaScript, o Response.formData() do navegador só lê multipart/form-data;
para multipart/mixed, separe pelo boundary do Content-Type:
const r = await fetch('/db/proc/API_PEDIDOS', {method: 'POST',
headers: {'Content-Type': 'application/json'}, body: JSON.stringify({CLIENTE: 7})});
const type = r.headers.get('Content-Type');
if (type.startsWith('multipart/mixed')) {
const boundary = type.split('boundary=')[1];
const parts = (await r.text()).split('--' + boundary).slice(1, -1).map(p => {
const [head, body] = p.split('\r\n\r\n');
return {name: /name="([^"]+)"/.exec(head)[1], body: body.replace(/\r\n$/, '')};
});
// parts[0].name === 'params', parts[1].name === 'data' ...
}| O que acontece | Causa | O que fazer |
|---|---|---|
404 Stored procedure X not found, or not allowed. |
não está no catálogo, fora do Filter ou de Schemas
|
confira o nome, as máscaras e o esquema |
404 em toda chamada |
ProcOptions.Enabled desligado (a rota não existe) |
ligue |
403 Stored procedures are not enabled... |
um dataset do RAL chamou com a rota desligada | ligue ProcOptions.Enabled
|
403 Stored procedure call rejected by OnValidateProc. |
a sua regra recusou | confira o evento |
500 com permission denied/no permission
|
o usuário do banco não tem GRANT EXECUTE
|
é o banco dizendo não, como deve |
This database has no stored procedures. |
SQLite | use outro banco |
Stored procedure X has more than one signature... |
sobrecarga no PostgreSQL | mande os nomes de parâmetro que distinguem as versões |
RALLegacyWire is on... |
o formato antigo não leva chamada de procedure | desligue RALLegacyWire nesse lado |
Começar
Servidor
- Rotas e parâmetros
- Ler o pedido e responder
- Resposta em fluxo 🚧
- Plugins 🚧
- Autenticação
- Banco de dados (DBWare)
- Servir site e arquivos
- Swagger e Postman
- Servidor SOAP 🚧
- HTTPS
- Criptografia ponto a ponto
- Segurança para produção
- Colocar no ar
- Desempenho
Cliente
Motores
Avançado
Ajuda
- Problemas comuns
- Atualizar o Indy
- Várias versões do Delphi
- Vídeos e guias
- Módulos de terceiros
- Licenças
- Contribuir
- Roadmap e changelog
🚧 = em desenvolvimento, pode mudar