---
id: 134c150de56c188c925476664026a1ff
author: Lemon Mint
title: 'gosuda.org/sqlite3: driver database/sql con CGO_ENABLED=0 basato su IPC a memoria condivisa'
description: Driver database/sql per SQLite 3.54.1 compatibile con Go, utilizzabile in sostituzione diretta, con CGO_ENABLED=0, isolamento dei worker tramite memoria condivisa HQQ, prefetch delle righe in batch da 64 KiB e binari con attestazione SLSA.
language: it
date: 2026-10-18T14:02:16.803040007Z
path: /sqlite3
go_package: gosuda.org/sqlite3
go_repourl: https://github.com/gosuda/hqq-sqlite3.git
category: database
---

`gosuda.org/sqlite3` è un driver `database/sql` per Go con `CGO_ENABLED=0` che esegue l'amalgama C di SQLite `3.54.1` upstream all'interno di un processo worker isolato, comunicando tramite gli anelli di memoria condivisa di [HQQ](https://gosuda.org/hqq). Si registra con il nome standard del driver `"sqlite3"`, supporta i parametri di query del DSN di `github.com/mattn/go-sqlite3`, incorpora binari server precompilati con attestazione SLSA per sei target di sistema operativo e architettura, e utilizza il pipelining delle richieste con prefetch di batch da 64 KiB per superare `cgo` in-process nelle query a più righe.

```bash
go get gosuda.org/sqlite3@v0.3.3540100
```

## Perché un Altro Driver SQLite per Go?

SQLite è una delle componenti software più diffuse al mondo, eppure il suo utilizzo da Go ha storicamente costretto gli ingegneri a una scelta scomoda tra semplicità di build e fedeltà del motore.

Da un lato si colloca `github.com/mattn/go-sqlite3`, il tradizionale standard della comunità. Esso integra direttamente l'amalgama C ufficiale di SQLite tramite `cgo`. Si ottengono il vero query planner C, la piena semantica di concorrenza WAL e una gestione della memoria C prevedibile. Il costo è `CGO_ENABLED=1`: le build locali e le pipeline CI rallentano, le immagini container minimali (scratch o distroless) richiedono librerie runtime C compatibili, la cross-compilazione verso un altro sistema operativo o un'altra architettura CPU richiede una toolchain C cross-compilante completa, e qualsiasi corruzione della memoria in C condivide lo spazio degli indirizzi del processo Go.

Dall'altro lato vi sono i port SQLite puri in Go, generati traducendo meccanicamente l'amalgama C in codice sorgente Go su un runtime C emulato (`libc`). Essi ripristinano `CGO_ENABLED=0`, rendendo le build banali. Sotto carichi di lavoro prolungati, tuttavia, l'heap C tradotto e le allocazioni Go per riga aumentano la pressione sul garbage collector, e sottili casi limite nel locking, nei busy timeout o nell'interruzione delle istruzioni possono divergere dalle build C native.

Abbiamo realizzato `gosuda.org/sqlite3` affinché le applicazioni Go non debbano scegliere tra `CGO_ENABLED=0` e il vero motore C di SQLite. Compilando l'amalgama C ufficiale di SQLite `3.54.1` in un server dedicato a memoria condivisa (`hqq-sqlite-server`) e comunicando tramite `gosuda.org/hqq`, il binario Go viene compilato con `CGO_ENABLED=0` pur eseguendo SQLite C non modificato e ottimizzato con `-O3` attraverso un confine di processo.

## Esecuzione Senza Configurazione: da `go get` a `sql.Open`

Sostituire `github.com/mattn/go-sqlite3` in una base di codice Go esistente richiede di modificare solo il percorso dell'import anonimo. Il driver si registra come `"sqlite3"` (e come `"hqq-sqlite"` per una selezione esplicita) e accetta le stesse stringhe di query DSN:

```go
package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"

	_ "gosuda.org/sqlite3"
)

func main() {
	dsn := "file:app.db?_journal_mode=WAL&_busy_timeout=5000&_txlock=immediate&_foreign_keys=on"
	db, err := sql.Open("sqlite3", dsn)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	ctx := context.Background()
	if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)`); err != nil {
		log.Fatal(err)
	}
	if _, err := db.ExecContext(ctx, `INSERT INTO users (name) VALUES (?)`, "Ada Lovelace"); err != nil {
		log.Fatal(err)
	}

	var id int64
	var name string
	if err := db.QueryRowContext(ctx, `SELECT id, name FROM users WHERE name = ?`, "Ada Lovelace").Scan(&id, &name); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("user #%d: %s\n", id, name)
}
```

### Binari Worker Incorporati con Attestazione SLSA

Una domanda naturale, quando si apprende che una libreria utilizza un processo worker C, è: _"Devo installare o distribuire un binario C separato insieme al mio programma Go?"_

No. `gosuda.org/sqlite3` incorpora binari `hqq-sqlite-server` precompilati per tutti e sei i target principali, direttamente all'interno di file Go con tag di piattaforma (`internal/embedded/embed_<goos>_<goarch>.go`). Poiché i vincoli di build di Go compilano solo il file corrispondente al target `GOOS` e `GOARCH`, la cross-compilazione con `GOOS=linux GOARCH=arm64 go build` incorpora automaticamente solo il binario server `linux/arm64` di circa 1,4 MiB.

| Sistema Operativo Target | Architettura Target (`GOARCH`) | Binario Incorporato                   | Toolchain di Build e Provenienza                  |
| :----------------------- | :----------------------------- | :------------------------------------ | :------------------------------------------------ |
| **Linux**                | `amd64`, `arm64`               | `hqq-sqlite-server_linux_<arch>`      | GCC `-O3`, SLSA Build L3 Provenance               |
| **macOS**                | `arm64`, `amd64`               | `hqq-sqlite-server_darwin_<arch>`     | AppleClang `-O3`, SLSA Build L3 Provenance        |
| **Windows**              | `amd64`                        | `hqq-sqlite-server_windows_amd64.exe` | MSVC `-O2`, SLSA Build L3 Provenance              |
| **Windows**              | `arm64`                        | `hqq-sqlite-server_windows_arm64.exe` | `zig cc -target aarch64-windows-gnu -O3`, SLSA L3 |

Quando `sql.Open` stabilisce la sua prima connessione, `autolaunch.go` materializza il binario incorporato nella directory della cache utente (`$XDG_CACHE_HOME/hqq/bin/<sha256>/`), verifica il suo digest crittografico SHA-256 rispetto alla mappa `ReleaseHashes` fissata in `hashes.go` tramite `gosuda.org/hqq/secureexec`, e avvia il worker con `--parent-stdin`, così che la sua durata sia strettamente vincolata al processo Go.

### Compatibilità DSN Clean-Room con `mattn/go-sqlite3`

Per funzionare come un vero sostituto diretto, il parser DSN (`dsn.go`) implementa una replica clean-room delle opzioni di connessione e dei pragma utilizzati nelle basi di codice `mattn/go-sqlite3`:

| Parametro di Query DSN                                                                        | Valori Accettati                                        | Effetto in SQLite                                                                                    |
| :-------------------------------------------------------------------------------------------- | :------------------------------------------------------ | :--------------------------------------------------------------------------------------------------- |
| `_busy_timeout` / `_timeout`                                                                  | Intero in millisecondi (es. `5000`)                     | Chiama `sqlite3_busy_timeout(db, ms)` all'apertura della sessione.                                   |
| `_journal_mode` / `_journal`                                                                  | `DELETE`, `TRUNCATE`, `PERSIST`, `MEMORY`, `WAL`, `OFF` | Esegue `PRAGMA journal_mode = <mode>`.                                                               |
| `_txlock`                                                                                     | `immediate`, `exclusive`, `deferred`                    | Seleziona `BEGIN IMMEDIATE`, `BEGIN EXCLUSIVE` o `BEGIN DEFERRED` per `db.BeginTx`.                  |
| `_foreign_keys` / `_fk`                                                                       | `on`, `true`, `1`, `off`, `false`, `0`                  | Esegue `PRAGMA foreign_keys = ON/OFF`.                                                               |
| `_synchronous` / `_sync`                                                                      | `0`/`OFF`, `1`/`NORMAL`, `2`/`FULL`, `3`/`EXTRA`        | Esegue `PRAGMA synchronous = <level>`.                                                               |
| `_cache_size`                                                                                 | Intero di pagine o `-KiB` (es. `-20000`)                | Esegue `PRAGMA cache_size = <val>`.                                                                  |
| `_loc`                                                                                        | `auto` o fuso orario IANA (es. `UTC`, `Asia/Seoul`)     | Analizza le colonne di testo `DATETIME`/`TIMESTAMP` in `*time.Time` nella località di destinazione.  |
| `mode`                                                                                        | `ro`, `rw`, `rwc`, `memory`                             | Imposta `SQLITE_OPEN_READONLY`, `SQLITE_OPEN_READWRITE`, `SQLITE_OPEN_CREATE` o `SQLITE_OPEN_MEMORY`. |
| `_case_sensitive_like`, `_query_only`, `_recursive_triggers`, `_secure_delete`, `_AutoVacuum` | Valori booleani o interi di modalità                    | Applica il corrispondente `PRAGMA` di sessione all'inizializzazione della connessione.               |

## Trasformare l'Isolamento di Processo in un Vantaggio di Throughput

A prima vista, eseguire SQLite in un processo separato sembra dover essere più lento che invocare `sqlite3_step` in-process tramite `cgo`. In pratica accade l'opposto non appena una query restituisce più di una singola riga.

### Il Costo Nascosto delle Chiamate `cgo` Riga per Riga

In un tipico driver SQLite basato su `cgo`, `database/sql` guida l'iterazione una riga e una colonna alla volta. La lettura di 1.000 righe con 4 colonne comporta:

- 1.000 chiamate `cgo` a `sqlite3_step`
- 4.000 chiamate `cgo` a `sqlite3_column_type` e `sqlite3_column_int64` / `sqlite3_column_text`
- migliaia di singoli attraversamenti di confine tra lo scheduler Go e lo stack C

### Pipelining delle Richieste e Prefetch dei Batch da 64 KiB

`gosuda.org/sqlite3` elimina gli attraversamenti di confine per ogni riga riprogettando il protocollo di comunicazione attorno a due meccanismi in `stmt.go`, `rows.go` e `sqlite3/server/sqlite3_hooks.c`:

```text
Go Client (stmt.go / rows.go)                       C11 Worker (sqlite3_hooks.c)
-----------------------------                       ----------------------------
1. Pipeline in 1 flight:
   ResetAsync + BindValuesAsync
   + StmtInfoAsync + FetchRowsAsync(128)  =======>  reset -> bind -> stmt_info ->
                                                    loop sqlite3_step() up to 128 rows
                                          <=======  Batch 1 (rows 1..128 packed in 64 KiB slot)
