Revision history: change sets + revisions + revert — the undo substrate (#48) (#61)
Build image / build-and-push (push) Successful in 5s
Build image / build-and-push (push) Successful in 5s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #61.
This commit is contained in:
@@ -71,15 +71,59 @@ func (d *DB) ListActivePlantingsForObject(ctx context.Context, objectID int64) (
|
||||
objectID)
|
||||
}
|
||||
|
||||
// ClearObjectPlantings soft-removes every active plop in an object in one UPDATE
|
||||
// ListPlantingsForObject returns every plop in an object, removed ones included.
|
||||
// Used when an object is deleted: the FK cascades its plantings away without the
|
||||
// service seeing them, so they are snapshotted first or the delete would not be
|
||||
// revertible. Always a non-nil slice.
|
||||
func (d *DB) ListPlantingsForObject(ctx context.Context, objectID int64) ([]domain.Planting, error) {
|
||||
return queryPlantings(ctx, d.sql,
|
||||
`SELECT `+plantingColumns+` FROM plantings WHERE object_id = ? ORDER BY id`,
|
||||
objectID)
|
||||
}
|
||||
|
||||
// RestorePlanting re-inserts a deleted plop under its ORIGINAL id, preserving
|
||||
// version and timestamps — the plop counterpart of RestoreObject, and subject to
|
||||
// the same reasoning. Its parent object must exist again first, or the FK
|
||||
// rejects it; the revert orders object restores ahead of planting restores.
|
||||
func (d *DB) RestorePlanting(ctx context.Context, p *domain.Planting) (*domain.Planting, error) {
|
||||
restored, err := scanPlanting(d.sql.QueryRowContext(ctx,
|
||||
`INSERT INTO plantings
|
||||
(id, object_id, plant_id, x_cm, y_cm, radius_cm, count, label, planted_at, removed_at,
|
||||
version, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||
strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
||||
RETURNING `+plantingColumns,
|
||||
p.ID, p.ObjectID, p.PlantID, p.XCM, p.YCM, p.RadiusCM, p.Count, p.Label,
|
||||
p.PlantedAt, p.RemovedAt, p.Version, p.CreatedAt,
|
||||
))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("store: restore planting: %w", err)
|
||||
}
|
||||
return restored, nil
|
||||
}
|
||||
|
||||
// ClearObjectPlantings soft-removes the given plops of an object in one UPDATE
|
||||
// (sets removed_at=date, bumps version) and returns how many rows it affected.
|
||||
func (d *DB) ClearObjectPlantings(ctx context.Context, objectID int64, date string) (int, error) {
|
||||
//
|
||||
// It takes explicit ids rather than clearing "every active plop" so the caller
|
||||
// can snapshot exactly the rows it is about to change. Clearing by predicate
|
||||
// would let a plop created between the caller's read and this UPDATE be removed
|
||||
// without a revision — cleared, but with no way to undo it.
|
||||
func (d *DB) ClearObjectPlantings(ctx context.Context, objectID int64, date string, ids []int64) (int, error) {
|
||||
if len(ids) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
args := make([]any, 0, len(ids)+2)
|
||||
args = append(args, date, objectID)
|
||||
for _, id := range ids {
|
||||
args = append(args, id)
|
||||
}
|
||||
res, err := d.sql.ExecContext(ctx,
|
||||
`UPDATE plantings
|
||||
SET removed_at = ?, version = version + 1,
|
||||
updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
|
||||
WHERE object_id = ? AND removed_at IS NULL`,
|
||||
date, objectID,
|
||||
WHERE object_id = ? AND removed_at IS NULL AND id IN (`+placeholders(len(ids))+`)`,
|
||||
args...,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("store: clear object plantings: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user