Install
$ agentstack add skill-delphicleancode-delphi-spec-kit-postgresql-database ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
PostgreSQL Database — Skill
Use this skill when working with PostgreSQL database in Delphi projects via FireDAC.
When to Use
- When configuring FireDAC connection with PostgreSQL
- When creating tables, sequences, functions, triggers and views
- When implementing Repositories with FireDAC + PostgreSQL
- When working with advanced types (JSONB, Arrays, UUID, ENUM)
- When implementing UPSERT, CTEs, Full-Text Search or Window Functions
- When planning schema migrations (versioned scripts)
PostgreSQL Versions
| Version | Relevant News | |--------|----------------------| | 12 | Generated Columns, CTE inlining, Partitioning improvements | | 13 | Incremental sorting, Parallel vacuum, Deduplication in B-tree | | 14 | Multirange types, SEARCH/CYCLE in recursive CTEs | | 15 | MERGE statement, JSON logging, UNIQUE NULL NOT DISTINCT | | 16 | Logical replication from standby, ANY_VALUE(), ICU default collations | | 17 | RETURNING OLD/NEW no MERGE, JSON_TABLE, Identity columns improvements |
> Recommendation: Use PostgreSQL 14+ for new projects. Enjoy MERGE, JSONB and partitioning.
FireDAC Connection with PostgreSQL
Minimum Configuration
unit MeuApp.Infra.Database.PostgreSQL.Connection;
interface
uses
FireDAC.Comp.Client,
FireDAC.Phys.PG, // Driver PostgreSQL
FireDAC.Phys.PGDef, // Defaults do PostgreSQL
FireDAC.Stan.Def,
FireDAC.Stan.Pool,
FireDAC.DApt;
type
///
/// Factory de connection PostgreSQL via FireDAC.
///
TPostgreSQLConnectionFactory = class
public
///
/// Cria e configura uma connection PostgreSQL.
///
/// Address do servidor
/// Nome do banco de data
/// User (default: postgres)
/// Senha do banco
/// Porta (default: 5432)
/// Connection FireDAC configurada e aberta
class function CreateConnection(
const AServer: string;
const ADatabase: string;
const AUserName: string = 'postgres';
const APassword: string = '';
APort: Integer = 5432
): TFDConnection;
///
/// Cria connection via connection string completa.
///
class function CreateFromConnectionString(
const AConnectionString: string
): TFDConnection;
end;
implementation
uses
System.SysUtils;
class function TPostgreSQLConnectionFactory.CreateConnection(
const AServer, ADatabase, AUserName, APassword: string;
APort: Integer): TFDConnection;
begin
if ADatabase.Trim.IsEmpty then
raise EArgumentException.Create('ADatabase não pode ser vazio');
Result := TFDConnection.Create(nil);
try
Result.DriverName := 'PG';
Result.Params.Values['Server'] := AServer;
Result.Params.Values['Port'] := APort.ToString;
Result.Params.Database := ADatabase;
Result.Params.UserName := AUserName;
Result.Params.Password := APassword;
{ Configurações recomendadas }
Result.Params.Values['CharacterSet'] := 'UTF8';
{ Opções do driver FireDAC }
Result.FormatOptions.StrsTrim2Len := True;
Result.FetchOptions.Mode := fmAll;
Result.ResourceOptions.AutoReconnect := True;
Result.TxOptions.Isolation := xiReadCommitted;
{ Schema padrão — 'public' por default, alterar se necessário }
// Result.Params.Values['MetaDefSchema'] := 'public';
Result.Connected := True;
except
Result.Free;
raise;
end;
end;
class function TPostgreSQLConnectionFactory.CreateFromConnectionString(
const AConnectionString: string): TFDConnection;
begin
Result := TFDConnection.Create(nil);
try
Result.ConnectionString := AConnectionString;
Result.Connected := True;
except
Result.Free;
raise;
end;
end;
FDPhysPGDriverLink — Configure Client Library
uses
FireDAC.Phys.PGWrapper,
FireDAC.Phys.PG;
var
LDriverLink: TFDPhysPGDriverLink;
begin
LDriverLink := TFDPhysPGDriverLink.Create(nil);
try
{ Apontar libpq.dll customizado (32/64-bit) }
LDriverLink.VendorLib := 'C:\PostgreSQL\bin\libpq.dll';
{ Windows: precisa também libintl-9.dll, libeay32.dll, ssleay32.dll no PATH }
finally
{ DriverLink vive por toda a aplicação — criar no DataModule }
end;
end;
Connection Pooling
{ Via FDManager }
FDManager.ConnectionDefs.AddConnectionDef;
with FDManager.ConnectionDefs.ConnectionDefByName('PG_POOL') do
begin
DriverID := 'PG';
Server := 'localhost';
Port := 5432;
Database := 'meubanco';
UserName := 'postgres';
Password := 'senha';
Params.Values['CharacterSet'] := 'UTF8';
Params.Values['Pooled'] := 'True';
Params.Values['POOL_MaximumItems'] := '50';
Params.Values['POOL_CleanupTimeout'] := '30000';
Params.Values['POOL_ExpireTimeout'] := '90000';
end;
SSL/TLS
{ Conexão segura com SSL }
Result.Params.Values['PGAdvanced'] := 'sslmode=require';
{ Para certificado de cliente: }
// Result.Params.Values['PGAdvanced'] :=
// 'sslmode=verify-full;sslcert=client-cert.pem;sslkey=client-key.pem;sslrootcert=ca.pem';
Data Types — PostgreSQL Mapping ↔ Delphi
| PostgreSQL | Delphi (FireDAC) | Note | |------------|------------------|------------| | INTEGER / INT4 | ftInteger / AsInteger | 32-bit | | BIGINT / INT8 | ftLargeint / AsLargeInt | 64-bit | | SMALLINT / INT2 | ftSmallint / AsSmallInt | 16-bit | | SERIAL | ftAutoInc / AsInteger | 32-bit auto-increment | | BIGSERIAL | ftAutoInc / AsLargeInt | 64-bit auto-increment | | VARCHAR(N) | ftString / AsString | Limited text | | TEXT | ftMemo / AsString | Unlimited Text | | NUMERIC(P,S) | ftBCD / AsCurrency | Monetary values | | DOUBLE PRECISION | ftFloat / AsFloat | Ponto flutuante | | REAL / FLOAT4 | ftSingle / AsSingle | 32-bit float | | DATE | ftDate / AsDateTime | Date only | | TIME | ftTime / AsDateTime | Just in time | | TIMESTAMP | ftDateTime / AsDateTime | Date + Time (without timezone) | | TIMESTAMPTZ | ftDateTime / AsDateTime | Date + Time (with timezone) | | BOOLEAN | ftBoolean / AsBoolean | TRUE/FALSE native | | UUID | ftGuid / AsString | Use gen_random_uuid() (PG 13+) | | JSON | ftMemo / AsString | JSON text (validated) | | JSONB | ftMemo / AsString | Binary JSON (indexable) | | BYTEA | ftBlob / AsBytes | Binary data | | ARRAY | ftMemo / AsString | PostgreSQL Array as Text | | INET / CIDR | ftString / AsString | Network addresses |
Sequences and Auto-Increment
SERIAL / BIGSERIAL (Legacy)
-- Cria coluna auto-increment automaticamente + sequence
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
-- Equivale a criar uma SEQUENCE + DEFAULT nextval('customers_id_seq')
IDENTITY Columns (Modern — SQL Standard)
-- Preferir sobre SERIAL em novos projetos (PG 10+)
CREATE TABLE customers (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
-- GENERATED BY DEFAULT: permite override manual do ID
CREATE TABLE products (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
Manual Sequences
CREATE SEQUENCE seq_order_number START WITH 1000 INCREMENT BY 1;
-- Usar no INSERT
INSERT INTO orders (order_number) VALUES (nextval('seq_order_number'));
RETURNING in Delphi
///
/// Insere customer e obtém o id e created_at gerados pelo banco.
/// RETURNING funciona com Open (igual ao Firebird).
///
procedure TPostgreSQLCustomerRepository.Insert(ACustomer: TCustomer);
var
LQuery: TFDQuery;
begin
LQuery := TFDQuery.Create(nil);
try
LQuery.Connection := FConnection;
LQuery.SQL.Text :=
'INSERT INTO customers (name, cpf, email, status) ' +
'VALUES (:name, :cpf, :email, :status) ' +
'RETURNING id, created_at';
LQuery.ParamByName('name').AsString := ACustomer.Name;
LQuery.ParamByName('cpf').AsString := ACustomer.Cpf;
LQuery.ParamByName('email').AsString := ACustomer.Email;
LQuery.ParamByName('status').AsSmallInt := Ord(ACustomer.Status);
{ RETURNING: usar Open para receber o resultado }
LQuery.Open;
ACustomer.Id := LQuery.FieldByName('id').AsInteger;
ACustomer.CreatedAt := LQuery.FieldByName('created_at').AsDateTime;
finally
LQuery.Free;
end;
end;
UPSERT — INSERT ... ON CONFLICT
-- Inserir ou atualizar se já existir (pela constraint unique)
INSERT INTO customers (cpf, name, email, status)
VALUES (:cpf, :name, :email, :status)
ON CONFLICT (cpf) DO UPDATE SET
name = EXCLUDED.name,
email = EXCLUDED.email,
status = EXCLUDED.status;
-- Ignorar se já existir (sem atualizar)
INSERT INTO customer_tags (customer_id, tag)
VALUES (:customer_id, :tag)
ON CONFLICT DO NOTHING;
In Delphi:
procedure TPostgreSQLCustomerRepository.Upsert(ACustomer: TCustomer);
var
LQuery: TFDQuery;
begin
LQuery := TFDQuery.Create(nil);
try
LQuery.Connection := FConnection;
LQuery.SQL.Text :=
'INSERT INTO customers (cpf, name, email, status) ' +
'VALUES (:cpf, :name, :email, :status) ' +
'ON CONFLICT (cpf) DO UPDATE SET ' +
' name = EXCLUDED.name, ' +
' email = EXCLUDED.email, ' +
' status = EXCLUDED.status ' +
'RETURNING id';
LQuery.ParamByName('cpf').AsString := ACustomer.Cpf;
LQuery.ParamByName('name').AsString := ACustomer.Name;
LQuery.ParamByName('email').AsString := ACustomer.Email;
LQuery.ParamByName('status').AsSmallInt := Ord(ACustomer.Status);
LQuery.Open;
ACustomer.Id := LQuery.FieldByName('id').AsInteger;
finally
LQuery.Free;
end;
end;
JSONB — Semi-Structured Data
Storage and Query
-- Tabela com coluna JSONB
CREATE TABLE customer_settings (
customer_id INTEGER REFERENCES customers(id),
settings JSONB NOT NULL DEFAULT '{}',
PRIMARY KEY (customer_id)
);
-- Inserir JSON
INSERT INTO customer_settings (customer_id, settings)
VALUES (1, '{"theme": "dark", "language": "pt-BR", "notifications": true}');
-- Consultar campo específico
SELECT settings->>'theme' AS theme FROM customer_settings WHERE customer_id = 1;
-- Filtrar por valor JSON
SELECT * FROM customer_settings WHERE settings @> '{"theme": "dark"}';
-- Índice GIN para busca rápida em JSONB
CREATE INDEX idx_settings_gin ON customer_settings USING GIN (settings);
In Delphi:
{ Inserir JSONB }
LQuery.SQL.Text :=
'INSERT INTO customer_settings (customer_id, settings) ' +
'VALUES (:customer_id, :settings::jsonb)';
LQuery.ParamByName('customer_id').AsInteger := ACustomerId;
LQuery.ParamByName('settings').AsString := AJsonString;
LQuery.ExecSQL;
{ Ler campo JSONB }
LQuery.SQL.Text :=
'SELECT settings->>''theme'' AS theme ' +
'FROM customer_settings WHERE customer_id = :id';
LQuery.ParamByName('id').AsInteger := ACustomerId;
LQuery.Open;
LTheme := LQuery.FieldByName('theme').AsString;
Full-Text Search (FTS)
-- Coluna tsvector para busca textual
ALTER TABLE products ADD COLUMN search_vector TSVECTOR;
-- Trigger para atualizar automaticamente
CREATE OR REPLACE FUNCTION update_search_vector() RETURNS TRIGGER AS $$
BEGIN
NEW.search_vector :=
setweight(to_tsvector('portuguese', COALESCE(NEW.name, '')), 'A') ||
setweight(to_tsvector('portuguese', COALESCE(NEW.description, '')), 'B');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_product_search BEFORE INSERT OR UPDATE
ON products FOR EACH ROW EXECUTE FUNCTION update_search_vector();
-- Índice GIN para FTS
CREATE INDEX idx_product_search ON products USING GIN (search_vector);
-- Buscar
SELECT * FROM products
WHERE search_vector @@ plainto_tsquery('portuguese', 'camisa azul')
ORDER BY ts_rank(search_vector, plainto_tsquery('portuguese', 'camisa azul')) DESC;
In Delphi:
function TProductRepository.Search(const ASearchTerm: string): TObjectList;
var
LQuery: TFDQuery;
begin
Result := TObjectList.Create(True);
LQuery := TFDQuery.Create(nil);
try
LQuery.Connection := FConnection;
LQuery.SQL.Text :=
'SELECT id, name, price, description ' +
'FROM products ' +
'WHERE search_vector @@ plainto_tsquery(''portuguese'', :term) ' +
'ORDER BY ts_rank(search_vector, plainto_tsquery(''portuguese'', :term)) DESC ' +
'LIMIT :limit';
LQuery.ParamByName('term').AsString := ASearchTerm;
LQuery.ParamByName('limit').AsInteger := 50;
LQuery.Open;
while not LQuery.Eof do
begin
Result.Add(MapToProduct(LQuery));
LQuery.Next;
end;
finally
LQuery.Free;
end;
end;
CTEs (Common Table Expressions)
-- CTE para queries complexas e legíveis
WITH active_customers AS (
SELECT id, name, email
FROM customers
WHERE status = 0
),
customer_orders AS (
SELECT customer_id, COUNT(*) AS total_orders, SUM(total_amount) AS total_spent
FROM orders
GROUP BY customer_id
)
SELECT ac.name, ac.email, co.total_orders, co.total_spent
FROM active_customers ac
LEFT JOIN customer_orders co ON co.customer_id = ac.id
ORDER BY co.total_spent DESC NULLS LAST;
Recursive CTE
-- Hierarquia de categorias
WITH RECURSIVE category_tree AS (
SELECT id, name, parent_id, 0 AS level
FROM categories
WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.name, c.parent_id, ct.level + 1
FROM categories c
JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT * FROM category_tree ORDER BY level, name;
WindowFunctions
-- Ranking de clientes por valor gasto
SELECT
c.name,
SUM(o.total_amount) AS total_spent,
RANK() OVER (ORDER BY SUM(o.total_amount) DESC) AS ranking,
ROW_NUMBER() OVER (ORDER BY SUM(o.total_amount) DESC) AS row_num,
SUM(o.total_amount) / SUM(SUM(o.total_amount)) OVER () * 100 AS percent_total
FROM customers c
JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name;
-- Média móvel de vendas por dia
SELECT
order_date::DATE AS day,
SUM(total_amount) AS daily_total,
AVG(SUM(total_amount)) OVER (ORDER BY order_date::DATE ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS moving_avg_7d
FROM orders
GROUP BY order_date::DATE
ORDER BY day;
Functions (PL/pgSQL)
-- Function que retorna valor (equivale a function no Delphi)
CREATE OR REPLACE FUNCTION fn_customer_full_name(p_id INTEGER)
RETURNS VARCHAR AS $$
DECLARE
v_name VARCHAR;
BEGIN
SELECT name INTO v_name FROM customers WHERE id = p_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Customer % not found', p_id;
END IF;
RETURN v_name;
END;
$$ LANGUAGE plpgsql;
-- Function que retorna tabela (equivale a Selectable Procedure no Firebird)
CREATE OR REPLACE FUNCTION fn_customers_by_status(p_status SMALLINT)
RETURNS TABLE (
o_id INTEGER,
o_name VARCHAR,
o_email VARCHAR,
o_status SMALLINT
) AS $$
BEGIN
RETURN QUERY
SELECT id, name, email, status
FROM customers
WHERE status = p_status
ORDER BY name;
END;
$$ LANGUAGE plpgsql;
-- Procedure (PG 11+ — sem retorno, apenas ação)
CREATE OR REPLACE PROCEDURE sp_deactivate_customer(p_id INTEGER)
LANGUAGE plpgsql AS $$
BEGIN
UPDATE customers SET status = 1, updated_at = NOW() WHERE id = p_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Customer % not found', p_id;
END IF;
END;
$$;
Call in Delphi:
{ Function escalar }
LQuery.SQL.Text := 'SELECT fn_customer_full_name(:id)';
LQuery.ParamByName('id').AsInteger := ACustomerId;
LQuery.Open;
LFullName := LQuery.Fields[0].AsString;
{ Function que retorna table (como SELECT) }
LQuery.SQL.Text := 'SELECT * FROM fn_customers_by_status(:status)';
LQuery.ParamByName('status').AsSmallInt := Ord(csActive);
LQuery.Open;
{ Procedure (PG 11+) }
LQuery.SQL.Text := 'CALL sp_deactivate_customer(:id)';
LQuery.ParamByName('id').AsInteger := ACustomerId;
LQuery.ExecSQL;
ENUM Types
-- Tipo enum nativo do PostgreSQL
CREATE TYPE order_status AS ENUM ('pending', 'processing', 'shipped', 'delivered', 'cancelled');
CREATE TABLE orders (
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [delphicleancode](https://github.com/delphicleancode)
- **Source:** [delphicleancode/delphi-spec-kit](https://github.com/delphicleancode/delphi-spec-kit)
- **License:** MIT
- **Homepage:** https://inovefast.com.br
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.