Saltar al contenido principal

Ejecutar el SQL en tu base de datos

Datafala devuelve el SQL; quien lo ejecuta es tu sistema. Esta página reúne lo que debe estar correcto antes de pasar a producción.

1. Usa un usuario de solo lectura​

El SQL Guard ya garantiza un único SELECT sobre las tablas registradas, pero el usuario de base de datos de solo lectura es tu última línea de defensa. Concede acceso solo a las tablas que registraste en el panel y configura un tiempo máximo por consulta, porque el Guard limita filas, no costo (un join grande entre tablas permitidas es válido).

PostgreSQL:

CREATE ROLE datafala_ro LOGIN PASSWORD '...';
GRANT CONNECT ON DATABASE meu_banco TO datafala_ro;
GRANT USAGE ON SCHEMA public TO datafala_ro;
GRANT SELECT ON public.pedidos, public.clientes, public.produtos TO datafala_ro;
ALTER ROLE datafala_ro SET statement_timeout = '5s';
ALTER ROLE datafala_ro SET default_transaction_read_only = on;

MySQL:

CREATE USER 'datafala_ro'@'%' IDENTIFIED BY '...';
GRANT SELECT ON meu_banco.pedidos TO 'datafala_ro'@'%';
GRANT SELECT ON meu_banco.clientes TO 'datafala_ro'@'%';
GRANT SELECT ON meu_banco.produtos TO 'datafala_ro'@'%';
-- Tiempo máximo por SELECT, en milisegundos (por sesión, al abrir la conexión):
SET SESSION max_execution_time = 5000;

Firebird 2.5+:

CREATE USER DATAFALA_RO PASSWORD '...';
GRANT SELECT ON TABLE PEDIDOS TO USER DATAFALA_RO;
GRANT SELECT ON TABLE CLIENTES TO USER DATAFALA_RO;

Concede acceso solo a las tablas registradas en el panel. Configura cada conexión para iniciar transacciones READ ONLY; en FDB, por ejemplo:

import fdb

con = fdb.connect(dsn="localhost:/dados/app.fdb", user="DATAFALA_RO", password="...")
con.begin(fdb.ISOLATION_LEVEL_READ_COMMITED_RO)

Firebird 2.5 y 3.0 no ofrecen timeout de sentencia en el servidor: usa el timeout o la cancelación que ofrece el driver y tu cliente. En Firebird 4.0 o más reciente, también puedes configurar SET STATEMENT TIMEOUT 5 SECOND para limitar la duración de la consulta.

2. Pasa siempre params​

El SQL usa placeholders con nombre al estilo %(nombre)s y los valores llegan en params:

{
"sql": "SELECT nome FROM clientes WHERE cidade = %(city)s AND nome LIKE 'A%%' LIMIT 100",
"params": { "city": "Campinas" }
}

Los drivers de Python (psycopg, psycopg2, PyMySQL, mysqlclient) aceptan este formato directamente y aplican el formateo de % a todo el texto. Por eso todo % que no es placeholder llega escapado como %%. Para Firebird, FDB (2.5+) y firebird-driver (3+) usan ?; convierte los placeholders antes de ejecutar.

Nunca ejecutes sin params

Pasa params incluso cuando sea {}, nunca None. Sin params el driver no formatea el SQL, y el %% llegaría a la base de datos como dos caracteres. Y nunca concatenes texto del usuario al SQL: los valores ya llegan separados.

3. Convierte los placeholders en otros lenguajes​

En otros drivers, convierte %(nombre)s al formato del driver y %% de vuelta a %. FDB, firebird-driver, Jaybird y node-firebird usan ? para parámetros posicionales; FireDAC en Delphi acepta parámetros con nombre como :name. Recorre el texto de izquierda a derecha reconociendo los dos patrones juntos, para que un %% seguido de paréntesis dentro de un literal no se confunda con un placeholder:

// %(name)s -> $1, $2... (PostgreSQL) o ? (MySQL/Firebird); %% -> %
export function toPositional(sql, params, style = "pg") {
const values = [];
const indexes = new Map();
const text = sql.replace(/%%|%\(([A-Za-z_][A-Za-z0-9_]*)\)s/g, (match, name) => {
if (match === "%%") return "%";
if (!(name in params)) throw new Error(`missing param ${name}`);
if (style === "pg") {
if (!indexes.has(name)) {
values.push(params[name]);
indexes.set(name, values.length);
}
return `$${indexes.get(name)}`;
}
values.push(params[name]); // "?" repite el valor en cada aparición
return "?";
});
return { text, values };
}

Ejemplos completos en Node.js y otros lenguajes.

4. Limita también de tu lado​

El Guard ya aplica LIMIT (por defecto 100, techo 1.000, ajustable con max_rows), salvo en agregaciones que devuelven una sola fila. Aun así, usa fetchmany o el equivalente para no cargar más de lo que muestra tu pantalla.

Alcance de datos por usuario o empresa​

Datafala genera el SQL a partir del schema, no de quien pregunta. SELECT SUM(total) FROM pedidos suma los pedidos de todas las empresas si la tabla tiene varias. Si tu sistema es multiempresa o tiene permisos por fila, elige una de estas defensas antes de habilitarlo para usuarios con acceso parcial:

  1. Row Level Security en PostgreSQL (recomendada). Activa RLS en las tablas expuestas, con una política que compara la columna de la empresa con una variable de sesión, y define la variable en la misma transacción de la consulta:

    ALTER TABLE pedidos ENABLE ROW LEVEL SECURITY;
    CREATE POLICY pedidos_por_empresa ON pedidos
    USING (empresa_id = current_setting('app.empresa_id')::int);
    with conn.transaction():
    conn.execute("SELECT set_config('app.empresa_id', %s, true)", [str(user.empresa_id)])
    rows = conn.execute(answer["sql"], answer["params"]).fetchall()

    El usuario datafala_ro no puede tener BYPASSRLS ni ser dueño de las tablas.

  2. Vistas por empresa. Registra en el panel el DDL de vistas ya filtradas, en lugar de las tablas.

  3. Uso restringido. Habilita la funcionalidad solo para quienes ya pueden ver todos los datos (por ejemplo, gerentes).