INTRODUZIONE
Cos'è SQLite

SQLite è una libreria C serverless, zero-configuration, transazionale che implementa un motore SQL embedded. L'intero database risiede in un singolo file cross-platform. Conforme ad ACID anche in presenza di crash e power loss.

CaratteristicaDettaglio
TipoLibreria in-process, no daemon, no IPC
File DBSingolo file portabile (little-endian, cross-OS)
Tipi SQLNULL, INTEGER, REAL, TEXT, BLOB (type affinity)
ConcorrenzaMulti-reader / single-writer (WAL: reader non bloccati)
Limite DB281 TB (teorico) — tipicamente < qualche GB
LicenzaPublic Domain
Interfacce C Principali
OggettoRuolo
sqlite3Handle di connessione al database
sqlite3_stmtStatement preparato (SQL compilato)
sqlite3_valueValore dinamico (usato nelle funzioni custom)
sqlite3_blobHandle per I/O BLOB incrementale
sqlite3_backupHandle per operazioni di backup online
sqlite3_contextContesto esecuzione di funzioni SQL utente
COMPILAZIONE & LINKING
Linking con libsqlite3

Linux/macOS forniscono tipicamente libsqlite3 come libreria di sistema. L'header è sqlite3.h, la libreria è -lsqlite3.

# Compilazione base
gcc -Wall -Wextra -std=c11 main.c -o app -lsqlite3

# Con pkg-config (se disponibile)
gcc main.c -o app $(pkg-config --cflags --libs sqlite3)

# Verifica presenza libreria
pkg-config --modversion sqlite3
ldconfig -p | grep sqlite3
Compilazione Static (Amalgamation)

SQLite distribuisce una amalgamation (file unico sqlite3.c + sqlite3.h) per embedding diretto. Nessuna dipendenza esterna richiesta.

# Download da sqlite.org/download.html
# Compila sqlite3.c insieme al tuo codice
gcc -O2 -DSQLITE_THREADSAFE=1 \
    main.c sqlite3.c -o app \
    -lpthread -ldl -lm
Macro di Compilazione Utili
MacroEffetto
SQLITE_THREADSAFE=0|1|20=single, 1=serialized, 2=multi-thread
SQLITE_ENABLE_FTS5Abilita full-text search v5
SQLITE_ENABLE_JSON1Funzioni JSON (integrato dal 3.38)
SQLITE_ENABLE_RTREEIndici R-tree (spatial)
SQLITE_ENABLE_COLUMN_METADATAAttiva sqlite3_column_database_name() ecc.
SQLITE_OMIT_DEPRECATEDRimuove API obsolete
SQLITE_DEFAULT_MEMSTATUS=0Disabilita tracking memoria (performance)
SQLITE_USE_URI=1Abilita parsing URI nei filename
Verifica Versione Runtime / Compile-time
#include <sqlite3.h>
#include <stdio.h>

int main(void) {
    /* Compile-time: macro #define */
    printf("Compile: %s (%d)\n",
           SQLITE_VERSION, SQLITE_VERSION_NUMBER);

    /* Runtime: funzioni della libreria dinamicamente linkata */
    printf("Runtime: %s (%d)\n",
           sqlite3_libversion(), sqlite3_libversion_number());
    printf("Source: %s\n", sqlite3_sourceid());
    return 0;
}
Se compile e runtime version divergono, la libreria dinamica è diversa dall'header. Usa sqlite3_libversion_number() per controlli condizionali a runtime.
HEADER & TIPI
Header
#include <sqlite3.h>   // tutta l'API pubblica

Un singolo header espone tutte le funzioni, macro e tipi opachi. Nessun sotto-header.

Tipi Opachi
sqlite3         *db;     // handle di connessione
sqlite3_stmt    *stmt;   // statement preparato
sqlite3_blob    *blob;   // handle BLOB per I/O incrementale
sqlite3_backup  *bkp;    // sessione di backup
sqlite3_value   *val;    // valore dinamico (in funzioni custom)
sqlite3_context *ctx;    // contesto di esecuzione UDF

/* Intero a 64 bit definito dalla libreria */
sqlite3_int64  id = 123456789012;
sqlite3_uint64 sz;
Costanti di Stato & Tipo

Codici di ritorno principali:

SQLITE_OK (0) — successo
SQLITE_ROW (100) — step ha prodotto riga
SQLITE_DONE (101) — step completato
SQLITE_BUSY (5) — DB lock
SQLITE_ERROR (1) — errore generico
SQLITE_MISUSE (21) — API usata scorrettamente
SQLITE_CONSTRAINT (19) — violazione vincolo

Tipi di storage (column_type):

SQLITE_INTEGER (1)
SQLITE_FLOAT (2)
SQLITE_TEXT (3)
SQLITE_BLOB (4)
SQLITE_NULL (5)

FLUSSO TIPICO (PREPARED STATEMENT)
Ciclo di Vita di una Query
1.
sqlite3_open_v2()
apre/crea il file database, alloca sqlite3*
2.
sqlite3_prepare_v2()
compila SQL in bytecode, alloca sqlite3_stmt*
3.
sqlite3_bind_*()
lega valori ai parametri ?, ?N, :nome
4.
sqlite3_step()
esegue; ritorna SQLITE_ROW per ogni riga, poi SQLITE_DONE
5.
sqlite3_column_*()
estrae i valori dalla riga corrente (dopo SQLITE_ROW)
6.
sqlite3_reset()
opzionale: riporta lo statement allo stato iniziale per riuso
7.
sqlite3_finalize()
dealloca lo statement
8.
sqlite3_close()
chiude la connessione (tutti gli stmt devono essere finalizzati)
Il ciclo prepare → bind → step → reset può essere ripetuto N volte: compila una sola volta, esegui molte. È il pattern più efficiente e sicuro (no SQL injection).
sqlite3_open()
Firma
int sqlite3_open(const char *filename, sqlite3 **ppDb);

Apre (o crea) un database. filename è una stringa UTF-8. Ritorna SQLITE_OK in caso di successo. Anche in caso di errore *ppDb è valido e deve essere chiuso con sqlite3_close().

