Recunosc că sunt un adept conceptului de ORM1. Din framework-urile pe care le-am folosit am profitat de Eloquent2, query-builder-ul Fluent sau de Doctrine3. Dar acest articol nu este despre ORM-uri, ci mai degrabă pentru cei care preferă să evite aceste librării și să scrie query-uri SQL de mână - oferindu-le un control mai mare și vizibilitate asupra cererii respective.
SQLc - SQL compiler
Însă ogranizarea și cuplarea acestor query-uri cu codul, este de obicei subiectul dezbaterilor, mai ales pentru că multe găuri de securitate ca SQL injection au ca punct comun această practică. SQLc este o alternativă care păstrează beneficiile optimizării și scrierii de cereri SQL de mână, însă generează cod nativ în limbajul configurat pentru a fi folosit în aplicație:
evită posibilele issue-uri de securitate: codul SQL este separat de aplicație și codul generat nativ, cu tipuri și interfețe este cel folosit în aplicație
organizează și separă eficient codul și configurația: un beneficiu notabil pentru audit-ul și depanarea query-urilor
codul poate fi re-generat și optimizat la nevoie
oferă suport pentru mai multe motoare de baze de date prin configurație
langauge agnostic
SQLc este disponibil pentru limbajele Go, Typescript, Kotlin și Python, și poate fi folosit cu MySQL și PostgreSQL. Suportul pentru SQLite este în beta și există implementări neoficiale pentru limbajele C# și F#:
Instalarea și inițializarea proiectului
sqlc este un utilitar din linia de comandă, așadar poate fi instalat cu brew, snap sau docker:
# Ubuntu
sudo snap install sqlc
# MacOS
brew install sqlc
# Docker
docker pull sqlc/sqlc
# go install (>1.21)
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
Și pentru windows desigur puteți descărca un sqlc.exe4.
Am instalat sqlc, pe WSL foloind snap și voi folosi ultima versiune a limbajului go
(1.23) după care inițializez un proiect cu mod init:
Configurarea
Utilitarul caută un fișier sqlc.(yaml|yml|json)
în directorul proiectului, și aici specificăm câteva căi către fișierele sql din care se va genera codul, generatorul folosit și desigur motorul pentru baza de date:
# ~/Workspace/sqlcdemo/sqlc.yml
---
version: "2"
sql:
- engine: "mysql"
queries: "queries.sql"
schema: "schema.sql"
gen:
go:
package: "moustacios"
out: "moustacios"
Definirea schemei
Deși nu rulează migrări, sqlc ține cont de structura bazei de date și va genera modele pe baza ei. Recomandarea este să definim schema în fișierul schema.sql. Baza de date pentru acest tutorial va avea un singur tabel respectiv unul de commments și patru coloane: id
, email
, comment_text
și bot_probability
:
-- ~/Workspace/sqlcdemo/schema.sql
CREATE TABLE comments (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL,
comment_text TEXT NOT NULL,
bot_probability TINYINT DEFAULT 0
);
Definirea interogărilor (queries)
Următorul pas, poate contraintuitiv, este să ne definim query-urile. Și facem asta într-un fișier denumit evident queries.sql. Vom defini câte o cerere pentru fiecare operație CRUD:
-- ~/Workspace/sqlcdemo/queries.sql
-- name: GetComment :one
SELECT * FROM comments
WHERE id = ? LIMIT 1;
-- name: ListComments :many
SELECT * FROM comments
ORDER BY bot_probability DESC;
-- name: SaveComment :execresult
INSERT INTO comments (
email, comment_text
) VALUES (
?, ?
);
-- name: DeleteComment :execresult
DELETE FROM comments
WHERE id = ?;
-- name: PurgeBotComments :execresult
DELETE FROM comments
WHERE bot_probability > ?;
Veți observa adnotările cu numele de interogări și tipul de rezultat așteptat. Acestea ajută sqlc
să genereze funcții și structuri sau obiecte specifice proiectului.
Generarea de cod
Odată ce avem cele trei fișiere: sqlc.yml, schema.sql și queries.sql putem rula comanda de generare de cod:
sqlc generate
Comanda sqlc generate
va genera structuri pentru modele, funcții pentru cereri și interfețe:
În practică
Și acum e un simplu exercițiu de utilizare a acestui cod, în aplicația noastră pentru a insera un comentariu, îl listăm și apoi ștergem:
Pentru a face build-ul rulăm comanda de import a driverului de mysql și apoi build:
Pentru a ne conecta la o bază de date și a rula efectiv interogările de mysql voi folosi containerul mysql de docker cu următoarea configurație de docker-compose.yml
:
# ~/Workspace/sqlcdemo/docker-compose.yml
services:
db:
image: mysql:lts
restart: always
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: sqlcdemo
MYSQL_USER: sqlcdemo
MYSQL_PASSWORD: secret
adminer:
image: adminer
restart: always
ports:
- "8080:8080"
Și voi schimba username și parola în string-ul meu de conectare din fișierul main.go
:
// main.go linia 18
db, err := sql.Open("mysql", "sqlcdemo:secret@tcp(localhost:3306)/sqlcdemo?parseTime=true")
Și rulez comanda de docker-compose și apoi build și run:
Pe github găsiți codul complet din acest exemplu. Acolo am adăugat și o cerere de marcare a unul comentariu ca bot comment și apoi am folosit integogarea PurgeBotComments
pentru a-l șterge: