Pular para o conteúdo principal

Outras linguagens

A regra é a mesma em qualquer linguagem: chame /v1/ask pelo seu backend, converta %(nome)s para o formato do seu driver numa única passada (trocando %% por %) e execute com um usuário somente leitura. No Firebird, o destino posicional é ?.

LinguagemDriverPlaceholder de destino
PHPPDO:nome (nomeado)
JavaJDBC / Jaybird? (posicional)
C#Npgsql / MySqlConnector@nome (nomeado)
Gopgx / database/sql$1 (PostgreSQL) ou ? (MySQL e drivers Firebird com placeholders posicionais)
PythonFDB (Firebird 2.5+) / firebird-driver (Firebird 3+)? (posicional)
Node.jsnode-firebird? (posicional)
DelphiFireDAC:nome (nomeado), veja Delphi

O padrão a reconhecer é %% ou %\(([A-Za-z_][A-Za-z0-9_]*)\)s. Para drivers Firebird posicionais, emita um ? e acrescente o valor correspondente para cada ocorrência do parâmetro; remova um dos % de cada %%.

PHP (PDO)​

<?php
function datafala_ask(string $question, string|int $userId): array {
$ch = curl_init('https://app.datafala.com.br/v1/ask');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DATAFALA_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'question' => $question,
'end_user' => hash_hmac('sha256', (string) $userId, getenv('DATAFALA_END_USER_SECRET')),
]),
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status !== 200) {
throw new RuntimeException($body['code'] ?? "http_$status");
}
return $body;
}

// %(name)s -> :name ; %% -> %
function datafala_to_pdo(string $sql): string {
return preg_replace_callback('/%%|%\(([A-Za-z_][A-Za-z0-9_]*)\)s/', function ($m) {
return $m[0] === '%%' ? '%' : ':' . $m[1];
}, $sql);
}

$answer = datafala_ask('Pedidos por status neste mês', $user->id);
// Com o mesmo placeholder repetido no SQL, ligue PDO::ATTR_EMULATE_PREPARES no PDO.
$stmt = $readonlyPdo->prepare(datafala_to_pdo($answer['sql']));
$stmt->execute($answer['params']);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

Java (JDBC)​

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

record Positional(String sql, List<Object> values) {}

final class DatafalaSql {
private static final Pattern TOKEN = Pattern.compile("%%|%\\(([A-Za-z_][A-Za-z0-9_]*)\\)s");

// %(name)s -> ? ; %% -> %
static Positional toJdbc(String sql, Map<String, Object> params) {
Matcher m = TOKEN.matcher(sql);
StringBuilder out = new StringBuilder();
List<Object> values = new ArrayList<>();
while (m.find()) {
if (m.group().equals("%%")) {
m.appendReplacement(out, "%");
} else {
values.add(params.get(m.group(1)));
m.appendReplacement(out, "?");
}
}
m.appendTail(out);
return new Positional(out.toString(), values);
}
}
Positional q = DatafalaSql.toJdbc(answer.sql(), answer.params());
try (PreparedStatement stmt = readonlyConnection.prepareStatement(q.sql())) {
stmt.setQueryTimeout(5);
for (int i = 0; i < q.values().size(); i++) {
stmt.setObject(i + 1, q.values().get(i));
}
try (ResultSet rs = stmt.executeQuery()) {
// ...
}
}

C# (.NET)​

using System.Text.RegularExpressions;

static class DatafalaSql
{
static readonly Regex Token = new(@"%%|%\(([A-Za-z_][A-Za-z0-9_]*)\)s");

// %(name)s -> @name ; %% -> %
public static string ToAdoNet(string sql) =>
Token.Replace(sql, m => m.Value == "%%" ? "%" : "@" + m.Groups[1].Value);
}
await using var cmd = new NpgsqlCommand(DatafalaSql.ToAdoNet(answer.Sql), readonlyConnection);
cmd.CommandTimeout = 5;
foreach (var (name, value) in answer.Params)
cmd.Parameters.AddWithValue(name, value ?? DBNull.Value);
await using var reader = await cmd.ExecuteReaderAsync();

Go​

var token = regexp.MustCompile(`%%|%\(([A-Za-z_][A-Za-z0-9_]*)\)s`)

// ToPositional converts %(name)s to $1, $2... (postgres) or ? (mysql / Firebird qmark drivers) and %% to %.
func ToPositional(sql string, params map[string]any, dialect string) (string, []any) {
var values []any
index := map[string]int{}
out := token.ReplaceAllStringFunc(sql, func(match string) string {
if match == "%%" {
return "%"
}
name := token.FindStringSubmatch(match)[1]
if dialect == "postgres" {
if _, ok := index[name]; !ok {
values = append(values, params[name])
index[name] = len(values)
}
return fmt.Sprintf("$%d", index[name])
}
values = append(values, params[name])
return "?"
})
return out, values
}
query, args := ToPositional(answer.SQL, answer.Params, answer.Dialect)
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
rows, err := readonlyDB.QueryContext(ctx, query, args...)