Repository navigation
dbware recursos
Home > Banco de dados (DBWare) > Recursos REST sobre tabelas 🚧
Important
Em desenvolvimento (2.0). Ainda não está numa versão lançada e pode mudar.
Vale para a 2.0 · Delphi XE em diante e Lazarus/FPC 3.2 em diante · Nível: intermediário
Em uma frase: o TRALDBCrudModule serve tabelas e views como recursos REST prontos
(listar, ler, inserir, trocar, alterar, apagar), com filtros, ordem e paginação pela URL,
sem você escrever uma rota nem um SELECT.
Você vai precisar: o pacote do módulo de banco da 2.0 (RALDBPackage + o link do seu
componente de acesso: FireDAC, Zeos ou SQLDB), um TRALDBModule já configurado (ver
DBWare) e um motor.
- É REST sobre tabelas:
GET /clientes,GET /clientes/1,POST,PUT,PATCH,DELETE. - Toda SQL é gerada pelo RAL, com parâmetros: valores da URL e do corpo nunca entram no texto da SQL.
- Todo nome que vem da URL (coluna de filtro,
_sort,_fields, chave do corpo) passa pelo catálogo da tabela, lido do próprio banco. Nome que o catálogo não conhece é 400. Por isso não há injeção de SQL pelos nomes. - O banco, o driver e o pool são os do
TRALDBModuleque você ligar.
Solte um TRALDBCrudModule (paleta RAL - Modules), ligue o Server e aponte o
DBModule para um TRALDBModule existente. Depois monte os Resources.
Crud.DBModule := DBModule1;
Res := Crud.Resources.AddResource('/produtos', 'produtos');
Res.VersionField := 'versao';| propriedade do módulo | |
|---|---|
DBModule |
o TRALDBModule cujo driver, conexão e pool os recursos usam |
Resources |
a lista de recursos |
TotalCount |
True: as listas respondem X-Total-Count (um COUNT a mais por lista). Falso por padrão |
FilterSyntaxes |
as sintaxes de filtro aceitas na URL: [fsSimple, fsRQL] por padrão |
CatalogLife |
minutos que o catálogo de uma tabela fica em cache; 0 lê a cada pedido. Padrão 10 |
Storage |
saída em storage (veja abaixo) |
TenantMode, TenantClaim, TenantHeader, TenantIdle
|
multi-inquilino (veja abaixo) |
OnGetTenant, OnGetTenantDatabase
|
eventos do multi-inquilino |
Cada item de Resources:
| propriedade | |
|---|---|
Route |
o caminho, como /produtos, sob o Domain do módulo |
TableName |
a tabela ou view (esquema.nome vale) |
KeyFields |
as colunas da chave, separadas por vírgula; vazio = a chave primária do catálogo. Chave composta viaja como 1,ABC na URL |
Operations |
o que o recurso responde: roList, roGet, roInsert, roUpdate, roPatch, roDelete (todas por padrão) |
Fields |
as colunas servidas, uma por linha; vazio = todas menos HiddenFields
|
HiddenFields |
colunas que nunca são lidas nem escritas |
ReadOnlyFields |
colunas lidas, nunca escritas |
Roles |
papéis que podem usar o recurso (qualquer um deles, separados por vírgula); vazio = todos |
ReadRoles, WriteRoles
|
papéis só da leitura ou só da escrita; vazio = Roles
|
MaxPageSize |
o máximo de linhas de uma lista (100 por padrão) |
DefaultSort |
a ordem das listas sem _sort, escrita como o _sort (-preco,nome) |
BaseFilter |
uma condição SQL sua (de confiança) somada a todo comando do recurso, como ativo = 1
|
SelectSQL |
um SELECT seu no lugar da tabela; só leitura. Filtro, ordem e página valem sobre as colunas dele |
VersionField |
coluna inteira que cresce de um a cada escrita; vira o ETag (veja concorrência) |
TenantField |
a coluna do inquilino, com TenantMode = tmColumn
|
OnBeforeInsert, OnBeforeUpdate, OnBeforeDelete
|
antes da escrita: altere os valores ou marque AAllow := False (responde 403) |
OnAfterWrite |
depois de inserir, trocar, alterar ou apagar, com o registro como ficou (nil no delete) |
Papéis vêm da identidade do pedido; ver autenticação.
| pedido | faz | resposta |
|---|---|---|
GET /rota |
lista (com filtros) | 200, array JSON |
GET /rota/chave |
lê um registro | 200 e ETag (com VersionField); 404 se não existe |
POST /rota |
insere; o corpo é um objeto JSON | 201, Location: /rota/chave e o registro |
PUT /rota/chave |
troca o registro; o que o corpo não traz vira nulo | 200 e o registro |
PATCH /rota/chave |
altera só o que o corpo traz | 200 e o registro |
DELETE /rota/chave |
apaga | 204; 404 se não existe |
| código | quando |
|---|---|
| 400 | filtro, coluna ou valor inválido; coluna oculta ou só de leitura no corpo; chave gerada pelo banco enviada no corpo |
| 403 | sem o papel; recurso de SelectSQL escrito; evento OnBefore* recusou; sem inquilino |
| 404 | o registro não existe (ou é de outro inquilino) |
| 405 | a operação não está em Operations
|
| 409 | chave duplicada ou violação de unicidade |
| 412 |
If-Match não bate com a versão atual |
Os erros vêm como problema RFC 9457 (application/problem+json).
coluna=valor compara por igualdade; um sufixo muda a operação.
| na URL | significa |
|---|---|
preco=10 |
igual |
preco_gt=10 |
maior que |
preco_gte=10 |
maior ou igual |
preco_lt=10 |
menor que |
preco_lte=10 |
menor ou igual |
preco_ne=10 |
diferente |
nome_like=Ana% |
LIKE (sem distinguir maiúsculas) com o seu padrão |
nome_cont=ana |
contém |
nome_sw=ana |
começa com |
nome_ew=ana |
termina com |
id_in=1,2,3 |
está na lista |
id_nin=1,2 |
não está na lista |
email_null=true |
é nulo (false: não é nulo) |
Vários filtros se juntam com E. Controles (começam com _):
| na URL | significa |
|---|---|
_sort=-preco,nome |
ordem; - é decrescente |
_limit=10 e _offset=20
|
página por deslocamento |
_page=3 e _per_page=10
|
página por número |
_fields=id,nome |
só essas colunas |
O tamanho da página nunca passa de MaxPageSize. Com TotalCount, a lista responde o
total sem paginar em X-Total-Count. Um _ desconhecido é 400.
Na sintaxe RQL o filtro todo vai num parâmetro rql:
/produtos?rql=and(eq(categoria,a),or(gt(preco,5),isnull(estoque)),not(eq(id,9))).
| operador | |
|---|---|
and(...), or(...), not(...)
|
grupos |
eq, ne, lt, le, gt, ge
|
comparações: eq(campo,valor)
|
like, contains, starts, ends
|
texto |
in(campo,a,b), out(campo,a,b)
|
está / não está na lista |
isnull(campo), notnull(campo)
|
nulidade |
sort(-campo,outro) |
ordem |
limit(tamanho,deslocamento) |
página |
Valor com vírgula ou parêntese vai entre aspas simples: eq(nome,'a,b').
FilterSyntaxes diz quais valem no módulo. O pedido escolhe com o parâmetro
filter-syntax (simple ou rql) ou o cabeçalho X-RAL-Filter-Syntax; o parâmetro
vence o cabeçalho.
Com VersionField (uma coluna inteira, por exemplo versao integer default 1):
- toda resposta de registro traz
ETag: "N"; - todo
PUT/PATCHsoma 1 à coluna; - mande
If-Match: "N"na escrita; se outro já mudou o registro, a resposta é 412 e nada é gravado. SemIf-Match(ou com*) a escrita passa.
TenantMode diz onde vivem os dados de cada inquilino:
| modo | como isola |
|---|---|
tmNone |
sem inquilinos (padrão) |
tmColumn |
uma coluna em cada tabela (TenantField do recurso): todo comando filtra por ela e todo insert a preenche |
tmSchema |
um esquema por inquilino no mesmo banco |
tmDatabase |
um banco por inquilino; OnGetTenantDatabase aponta um driver novo (configurado como o DBModule) para o banco dele. Os pools ociosos por mais de TenantIdle segundos são liberados |
O nome do inquilino vem de TenantClaim (da identidade), depois de TenantHeader, e
OnGetTenant pode trocá-lo; nome vazio recusa o pedido com 403.
Warning
A SQL que o cliente manda no DBWare clássico (o SELECT do dataset) não passa por
este módulo e só é isolada por inquilino nos modos tmDatabase e tmSchema. Em
tmColumn ela enxerga todas as linhas.
Ligue Storage a um TRALStorageLink (ver Storage XML e DBWare).
A lista responde nesse formato quando o Accept do pedido traz o tipo do storage; sem ele,
JSON.
O catálogo (RALDBTableInfo) lê do banco as colunas, tipos, chave primária, se aceitam
nulo, valor padrão e o que o próprio banco preenche (identidade, sequência, auto_increment,
rowid). Cada banco tem seu dialeto para aspas, paginação e LIKE sem maiúsculas:
| banco | paginação | detalhe |
|---|---|---|
| Firebird 3+ | ROWS a TO b |
|
| PostgreSQL | LIMIT/OFFSET |
ILIKE |
| MySQL e MariaDB | LIMIT/OFFSET |
identificadores com crase |
| SQLite | LIMIT/OFFSET |
upper(...) like upper(...) |
Testado de verdade com SQLite, em SQLDB (Lazarus) e FireDAC (Delphi). Firebird, PostgreSQL e MySQL/MariaDB têm o dialeto escrito, mas ainda sem teste contra o banco.
| sintoma | causa |
|---|---|
| 400 num filtro | a coluna não existe no catálogo, é HiddenFields ou fora de Fields, ou o valor não cabe no tipo |
400 no POST
|
o corpo traz a chave que o banco gera, ou coluna oculta/só de leitura |
403 no POST de um recurso de SelectSQL
|
o recurso é só leitura |
| 405 | a operação não está em Operations
|
| 412 em toda escrita | o If-Match é de uma versão antiga: leia o registro de novo |
X-Total-Count não vem |
TotalCount está desligado |
| 500 "DBModule is not set" | falta ligar o DBModule
|
- Banco de dados (DBWare) e Stored procedures
- Autenticação, para os papéis
- Swagger e Postman
Começar
Servidor
- Rotas e parâmetros
- Rotas avançadas: tipos, grupos, versões 🚧
- Ler o pedido e responder
- Objetos no corpo 🚧
- Resposta em fluxo 🚧
- Plugins 🚧
- Autenticação
- Banco de dados (DBWare)
- Servir site e arquivos
- JSON-RPC 2.0 🚧
- Servidor MCP (IA) 🚧
-
Protocol Buffers 🚧
- gRPC 🚧
- WebSocket 🚧
- Hubs (WebSocket) 🚧
- Swagger e Postman
- Servidor SOAP 🚧
- WS-Security 🚧
- HTTPS
- Criptografia ponto a ponto
- Segurança para produção
- Colocar no ar
- Desempenho
- Testes em memória 🚧
MQTT 🚧
- O que é e quando usar 🚧
- Cliente MQTT 🚧
- Broker MQTT 🚧
- MQTT-SN 🚧
- Ponte REST 🚧
- Segurança 🚧
- Rede ruim 🚧
E-mail 🚧
Cliente
Motores
- Qual motor escolher?
- Compatibilidade
- Recursos
- QUIC
- Motor NgHttp2 (HTTP/2) 🚧
- Motor XSocket 🚧
- Motor AdvancedHTTP (experimental) 🚧
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