MySQL - Banco de Dados Relacional Popular
Visão Geral
MySQL é o sistema de gerenciamento de banco de dados relacional open-source mais popular do mundo, utilizado por milhões de aplicações web. O Lumina Flow Builder oferece integração completa com MySQL através de 5 operações principais.
Operações Disponíveis
1. executeQuery - Consultas SQL Personalizadas
Executa qualquer consulta SQL personalizada (SELECT, CREATE, ALTER, etc).
Código interno (mysql.executor.ts:94-103):
private async executeQuery(connection: Connection, query: string, context: ExecutionContext): Promise<any> {
const replacedQuery = this.replaceVariables(query, context.variables);
const [rows, fields] = await connection.query(replacedQuery);
return {
rows: Array.isArray(rows) ? rows : [rows],
rowCount: Array.isArray(rows) ? rows.length : 1,
fields: fields,
};
}
Exemplo:
{
"operation": "executeQuery",
"config": {
"host": "localhost",
"port": 3306,
"database": "ecommerce",
"user": "app_user",
"password": "senha123",
"ssl": false
},
"query": "SELECT * FROM clientes WHERE cidade = '{{cidade}}' ORDER BY nome LIMIT 10",
"responseVariable": "clientes"
}
2. insert - Inserir Registro
Insere novo registro na tabela usando prepared statements com placeholders ?.
Código interno (mysql.executor.ts:105-124):
private async insertRow(
connection: Connection,
table: string,
values: Record<string, any>,
context: ExecutionContext,
): Promise<any> {
const replacedValues = this.replaceObjectVariables(values, context.variables);
const columns = Object.keys(replacedValues).join(', ');
const placeholders = Object.keys(replacedValues).map(() => '?').join(', ');
const valueArray = Object.values(replacedValues);
const query = `INSERT INTO ${table} (${columns}) VALUES (${placeholders})`;
const [result] = await connection.query(query, valueArray);
return {
insertId: (result as any).insertId,
affectedRows: (result as any).affectedRows,
};
}
Diferença do PostgreSQL: MySQL retorna insertId (não insertedRow completo)
Exemplo:
{
"operation": "insert",
"table": "clientes",
"values": {
"nome": "{{nome}}",
"email": "{{email}}",
"telefone": "{{telefone}}",
"data_cadastro": "NOW()"
},
"responseVariable": "resultado"
}
3. update - Atualizar Registro
Atualiza registros existentes baseado em condições WHERE.
Código interno (mysql.executor.ts:126-153):
private async updateRow(
connection: Connection,
table: string,
values: Record<string, any>,
conditions: Record<string, any>,
context: ExecutionContext,
): Promise<any> {
const replacedValues = this.replaceObjectVariables(values, context.variables);
const replacedConditions = this.replaceObjectVariables(conditions, context.variables);
const setClause = Object.keys(replacedValues)
.map(key => `${key} = ?`)
.join(', ');
const whereClause = Object.keys(replacedConditions)
.map(key => `${key} = ?`)
.join(' AND ');
const valueArray = [...Object.values(replacedValues), ...Object.values(replacedConditions)];
const query = `UPDATE ${table} SET ${setClause} WHERE ${whereClause}`;
const [result] = await connection.query(query, valueArray);
return {
affectedRows: (result as any).affectedRows,
changedRows: (result as any).changedRows,
};
}
Diferença do PostgreSQL: MySQL retorna affectedRows e changedRows (não registros atualizados)
Exemplo:
{
"operation": "update",
"table": "pedidos",
"values": {
"status": "enviado",
"data_envio": "NOW()"
},
"conditions": {
"id": "{{pedidoId}}",
"status": "pendente"
},
"responseVariable": "resultado"
}
4. delete - Deletar Registro
Remove registros da tabela baseado em condições WHERE.
Código interno (mysql.executor.ts:155-176):
private async deleteRow(
connection: Connection,
table: string,
conditions: Record<string, any>,
context: ExecutionContext,
): Promise<any> {
const replacedConditions = this.replaceObjectVariables(conditions, context.variables);
const whereClause = Object.keys(replacedConditions)
.map(key => `${key} = ?`)
.join(' AND ');
const valueArray = Object.values(replacedConditions);
const query = `DELETE FROM ${table} WHERE ${whereClause}`;
const [result] = await connection.query(query, valueArray);
return {
affectedRows: (result as any).affectedRows,
deleted: (result as any).affectedRows > 0,
};
}
Exemplo:
{
"operation": "delete",
"table": "sessoes",
"conditions": {
"user_id": "{{userId}}",
"expires_at": "< NOW()"
},
"responseVariable": "resultado"
}
5. insertOrUpdate (Upsert) - Inserir ou Atualizar
Insere registro se não existe, atualiza se já existe usando ON DUPLICATE KEY UPDATE.
Código interno (mysql.executor.ts:178-205):
private async upsertRow(
connection: Connection,
table: string,
values: Record<string, any>,
context: ExecutionContext,
): Promise<any> {
const replacedValues = this.replaceObjectVariables(values, context.variables);
const columns = Object.keys(replacedValues).join(', ');
const placeholders = Object.keys(replacedValues).map(() => '?').join(', ');
const updateClause = Object.keys(replacedValues)
.map(key => `${key} = VALUES(${key})`)
.join(', ');
const valueArray = Object.values(replacedValues);
const query = `
INSERT INTO ${table} (${columns})
VALUES (${placeholders})
ON DUPLICATE KEY UPDATE ${updateClause}
`;
const [result] = await connection.query(query, valueArray);
return {
insertId: (result as any).insertId,
affectedRows: (result as any).affectedRows,
};
}
Diferença do PostgreSQL:
- MySQL usa ON DUPLICATE KEY UPDATE (não ON CONFLICT)
- Não precisa especificar conflictKeys (usa chave UNIQUE/PK automaticamente)
- Usa VALUES(campo) para referenciar valores inseridos
Exemplo:
{
"operation": "insertOrUpdate",
"table": "produtos",
"values": {
"sku": "{{sku}}",
"nome": "{{nome}}",
"preco": "{{preco}}",
"atualizado_em": "NOW()"
},
"responseVariable": "produto"
}
Configuração de Conexão
{
"host": "localhost",
"port": 3306,
"database": "nome_database",
"user": "usuario",
"password": "senha",
"ssl": false
}
Principais Diferenças do PostgreSQL
| Aspecto | MySQL | PostgreSQL |
|---|---|---|
| Placeholders | ? |
$1, $2, $3 |
| INSERT retorno | insertId (número) |
insertedRow (objeto completo) |
| UPDATE retorno | affectedRows, changedRows |
updatedRows (array), rowCount |
| UPSERT sintaxe | ON DUPLICATE KEY UPDATE |
ON CONFLICT ... DO UPDATE |
| UPSERT config | Detecta UNIQUE/PK automaticamente | Requer conflictKeys |
| AUTO_INCREMENT | AUTO_INCREMENT |
SERIAL ou IDENTITY |
| Funções data | NOW(), CURDATE() |
CURRENT_TIMESTAMP, NOW() |
| Strings | Case-insensitive por padrão | Case-sensitive |
| Limite | LIMIT 10 |
LIMIT 10 |
| Tipos JSON | JSON | JSON, JSONB (binário) |
Tipos de Dados Comuns
| Tipo | Descrição | Exemplo |
|---|---|---|
| INT | Número inteiro | 42 |
| BIGINT | Inteiro grande | 9223372036854775807 |
| DECIMAL(p,s) | Decimal preciso | DECIMAL(10,2) |
| VARCHAR(n) | Texto variável | VARCHAR(255) |
| TEXT | Texto longo | TEXT |
| TINYINT(1) | Boolean | 0, 1 |
| DATE | Data | '2025-01-15' |
| DATETIME | Data e hora | '2025-01-15 14:30:00' |
| TIMESTAMP | Timestamp | '2025-01-15 14:30:00' |
| JSON | Objeto JSON | '{"nome": "João"}' |
| ENUM | Lista de valores | ENUM('ativo', 'inativo') |
Exemplo Flow Completo
{
"name": "MySQL - Sistema de Cadastro",
"nodes": [
{
"id": "start_1",
"type": "start",
"position": { "x": 100, "y": 100 },
"data": { "label": "Início" }
},
{
"id": "email_1",
"type": "email",
"position": { "x": 300, "y": 100 },
"data": {
"label": "Email",
"parameters": {
"message": "Seu email:",
"variable": "email"
}
}
},
{
"id": "input_1",
"type": "input",
"position": { "x": 500, "y": 100 },
"data": {
"label": "Nome",
"parameters": {
"message": "Seu nome:",
"variable": "nome"
}
}
},
{
"id": "mysql_1",
"type": "mysql",
"position": { "x": 700, "y": 100 },
"data": {
"label": "Salvar Cliente",
"parameters": {
"operation": "insert",
"config": {
"host": "localhost",
"port": 3306,
"database": "crm",
"user": "app_user",
"password": "senha123",
"ssl": false
},
"table": "clientes",
"values": {
"nome": "{{nome}}",
"email": "{{email}}",
"data_cadastro": "NOW()",
"ativo": 1
},
"responseVariable": "cliente"
}
}
},
{
"id": "message_1",
"type": "message",
"position": { "x": 900, "y": 100 },
"data": {
"label": "Sucesso",
"parameters": {
"message": "Cliente cadastrado!\n\nID: {{cliente.insertId}}\nNome: {{nome}}\nEmail: {{email}}"
}
}
},
{
"id": "end_1",
"type": "end",
"position": { "x": 1100, "y": 100 },
"data": { "label": "Fim" }
}
],
"edges": [
{ "source": "start_1", "target": "email_1" },
{ "source": "email_1", "target": "input_1" },
{ "source": "input_1", "target": "mysql_1" },
{ "source": "mysql_1", "target": "message_1" },
{ "source": "message_1", "target": "end_1" }
]
}
Boas Práticas MySQL
✅ SIM: - Use InnoDB como engine (suporta transações) - Defina índices em colunas de WHERE e JOIN - Use prepared statements (implementado automaticamente) - Configure charset utf8mb4 para emojis - Use LIMIT em queries grandes - Normalize dados (3FN)
❌ NÃO: - Não use MyISAM em produção (sem transações) - Não use SELECT * desnecessariamente - Não faça queries em loops - Não ignore índices - Não exponha credenciais - Não delete/update sem WHERE
Recursos de Aprendizado
Troubleshooting
"Access denied for user"
- Verifique usuário e senha
- Confirme permissões do usuário:
GRANT ALL ON database.* TO 'user'@'host' - Teste com:
mysql -u user -p
"Unknown database"
- Database não existe
- Crie com:
CREATE DATABASE nome_database
"Table doesn't exist"
- Tabela não existe ou nome incorreto
- MySQL é case-sensitive em Linux
- Liste tabelas:
SHOW TABLES
Próximos Passos
→ PostgreSQL - Alternativa mais avançada → Microsoft SQL Server - Solução enterprise → MongoDB - Banco NoSQL