98 lines
2.6 KiB
Go
98 lines
2.6 KiB
Go
package database
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"git.adyxax.org/adyxax/tfstated/pkg/model"
|
|
"go.n16f.net/uuid"
|
|
)
|
|
|
|
// Atomically check the lock status of a state and lock it if unlocked. Returns
|
|
// true if the function locked the state, otherwise returns false and the lock
|
|
// parameter is updated to the value of the existing lock
|
|
func (db *DB) SetLockOrGetExistingLock(path string, lock any) (bool, error) {
|
|
ret := false
|
|
return ret, db.WithTransaction(func(tx *sql.Tx) error {
|
|
var lockData []byte
|
|
err := tx.QueryRowContext(db.ctx,
|
|
`SELECT json_extract(lock, '$')
|
|
FROM states
|
|
WHERE path = ?;`,
|
|
path).Scan(&lockData)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
if lockData, err = json.Marshal(lock); err != nil {
|
|
return fmt.Errorf("failed to marshall lock data: %w", err)
|
|
}
|
|
var stateId uuid.UUID
|
|
if err := stateId.Generate(uuid.V7); err != nil {
|
|
return fmt.Errorf("failed to generate state id: %w", err)
|
|
}
|
|
_, err := tx.ExecContext(db.ctx,
|
|
`INSERT INTO states(id, path, lock)
|
|
VALUES (?, ?, jsonb(?))`,
|
|
stateId, path, lockData)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create new state: %w", err)
|
|
}
|
|
ret = true
|
|
return nil
|
|
}
|
|
return fmt.Errorf("failed to select lock data from state: %w", err)
|
|
}
|
|
if lockData != nil {
|
|
if err := json.Unmarshal(lockData, lock); err != nil {
|
|
return fmt.Errorf("failed to unmarshal lock data: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
if lockData, err = json.Marshal(lock); err != nil {
|
|
return fmt.Errorf("failed to marshal lock data: %w", err)
|
|
}
|
|
_, err = tx.ExecContext(db.ctx,
|
|
`UPDATE states
|
|
SET lock = jsonb(?)
|
|
WHERE path = ?;`,
|
|
lockData, path)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set lock data: %w", err)
|
|
}
|
|
ret = true
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (db *DB) Unlock(path string, lock any) (bool, error) {
|
|
data, err := json.Marshal(lock)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to marshal lock data: %w", err)
|
|
}
|
|
result, err := db.Exec(
|
|
`UPDATE states
|
|
SET lock = NULL
|
|
WHERE path = ? and lock = jsonb(?);`,
|
|
path, data)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to update state: %w", err)
|
|
}
|
|
n, err := result.RowsAffected()
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to get affected rows: %w", err)
|
|
}
|
|
return n == 1, nil
|
|
}
|
|
|
|
func (db *DB) ForceUnlock(state *model.State) error {
|
|
_, err := db.Exec(
|
|
`UPDATE states
|
|
SET lock = NULL
|
|
WHERE id = ?;`,
|
|
state.Id)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update state: %w", err)
|
|
}
|
|
return nil
|
|
}
|