Filename Speciali
FilenameSignificato
"file.db"Database su disco (relativo al cwd)
":memory:"Database in RAM, distrutto alla chiusura
"" (stringa vuota)Database temporaneo su disco, cancellato alla chiusura
"file:foo.db?mode=ro"URI (richiede flag o SQLITE_USE_URI)
Esempio Base
#include <stdio.h>
#include <sqlite3.h>

int main(void) {
    sqlite3 *db = NULL;
    int rc = sqlite3_open("test.db", &db);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "open: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);   // va chiamata anche in errore
        return 1;
    }

    /* ... uso del DB ... */

    sqlite3_close(db);
    return 0;
}
sqlite3_open_v2()
Firma & Flag
int sqlite3_open_v2(
    const char  *filename,   // UTF-8 path o URI
    sqlite3    **ppDb,       // out: handle
    int          flags,       // SQLITE_OPEN_*
    const char  *zVfs        // VFS name (NULL = default)
);
FlagSignificato
SQLITE_OPEN_READONLYSola lettura; errore se il file non esiste
SQLITE_OPEN_READWRITELettura/scrittura; errore se non esiste
SQLITE_OPEN_CREATECrea se non esiste (con READWRITE)
SQLITE_OPEN_URIInterpreta filename come URI
SQLITE_OPEN_MEMORYDB in memoria puro
SQLITE_OPEN_NOMUTEXMulti-thread mode (no serial. interna)
SQLITE_OPEN_FULLMUTEXSerialized mode (più protezione, più lento)
SQLITE_OPEN_SHAREDCACHEShared cache (deprecato in molti casi)
SQLITE_OPEN_PRIVATECACHECache privata (default in molti build)
SQLITE_OPEN_NOFOLLOWRifiuta symlink
Esempio
sqlite3 *db;
int rc = sqlite3_open_v2(
    "data.db",
    &db,
    SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX,
    NULL);

if (rc != SQLITE_OK) {
    fprintf(stderr, "open_v2: %s\n", sqlite3_errmsg(db));
    sqlite3_close_v2(db);
    return 1;
}
sqlite3_open() equivale a open_v2 con flag READWRITE | CREATE e nessun URI. Per controllo fine preferisci sempre open_v2.
sqlite3_close() & close_v2()
Differenze
int sqlite3_close   (sqlite3 *db);   // fallisce se ci sono stmt/blob non finalizzati
int sqlite3_close_v2(sqlite3 *db);   // chiusura "zombie": chiude quando tutti gli stmt finalizzati

close() ritorna SQLITE_BUSY se esistono statement preparati ancora vivi. close_v2() marca la connessione per chiusura deferita e non fallisce mai per quel motivo.

Cleanup Ordinato
/* Enumerare e finalizzare tutti gli stmt rimasti */
sqlite3_stmt *s = NULL;
while ((s = sqlite3_next_stmt(db, s)) != NULL)
    sqlite3_finalize(s);

sqlite3_close(db);
URI FILENAME
Sintassi

Con flag SQLITE_OPEN_URI (o macro di compile SQLITE_USE_URI=1), il filename diventa un URI con query string.

file:data.db                              // relativo
file:/var/lib/app.db                      // assoluto
file:data.db?mode=ro                      // read-only
file:data.db?mode=rwc&cache=shared         // rw+create, cache condivisa
file::memory:?cache=shared                // memoria condivisa tra connessioni
file:/tmp/t.db?psow=0&nolock=1
ParametroValori
modero / rw / rwc / memory
cacheshared / private
psowPowersafe overwrite (0/1)
nolockDisabilita file locking (solo lettura)
immutableIl file non sarà mai modificato
vfsNome VFS da usare
sqlite3_exec() — ESECUZIONE RAPIDA
Firma
int sqlite3_exec(
    sqlite3  *db,
    const char *sql,              // SQL (può contenere più statement)
    int (*callback)(void*, int, char**, char**),
    void     *arg,             // passato al callback come 1° arg
    char    **errmsg           // out: msg errore (da liberare)
);

Wrapper che fa prepare → step → finalize per ogni statement nella stringa SQL. Ideale per DDL e statement "fire and forget" senza parametri.

NON usare sqlite3_exec() con SQL costruito concatenando input utente — SQL injection. Usa prepared statement + bind.
Esempio Senza Callback (DDL)
char *err = NULL;
const char *ddl =
    "CREATE TABLE IF NOT EXISTS utenti ("
    "  id   INTEGER PRIMARY KEY AUTOINCREMENT,"
    "  nome TEXT NOT NULL UNIQUE,"
    "  eta  INTEGER CHECK(eta >= 0)"
    ");";

int rc = sqlite3_exec(db, ddl, NULL, NULL, &err);
if (rc != SQLITE_OK) {
    fprintf(stderr, "DDL: %s\n", err);
    sqlite3_free(err);  // errmsg allocato internamente
}
Esempio con Callback
static int dump_row(void *ud, int ncol, char **vals, char **cols) {
    (void)ud;
    for (int i = 0; i < ncol; i++)
        printf("%s=%s  ", cols[i], vals[i] ? vals[i] : "NULL");
    putchar('\n');
    return 0;  // !=0 interrompe l'esecuzione con SQLITE_ABORT
}

char *err = NULL;
sqlite3_exec(db, "SELECT id,nome FROM utenti;", dump_row, NULL, &err);
CALLBACK DI RISULTATI
Prototipo Dettagliato
int callback(
    void  *user_data,    // 4° arg di sqlite3_exec()
    int    ncol,         // numero colonne della riga
    char **values,       // array di stringhe (valori come TEXT, NULL possibile)
    char **column_names  // array di stringhe (nomi colonne)
);
// return 0 = continua; return !=0 = abort (sqlite3_exec ritorna SQLITE_ABORT)
AspettoNota
MemoriaTutti i puntatori sono validi solo durante la chiamata. Copia se serve tenerli.
TipiTutti i valori arrivano come char* (stringa). Tipi nativi solo con prepared statement.
NULLvalues[i] == NULL quando la colonna è NULL SQL.
ErroriNon chiamare altre API SQLite sulla stessa db dal callback.
Pattern: User-Data Tipizzato
typedef struct { int count; long sum; } agg_t;

