change package name
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
# 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
|
||||
|
||||
```bash
|
||||
go get trankilou.fr/lasebuche
|
||||
```
|
||||
|
||||
## Utilisation
|
||||
|
||||
### Configuration
|
||||
|
||||
Importez le package et creez une table :
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
```go
|
||||
user, err := table.Get("user-id-123")
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
// Utilisateur non trouve
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
#### SelectOne
|
||||
|
||||
```go
|
||||
user, err := table.SelectOne("email = $1", "john.doe@example.com")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
#### SelectWhere
|
||||
|
||||
```go
|
||||
users, err := table.SelectWhere("enabled = $1", true)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, u := range users {
|
||||
// Traiter chaque utilisateur
|
||||
}
|
||||
```
|
||||
|
||||
#### Update
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
```go
|
||||
err := table.Delete("user-id-123")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
#### DeleteWhere
|
||||
|
||||
```go
|
||||
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.
|
||||
|
||||
```go
|
||||
// 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 :
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
```go
|
||||
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.
|
||||
Reference in New Issue
Block a user