This commit is contained in:
2025-08-30 10:30:53 +03:00
parent 2e7ab5e382
commit 3654383ad2
9 changed files with 34 additions and 40 deletions

View File

@@ -61,10 +61,8 @@ type Repo[T any] interface {
Read(ctx context.Context, id string) (*T, error) Read(ctx context.Context, id string) (*T, error)
// Update updates object with id. // Update updates object with id.
Update(ctx context.Context, id string, v *T) error Update(ctx context.Context, id string, v *T) error
// Delete performs soft-delete (actually marks object as unavailable). // Delete deletes object with id.
Delete(ctx context.Context, id string) error Delete(ctx context.Context, id string) error
// Purge actually deletes database record with id.
Purge(ctx context.Context, id string) error
} }
``` ```

View File

@@ -13,9 +13,9 @@ CREATE TABLE IF NOT EXISTS -- (
id text PRIMARY KEY, id text PRIMARY KEY,
created_at timestamp with time zone, created_at timestamp with time zone,
updated_at timestamp with time zone, updated_at timestamp with time zone,
deleted_at timestamp with time zone,
payload jsonb payload jsonb
)` )
`
func initDB(ctx context.Context, db *sql.DB, tablename string) error { func initDB(ctx context.Context, db *sql.DB, tablename string) error {
if tablename == "" { if tablename == "" {

26
repo.go
View File

@@ -16,17 +16,15 @@ var (
) )
type Repo[T any] interface { type Repo[T any] interface {
// Create inserts object into repository table. If id already exists // Create saves object to the repository. If id already exists
// in the database it is an error. // in the database it is an error.
Create(ctx context.Context, id string, v *T) error Create(ctx context.Context, id string, v *T) error
// Read returns object with specified id or ErrNotFound // Read returns object with specified id or ErrNotFound
Read(ctx context.Context, id string) (*T, error) Read(ctx context.Context, id string) (*T, error)
// Update updates object with id. // Update updates object with id.
Update(ctx context.Context, id string, v *T) error Update(ctx context.Context, id string, v *T) error
// Delete performs soft-delete (actually marks object as unavailable). // Delete deletes object from the database.
Delete(ctx context.Context, id string) error Delete(ctx context.Context, id string) error
// Purge actually deletes database record with id.
Purge(ctx context.Context, id string) error
} }
// OpenOrCreate accepts *sql.DB and tablename and tries to create the named table // OpenOrCreate accepts *sql.DB and tablename and tries to create the named table
@@ -65,7 +63,7 @@ func (r *repo[T]) Create(ctx context.Context, id string, v *T) error {
} }
func (r *repo[T]) Read(ctx context.Context, id string) (*T, error) { func (r *repo[T]) Read(ctx context.Context, id string) (*T, error) {
query := "SELECT payload FROM " + r.table + " WHERE id = $1 AND deleted_at is NULL" query := "SELECT payload FROM " + r.table + " WHERE id = $1"
row := r.db.QueryRowContext(ctx, query, id) row := r.db.QueryRowContext(ctx, query, id)
@@ -94,7 +92,7 @@ func (r *repo[T]) Update(ctx context.Context, id string, v *T) error {
return err return err
} }
query := "UPDATE " + r.table + " SET updated_at = $1, payload = $2 WHERE id = $3 AND deleted_at IS NULL" query := "UPDATE " + r.table + " SET updated_at = $1, payload = $2 WHERE id = $3"
if err := r.execContext(ctx, query, now, string(b), id); err != nil { if err := r.execContext(ctx, query, now, string(b), id); err != nil {
return err return err
@@ -103,21 +101,8 @@ func (r *repo[T]) Update(ctx context.Context, id string, v *T) error {
return nil return nil
} }
// Delete performs soft-delete. It just marks the record as deleted and it will // Delete deletes record with cpecified id.
// no longer be available to Read and Update methods.
func (r *repo[T]) Delete(ctx context.Context, id string) error { func (r *repo[T]) Delete(ctx context.Context, id string) error {
now := time.Now()
query := "UPDATE " + r.table + " SET deleted_at = $1 WHERE id = $2 AND deleted_at IS NULL"
if err := r.execContext(ctx, query, now, id); err != nil {
return err
}
return nil
}
func (r *repo[T]) Purge(ctx context.Context, id string) error {
query := "DELETE FROM " + r.table + " WHERE id = $1" query := "DELETE FROM " + r.table + " WHERE id = $1"
if err := r.execContext(ctx, query, id); err != nil { if err := r.execContext(ctx, query, id); err != nil {
@@ -132,6 +117,7 @@ func (r *repo[T]) execContext(ctx context.Context, query string, args ...any) er
if err != nil { if err != nil {
return errors.Join(ErrExecQuery, err) return errors.Join(ErrExecQuery, err)
} }
affected, err := res.RowsAffected() affected, err := res.RowsAffected()
if err != nil { if err != nil {
return errors.Join(ErrExecQuery, err) return errors.Join(ErrExecQuery, err)

View File

@@ -2,6 +2,7 @@
test: test:
docker run --rm -d -e POSTGRES_PASSWORD=postgres --name test-postgres -p 5432:5432 postgres:15.6-bookworm docker run --rm -d -e POSTGRES_PASSWORD=postgres --name test-postgres -p 5432:5432 postgres:15.6-bookworm
# wait for postgres to launch
sleep 10 sleep 10
PGX_DSN=postgres://postgres:postgres@127.0.0.1:5432 go test -v -cover PGX_DSN=postgres://postgres:postgres@127.0.0.1:5432 go test -v -cover
docker stop test-postgres docker stop test-postgres

View File

@@ -3,7 +3,7 @@ module pkgtest
go 1.24.2 go 1.24.2
require ( require (
code.uint32.ru/tiny/repo v0.0.0-20250504181319-b6dc6d3fce1a code.uint32.ru/tiny/repo v1.0.0
github.com/jackc/pgx/v5 v5.7.4 github.com/jackc/pgx/v5 v5.7.4
github.com/mattn/go-sqlite3 v1.14.28 github.com/mattn/go-sqlite3 v1.14.28
) )

View File

@@ -1,5 +1,5 @@
code.uint32.ru/tiny/repo v0.0.0-20250504181319-b6dc6d3fce1a h1:xlO71KbJ4fl3F05JN/I8rY3MjVF0CV3B1awa4TB6GN0= code.uint32.ru/tiny/repo v1.0.0 h1:hWLL+atKth6XwKawQcnaBkQyuiGlS7+r4pqNhvUl4XE=
code.uint32.ru/tiny/repo v0.0.0-20250504181319-b6dc6d3fce1a/go.mod h1:yk97QS1fB9mdT/5iRN4ufo6IwZRDYHFl1kDtBSTpuk0= code.uint32.ru/tiny/repo v1.0.0/go.mod h1:yk97QS1fB9mdT/5iRN4ufo6IwZRDYHFl1kDtBSTpuk0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

6
test/go.work Normal file
View File

@@ -0,0 +1,6 @@
go 1.24.5
use (
.
..
)

15
test/go.work.sum Normal file
View File

@@ -0,0 +1,15 @@
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@@ -127,15 +127,6 @@ func dotest(ctx context.Context, db *sql.DB, t *testing.T) {
t.Errorf("incorrect error on delete for non-existing id, want: %v, have: %v", repo.ErrNotFound, err) t.Errorf("incorrect error on delete for non-existing id, want: %v, have: %v", repo.ErrNotFound, err)
} }
if err := r.Purge(ctx, testID); err != nil {
t.Error(err)
}
if err := r.Purge(ctx, testID); err == nil || !errors.Is(err, repo.ErrNotFound) {
// can only purge once
t.Errorf("incorrect error on delete for non-existing id, want: %v, have: %v", repo.ErrNotFound, err)
}
if _, err := r.Read(ctx, incorrectID); err == nil || !errors.Is(err, repo.ErrNotFound) { if _, err := r.Read(ctx, incorrectID); err == nil || !errors.Is(err, repo.ErrNotFound) {
t.Errorf("incorrect error on read for non-existing id, want: %v, have: %v", repo.ErrNotFound, err) t.Errorf("incorrect error on read for non-existing id, want: %v, have: %v", repo.ErrNotFound, err)
} }
@@ -148,7 +139,4 @@ func dotest(ctx context.Context, db *sql.DB, t *testing.T) {
t.Errorf("incorrect error on delete for non-existing id, want: %v, have: %v", repo.ErrNotFound, err) t.Errorf("incorrect error on delete for non-existing id, want: %v, have: %v", repo.ErrNotFound, err)
} }
if err := r.Purge(ctx, incorrectID); err == nil || !errors.Is(err, repo.ErrNotFound) {
t.Errorf("incorrect error on purge for non-existing id, want: %v, have: %v", repo.ErrNotFound, err)
}
} }