static int aggregate(void *ud, int nc, char **v, char **cn) {
    (void)nc; (void)cn;
    agg_t *a = (agg_t *)ud;
    a->count++;
    if (v[0]) a->sum += atol(v[0]);
    return 0;
}

agg_t a = {0,0};
sqlite3_exec(db, "SELECT eta FROM utenti;", aggregate, &a, NULL);
printf("%d righe, somma=%ld\n", a.count, a.sum);
Per query con tipi non-testo (BLOB, grandi numeri) o binding di input, preferisci sempre prepared statement + sqlite3_column_*.
sqlite3_prepare_v2()
Firma
int sqlite3_prepare_v2(
    sqlite3       *db,
    const char    *zSql,        // SQL UTF-8
    int             nByte,       // lunghezza in byte, o -1 per strlen
    sqlite3_stmt **ppStmt,      // out: stmt compilato
    const char   **pzTail        // out: primo byte dopo il primo stmt
);

Compila un solo statement SQL. Se zSql contiene più statement, *pzTail punta all'inizio del successivo: loop su prepare_v2 per eseguirli tutti.

VarianteDifferenza
sqlite3_prepare()LEGACY: gestione errori peggiore, non usare.
sqlite3_prepare_v2()Versione standard raccomandata.
sqlite3_prepare_v3()Aggiunge flag (SQLITE_PREPARE_PERSISTENT, ecc.).
sqlite3_prepare16_v2()Input UTF-16.
Esempio
sqlite3_stmt *stmt = NULL;
const char *sql = "INSERT INTO utenti(nome, eta) VALUES (?, ?);";

int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
    fprintf(stderr, "prepare: %s\n", sqlite3_errmsg(db));
    return -1;
}
Flag di prepare_v3
int sqlite3_prepare_v3(db, sql, -1, flags, &stmt, NULL);
FlagSignificato
SQLITE_PREPARE_PERSISTENTStmt riusato a lungo: hint per il planner (evita cache eviction).
SQLITE_PREPARE_NORMALIZEAbilita sqlite3_normalized_sql().
SQLITE_PREPARE_NO_VTABNon usare virtual table.
sqlite3_step()
Semantica
int sqlite3_step(sqlite3_stmt *stmt);
RitornoSignificato
SQLITE_ROW (100)Riga disponibile: leggi con column_*, poi richiama step
SQLITE_DONE (101)Esecuzione completata
SQLITE_BUSY (5)DB lock, retry o abort
SQLITE_CONSTRAINTViolazione di vincolo (UNIQUE, CHECK, NOT NULL, FK)
SQLITE_ERRORErrore generico (controlla errmsg)
SQLITE_MISUSEUso scorretto (es. step dopo finalize)
Loop Tipico SELECT
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
    int         id   = sqlite3_column_int (stmt, 0);
    const unsigned char *nome
                     = sqlite3_column_text(stmt, 1);
    printf("%d %s\n", id, nome);
}
if (rc != SQLITE_DONE)
    fprintf(stderr, "step: %s\n", sqlite3_errmsg(db));
Esecuzione Single-Shot (INSERT/UPDATE)
if (sqlite3_step(stmt) != SQLITE_DONE) {
    fprintf(stderr, "insert: %s\n", sqlite3_errmsg(db));
}
sqlite3_int64 last_id = sqlite3_last_insert_rowid(db);
int           changes = sqlite3_changes(db);
sqlite3_reset() & sqlite3_clear_bindings()
Firme
int sqlite3_reset         (sqlite3_stmt *stmt);
int sqlite3_clear_bindings(sqlite3_stmt *stmt);

reset() riporta lo stato di esecuzione allo stadio "pronto per il primo step", senza cancellare i bind. clear_bindings() azzera tutti i parametri a NULL.

Dopo SQLITE_DONE, non è obbligatorio chiamare reset() prima di finalize(). Il motivo di usarlo è riusare lo stesso statement con nuovi bind.
Pattern: Insert Ripetuti
const char *sql = "INSERT INTO utenti(nome, eta) VALUES (?,?);";
sqlite3_stmt *s;
sqlite3_prepare_v2(db, sql, -1, &s, NULL);

for (int i = 0; i < n; i++) {
    sqlite3_bind_text(s, 1, names[i], -1, SQLITE_TRANSIENT);
    sqlite3_bind_int (s, 2, ages[i]);

    if (sqlite3_step(s) != SQLITE_DONE)
        fprintf(stderr, "err: %s\n", sqlite3_errmsg(db));

    sqlite3_reset(s);          // pronto per il prossimo
    sqlite3_clear_bindings(s); // opzionale (nuovi bind li sovrascrivono)
}
sqlite3_finalize(s);
sqlite3_finalize()
Firma
int sqlite3_finalize(sqlite3_stmt *stmt);

Distrugge lo statement e libera tutte le risorse associate. Sicura da chiamare su NULL (noop). Va chiamata sempre prima di sqlite3_close() (ma non prima di close_v2()).

Fuga di statement non finalizzati → sqlite3_close() ritorna SQLITE_BUSY e il database rimane aperto.
Idioma RAII-like
#define SQL_FINALIZE(s) do {            \
    if (s) { sqlite3_finalize(s); s = NULL; } \
} while(0)

sqlite3_stmt *stmt = NULL;
/* ... prepare, bind, step ... */
SQL_FINALIZE(stmt);
sqlite3_bind_*() — BIND DEI PARAMETRI
Famiglia di Funzioni
int sqlite3_bind_int    (sqlite3_stmt*, int i, int    v);
int sqlite3_bind_int64  (sqlite3_stmt*, int i, sqlite3_int64 v);
int sqlite3_bind_double (sqlite3_stmt*, int i, double v);
int sqlite3_bind_text   (sqlite3_stmt*, int i,
                          const char* v, int n, void(*fin)(void*));
int sqlite3_bind_text16 (sqlite3_stmt*, int i,
                          const void* v, int n, void(*fin)(void*));
