Lasebuche ORM
Une library ORM (Object-Relational Mapping) legere et generique pour Go. Permet de mapper des structs Go a des tables SQL avec un minimum de configuration.
Fonctionnalites
- CRUD complet : Create, Read, Update, Delete operations
- Versionnement optimiste : Controle de concurrence avec VersionId
- Auto-generation des IDs : Generation automatique des identifiants uniques
- Gestion des timestamps : DateCreated et DateUpdated auto-remplis
- Support SQLite : Integre un dialecte SQLite
- Requetes parametrees : Protection contre les injections SQL
- Mapping automatique : Mapping entre structs Go et tables SQL via tags
Installation
go get trankilou.fr/lasebuche
Utilisation
Configuration
Importez le package et creez une table :
package main
import (
"database/sql"
"log"
orm "trankilou.fr/lasebuche"
_ "modernc.org/sqlite"
)
type User struct {
ID string `db:"id" json:"id"`
Firstname string `db:"firstname" json:"firstname,omitempty"`
Lastname string `db:"lastname" json:"lastname,omitempty"`
Email string `db:"email" json:"email,omitempty"`
Enabled bool `db:"enabled" json:"enabled"`
VersionId string `db:"_version" json:"_version"`
DateCreated time.Time `db:"_date_created" json:"_date_created"`
DateUpdated *time.Time `db:"_date_updated" json:"_date_updated"`
}
func main() {
// Ouvrir la connexion a la base de donnees
db, err := sql.Open("sqlite", "mydb.sqlite")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Creer le dialecte
dialect := orm.NewSqliteDialect()
// Creer une table ORM
table, err := orm.NewTable[User](db, dialect, User{}, "users")
if err != nil {
log.Fatal(err)
}
// Synchroniser le schema
err = table.Sync()
if err != nil {
log.Fatal(err)
}
// Utiliser l'ORM...
}
Operations CRUD
Insert
user := &User{
Firstname: "John",
Lastname: "Doe",
Email: "john.doe@example.com",
Enabled: true,
}
insertedUser, err := table.Insert(user)
if err != nil {
log.Fatal(err)
}
// ID, VersionId, et DateCreated sont auto-remplis
Get
user, err := table.Get("user-id-123")
if err != nil {
if err == sql.ErrNoRows {
// Utilisateur non trouve
}
log.Fatal(err)
}
SelectOne
user, err := table.SelectOne("email = $1", "john.doe@example.com")
if err != nil {
log.Fatal(err)
}
SelectWhere
users, err := table.SelectWhere("enabled = $1", true)
if err != nil {
log.Fatal(err)
}
for _, u := range users {
// Traiter chaque utilisateur
}
Update
user.Firstname = "Jane"
updatedUser, err := table.Update(user)
if err != nil {
// Peut echouer si VersionId ne correspond pas (versionnement optimiste)
log.Fatal(err)
}
// VersionId est regenere apres chaque mise a jour
Delete
err := table.Delete("user-id-123")
if err != nil {
log.Fatal(err)
}
DeleteWhere
err := table.DeleteWhere("email LIKE $1", "%@example.com")
if err != nil {
log.Fatal(err)
}
Versionnement Optimiste
Le systeme utilise un champ VersionId pour implementer le versionnement optimiste. A chaque mise a jour, un nouveau VersionId est genere. Si une mise a jour est tentee avec un VersionId obsolète, l'operation echouera.
// Premier utilisateur
user1, _ := table.Get("user-id")
version1 := user1.VersionId
// Deuxieme lecture (apres une mise a jour par quelqu'un d'autre)
user2, _ := table.Get("user-id")
version2 := user2.VersionId // Different de version1
// Tentative de mise a jour avec l'ancienne version
user1.Firstname = "NewName"
_, err := table.Update(user1) // Echouera car VersionId est obsolète
if err != nil {
// Erreur: "no rows affected: record not found or version mismatch"
}
Struct Tags
Les structs doivent utiliser le tag db pour mapper les champs aux colonnes SQL :
type Product struct {
ID string `db:"id"` // Colonne 'id'
Name string `db:"name"` // Colonne 'name'
Price float64 `db:"price"` // Colonne 'price'
CreatedAt time.Time `db:"created_at"` // Colonne 'created_at'
VersionId string `db:"version"` // Colonne 'version' pour le versionnement
}
Champs Speciaux
- ID : Champ string, auto-rempli avec GenID() lors de l'insertion
- VersionId : Champ string, auto-rempli avec GenID() lors de l'insertion, regenere a chaque mise a jour
- DateCreated : Champ time.Time, auto-rempli avec l'heure actuelle lors de l'insertion
- DateUpdated : Champ *time.Time ou time.Time, mis a jour avec l'heure actuelle lors de chaque mise a jour
API Reference
Table[T]
NewTable[T](db *sql.DB, dialect Dialect, sample T, tablename string) (Table[T], error)
Cree une nouvelle instance de table ORM.
(t Table[T]) Sync() error
Synchronise le schema de la table avec la base de donnees.
(t Table[T]) Insert(value *T) (*T, error)
Insere un nouvel enregistrement. Retourne l'objet avec les champs auto-remplis.
(t Table[T]) Get(id string) (*T, error)
Recupere un enregistrement par son ID.
(t Table[T]) SelectOne(where string, args ...any) (*T, error)
Recupere un seul enregistrement avec une clause WHERE.
(t Table[T]) SelectWhere(where string, args ...any) ([]*T, error)
Recupere plusieurs enregistrements avec une clause WHERE.
(t Table[T]) Update(value *T) (*T, error)
Met a jour un enregistrement. Genere un nouveau VersionId. Retourne une erreur si le VersionId ne correspond pas a la version actuelle.
(t Table[T]) Delete(id string) error
Supprime un enregistrement par son ID.
(t Table[T]) DeleteWhere(where string, args ...any) error
Supprime des enregistrements avec une clause WHERE.
(t Table[T]) Debug()
Affiche les requetes SQL pre-compilees pour le debogage.
Dialect
NewSqliteDialect() Dialect
Cree un dialecte pour SQLite.
Exemple Complet
package main
import (
"database/sql"
"fmt"
"log"
"time"
orm "trankilou.fr/lasebuche"
_ "modernc.org/sqlite"
)
type Task struct {
ID string `db:"id"`
Title string `db:"title"`
Description string `db:"description"`
Completed bool `db:"completed"`
VersionId string `db:"version"`
DateCreated time.Time `db:"created_at"`
DateUpdated *time.Time `db:"updated_at"`
}
func main() {
db, err := sql.Open("sqlite", "./tasks.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
dialect := orm.NewSqliteDialect()
table, err := orm.NewTable[Task](db, dialect, Task{}, "tasks")
if err != nil {
log.Fatal(err)
}
// Synchroniser la table
err = table.Sync()
if err != nil {
log.Fatal(err)
}
// Inserer une tache
task := &Task{
Title: "Apprendre Go",
Description: "Etudier le langage Go",
Completed: false,
}
inserted, err := table.Insert(task)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Tache creee avec ID: %s, Version: %s\n", inserted.ID, inserted.VersionId)
// Mettre a jour la tache
inserted.Title = "Maitriser Go"
updated, err := table.Update(inserted)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Tache mise a jour, Nouvelle version: %s\n", updated.VersionId)
// Recuperer toutes les taches
tasks, err := table.SelectWhere("")
if err != nil {
log.Fatal(err)
}
for _, t := range tasks {
fmt.Printf("Tache: %s (terminee: %v)\n", t.Title, t.Completed)
}
// Supprimer la tache
err = table.Delete(inserted.ID)
if err != nil {
log.Fatal(err)
}
fmt.Println("Tache supprimee")
}
Dependances
- Go 1.18+ (pour les generics)
- modernc.org/sqlite (driver SQLite)
- github.com/sixafter/nanoid (generation d'IDs)
Contribution
Les contributions sont les bienvenues ! Ouvrez une issue ou soumettez une pull request.
Licence
Ce projet est sous licence MIT.