2. Immediately fire prefetch:
   FetchRowsAsync(128) (Batch 2)          =======>  loop sqlite3_step() for rows 129..256
   While C steps Batch 2, Go caller
   scans rows 1..128 from Batch 1!        <=======  Batch 2 ready in shared memory
```

1. **Pipelining delle Query a Singolo Volo (`stmt.go`)**: Quando `QueryContext` o `Stmt.QueryContext` esegue un'istruzione di sola lettura, `s.query()` invia in modo asincrono `reset`, `bind_values`, `stmt_info` (alla prima esecuzione) e `fetch_rows` in un unico volo nella memoria condivisa. Tutte e quattro le operazioni vengono eseguite in sequenza sul worker C e restituiscono il risultato in un solo viaggio di andata e ritorno.
2. **Cicli Batch C Stretti e Prefetch Asincrono (`rows.go`)**: All'interno di `fetch_rows` (`sqlite3_hooks.c`), il worker C esegue `sqlite3_step(stmt)` in un ciclo C nativo, codificando fino a `FetchSize` righe (predefinito 128) direttamente nello slot di risposta in memoria condivisa da 64 KiB, fino al raggiungimento del limite del batch, al riempimento dello slot o al verificarsi di `SQLITE_DONE`. Non appena `rows.ingest()` decodifica il Batch 1, invia `FetchRowsAsync` per il Batch 2 prima di restituire la prima riga al chiamante. Mentre il codice Go scandisce il Batch 1, il worker C sta già eseguendo i passi per il Batch 2 su un altro core della CPU.

Su Apple M4 (`darwin/arm64`), confrontando `gosuda.org/sqlite3` (`CGO_ENABLED=0`) con `github.com/mattn/go-sqlite3` (`CGO_ENABLED=1`) sullo stesso database SQLite, emerge l'effetto del batching e del pipelining:

| Carico di Lavoro                    | `gosuda.org/sqlite3` (`CGO_ENABLED=0`) | `mattn/go-sqlite3` (`CGO_ENABLED=1`) | Velocità Relativa                    |
| :---------------------------------- | -------------------------------------: | -----------------------------------: | :----------------------------------- |
| **Selezione Puntuale (`QueryRow` per PK)** |        `3.52 µs/op` (9 B, 1 alloc)     |   `3.56 µs/op` (232 B, 11 allocs)    | **1,01x (parità, 11x meno allocazioni)** |
| **Scansione di 100 Righe (`rows.Next`)**   |   `20.41 µs/op` (30.5 KB, 211 allocs)  |  `36.34 µs/op` (21.8 KB, 309 allocs) | **1,78x più veloce**                 |
| **Scansione di 1.000 Righe (`rows.Next`)** | `125.92 µs/op` (296 KB, 2.030 allocs)  | `349.10 µs/op` (216 KB, 3.009 allocs) | **2,77x più veloce**                 |
| **Inserimento Preparato (Autocommit)**     |       `5.33 µs/op` (0 B, 0 allocs)     |    `5.88 µs/op` (120 B, 5 allocs)    | **1,10x più veloce (0 allocazioni)** |
| **Batch di Transazione da 100 Righe**      |     `189.99 µs/op` (16 B, 1 alloc)     | `191.50 µs/op` (12.1 KB, 503 allocs) | **1,01x (500x meno allocazioni)**    |

## Cancellazione, Sandboxing dei Percorsi e Ripristino del Pool di Connessioni

Eseguire SQLite in un worker isolato rafforza anche il modo in cui il driver gestisce timeout, SQL non attendibile e confini del filesystem:

- **Cancellazione Deterministica del Contesto (`conn.go` e `errors.go`)**: Ogni chiamata del driver attiva una leggera guardia `context.AfterFunc` (`cn.guard`). Se il `context.Context` del chiamante viene annullato o la sua scadenza si esaurisce mentre una query ricorsiva lenta o un'attesa di lock è in esecuzione sul server, la guardia contrassegna la connessione come non valida, interrompe il collegamento di sessione locale e invia un frame `Cancel()` fuori banda. Il worker C invoca immediatamente `sqlite3_interrupt(db)`, sbloccando `sqlite3_step`, mentre il driver Go associa lo stato `SQLITE_INTERRUPT` risultante a `context.Canceled` o `context.DeadlineExceeded` e istruisce `database/sql` a sostituire la connessione nel pool.
- **Sandboxing delle Directory (`sqlsrv_pathrules.c`)**: Il worker C applica le regole di percorso `--allow-dir` a ogni chiamata `open_v2`, risolvendo i link simbolici e rifiutando l'attraversamento relativo `..` al di fuori della directory consentita. `sqlite3_load_extension` e `SQLITE_LIMIT_ATTACHED` sono disabilitati per impostazione predefinita, così che un'iniezione SQL non possa uscire dalla directory del database né caricare oggetti condivisi arbitrari.

## Struttura del Repository

Il repository è strutturato in modo che il pacchetto di primo livello fornisca il driver `database/sql` pronto all'uso, mentre `launcher/` e `sqlite3/` restano accessibili per distribuzioni avanzate con server personalizzati:

```text
gosuda.org/sqlite3
├── driver.go / api.go         # Registrazione del driver database/sql ("sqlite3") e API Connector
├── dsn.go                     # Parser DSN clean-room di mattn/go-sqlite3 e configurazione dei pragma
├── conn.go / tx.go            # Ciclo di vita della sessione, guardia del contesto e macchina a stati delle transazioni
├── stmt.go / rows.go          # Pipelining delle richieste a singolo volo e prefetch dei batch da 64 KiB
├── errors.go                  # Codici di errore primari/estesi tipizzati di SQLite e corrispondenza sentinella
├── autolaunch.go / hashes.go  # Materializzazione del server incorporato senza configurazione e fissaggio SHA-256
├── internal/embedded/         # Binari //go:embed con tag di piattaforma (linux, darwin, windows)
├── launcher/                  # Supervisore di processo configurabile per distribuzioni con server personalizzati
└── sqlite3/                   # Client Go hqq-bindgen generato e sorgente del server SQLite C11
    └── server/                # Implementazione del server C11 (sqlite3_hooks.c, sqlsrv_policy.c)
```

## Per Iniziare

Installa il modulo con `go get` e apri il database con `sql.Open("sqlite3", dsn)`:

```bash
go get gosuda.org/sqlite3@v0.3.3540100
```

- **Repository GitHub**: [https://github.com/gosuda/hqq-sqlite3](https://github.com/gosuda/hqq-sqlite3)
- **Riferimento DSN e Pragma**: [docs/DSN.md](https://github.com/gosuda/hqq-sqlite3/blob/main/docs/DSN.md)
- **Architettura e Protocollo di Comunicazione**: [docs/ARCHITECTURE.md](https://github.com/gosuda/hqq-sqlite3/blob/main/docs/ARCHITECTURE.md)