int sqlite3_bind_blob   (sqlite3_stmt*, int i,
                          const void* v, int n, void(*fin)(void*));
int sqlite3_bind_zeroblob(sqlite3_stmt*, int i, int n);
int sqlite3_bind_null   (sqlite3_stmt*, int i);
int sqlite3_bind_value  (sqlite3_stmt*, int i, const sqlite3_value*);
int sqlite3_bind_pointer(sqlite3_stmt*, int i,
                          void*, const char* type, void(*fin)(void*));
Gli indici partono da 1, non da 0. La colonna 0 ritornata da sqlite3_column_* parte invece da 0. Facile fonte di bug.
Distruttori: TRANSIENT vs STATIC

L'ultimo parametro di bind_text/blob indica a SQLite come gestire il buffer passato.

ValoreSemantica
SQLITE_STATICIl buffer è valido per tutta la vita dello statement (no copia).
SQLITE_TRANSIENTSQLite copia subito il dato; il buffer originale può essere liberato.
Puntatore a funzioneChiamata da SQLite quando il dato non serve più (free, custom, ecc.).
char buf[64];
snprintf(buf, sizeof buf, "Mario-%d", i);

// STATIC: buf è stabile per tutta la vita dello stmt? No, è temporaneo → TRANSIENT
sqlite3_bind_text(s, 1, buf, -1, SQLITE_TRANSIENT);

// Stringa letterale: TEXT segment immortale → STATIC
sqlite3_bind_text(s, 2, "admin", -1, SQLITE_STATIC);

// Buffer malloc-ato che vogliamo cedere: passa free
char *heap = strdup("xxxx");
sqlite3_bind_text(s, 3, heap, -1, free);
Passare SQLITE_STATIC con un buffer automatico/di stack che esce di scope è undefined behavior. In dubbio: SQLITE_TRANSIENT.
Lunghezze & Conversioni
Parametro nSignificato in bind_text
n = -1Usa strlen per determinare la lunghezza (stringa C terminata)
n >= 0Numero di byte (non include \0 terminatore)
/* Bind di un BLOB */
unsigned char data[256];
sqlite3_bind_blob(s, 1, data, sizeof data, SQLITE_TRANSIENT);

/* Bind di BLOB vuoto di N byte (da riempire con incremental I/O) */
sqlite3_bind_zeroblob(s, 1, 1024);

/* Bind di NULL esplicito */
sqlite3_bind_null(s, 3);
PARAMETRI NOMINATI
Sintassi SQL
FormaDescrizione
?Anonimo, posizione automatica
?NNNPosizionale esplicito (NNN = 1..999)
:nomeNominato (colon-prefix)
@nomeNominato (at-prefix, stile T-SQL)
$nomeNominato (dollar-prefix, stile TCL)
Risoluzione Nome → Indice
int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName);
const char* sqlite3_bind_parameter_name(sqlite3_stmt*, int i);
int sqlite3_bind_parameter_count(sqlite3_stmt*);

Il nome include il prefisso (es. ":nome", non "nome").

const char *sql =
    "UPDATE utenti SET eta = :eta WHERE nome = :nome;";

sqlite3_stmt *s;
sqlite3_prepare_v2(db, sql, -1, &s, NULL);

int iEta  = sqlite3_bind_parameter_index(s, ":eta");
int iNome = sqlite3_bind_parameter_index(s, ":nome");

sqlite3_bind_int (s, iEta, 30);
sqlite3_bind_text(s, iNome, "Mario", -1, SQLITE_STATIC);

sqlite3_step(s);
sqlite3_finalize(s);
INTROSPEZIONE DI UNO STATEMENT
Funzioni
const char* sqlite3_sql           (sqlite3_stmt*);  // testo originale
char*       sqlite3_expanded_sql  (sqlite3_stmt*);  // con parametri bind-ati (free con sqlite3_free)
const char* sqlite3_normalized_sql(sqlite3_stmt*);  // forma normalizzata (prepare_v3)
int         sqlite3_stmt_readonly (sqlite3_stmt*);  // != 0 se non modifica il DB
int         sqlite3_stmt_busy     (sqlite3_stmt*);  // != 0 se in corso di esecuzione
int         sqlite3_data_count    (sqlite3_stmt*);  // colonne dopo SQLITE_ROW
int         sqlite3_column_count  (sqlite3_stmt*);  // colonne della query
sqlite3_column_*() — LETTURA RIGHE
Accesso ai Valori (indice base 0)
int              sqlite3_column_int    (sqlite3_stmt*, int i);
sqlite3_int64    sqlite3_column_int64  (sqlite3_stmt*, int i);
double           sqlite3_column_double (sqlite3_stmt*, int i);
const unsigned char*
                 sqlite3_column_text   (sqlite3_stmt*, int i);
const void*      sqlite3_column_text16 (sqlite3_stmt*, int i);
const void*      sqlite3_column_blob   (sqlite3_stmt*, int i);
int              sqlite3_column_bytes  (sqlite3_stmt*, int i);
int              sqlite3_column_bytes16(sqlite3_stmt*, int i);
int              sqlite3_column_type   (sqlite3_stmt*, int i);
sqlite3_value*   sqlite3_column_value  (sqlite3_stmt*, int i);
FunzioneNota
column_text()Ritorna const unsigned char* UTF-8, terminata da \0.
column_blob()NON terminata; usa column_bytes() per la dimensione.
column_bytes()Numero di byte del valore BLOB/TEXT (escluso \0).
column_type()Ritorna SQLITE_INTEGER, _FLOAT, _TEXT, _BLOB, _NULL.
I puntatori ritornati da column_text/blob sono validi solo fino alla prossima sqlite3_step/reset/finalize sullo stesso stmt, o a una qualsiasi conversione di tipo su quella colonna. Copia subito se serve persistere.
Esempio Completo
const char *sql = "SELECT id, nome, eta FROM utenti WHERE eta > ?;";
sqlite3_stmt *s;

if (sqlite3_prepare_v2(db, sql, -1, &s, NULL) != SQLITE_OK) goto err;
sqlite3_bind_int(s, 1, 18);

