2026-08-08 20:43:00 +02:00
2026-08-08 21:23:02 +02:00
2026-08-08 20:39:31 +02:00
2026-08-08 20:36:24 +02:00
2026-08-08 22:18:25 +02:00
2026-08-08 22:18:25 +02:00
2026-08-08 20:39:31 +02:00
2026-08-08 21:23:02 +02:00

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.

S
Description
No description provided
Readme 49 KiB
Languages
Go 100%