L'interfaccia è progettata per essere semplice e performante, usando template literal con tag per le query e offrendo funzionalità come connection pooling, transazioni e prepared statements.
import { sql, SQL } from "bun";
// PostgreSQL (predefinito)
const users = await sql`
SELECT * FROM users
WHERE active = ${true}
LIMIT ${10}
`;
// Con MySQL
const mysql = new SQL("mysql://user:pass@localhost:3306/mydb");
const mysqlResults = await mysql`
SELECT * FROM users
WHERE active = ${true}
`;
// Con SQLite
const sqlite = new SQL("sqlite://myapp.db");
const sqliteResults = await sqlite`
SELECT * FROM users
WHERE active = ${1}
`;Caratteristiche
- Template literal con tag per proteggere contro SQL injection
- Transazioni
- Parametri nominali e posizionali
- Connection pooling
- Supporto
BigInt - Supporto autenticazione SASL (SCRAM-SHA-256), MD5 e Clear Text
- Timeout di connessione
- Restituzione di righe come oggetti dati, array di array o Buffer
- Supporto protocollo binario per maggiore velocità
- Supporto TLS (e modalità auth)
- Configurazione automatica con variabili d'ambiente
Supporto Database
Bun.SQL fornisce un'API unificata per più sistemi di database:
PostgreSQL
PostgreSQL è usato quando:
- La stringa di connessione non corrisponde ai pattern SQLite o MySQL (è l'adapter di fallback)
- La stringa di connessione usa esplicitamente i protocolli
postgres://opostgresql:// - Non viene fornita alcuna stringa di connessione e le variabili d'ambiente puntano a PostgreSQL
import { sql } from "bun";
// Usa PostgreSQL se DATABASE_URL non è impostato o è un URL PostgreSQL
await sql`SELECT ...`;
import { SQL } from "bun";
const pg = new SQL("postgres://user:pass@localhost:5432/mydb");
await pg`SELECT ...`;MySQL
Il supporto MySQL è integrato in Bun.SQL, fornendo la stessa interfaccia con template literal con tag con piena compatibilità per MySQL 5.7+ e MySQL 8.0+:
import { SQL } from "bun";
// Connessione MySQL
const mysql = new SQL("mysql://user:password@localhost:3306/database");
const mysql2 = new SQL("mysql2://user:password@localhost:3306/database"); // anche il protocollo mysql2 funziona
// Usando un oggetto opzioni
const mysql3 = new SQL({
adapter: "mysql",
hostname: "localhost",
port: 3306,
database: "myapp",
username: "dbuser",
password: "secretpass",
});
// Funziona con parametri - usa automaticamente prepared statements
const users = await mysql`SELECT * FROM users WHERE id = ${userId}`;
// Le transazioni funzionano come PostgreSQL
await mysql.begin(async tx => {
await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = ${userId}`;
});
// Insert multipli
const newUsers = [
{ name: "Alice", email: "alice@example.com" },
{ name: "Bob", email: "bob@example.com" },
];
await mysql`INSERT INTO users ${mysql(newUsers)}`;Formati stringhe di connessione MySQL">
MySQL accetta vari formati URL per le stringhe di connessione:
// Protocollo standard mysql://
new SQL("mysql://user:pass@localhost:3306/database");
new SQL("mysql://user:pass@localhost/database"); // Porta predefinita 3306
// Protocollo mysql2:// (compatibilità con pacchetto npm mysql2)
new SQL("mysql2://user:pass@localhost:3306/database");
// Con parametri di query
new SQL("mysql://user:pass@localhost/db?ssl=true");
// Connessione socket Unix
new SQL("mysql://user:pass@/database?socket=/var/run/mysqld/mysqld.sock");Caratteristiche specifiche di MySQL">
I database MySQL supportano:
- Prepared statements: Creati automaticamente per query parametrizzate con caching delle statement
- Protocollo binario: Per migliori prestazioni con prepared statements e gestione accurata dei tipi
- Set di risultati multipli: Supporto per stored procedure che restituiscono set di risultati multipli
- Plugin di autenticazione: Supporto per mysql_native_password, caching_sha2_password (predefinito MySQL 8.0) e sha256_password
- Connessioni SSL/TLS: Modalità SSL configurabili simili a PostgreSQL
- Attributi di connessione: Informazioni client inviate al server per il monitoraggio
- Query pipelining: Esegui multiple prepared statements senza attendere le risposte
SQLite
Il supporto SQLite è integrato in Bun.SQL, fornendo la stessa interfaccia con template literal con tag:
import { SQL } from "bun";
// Database in memoria
const memory = new SQL(":memory:");
const memory2 = new SQL("sqlite://:memory:");
// Database su file
const sql1 = new SQL("sqlite://myapp.db");
// Usando un oggetto opzioni
const sql2 = new SQL({
adapter: "sqlite",
filename: "./data/app.db",
});
// Per nomi file semplici, specifica l'adapter esplicitamente
const sql3 = new SQL("myapp.db", { adapter: "sqlite" });Formati stringhe di connessione SQLite">
SQLite accetta vari formati URL per le stringhe di connessione:
// Protocollo standard sqlite://
new SQL("sqlite://path/to/database.db");
new SQL("sqlite:path/to/database.db"); // Senza slash
// Protocollo file:// (anche riconosciuto come SQLite)
new SQL("file://path/to/database.db");
new SQL("file:path/to/database.db");
// Database speciale :memory:
new SQL(":memory:");
new SQL("sqlite://:memory:");
new SQL("file://:memory:");
// Percorsi relativi e assoluti
new SQL("sqlite://./local.db"); // Relativo alla directory corrente
new SQL("sqlite://../parent/db.db"); // Directory superiore
new SQL("sqlite:///absolute/path.db"); // Percorso assoluto
// Con parametri di query
new SQL("sqlite://data.db?mode=ro"); // Modalità sola lettura
new SQL("sqlite://data.db?mode=rw"); // Modalità lettura-scrittura (no creazione)
new SQL("sqlite://data.db?mode=rwc"); // Modalità lettura-scrittura-creazione (predefinita)<Note>
I nomi file semplici senza protocollo (come `"myapp.db"`) richiedono di specificare esplicitamente `{ adapter: "sqlite" }` per evitare ambiguità con PostgreSQL.
</Note>
Opzioni specifiche di SQLite">
I database SQLite supportano opzioni di configurazione aggiuntive:
const sql = new SQL({
adapter: "sqlite",
filename: "app.db",
// Opzioni specifiche di SQLite
readonly: false, // Apri in modalità sola lettura
create: true, // Crea il database se non esiste
readwrite: true, // Apri per lettura e scrittura
// Opzioni aggiuntive Bun:sqlite
strict: true, // Abilita modalità strict
safeIntegers: false, // Usa numeri JavaScript per gli interi
});I parametri di query nell'URL vengono analizzati per impostare queste opzioni:
?mode=ro→readonly: true?mode=rw→readonly: false, create: false?mode=rwc→readonly: false, create: true(predefinito)
Inserimento dati
Puoi passare valori JavaScript direttamente al template literal SQL e l'escaping verrà gestito per te.
import { sql } from "bun";
// Insert base con valori diretti
const [user] = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
// Usando l'helper oggetto per una sintassi più pulita
const userData = {
name: "Alice",
email: "alice@example.com",
};
const [newUser] = await sql`
INSERT INTO users ${sql(userData)}
RETURNING *
`;
// Si espande a: INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')Insert multiplo
Puoi anche passare array di oggetti al template literal SQL e verrà espanso in una statement INSERT INTO ... VALUES ....
const users = [
{ name: "Alice", email: "alice@example.com" },
{ name: "Bob", email: "bob@example.com" },
{ name: "Charlie", email: "charlie@example.com" },
];
await sql`INSERT INTO users ${sql(users)}`;Scegliere le colonne da inserire
Puoi usare sql(object, ...string) per scegliere quali colonne inserire. Ognuna delle colonne deve essere definita sull'oggetto.
const user = {
name: "Alice",
email: "alice@example.com",
age: 25,
};
await sql`INSERT INTO users ${sql(user, "name", "email")}`;
// Inserisce solo le colonne name e email, ignorando altri campiRisultati delle Query
Per impostazione predefinita, il client SQL di Bun restituisce i risultati delle query come array di oggetti, dove ogni oggetto rappresenta una riga con i nomi delle colonne come chiavi. Tuttavia, ci sono casi in cui potresti volere i dati in un formato diverso. Il client fornisce due metodi aggiuntivi per questo scopo.
Formato sql``.values()
Il metodo sql``.values() restituisce le righe come array di valori invece che oggetti. Ogni riga diventa un array dove i valori sono nello stesso ordine delle colonne nella tua query.
const rows = await sql`SELECT * FROM users`.values();
console.log(rows);Questo restituisce qualcosa come:
[
["Alice", "alice@example.com"],
["Bob", "bob@example.com"],
];sql``.values() è particolarmente utile se nella query vengono restituiti nomi di colonne duplicati. Quando si usano oggetti (predefinito), l'ultimo nome di colonna è usato come chiave nell'oggetto, il che significa che i nomi di colonne duplicati si sovrascrivono a vicenda — ma quando si usa sql``.values(), ogni colonna è presente nell'array quindi puoi accedere ai valori delle colonne duplicate per indice.
Formato sql``.raw()
Il metodo .raw() restituisce le righe come array di oggetti Buffer. Questo può essere utile per lavorare con dati binari o per motivi di prestazioni.
const rows = await sql`SELECT * FROM users`.raw();
console.log(rows); // [[Buffer, Buffer], [Buffer, Buffer], [Buffer, Buffer]]Frammenti SQL
Un bisogno comune nelle applicazioni database è la capacità di costruire query dinamicamente in base alle condizioni runtime. Bun fornisce modi sicuri per farlo senza rischiare SQL injection.
Nomi di tabelle dinamici
Quando hai bisogno di riferirti a tabelle o schema dinamicamente, usa l'helper sql() per garantire un escaping corretto:
// Riferisciti in sicurezza alle tabelle dinamicamente
await sql`SELECT * FROM ${sql("users")}`;
// Con qualifica dello schema
await sql`SELECT * FROM ${sql("public.users")}`;Query condizionali
Puoi usare l'helper sql() per costruire query con clausole condizionali. Questo ti permette di creare query flessibili che si adattano alle esigenze della tua applicazione:
// Clausole WHERE opzionali
const filterAge = true;
const minAge = 21;
const ageFilter = sql`AND age > ${minAge}`;
await sql`
SELECT * FROM users
WHERE active = ${true}
${filterAge ? ageFilter : sql``}
`;Colonne dinamiche negli update
Puoi usare sql(object, ...string) per scegliere quali colonne aggiornare. Ognuna delle colonne deve essere definita sull'oggetto. Se le colonne non sono informate tutte le chiavi saranno usate per aggiornare la riga.
await sql`UPDATE users SET ${sql(user, "name", "email")} WHERE id = ${user.id}`;
// usa tutte le chiavi dall'oggetto per aggiornare la riga
await sql`UPDATE users SET ${sql(user)} WHERE id = ${user.id}`;Valori dinamici e where in
Anche le liste di valori possono essere create dinamicamente, rendendo semplici anche le query where in. Opzionalmente puoi passare un array di oggetti e informare quale chiave usare per creare la lista.
await sql`SELECT * FROM users WHERE id IN ${sql([1, 2, 3])}`;
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
];
await sql`SELECT * FROM users WHERE id IN ${sql(users, "id")}`;Helper sql.array
L'helper sql.array crea literal di array PostgreSQL da array JavaScript:
// Crea literal di array per PostgreSQL
await sql`INSERT INTO tags (items) VALUES (${sql.array(["red", "blue", "green"])})`;
// Genera: INSERT INTO tags (items) VALUES (ARRAY['red', 'blue', 'green'])
// Funziona anche con array numerici
await sql`SELECT * FROM products WHERE ids = ANY(${sql.array([1, 2, 3])})`;
// Genera: SELECT * FROM products WHERE ids = ANY(ARRAY[1, 2, 3])NOTE
`sql.array` è solo per PostgreSQL. Array multidimensionali e elementi NULL potrebbero non essere ancora supportati.sql``.simple()
Il protocollo wire di PostgreSQL supporta due tipi di query: "simple" ed "extended". Le query simple possono contenere multiple statement ma non supportano parametri, mentre le query extended (predefinite) supportano parametri ma permettono solo una statement.
Per eseguire multiple statement in una singola query, usa sql``.simple():
// Multiple statement in una query
await sql`
SELECT 1;
SELECT 2;
`.simple();Le query simple sono spesso utili per migrazioni di database e script di setup.
Nota che le query simple non possono usare parametri (${value}). Se hai bisogno di parametri, devi dividere la tua query in statement separate.
Query in file
Puoi usare il metodo sql.file per leggere una query da un file ed eseguirla, se il file include $1, $2, ecc puoi passare parametri alla query. Se non vengono usati parametri può eseguire multiple comandi per file.
const result = await sql.file("query.sql", [1, 2, 3]);Query unsafe
Puoi usare la funzione sql.unsafe per eseguire stringhe SQL raw. Usa questo con cautela, poiché non eseguirà l'escape dell'input utente. Eseguire più di un comando per query è permesso se non vengono usati parametri.
// Multiple comandi senza parametri
const result = await sql.unsafe(`
SELECT ${userColumns} FROM users;
SELECT ${accountColumns} FROM accounts;
`);
// Usando parametri (solo un comando è permesso)
const result = await sql.unsafe("SELECT " + dangerous + " FROM users WHERE id = $1", [id]);Eseguire e annullare query
Il SQL di Bun è lazy, il che significa che inizierà a eseguire solo quando awaited o eseguito con .execute(). Puoi annullare una query attualmente in esecuzione chiamando il metodo cancel() sull'oggetto query.
const query = sql`SELECT * FROM users`.execute();
setTimeout(() => query.cancel(), 100);
await query;Variabili d'ambiente Database
I parametri di connessione sql possono essere configurati usando variabili d'ambiente. Il client controlla queste variabili in un ordine specifico di precedenza e rileva automaticamente il tipo di database in base al formato della stringa di connessione.
Rilevamento automatico del database
Quando usi Bun.sql() senza argomenti o new SQL() con una stringa di connessione, l'adapter viene rilevato automaticamente in base al formato URL:
Rilevamento automatico MySQL
MySQL è selezionato automaticamente quando la stringa di connessione corrisponde a questi pattern:
mysql://...- URL protocollo MySQLmysql2://...- URL protocollo MySQL2 (alias di compatibilità)
// Questi usano tutti MySQL automaticamente (nessun adapter necessario)
const sql1 = new SQL("mysql://user:pass@localhost/mydb");
const sql2 = new SQL("mysql2://user:pass@localhost:3306/mydb");
// Funziona con la variabile d'ambiente DATABASE_URL
DATABASE_URL="mysql://user:pass@localhost/mydb" bun run app.js
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb" bun run app.jsRilevamento automatico SQLite
SQLite è selezionato automaticamente quando la stringa di connessione corrisponde a questi pattern:
:memory:- Database in memoriasqlite://...- URL protocollo SQLitesqlite:...- Protocollo SQLite senza slashfile://...- URL protocollo filefile:...- Protocollo file senza slash
// Questi usano tutti SQLite automaticamente (nessun adapter necessario)
const sql1 = new SQL(":memory:");
const sql2 = new SQL("sqlite://app.db");
const sql3 = new SQL("file://./database.db");
// Funziona con la variabile d'ambiente DATABASE_URL
DATABASE_URL=":memory:" bun run app.js
DATABASE_URL="sqlite://myapp.db" bun run app.js
DATABASE_URL="file://./data/app.db" bun run app.jsRilevamento automatico PostgreSQL
PostgreSQL è il predefinito per le stringhe di connessione che non corrispondono ai pattern MySQL o SQLite:
# PostgreSQL è rilevato per questi pattern
DATABASE_URL="postgres://user:pass@localhost:5432/mydb" bun run app.js
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" bun run app.js
# O qualsiasi URL che non corrisponde ai pattern MySQL o SQLite
DATABASE_URL="localhost:5432/mydb" bun run app.jsVariabili d'ambiente MySQL
Le connessioni MySQL possono essere configurate tramite variabili d'ambiente:
# URL di connessione primario (controllato per primo)
MYSQL_URL="mysql://user:pass@localhost:3306/mydb"
# Alternativa: DATABASE_URL con protocollo MySQL
DATABASE_URL="mysql://user:pass@localhost:3306/mydb"
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb"Se non viene fornito alcun URL di connessione, MySQL controlla questi parametri individuali:
| Variabile d'ambiente | Valore predefinito | Descrizione |
|---|---|---|
MYSQL_HOST | localhost | Host del database |
MYSQL_PORT | 3306 | Porta del database |
MYSQL_USER | root | Utente del database |
MYSQL_PASSWORD | (vuoto) | Password del database |
MYSQL_DATABASE | mysql | Nome del database |
MYSQL_URL | (vuoto) | URL di connessione primario MySQL |
TLS_MYSQL_DATABASE_URL | (vuoto) | URL connessione abilitata SSL/TLS |
Variabili d'ambiente PostgreSQL
Le seguenti variabili d'ambiente possono essere usate per definire la connessione PostgreSQL:
| Variabile d'ambiente | Descrizione |
|---|---|
POSTGRES_URL | URL di connessione primario per PostgreSQL |
DATABASE_URL | URL di connessione alternativo (rilevato automaticamente) |
PGURL | URL di connessione alternativo |
PG_URL | URL di connessione alternativo |
TLS_POSTGRES_DATABASE_URL | URL connessione abilitata SSL/TLS |
TLS_DATABASE_URL | URL connessione abilitata SSL/TLS alternativo |
Se non viene fornito alcun URL di connessione, il sistema controlla i seguenti parametri individuali:
| Variabile d'ambiente | Variabili di fallback | Valore predefinito | Descrizione |
|---|---|---|---|
PGHOST | - | localhost | Host del database |
PGPORT | - | 5432 | Porta del database |
PGUSERNAME | PGUSER, USER, USERNAME | postgres | Utente del database |
PGPASSWORD | - | (vuoto) | Password del database |
PGDATABASE | - | username | Nome del database |
Variabili d'ambiente SQLite
Le connessioni SQLite possono essere configurate tramite DATABASE_URL quando contiene un URL compatibile con SQLite:
# Questi sono tutti riconosciuti come SQLite
DATABASE_URL=":memory:"
DATABASE_URL="sqlite://./app.db"
DATABASE_URL="file:///absolute/path/to/db.sqlite"Nota: Le variabili d'ambiente specifiche di PostgreSQL (POSTGRES_URL, PGHOST, ecc.) vengono ignorate quando si usa SQLite.
Preconnessione Runtime
Bun può preconnettersi a PostgreSQL all'avvio per migliorare le prestazioni stabilendo connessioni al database prima che il codice della tua applicazione venga eseguito. Questo è utile per ridurre la latenza di connessione sulla prima query al database.
# Abilita preconnessione PostgreSQL
bun --sql-preconnect index.js
# Funziona con la variabile d'ambiente DATABASE_URL
DATABASE_URL=postgres://user:pass@localhost:5432/db bun --sql-preconnect index.js
# Può essere combinato con altri flag runtime
bun --sql-preconnect --hot index.jsIl flag --sql-preconnect stabilirà automaticamente una connessione PostgreSQL usando le tue variabili d'ambiente configurate all'avvio. Se la connessione fallisce, non crasherà la tua applicazione - l'errore verrà gestito in modo elegante.
Opzioni di connessione
Puoi configurare manualmente la tua connessione al database passando opzioni al costruttore SQL. Le opzioni variano a seconda dell'adapter del database:
Opzioni MySQL
import { SQL } from "bun";
const sql = new SQL({
// Richiesto per MySQL quando si usa l'oggetto opzioni
adapter: "mysql",
// Dettagli connessione
hostname: "localhost",
port: 3306,
database: "myapp",
username: "dbuser",
password: "secretpass",
// Connessione socket Unix (alternativa a hostname/port)
// socket: "/var/run/mysqld/mysqld.sock",
// Impostazioni connection pool
max: 20, // Connessioni massime nel pool (predefinito: 10)
idleTimeout: 30, // Chiudi connessioni inattive dopo 30s
maxLifetime: 0, // Durata connessione in secondi (0 = per sempre)
connectionTimeout: 30, // Timeout durante la creazione di nuove connessioni
// Opzioni SSL/TLS
ssl: "prefer", // o "disable", "require", "verify-ca", "verify-full"
// tls: {
// rejectUnauthorized: true,
// ca: "path/to/ca.pem",
// key: "path/to/key.pem",
// cert: "path/to/cert.pem",
// },
// Callback
onconnect: client => {
console.log("Connesso a MySQL");
},
onclose: (client, err) => {
if (err) {
console.error("Errore connessione MySQL:", err);
} else {
console.log("Connessione MySQL chiusa");
}
},
});Opzioni PostgreSQL
import { SQL } from "bun";
const sql = new SQL({
// Dettagli connessione (l'adapter è rilevato automaticamente come PostgreSQL)
url: "postgres://user:pass@localhost:5432/dbname",
// Parametri di connessione alternativi
hostname: "localhost",
port: 5432,
database: "myapp",
username: "dbuser",
password: "secretpass",
// Impostazioni connection pool
max: 20, // Connessioni massime nel pool
idleTimeout: 30, // Chiudi connessioni inattive dopo 30s
maxLifetime: 0, // Durata connessione in secondi (0 = per sempre)
connectionTimeout: 30, // Timeout durante la creazione di nuove connessioni
// Opzioni SSL/TLS
tls: true,
// tls: {
// rejectUnauthorized: true,
// requestCert: true,
// ca: "path/to/ca.pem",
// key: "path/to/key.pem",
// cert: "path/to/cert.pem",
// checkServerIdentity(hostname, cert) {
// ...
// },
// },
// Callback
onconnect: client => {
console.log("Connesso a PostgreSQL");
},
onclose: client => {
console.log("Connessione PostgreSQL chiusa");
},
});Opzioni SQLite
import { SQL } from "bun";
const sql = new SQL({
// Richiesto per SQLite
adapter: "sqlite",
filename: "./data/app.db", // o ":memory:" per database in memoria
// Modalità di accesso specifiche di SQLite
readonly: false, // Apri in modalità sola lettura
create: true, // Crea il database se non esiste
readwrite: true, // Consenti operazioni di lettura e scrittura
// Gestione dati SQLite
strict: true, // Abilita modalità strict per maggiore sicurezza dei tipi
safeIntegers: false, // Usa BigInt per interi che superano l'intervallo numeri JS
// Callback
onconnect: client => {
console.log("Database SQLite aperto");
},
onclose: client => {
console.log("Database SQLite chiuso");
},
});Note sulla connessione SQLite">
- Connection Pooling: SQLite non usa connection pooling poiché è un database basato su file. Ogni istanza
SQLrappresenta una singola connessione. - Transazioni: SQLite supporta transazioni annidate tramite savepoint, simile a PostgreSQL.
- Accesso concorrente: SQLite gestisce l'accesso concorrente tramite locking dei file. Usa la modalità WAL per una migliore concorrenza.
- Database in memoria: Usare
:memory:crea un database temporaneo che esiste solo per la durata della connessione.
Password dinamiche
Quando i client hanno bisogno di usare schemi di autenticazione alternativi come token di accesso o connessioni a database con password rotanti, fornisci una funzione sincrona o asincrona che risolverà il valore della password dinamica al momento della connessione.
import { SQL } from "bun";
const sql = new SQL(url, {
// Altra configurazione connessione
...
// Funzione password per l'utente del database
password: async () => await signer.getAuthToken(),
});Caratteristiche specifiche di SQLite
Esecuzione query
SQLite esegue le query in modo sincrono, a differenza di PostgreSQL che usa I/O asincrono. Tuttavia, l'API rimane coerente usando Promise:
const sqlite = new SQL("sqlite://app.db");
// Funziona come PostgreSQL, ma esegue in modo sincrono sotto il cofano
const users = await sqlite`SELECT * FROM users`;
// I parametri funzionano in modo identico
const user = await sqlite`SELECT * FROM users WHERE id = ${userId}`;Pragmi SQLite
Puoi usare statement PRAGMA per configurare il comportamento di SQLite:
const sqlite = new SQL("sqlite://app.db");
// Abilita chiavi esterne
await sqlite`PRAGMA foreign_keys = ON`;
// Imposta la modalità journal su WAL per una migliore concorrenza
await sqlite`PRAGMA journal_mode = WAL`;
// Controlla l'integrità
const integrity = await sqlite`PRAGMA integrity_check`;Differenze nei tipi di dati
SQLite ha un sistema di tipi più flessibile di PostgreSQL:
// SQLite memorizza i dati in 5 classi di archiviazione: NULL, INTEGER, REAL, TEXT, BLOB
const sqlite = new SQL("sqlite://app.db");
// SQLite è più flessibile con i tipi
await sqlite`
CREATE TABLE flexible (
id INTEGER PRIMARY KEY,
data TEXT, -- Può memorizzare numeri come stringhe
value NUMERIC, -- Può memorizzare interi, reali o testo
blob BLOB -- Dati binari
)
`;
// I valori JavaScript vengono convertiti automaticamente
await sqlite`INSERT INTO flexible VALUES (${1}, ${"text"}, ${123.45}, ${Buffer.from("binary")})`;Transazioni
Per avviare una nuova transazione, usa sql.begin. Questo metodo funziona sia per PostgreSQL che per SQLite. Per PostgreSQL, riserva una connessione dedicata dal pool. Per SQLite, inizia una transazione sulla singola connessione.
Il comando BEGIN viene inviato automaticamente, incluse eventuali configurazioni opzionali che specifichi. Se si verifica un errore durante la transazione, viene attivato un ROLLBACK per garantire che il processo continui senza problemi.
Transazioni base
await sql.begin(async tx => {
// Tutte le query in questa funzione vengono eseguite in una transazione
await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`;
// La transazione viene automaticamente commessa se non vengono lanciati errori
// Rollback se si verifica un errore
});È anche possibile pipeline le richieste in una transazione se necessario restituendo un array con le query dalla funzione callback così:
await sql.begin(async tx => {
return [
tx`INSERT INTO users (name) VALUES (${"Alice"})`,
tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`,
];
});Savepoint
I savepoint in SQL creano checkpoint intermedi all'interno di una transazione, consentendo rollback parziali senza influenzare l'intera operazione. Sono utili in transazioni complesse, consentendo il recupero dagli errori e mantenendo risultati coerenti.
await sql.begin(async tx => {
await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
await tx.savepoint(async sp => {
// Questa parte può essere sottoposta a rollback separatamente
await sp`UPDATE users SET status = 'active'`;
if (someCondition) {
throw new Error("Rollback al savepoint");
}
});
// Continua con la transazione anche se il savepoint è stato sottoposto a rollback
await tx`INSERT INTO audit_log (action) VALUES ('user_created')`;
});Transazioni distribuite
Two-Phase Commit (2PC) è un protocollo di transazione distribuita dove la Fase 1 ha il coordinatore che prepara i nodi assicurando che i dati siano scritti e pronti per il commit, mentre la Fase 2 finalizza con i nodi che eseguono il commit o il rollback in base alla decisione del coordinatore. Questo processo garantisce la durabilità dei dati e una corretta gestione dei lock.
In PostgreSQL e MySQL, le transazioni distribuite persistono oltre la loro sessione originale, consentendo a utenti privilegiati o coordinatori di eseguire il commit o il rollback successivamente. Questo supporta transazioni distribuite robuste, processi di recupero e operazioni amministrative.
Ogni sistema di database implementa le transazioni distribuite in modo diverso:
PostgreSQL le supporta nativamente tramite transazioni preparate, mentre MySQL usa le transazioni XA.
Se si verificano eccezioni durante la transazione distribuita e non vengono catturate, il sistema eseguirà automaticamente il rollback di tutte le modifiche. Quando tutto procede normalmente, mantieni la flessibilità di eseguire il commit o il rollback della transazione successivamente.
// Inizia una transazione distribuita
await sql.beginDistributed("tx1", async tx => {
await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
});
// Successivamente, commit o rollback
await sql.commitDistributed("tx1");
// oppure
await sql.rollbackDistributed("tx1");Autenticazione
Bun supporta l'autenticazione SCRAM-SHA-256 (SASL), MD5 e Clear Text. SASL è raccomandato per una migliore sicurezza. Consulta Autenticazione SASL di Postgres per maggiori informazioni.
Panoramica modalità SSL
PostgreSQL supporta diverse modalità SSL/TLS per controllare come vengono stabilite le connessioni sicure. Queste modalità determinano il comportamento durante la connessione e il livello di verifica del certificato eseguito.
const sql = new SQL({
hostname: "localhost",
username: "user",
password: "password",
ssl: "disable", // | "prefer" | "require" | "verify-ca" | "verify-full"
});| Modalità SSL | Descrizione |
|---|---|
disable | Nessun SSL/TLS usato. Le connessioni falliscono se il server richiede SSL. |
prefer | Prova prima SSL, ripiega su non-SSL se SSL fallisce. Modalità predefinita se non specificata. |
require | Richiede SSL senza verifica del certificato. Fallisce se SSL non può essere stabilito. |
verify-ca | Verifica che il certificato del server sia firmato da una CA attendibile. Fallisce se la verifica fallisce. |
verify-full | Modalità più sicura. Verifica certificato e corrispondenza hostname. Protegge contro certificati non attendibili e attacchi MITM. |
Uso con stringhe di connessione
La modalità SSL può anche essere specificata nelle stringhe di connessione:
// Usando la modalità prefer
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=prefer");
// Usando la modalità verify-full
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=verify-full");Connection Pooling
Il client SQL di Bun gestisce automaticamente un connection pool, che è un pool di connessioni al database riutilizzate per multiple query. Questo aiuta a ridurre l'overhead di stabilire e chiudere connessioni per ogni query e aiuta anche a gestire il numero di connessioni concorrenti al database.
const sql = new SQL({
// Configurazione pool
max: 20, // Massimo 20 connessioni concorrenti
idleTimeout: 30, // Chiudi connessioni inattive dopo 30s
maxLifetime: 3600, // Durata massima connessione 1 ora
connectionTimeout: 10, // Timeout connessione 10s
});Nessuna connessione verrà fatta finché non viene fatta una query.
const sql = Bun.SQL(); // nessuna connessione viene creata
await sql`...`; // il pool viene avviato fino a max raggiunto (se possibile), viene usata la prima connessione disponibile
await sql`...`; // la connessione precedente viene riutilizzata
// due connessioni sono usate ora allo stesso tempo
await Promise.all([
sql`INSERT INTO users ${sql({ name: "Alice" })}`,
sql`UPDATE users SET name = ${user.name} WHERE id = ${user.id}`,
]);
await sql.close(); // attendi che tutte le query finiscano e chiudi tutte le connessioni dal pool
await sql.close({ timeout: 5 }); // attendi 5 secondi e chiudi tutte le connessioni dal pool
await sql.close({ timeout: 0 }); // chiudi tutte le connessioni dal pool immediatamenteConnessioni riservate
Bun ti permette di riservare una connessione dal pool e restituisce un client che incapsula la singola connessione. Questo può essere usato per eseguire query su una connessione isolata.
// Ottieni connessione esclusiva dal pool
const reserved = await sql.reserve();
try {
await reserved`INSERT INTO users (name) VALUES (${"Alice"})`;
} finally {
// Importante: Rilascia connessione al pool
reserved.release();
}
// O usando Symbol.dispose
{
using reserved = await sql.reserve();
await reserved`SELECT 1`;
} // Rilasciato automaticamentePrepared Statements
Per impostazione predefinita, il client SQL di Bun crea automaticamente prepared statement nominati per le query dove si può dedurre che la query è statica. Questo fornisce migliori prestazioni. Tuttavia, puoi cambiare questo comportamento impostando prepare: false nelle opzioni di connessione:
const sql = new SQL({
// ... altre opzioni ...
prepare: false, // Disabilita persistenza prepared statement nominati sul server
});Quando prepare: false è impostato:
Le query vengono ancora eseguite usando il protocollo "extended", ma vengono eseguite usando prepared statement senza nome, un prepared statement senza nome dura solo finché non viene emessa la prossima statement Parse che specifica la statement senza nome come destinazione.
- Il binding dei parametri è comunque sicuro contro SQL injection
- Ogni query viene analizzata e pianificata da zero dal server
- Le query non verranno pipelined
Potresti voler usare prepare: false quando:
- Usi PGBouncer in modalità transazione (anche se dalla versione 1.21.0 di PGBouncer, i prepared statement a livello di protocollo sono supportati se configurati correttamente)
- Debug di piani di esecuzione query
- Lavori con SQL dinamico dove i piani di query devono essere rigenerati frequentemente
- Più di un comando per query non sarà supportato (a meno che non usi
sql``.simple())
Nota che disabilitare i prepared statement potrebbe influire sulle prestazioni per le query eseguite frequentemente con parametri diversi, poiché il server deve analizzare e pianificare ogni query da zero.
Gestione errori
Il client fornisce errori tipizzati per diversi scenari di fallimento. Gli errori sono specifici del database ed estendono classi di errori base:
Classi di errore
import { SQL } from "bun";
try {
await sql`SELECT * FROM users`;
} catch (error) {
if (error instanceof SQL.PostgresError) {
// Errore specifico di PostgreSQL
console.log(error.code); // Codice errore PostgreSQL
console.log(error.detail); // Messaggio di errore dettagliato
console.log(error.hint); // Suggerimento utile da PostgreSQL
} else if (error instanceof SQL.SQLiteError) {
// Errore specifico di SQLite
console.log(error.code); // Codice errore SQLite (es. "SQLITE_CONSTRAINT")
console.log(error.errno); // Numero errore SQLite
console.log(error.byteOffset); // Offset byte nella statement SQL (se disponibile)
} else if (error instanceof SQL.SQLError) {
// Errore SQL generico (classe base)
console.log(error.message);
}
}Codici di errore specifici di PostgreSQL">
Errori di connessione PostgreSQL
| Errori di connessione | Descrizione |
|---|---|
ERR_POSTGRES_CONNECTION_CLOSED | La connessione è stata terminata o non è mai stata stabilita |
ERR_POSTGRES_CONNECTION_TIMEOUT | Fallimento stabilimento connessione entro il timeout |
ERR_POSTGRES_IDLE_TIMEOUT | Connessione chiusa per inattività |
ERR_POSTGRES_LIFETIME_TIMEOUT | Connessione ha superato la durata massima |
ERR_POSTGRES_TLS_NOT_AVAILABLE | Connessione SSL/TLS non disponibile |
ERR_POSTGRES_TLS_UPGRADE_FAILED | Fallimento upgrade connessione a SSL/TLS |
Errori di autenticazione
| Errori di autenticazione | Descrizione |
|---|---|
ERR_POSTGRES_AUTHENTICATION_FAILED_PBKDF2 | Autenticazione password fallita |
ERR_POSTGRES_UNKNOWN_AUTHENTICATION_METHOD | Il server ha richiesto metodo auth sconosciuto |
ERR_POSTGRES_UNSUPPORTED_AUTHENTICATION_METHOD | Il server ha richiesto metodo auth non supportato |
ERR_POSTGRES_INVALID_SERVER_KEY | Chiave server non valida durante autenticazione |
ERR_POSTGRES_INVALID_SERVER_SIGNATURE | Firma server non valida |
ERR_POSTGRES_SASL_SIGNATURE_INVALID_BASE64 | Codifica firma SASL non valida |
ERR_POSTGRES_SASL_SIGNATURE_MISMATCH | Verifica firma SASL fallita |
Errori di query
| Errori di query | Descrizione |
|---|---|
ERR_POSTGRES_SYNTAX_ERROR | Sintassi SQL non valida (estende SyntaxError) |
ERR_POSTGRES_SERVER_ERROR | Errore generale dal server PostgreSQL |
ERR_POSTGRES_INVALID_QUERY_BINDING | Binding parametri non valido |
ERR_POSTGRES_QUERY_CANCELLED | Query è stata annullata |
ERR_POSTGRES_NOT_TAGGED_CALL | Query è stata chiamata senza tagged call |
Errori di tipo di dati
| Errori di tipo di dati | Descrizione |
|---|---|
ERR_POSTGRES_INVALID_BINARY_DATA | Formato dati binari non valido |
ERR_POSTGRES_INVALID_BYTE_SEQUENCE | Sequenza byte non valida |
ERR_POSTGRES_INVALID_BYTE_SEQUENCE_FOR_ENCODING | Errore di codifica |
ERR_POSTGRES_INVALID_CHARACTER | Carattere non valido nei dati |
ERR_POSTGRES_OVERFLOW | Overflow numerico |
ERR_POSTGRES_UNSUPPORTED_BYTEA_FORMAT | Formato binario non supportato |
ERR_POSTGRES_UNSUPPORTED_INTEGER_SIZE | Dimensione intero non supportata |
ERR_POSTGRES_MULTIDIMENSIONAL_ARRAY_NOT_SUPPORTED_YET | Array multidimensionali non supportati |
ERR_POSTGRES_NULLS_IN_ARRAY_NOT_SUPPORTED_YET | Valori NULL negli array non supportati |
Errori di protocollo
| Errori di protocollo | Descrizione |
|---|---|
ERR_POSTGRES_EXPECTED_REQUEST | Richiesta client attesa |
ERR_POSTGRES_EXPECTED_STATEMENT | Prepared statement atteso |
ERR_POSTGRES_INVALID_BACKEND_KEY_DATA | Dati chiave backend non validi |
ERR_POSTGRES_INVALID_MESSAGE | Messaggio protocollo non valido |
ERR_POSTGRES_INVALID_MESSAGE_LENGTH | Lunghezza messaggio non valida |
ERR_POSTGRES_UNEXPECTED_MESSAGE | Tipo messaggio inatteso |
Errori di transazione
| Errori di transazione | Descrizione |
|---|---|
ERR_POSTGRES_UNSAFE_TRANSACTION | Operazione transazione non sicura rilevata |
ERR_POSTGRES_INVALID_TRANSACTION_STATE | Stato transazione non valido |
Errori specifici di SQLite
Gli errori SQLite forniscono codici di errore e numeri che corrispondono ai codici di errore standard di SQLite:
Codici di errore SQLite comuni">
| Codice errore | errno | Descrizione |
|---|---|---|
SQLITE_CONSTRAINT | 19 | Violazione vincolo (UNIQUE, CHECK, NOT NULL, ecc.) |
SQLITE_BUSY | 5 | Database è bloccato |
SQLITE_LOCKED | 6 | Tabella nel database è bloccata |
SQLITE_READONLY | 8 | Tentativo di scrittura su database di sola lettura |
SQLITE_IOERR | 10 | Errore I/O disco |
SQLITE_CORRUPT | 11 | Immagine disco database è danneggiata |
SQLITE_FULL | 13 | Database o disco è pieno |
SQLITE_CANTOPEN | 14 | Impossibile aprire il file del database |
SQLITE_PROTOCOL | 15 | Errore protocollo lock database |
SQLITE_SCHEMA | 17 | Schema del database è cambiato |
SQLITE_TOOBIG | 18 | Stringa o BLOB supera il limite dimensione |
SQLITE_MISMATCH | 20 | Mancata corrispondenza tipo di dati |
SQLITE_MISUSE | 21 | Libreria usata in modo errato |
SQLITE_AUTH | 23 | Autorizzazione negata |
Esempio gestione errori:
const sqlite = new SQL("sqlite://app.db");
try {
await sqlite`INSERT INTO users (id, name) VALUES (1, 'Alice')`;
await sqlite`INSERT INTO users (id, name) VALUES (1, 'Bob')`; // ID duplicato
} catch (error) {
if (error instanceof SQL.SQLiteError) {
if (error.code === "SQLITE_CONSTRAINT") {
console.log("Violazione vincolo:", error.message);
// Gestisci violazione vincolo unique
}
}
}Numeri e BigInt
Il client SQL di Bun include una gestione speciale per numeri grandi che superano l'intervallo di un intero a 53 bit. Ecco come funziona:
import { sql } from "bun";
const [{ x, y }] = await sql`SELECT 9223372036854777 as x, 12345 as y`;
console.log(typeof x, x); // "string" "9223372036854777"
console.log(typeof y, y); // "number" 12345BigInt invece di stringhe
Se hai bisogno di numeri grandi come BigInt invece di stringhe, puoi abilitarlo impostando l'opzione bigint su true quando inizializzi il client SQL:
const sql = new SQL({
bigint: true,
});
const [{ x }] = await sql`SELECT 9223372036854777 as x`;
console.log(typeof x, x); // "bigint" 9223372036854777nRoadmap
Ci sono ancora alcune cose che non abbiamo finito.
- Precaricamento connessioni tramite flag CLI
--db-preconnect - Trasformazioni nomi colonne (es.
snake_caseacamelCase). Questo è per lo più bloccato su un'implementazione unicode-aware per cambiare il case in C++ usandoWTF::Stringdi WebKit. - Trasformazioni tipi di colonne
Caratteristiche specifiche del database
Metodi di autenticazione
MySQL supporta multiple plugin di autenticazione che vengono negoziati automaticamente:
mysql_native_password- Autenticazione MySQL tradizionale, ampiamente compatibilecaching_sha2_password- Predefinito in MySQL 8.0+, più sicuro con scambio chiavi RSAsha256_password- Autenticazione basata su SHA-256
Il client gestisce automaticamente il cambio plugin di autenticazione quando richiesto dal server, incluso lo scambio sicuro di password su connessioni non-SSL.
Prepared Statements e prestazioni
MySQL usa prepared statement lato server per tutte le query parametrizzate:
// Questo crea automaticamente un prepared statement sul server
const user = await mysql`SELECT * FROM users WHERE id = ${userId}`;
// I prepared statement vengono cached e riutilizzati per query identiche
for (const id of userIds) {
// Lo stesso prepared statement viene riutilizzato
await mysql`SELECT * FROM users WHERE id = ${id}`;
}
// Query pipelining - multiple statement inviate senza attendere risposte
const [users, orders, products] = await Promise.all([
mysql`SELECT * FROM users WHERE active = ${true}`,
mysql`SELECT * FROM orders WHERE status = ${"pending"}`,
mysql`SELECT * FROM products WHERE in_stock = ${true}`,
]);Set di risultati multipli
MySQL può restituire set di risultati multipli da query multi-statement:
const mysql = new SQL("mysql://user:pass@localhost/mydb");
// Query multi-statement con metodo simple()
const multiResults = await mysql`
SELECT * FROM users WHERE id = 1;
SELECT * FROM orders WHERE user_id = 1;
`.simple();Set di caratteri e collazioni
Bun.SQL usa automaticamente il set di caratteri utf8mb4 per le connessioni MySQL, garantendo pieno supporto Unicode inclusi emoji. Questo è il set di caratteri raccomandato per applicazioni MySQL moderne.
Attributi di connessione
Bun invia automaticamente informazioni client a MySQL per un migliore monitoraggio:
// Questi attributi vengono inviati automaticamente:
// _client_name: "Bun"
// _client_version: <versione bun>
// Puoi vederli in performance_schema.session_connect_attrs di MySQLGestione tipi
I tipi MySQL vengono automaticamente convertiti in tipi JavaScript:
| Tipo MySQL | Tipo JavaScript | Note |
|---|---|---|
| INT, TINYINT, MEDIUMINT | number | Entro intervallo integer sicuro |
| BIGINT | string, number o BigInt | Se il valore rientra in i32/u32 sarà number altrimenti string o BigInt in base all'opzione bigint |
| DECIMAL, NUMERIC | string | Per preservare la precisione |
| FLOAT, DOUBLE | number | |
| DATE | Date | Oggetto Date JavaScript |
| DATETIME, TIMESTAMP | Date | Con gestione fuso orario |
| TIME | number | Totale di microsecondi |
| YEAR | number | |
| CHAR, VARCHAR, VARSTRING, STRING | string | |
| TINY TEXT, MEDIUM TEXT, TEXT, LONG TEXT | string | |
| TINY BLOB, MEDIUM BLOB, BLOG, LONG BLOB | string | I tipi BLOB sono alias per i tipi TEXT |
| JSON | object/array | Analizzato automaticamente |
| BIT(1) | boolean | BIT(1) in MySQL |
| GEOMETRY | string | Dati geometria |
Differenze da PostgreSQL
Sebbene l'API sia unificata, ci sono alcune differenze comportamentali:
- Placeholder parametri: MySQL usa
?internamente ma Bun converte automaticamente in stile$1, $2 - Clausola RETURNING: MySQL non supporta RETURNING; usa
result.lastInsertRowido un SELECT separato - Tipi array: MySQL non ha tipi array nativi come PostgreSQL
Caratteristiche specifiche di MySQL
Non abbiamo ancora implementato il supporto per LOAD DATA INFILE
Caratteristiche specifiche di PostgreSQL
Non abbiamo ancora implementato:
- Supporto
COPY - Supporto
LISTEN - Supporto
NOTIFY
Non abbiamo anche implementato alcune delle funzionalità più uncommon come:
- Autenticazione GSSAPI
- Supporto
SCRAM-SHA-256-PLUS - Tipi Point e PostGIS
- Tutti i tipi di array integer multidimensionali (solo alcuni tipi sono supportati)
Pattern comuni e best practice
Lavorare con set di risultati MySQL
// Ottenere ID insert dopo INSERT
const result = await mysql`INSERT INTO users (name) VALUES (${"Alice"})`;
console.log(result.lastInsertRowid); // LAST_INSERT_ID() di MySQL
// Gestire righe interessate
const updated = await mysql`UPDATE users SET active = ${false} WHERE age < ${18}`;
console.log(updated.affectedRows); // Numero di righe aggiornate
// Usare funzioni specifiche di MySQL
const now = await mysql`SELECT NOW() as current_time`;
const uuid = await mysql`SELECT UUID() as id`;Gestione errori MySQL
try {
await mysql`INSERT INTO users (email) VALUES (${"duplicate@email.com"})`;
} catch (error) {
if (error.code === "ER_DUP_ENTRY") {
console.log("Voce duplicata rilevata");
} else if (error.code === "ER_ACCESS_DENIED_ERROR") {
console.log("Accesso negato");
} else if (error.code === "ER_BAD_DB_ERROR") {
console.log("Il database non esiste");
}
// I codici errore MySQL sono compatibili con i pacchetti mysql/mysql2
}Consigli prestazioni per MySQL
- Usa connection pooling: Imposta dimensione pool
maxappropriata in base al tuo carico di lavoro - Abilita prepared statements: Sono abilitati di default e migliorano le prestazioni
- Usa transazioni per operazioni bulk: Raggruppa query correlate in transazioni
- Indicizza correttamente: MySQL si affida molto agli indici per le prestazioni delle query
- Usa set di caratteri
utf8mb4: È impostato di default e gestisce tutti i caratteri Unicode
Domande frequenti
Perché questo è `Bun.sql` e non `Bun.postgres`?
Il piano era aggiungere più driver di database in futuro. Ora con il supporto MySQL aggiunto, questa API unificata supporta PostgreSQL, MySQL e SQLite.
Come faccio a sapere quale adapter database viene usato?
L'adapter viene rilevato automaticamente dalla stringa di connessione:
- URL che iniziano con `mysql://` o `mysql2://` usano MySQL
- URL che corrispondono ai pattern SQLite (`:memory:`, `sqlite://`, `file://`) usano SQLite
- Tutto il resto usa PostgreSQL di default
Sono supportate le stored procedure MySQL?">
Sì, le stored procedure sono pienamente supportate inclusi parametri OUT e set di risultati multipli:
```ts
// Chiama stored procedure
const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;
// Ottieni parametro OUT
const outParam = await mysql`SELECT @total_orders as total`;
```
Posso usare sintassi SQL specifica di MySQL?">
Sì, puoi usare qualsiasi sintassi specifica di MySQL:
```ts
// La sintassi specifica di MySQL funziona bene
await mysql`SET @user_id = ${userId}`;
await mysql`SHOW TABLES`;
await mysql`DESCRIBE users`;
await mysql`EXPLAIN SELECT * FROM users WHERE id = ${id}`;
```
Perché non usare semplicemente una libreria esistente?
Pacchetti npm come postgres.js, pg e node-postgres possono essere usati anche in Bun. Sono ottime opzioni.
Due motivi:
- Pensiamo che sia più semplice per gli sviluppatori avere un driver di database integrato in Bun. Il tempo che passi a cercare librerie è tempo che potresti usare per costruire la tua app.
- Sfruttiamo alcuni interni del motore JavaScriptCore per rendere più veloce creare oggetti che sarebbero difficili da implementare in una libreria
Ringraziamenti
Un enorme ringraziamento a @porsager per postgres.js per l'ispirazione per l'interfaccia API.