int rc;
while ((rc = sqlite3_step(s)) == SQLITE_ROW) {
    int id              = sqlite3_column_int  (s, 0);
    const unsigned char *n = sqlite3_column_text (s, 1);
    int eta             = sqlite3_column_int  (s, 2);

    if (sqlite3_column_type(s, 1) == SQLITE_NULL) n = (const unsigned char*)"(null)";
    printf("%3d | %-20s | %d\n", id, n, eta);
}
if (rc != SQLITE_DONE)
    fprintf(stderr, "step: %s\n", sqlite3_errmsg(db));

err:
sqlite3_finalize(s);
METADATI COLONNE
Funzioni
const char* sqlite3_column_name         (sqlite3_stmt*, int N);
const char* sqlite3_column_decltype     (sqlite3_stmt*, int N);

/* Richiedono SQLITE_ENABLE_COLUMN_METADATA */
const char* sqlite3_column_database_name(sqlite3_stmt*, int N);
const char* sqlite3_column_table_name   (sqlite3_stmt*, int N);
const char* sqlite3_column_origin_name  (sqlite3_stmt*, int N);

column_decltype ritorna il tipo dichiarato nella CREATE TABLE (es. "INTEGER", "VARCHAR(50)"), non il tipo storage corrente. SQLite è typeless: usa type affinity.

Stampa Intestazione Dinamica
int nc = sqlite3_column_count(s);
for (int i = 0; i < nc; i++)
    printf("%-15s ", sqlite3_column_name(s, i));
putchar('\n');
TYPE AFFINITY
Le 5 Classi di Storage

