Skip to content

SQLite

Scrii cod SQL simplu SQLite în unul sau mai multe fișiere .sql. Platforma rulează o instanță SQLite în browser prin intermediul build-ului oficial @sqlite.org/sqlite-wasm — fiecare fișier .sql care nu este de testare se execută automat la salvare pe aceeași bază de date în memorie (in-memory), în ordine lexicografică.

Proiectul inițial (seed) vine cu un fișier script.sql care creează un tabel greetings, inserează rândul (1, 'Hello, World!') și îl selectează înapoi — extinde-l adăugând mai multe elemente de schemă, date de test (seed) și interogări.

Fișiere de intrare

Fișierele soluției se află în rădăcina proiectului:

  • script.sql — fișier de soluție editabil. Proiectul de pornire conține un ghid TODO pentru crearea tabelului greetings și selectarea mesajului.
  • Poți redenumi script.sql în orice dorești și poți crea fișiere .sql suplimentare (le poți organiza în submape dacă vrei). Instrumentul de rulare automată (auto-runner) descoperă fiecare fișier .sql care nu este de test din arborele de fișiere.

Fișierele rulează în ordine lexicografică după calea completă. Pune un prefix numelor de fișiere (01-schema.sql, 02-data.sql, 03-queries.sql) atunci când ordinea de execuție contează.

Database playground

Panoul din dreapta este o filă live Database care comunică cu aceeași instanță SQLite în memorie:

  • Rezultatul rulării automate (Auto-run result) — fiecare SELECT (sau orice altă instrucțiune care returnează rânduri) din fișierele tale .sql se randează aici, grupat după fișierul sursă, la fiecare salvare.
  • Tabele (Tables) — bara laterală a schemei populată din sqlite_master care listează fiecare tabel, plus coloanele și tipurile sale (prin PRAGMA table_info). Fă clic pe Preview pe un tabel pentru a rula instantaneu SELECT * FROM <table> LIMIT 20 în fila de interogări ad-hoc.
  • Interogare ad-hoc (Ad-hoc query) — editor SQL bazat pe Monaco pentru interogări ocazionale pe baza de date live. Ctrl/⌘ + Enter rulează interogarea fără a modifica fișierele sursă.

Baza de date este în memorie: reîmprospătarea previzualizării o șterge. Pentru a curăța fără reîncărcare, rulează DROP TABLE … în orice fișier sursă sau în fila de interogări ad-hoc.

Versiune

Rulează sqlite-wasm v3.46.1-build3 (SQLite WASM) cu Vitest v3.2.4 pe Node v22.

Limbaje suportate

SQL (dialectul SQLite)

Cadru de testare

Vitest cu utilitarul @dojocode/sql-test-helpers.

Memento-uri speciale și detalii de implementare

  • Dialect SQLite standard. Folosește INTEGER PRIMARY KEY AUTOINCREMENT (NU SERIAL din PostgreSQL), INSERT OR IGNORE / INSERT OR REPLACE pentru operațiuni de tip upsert.
  • Introspecția schemei folosește sqlite_master (SELECT name FROM sqlite_master WHERE type = 'table') și PRAGMA table_info(<table>) pentru metadatele coloanelor.
  • Fișierele care se potrivesc cu modelul *.test.sql sunt ignorate de rularea automată — fiind rezervate pentru un viitor rulător de teste SQL brute.
  • Testele sunt scrise în TypeScript (*.test.ts) folosind utilitarul partajat createSqliteTestDb(import.meta.url). Acesta pornește o instanță nouă de sqlite-wasm (cu avertismentul de inițializare OPFS dezactivat) și încarcă automat fiecare fișier .sql care nu este de test împreună cu testul, înainte de rularea aserțiunilor.
  • query() și exec() sunt sincrone aici — spre deosebire de utilitarul PGlite, nu este necesar await pentru apelurile individuale (utilitarul în sine este asincron doar pentru că inițializarea modulului sqlite-wasm de la bază returnează un Promise).

Exemplu cu Vitest:

typescript
import { describe, it, expect, beforeAll } from 'vitest';
import { createSqliteTestDb, type SqliteTestDb } from '@dojocode/sql-test-helpers/sqlite';

let db: SqliteTestDb;

beforeAll(async () => {
  db = await createSqliteTestDb(import.meta.url);
});

describe('greetings', () => {
  it("contains the row with message 'Hello, World!'", () => {
    const rows = db.query<{ message: string }>(
      'SELECT message FROM greetings WHERE id = 1'
    );
    expect(rows[0]?.message).toBe('Hello, World!');
  });

  it('has exactly one row', () => {
    const rows = db.query<{ n: number }>('SELECT COUNT(*) AS n FROM greetings');
    expect(rows[0].n).toBe(1);
  });
});

Utilitarul returnează { query<T>(sql): T[], exec(sql): void, raw: sqlite3.oo1.DB }. Atât query, cât și exec sunt sincrone — returnează rezultatele sau modifică baza de date imediat. Folosește raw doar pentru cazuri avansate (instrucțiuni pregătite cu parametri asociați, tranzacții, familia wasm.poke*).

Biblioteci incluse

Cum se depanează

Trei moduri de a verifica ce face codul tău SQL:

1. Fila de rezultate Auto-run (Auto-run result)

Fiecare rezultat SELECT este randat acolo cu eticheta fișierului său sursă. Dacă o interogare nu returnează nimic, secțiunea este omisă — acesta fiind în sine un semnal util că un rând nu a fost inserat sau că un filtru WHERE este prea restrictiv.

2. Fila Tabele (Tables)

Confirmă ce tabele există și tipurile coloanelor lor în acest moment. Dacă întâmpini erori de tip "no such table", înseamnă că tabelul nu a fost creat — de obicei din cauza unei erori de sintaxă într-o instrucțiune CREATE anterioară.

3. Fila de interogări ad-hoc (Ad-hoc query)

Rulează instrucțiuni SELECT punctuale pe baza de date live, fără a modifica fișierele sursă:

sql
SELECT name FROM sqlite_master WHERE type = 'table';
PRAGMA table_info(greetings);
SELECT * FROM greetings WHERE id = 1;

Greșeli frecvente

  • Utilizarea sintaxei PostgreSQLSERIAL nu există în SQLite; folosește INTEGER PRIMARY KEY AUTOINCREMENT. ON CONFLICT (col) DO NOTHING funcționează de asemenea, dar INSERT OR IGNORE este mai idiomatic în SQLite.
  • Omiterea INSERT OR IGNORE — rularea repetată a scriptului va eșua cu erori de încălcare a constrângerilor de unicitate dacă o rulare anterioară a inserat deja aceleași rânduri.
  • Ordinea de execuție între directoare — fișierele sunt sortate după calea lor completă, deci a/02-init.sql rulează ÎNAINTE de b/01-init.sql. Pune prefixe și directoarelor atunci când ordinea contează între directoare diferite.