In SQLite i valori hanno tipo, le colonne hanno affinità. Una colonna INTEGER può contenere un TEXT (se non c'è STRICT).

Tipo DichiaratoAffinità
INT, INTEGER, BIGINT, SMALLINT...INTEGER
TEXT, CHAR, VARCHAR(n), CLOBTEXT
BLOB, (nessun tipo)BLOB
REAL, DOUBLE, FLOATREAL
NUMERIC, DECIMAL, BOOLEAN, DATENUMERIC
Dal 3.37: tabelle STRICT rifiutano tipi non conformi. Dal 3.38: tipo ANY esplicito per colonne senza conversione.
TRANSAZIONI — BEGIN / COMMIT / ROLLBACK
Modello

SQLite è ACID. Per default opera in autocommit: ogni statement è una transazione. BEGIN apre una transazione esplicita, COMMIT la conferma, ROLLBACK la annulla.

sqlite3_exec(db, "BEGIN TRANSACTION;", 0, 0, 0);
/* ... molte INSERT/UPDATE ... */
sqlite3_exec(db, "COMMIT;", 0, 0, 0);

// oppure, su errore:
sqlite3_exec(db, "ROLLBACK;", 0, 0, 0);
Raggruppare N INSERT in una sola transazione è decisivo per le performance: si passa da ~N fsync() a uno solo. Fattore 100×–1000× tipico.
Pattern con Rollback su Errore
int bulk_insert(sqlite3 *db, const char **names, int n) {
    char *err = NULL;
    int rc;

    if (sqlite3_exec(db, "BEGIN IMMEDIATE;", 0, 0, &err) != SQLITE_OK)
        goto fail;

    sqlite3_stmt *s;
    sqlite3_prepare_v2(db,
        "INSERT INTO utenti(nome) VALUES(?);", -1, &s, NULL);

    for (int i = 0; i < n; i++) {
        sqlite3_bind_text(s, 1, names[i], -1, SQLITE_STATIC);
        if (sqlite3_step(s) != SQLITE_DONE) {
            sqlite3_finalize(s);
            goto rollback;
        }
        sqlite3_reset(s);
    }
    sqlite3_finalize(s);

    return sqlite3_exec(db, "COMMIT;", 0, 0, 0);

rollback:
    sqlite3_exec(db, "ROLLBACK;", 0, 0, 0);
fail:
    sqlite3_free(err);
    return -1;
}
SAVEPOINT (TRANSAZIONI ANNIDATE)
Sintassi
SAVEPOINT   sp1;         -- crea punto di salvataggio
-- ... statement ...
RELEASE     sp1;         -- "commit" del savepoint
ROLLBACK TO sp1;         -- rollback parziale, la tx principale resta attiva

I savepoint sono annidabili, si comportano come mini-transazioni dentro una transazione. Utili per parsing/import a stadi con recupero.

sqlite3_exec(db, "BEGIN;", 0,0,0);
sqlite3_exec(db, "SAVEPOINT chunk;", 0,0,0);

if (processing_failed)
    sqlite3_exec(db, "ROLLBACK TO chunk;", 0,0,0);
else
    sqlite3_exec(db, "RELEASE chunk;", 0,0,0);

sqlite3_exec(db, "COMMIT;", 0,0,0);
MODALITÀ DI TRANSAZIONE
Tipi di BEGIN
SintassiLock acquisitoQuando
BEGIN / BEGIN DEFERREDNessuno inizialmente; SHARED sul primo SELECT, RESERVED sul primo WRITEDefault. Rischio SQLITE_BUSY su upgrade
BEGIN IMMEDIATERESERVED subitoPer transazioni che sicuramente scriveranno
BEGIN EXCLUSIVEEXCLUSIVE subito (no altri reader, non-WAL)Operazioni esclusive (rare)
In modalità WAL, EXCLUSIVE non blocca i reader. Per logica "read then write", BEGIN IMMEDIATE è la scelta sicura per evitare deadlock di upgrade.
Journal Mode
PRAGMA journal_mode = WAL;       -- Write-Ahead Logging (concorrenza ottima)
PRAGMA journal_mode = DELETE;    -- default classico
PRAGMA journal_mode = TRUNCATE;
PRAGMA journal_mode = PERSIST;
PRAGMA journal_mode = MEMORY;    -- non durabile
PRAGMA journal_mode = OFF;       -- NO rollback, NO crash safety

PRAGMA synchronous = FULL;       -- default, max durabilità
PRAGMA synchronous = NORMAL;     -- OK con WAL, 2–3× più veloce
PRAGMA synchronous = OFF;        -- rischio corruzione su crash
AUTOCOMMIT & STATO TRANSAZIONE
Funzioni
int sqlite3_get_autocommit(sqlite3 *db);
// 1 = autocommit attivo (nessuna tx aperta)
// 0 = tx in corso

int sqlite3_txn_state(sqlite3 *db, const char *zSchema);
// SQLITE_TXN_NONE  (0)    : nessuna tx
// SQLITE_TXN_READ  (1)    : tx di sola lettura
// SQLITE_TXN_WRITE (2)    : tx di scrittura

Utile per wrapping difensivo: se la routine ritorna con get_autocommit() == 0, c'è una transazione dimenticata.

GESTIONE ERRORI — CODICI
Codici Principali
CodiceValSignificato
SQLITE_OK0Successo
SQLITE_ERROR1Errore SQL / generico
SQLITE_INTERNAL2Bug interno (non dovresti vederlo)
SQLITE_PERM3Permessi insufficienti
SQLITE_ABORT4Abort (callback ha ritornato !=0)
SQLITE_BUSY5File lock; ritenta
SQLITE_LOCKED6Deadlock sulla stessa connessione
SQLITE_NOMEM7malloc fallito
SQLITE_READONLY8Scrittura su DB read-only
SQLITE_INTERRUPT9sqlite3_interrupt()
SQLITE_IOERR10Errore I/O sul VFS
SQLITE_CORRUPT11File corrotto
SQLITE_NOTFOUND12Non trovato
SQLITE_FULL13DB pieno (disco)
SQLITE_CANTOPEN14Impossibile aprire il file
SQLITE_SCHEMA17Schema cambiato (stmt va ricompilato)
SQLITE_CONSTRAINT19Violazione vincolo
SQLITE_MISMATCH20Tipo incompatibile
SQLITE_MISUSE21API usata male
SQLITE_RANGE25Indice bind fuori range
SQLITE_NOTADB26File non è un DB SQLite
SQLITE_ROW100Riga disponibile
SQLITE_DONE101Esecuzione completata
MESSAGGI & EXTENDED CODES
Funzioni
int         sqlite3_errcode         (sqlite3*);          // codice primario
int         sqlite3_extended_errcode(sqlite3*);          // codice esteso
const char* sqlite3_errmsg          (sqlite3*);          // messaggio, NUL-term
const char* sqlite3_errmsg16        (sqlite3*);
const char* sqlite3_errstr          (int rc);             // descrizione statica
int         sqlite3_error_offset    (sqlite3*);          // offset token errore

int sqlite3_extended_result_codes(sqlite3*, int onoff); // attiva extended
Extended Result Codes

Ogni codice base può essere esteso come CODICE_BASE | (sub_code << 8). Permette diagnosi fine.

ExtendedSignificato
SQLITE_CONSTRAINT_PRIMARYKEYViolazione PK
SQLITE_CONSTRAINT_UNIQUEUNIQUE fallita
SQLITE_CONSTRAINT_NOTNULLNOT NULL violato
SQLITE_CONSTRAINT_CHECKCHECK fallita
SQLITE_CONSTRAINT_FOREIGNKEYFK violata
SQLITE_IOERR_WRITEI/O in scrittura
SQLITE_IOERR_FSYNCfsync fallito
SQLITE_READONLY_RECOVERYServe recupero WAL
SQLITE_BUSY_RECOVERYRecovery WAL in corso
SQLITE_BUSY_SNAPSHOTSnapshot troppo vecchio (WAL)
Helper di Logging
static void sql_die(sqlite3 *db, const char *ctx) {
    fprintf(stderr,
        "[%s] rc=%d ext=%d off=%d: %s\n",
        ctx,
        sqlite3_errcode(db),
        sqlite3_extended_errcode(db),
        sqlite3_error_offset(db),
        sqlite3_errmsg(db));
    exit(1);
}

/* Abilita extended codes all'inizio */
sqlite3_extended_result_codes(db, 1);
BUSY HANDLER & TIMEOUT
Firma
int sqlite3_busy_timeout(sqlite3*, int ms);
int sqlite3_busy_handler(sqlite3*, int(*cb)(void*,int), void*);

busy_timeout(ms) imposta un handler built-in che effettua sleep-retry per fino a ms millisecondi prima di ritornare SQLITE_BUSY. Zero lo disabilita.

Custom Busy Handler
static int my_busy(void *ud, int n) {
    (void)ud;
    if (n > 10) return 0;   // ritorna 0 → SQLite dà SQLITE_BUSY
    usleep(100000);          // 100 ms
    return 1;                 // ritorna !=0 → ritenta
}
sqlite3_busy_handler(db, my_busy, NULL);
Non settare entrambi busy_handler e busy_timeout: l'ultima chiamata vince. Di solito busy_timeout(5000) è sufficiente.
PRAGMA UTILI
Tuning Performance & Integrità
PRAGMA foreign_keys = ON;            -- attiva enforcement FK (default OFF)
PRAGMA journal_mode = WAL;            -- concorrenza read/write
PRAGMA synchronous  = NORMAL;         -- bilanciato con WAL
PRAGMA temp_store   = MEMORY;         -- tmp in RAM
PRAGMA cache_size   = -20000;         -- 20 MB di cache (negativo = KB)
PRAGMA mmap_size    = 268435456;      -- 256 MB di mmap
PRAGMA page_size    = 4096;           -- solo prima del primo CREATE

PRAGMA integrity_check;               -- verifica integrità
PRAGMA quick_check;                   -- verifica veloce
PRAGMA wal_checkpoint(TRUNCATE);      -- checkpoint WAL
PRAGMA optimize;                      -- ANALYZE selettivo (3.18+)
Introspezione Schema
PRAGMA table_info('utenti');
PRAGMA table_list;                    -- 3.37+
PRAGMA index_list('utenti');
PRAGMA index_info('idx_nome');
PRAGMA foreign_key_list('ordini');
PRAGMA database_list;
PRAGMA user_version = 3;              -- int 32 libero per schema versioning
BLOB INCREMENTALE
API
int sqlite3_blob_open(
    sqlite3 *db, const char *zDb, const char *zTable,
    const char *zColumn, sqlite3_int64 iRow,
    int flags, sqlite3_blob **ppBlob);

int sqlite3_blob_bytes  (sqlite3_blob*);
int sqlite3_blob_read   (sqlite3_blob*, void *buf, int n, int off);
int sqlite3_blob_write  (sqlite3_blob*, const void*, int n, int off);
int sqlite3_blob_reopen (sqlite3_blob*, sqlite3_int64 newRow);
int sqlite3_blob_close  (sqlite3_blob*);

Permette lettura/scrittura di un BLOB a chunk, senza caricarlo tutto in memoria. La dimensione è fissa (non può crescere): prima allocala con zeroblob().

Esempio
/* Pre-allocare 1 MB */
sqlite3_exec(db,
    "INSERT INTO files(name,data) VALUES('foo.bin', zeroblob(1048576));",
    0,0,0);

sqlite3_int64 rowid = sqlite3_last_insert_rowid(db);
sqlite3_blob *b;
sqlite3_blob_open(db, "main", "files", "data", rowid, 1, &b);

char chunk[4096];
for (int off = 0; off < 1048576; off += sizeof chunk) {
    /* ... riempi chunk ... */
    sqlite3_blob_write(b, chunk, sizeof chunk, off);
}
sqlite3_blob_close(b);
BACKUP API (ONLINE)
Flusso
sqlite3 *src, *dst;
sqlite3_open("live.db",   &src);
sqlite3_open("backup.db", &dst);

sqlite3_backup *bkp = sqlite3_backup_init(dst, "main", src, "main");
if (bkp) {
    /* copia tutte le pagine in una volta (-1) oppure in step */
    do {
        int rc = sqlite3_backup_step(bkp, 100);  // 100 pagine per step
        if (rc == SQLITE_OK || rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
            sqlite3_sleep(50);
            continue;
        }
        if (rc == SQLITE_DONE) break;
        fprintf(stderr, "backup: %s\n", sqlite3_errmsg(dst));
        break;
    } while (1);
    sqlite3_backup_finish(bkp);
}

sqlite3_close(src); sqlite3_close(dst);
Idiomatica per snapshot atomici di un DB in uso. Progress bar: sqlite3_backup_remaining() e sqlite3_backup_pagecount().
HOOKS & CALLBACK
Hook Principali
FunzioneTrigger
sqlite3_commit_hook()Prima di ogni COMMIT
sqlite3_rollback_hook()Prima di ogni ROLLBACK
sqlite3_update_hook()Dopo INSERT/UPDATE/DELETE su tabella
sqlite3_trace_v2()Tracing SQL (statement/profile/row/close)
sqlite3_progress_handler()Ogni N VM opcode (abort di query lunghe)
sqlite3_wal_hook()Dopo COMMIT in WAL mode
sqlite3_authorizer()Controllo permessi a compile-time di prepare
Progress Handler
static int progress(void *ud) {
    return cancel_requested ? 1 : 0;  // !=0 → SQLITE_INTERRUPT
}
sqlite3_progress_handler(db, 1000, progress, NULL);

/* Interruzione immediata da altro thread: */
sqlite3_interrupt(db);
Update Hook (Audit)
static void audit(void *ud, int op,
                  const char *db_name,
                  const char *tbl,
                  sqlite3_int64 rowid) {
    const char *o = (op==SQLITE_INSERT) ? "INS" :
                      (op==SQLITE_UPDATE) ? "UPD" : "DEL";
    fprintf(stderr, "[%s] %s.%s rowid=%lld\n", o, db_name, tbl, rowid);
}
sqlite3_update_hook(db, audit, NULL);
FUNZIONI SQL CUSTOM (UDF)
Registrazione
int sqlite3_create_function(
    sqlite3     *db,
    const char  *zName,     // nome SQL della funzione
    int           nArg,      // numero argomenti (-1 = variadic)
    int           eTextRep,  // SQLITE_UTF8 | SQLITE_DETERMINISTIC
    void         *pApp,
    void (*xFunc)(sqlite3_context*, int, sqlite3_value**),
    void (*xStep)(sqlite3_context*, int, sqlite3_value**),  // per aggregati
    void (*xFinal)(sqlite3_context*)
);
Esempio: Funzione Scalare
static void sq_fn(sqlite3_context *ctx, int n, sqlite3_value **argv) {
    if (n < 1) { sqlite3_result_null(ctx); return; }
    double x = sqlite3_value_double(argv[0]);
    sqlite3_result_double(ctx, x * x);
}

sqlite3_create_function(db, "sq", 1,
    SQLITE_UTF8 | SQLITE_DETERMINISTIC,
    NULL, sq_fn, NULL, NULL);

/* Ora da SQL: SELECT sq(5.0);  → 25.0 */
Set di resultUso
sqlite3_result_int / _int64Intero
sqlite3_result_doubleFloat
sqlite3_result_textStringa (con TRANSIENT/STATIC)
sqlite3_result_blobBuffer binario
sqlite3_result_nullNULL SQL
sqlite3_result_errorPropaga errore
GESTIONE MEMORIA
Allocatori SQLite
void*  sqlite3_malloc (int n);            // int: max 2 GB
void*  sqlite3_malloc64(sqlite3_uint64 n);
void*  sqlite3_realloc(void*, int);
void   sqlite3_free   (void*);

char*  sqlite3_mprintf(const char*, ...);  // printf-like, libera con sqlite3_free
char*  sqlite3_vmprintf(const char*, va_list);

Stringhe e messaggi ritornati/richiesti da API SQLite (es. errmsg di exec, expanded_sql) devono essere liberati con sqlite3_free, non con free().

Formatter Sicuro — %q, %Q, %w
/* %q : escape di singola quote in stringa SQL (no quote esterne)
   %Q : come %q, ma aggiunge le quote (o NULL se pointer == NULL)
   %w : escape per identificatori (nomi tabella/colonna) */

char *sql = sqlite3_mprintf(
    "INSERT INTO log VALUES(%Q);", user_input);
sqlite3_exec(db, sql, 0,0,0);
sqlite3_free(sql);
NON usare sprintf/strcat per costruire SQL con dati utente. Preferisci sempre prepared statement + bind. Se proprio devi, usa %Q.
THREADING
Tre Modalità
ModalitàDescrizione
Single-thread (THREADSAFE=0)No locking. DB accessibile da un solo thread.
Multi-thread (THREADSAFE=2)Thread diversi possono usare connessioni diverse.
Serialized (THREADSAFE=1, default)Qualsiasi thread, qualsiasi connessione. Lock interno.
int sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);
int sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
int sqlite3_config(SQLITE_CONFIG_SERIALIZED);
int sqlite3_threadsafe(void);  // 0/1/2
Best practice: una connessione per thread in modalità multi-thread, serializza esplicitamente per DB. Evita di passare sqlite3_stmt* tra thread.
PATTERN CRUD COMPLETO
Wrapper Minimale
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>

#define SQL_OK(db, rc)                                       \
    do { if ((rc) != SQLITE_OK && (rc) != SQLITE_DONE     \
              && (rc) != SQLITE_ROW) {                       \
        fprintf(stderr, "[%s:%d] %s\n", __FILE__, __LINE__, \
                sqlite3_errmsg(db)); exit(1); } } while(0)

static sqlite3 *db_open(const char *path) {
    sqlite3 *db;
    int rc = sqlite3_open_v2(path, &db,
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
    SQL_OK(db, rc);
    sqlite3_extended_result_codes(db, 1);
    sqlite3_busy_timeout(db, 5000);
    sqlite3_exec(db, "PRAGMA journal_mode=WAL;", 0, 0, 0);
    sqlite3_exec(db, "PRAGMA foreign_keys=ON;", 0, 0, 0);
    return db;
}

static void db_init(sqlite3 *db) {
    const char *ddl =
        "CREATE TABLE IF NOT EXISTS utenti ("
        "  id   INTEGER PRIMARY KEY AUTOINCREMENT,"
        "  nome TEXT NOT NULL UNIQUE,"
        "  eta  INTEGER CHECK(eta >= 0));";
    char *err = NULL;
    if (sqlite3_exec(db, ddl, 0, 0, &err) != SQLITE_OK) {
        fprintf(stderr, "ddl: %s\n", err); sqlite3_free(err); exit(1);
    }
}

static sqlite3_int64
user_insert(sqlite3 *db, const char *nome, int eta) {
    sqlite3_stmt *s;
    SQL_OK(db, sqlite3_prepare_v2(db,
        "INSERT INTO utenti(nome,eta) VALUES(?1,?2);",
        -1, &s, NULL));

    sqlite3_bind_text(s, 1, nome, -1, SQLITE_TRANSIENT);
    sqlite3_bind_int (s, 2, eta);

    int rc = sqlite3_step(s);
    sqlite3_finalize(s);
    if (rc != SQLITE_DONE) { fprintf(stderr, "ins: %s\n", sqlite3_errmsg(db)); return -1; }
    return sqlite3_last_insert_rowid(db);
}

static int
user_find_by_name(sqlite3 *db, const char *nome,
                  int *out_id, int *out_eta) {
    sqlite3_stmt *s;
    SQL_OK(db, sqlite3_prepare_v2(db,
        "SELECT id,eta FROM utenti WHERE nome=?1;",
        -1, &s, NULL));

    sqlite3_bind_text(s, 1, nome, -1, SQLITE_STATIC);

    int rc = sqlite3_step(s), found = 0;
    if (rc == SQLITE_ROW) {
        *out_id  = sqlite3_column_int(s, 0);
        *out_eta = sqlite3_column_int(s, 1);
        found = 1;
    }
    sqlite3_finalize(s);
    return found;
}

int main(void) {
    sqlite3 *db = db_open("app.db");
    db_init(db);

    sqlite3_exec(db, "BEGIN;", 0, 0, 0);
    user_insert(db, "Mario", 30);
    user_insert(db, "Luisa", 25);
    sqlite3_exec(db, "COMMIT;", 0, 0, 0);

    int id, eta;
    if (user_find_by_name(db, "Mario", &id, &eta))
        printf("Mario id=%d eta=%d\n", id, eta);

    sqlite3_close(db);
    return 0;
}
PERFORMANCE TIPS
Regole d'Oro

Do:

Una transazione per N operazioni batch
journal_mode=WAL per concorrenza
synchronous=NORMAL con WAL
Riusa prepared statement (prepare+reset)
• Indici su colonne WHERE/JOIN
PRAGMA optimize a chiusura connessione
EXPLAIN QUERY PLAN ... per diagnosi
mmap_size su DB read-heavy

Don't:

• Concatenare stringhe per costruire SQL
• Una transazione per ogni INSERT
synchronous=OFF senza accettare data loss
• Tenere transazioni aperte a lungo
• Aprire/chiudere connessione per ogni query
• Ignorare SQLITE_BUSY (imposta timeout)
SELECT * quando servono 2 colonne
• Condividere sqlite3* tra thread senza serializzare

EXPLAIN QUERY PLAN
sqlite3_stmt *s;
sqlite3_prepare_v2(db,
    "EXPLAIN QUERY PLAN SELECT * FROM utenti WHERE nome=?;",
    -1, &s, NULL);
sqlite3_bind_text(s, 1, "Mario", -1, SQLITE_STATIC);

while (sqlite3_step(s) == SQLITE_ROW)
    printf("%s\n", sqlite3_column_text(s, 3));

sqlite3_finalize(s);
/* Output atteso (con indice):
   SEARCH utenti USING INDEX sqlite_autoindex_utenti_1 (nome=?)
   Senza indice:
   SCAN utenti  ← attenzione, full table scan */
Comparativa Transazioni
ScenarioINSERT/sec (tipico SSD)
1 INSERT per transazione (autocommit)∼ 100–500
Batch da 1000 in una BEGIN/COMMIT∼ 50.000–200.000
WAL + synchronous=NORMAL+30–50%
Prepared statement riusato+20–40%
Numeri indicativi, dipendono da schema, indici, CPU, filesystem. Misura sempre con il tuo workload reale (.timer on in sqlite3 CLI).