satellite/metainfo/metabase: use txutil.WithTx in delete

Change-Id: I75965c4dcf57479ace2367f5d3069d785628e86a
This commit is contained in:
Egon Elbre 2020-11-12 13:56:15 +02:00
parent bc460cd62d
commit 8182d8a726

View File

@ -14,6 +14,7 @@ import (
"storj.io/common/storj" "storj.io/common/storj"
"storj.io/storj/private/dbutil/pgutil" "storj.io/storj/private/dbutil/pgutil"
"storj.io/storj/private/dbutil/txutil"
"storj.io/storj/private/tagsql" "storj.io/storj/private/tagsql"
) )
@ -101,63 +102,52 @@ func (db *DB) DeleteObjectExactVersion(ctx context.Context, opts DeleteObjectExa
return DeleteObjectResult{}, err return DeleteObjectResult{}, err
} }
tx, err := db.db.BeginTx(ctx, nil) err = txutil.WithTx(ctx, db.db, nil, func(ctx context.Context, tx tagsql.Tx) error {
if err != nil { rows, err := tx.Query(ctx, `
return DeleteObjectResult{}, Error.New("failed BeginTx: %w", err) DELETE FROM objects
} WHERE
committed := false project_id = $1 AND
defer func() { bucket_name = $2 AND
if !committed { object_key = $3 AND
err = errs.Combine(err, Error.Wrap(tx.Rollback())) version = $4 AND
status = 1
RETURNING
version, stream_id,
created_at, expires_at,
status, segment_count,
encrypted_metadata_nonce, encrypted_metadata,
total_encrypted_size, fixed_segment_size,
encryption;
`, opts.ProjectID, opts.BucketName, []byte(opts.ObjectKey), opts.Version)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return storj.ErrObjectNotFound.Wrap(Error.Wrap(err))
}
return Error.New("unable to delete object: %w", err)
} }
}()
rows, err := tx.Query(ctx, ` result.Objects, err = scanObjectDeletion(opts.ObjectLocation, rows)
DELETE FROM objects if err != nil {
WHERE return err
project_id = $1 AND
bucket_name = $2 AND
object_key = $3 AND
version = $4 AND
status = 1
RETURNING
version, stream_id,
created_at, expires_at,
status, segment_count,
encrypted_metadata_nonce, encrypted_metadata,
total_encrypted_size, fixed_segment_size,
encryption;
`, opts.ProjectID, opts.BucketName, []byte(opts.ObjectKey), opts.Version)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return DeleteObjectResult{}, storj.ErrObjectNotFound.Wrap(Error.Wrap(err))
} }
return DeleteObjectResult{}, Error.New("unable to delete object: %w", err)
}
result.Objects, err = scanObjectDeletion(opts.ObjectLocation, rows) if len(result.Objects) == 0 {
return storj.ErrObjectNotFound.Wrap(Error.New("no rows deleted"))
}
segmentInfos, err := deleteSegments(ctx, tx, result.Objects)
if err != nil {
return err
}
if len(segmentInfos) != 0 {
result.Segments = segmentInfos
}
return nil
})
if err != nil { if err != nil {
return DeleteObjectResult{}, err return DeleteObjectResult{}, err
} }
if len(result.Objects) == 0 {
return DeleteObjectResult{}, storj.ErrObjectNotFound.Wrap(Error.New("no rows deleted"))
}
segmentInfos, err := deleteSegments(ctx, tx, result.Objects)
if err != nil {
return DeleteObjectResult{}, err
}
if len(segmentInfos) != 0 {
result.Segments = segmentInfos
}
err, committed = tx.Commit(), true
if err != nil {
return DeleteObjectResult{}, Error.New("unable to commit tx: %w", err)
}
return result, nil return result, nil
} }
@ -169,85 +159,75 @@ func (db *DB) DeleteObjectLatestVersion(ctx context.Context, opts DeleteObjectLa
return DeleteObjectResult{}, err return DeleteObjectResult{}, err
} }
tx, err := db.db.BeginTx(ctx, nil) err = txutil.WithTx(ctx, db.db, nil, func(ctx context.Context, tx tagsql.Tx) error {
if err != nil { // TODO different sql for Postgres and CockroachDB
return DeleteObjectResult{}, Error.New("failed BeginTx: %w", err) // version ONLY for cockroachdb
} // Postgres doesn't support ORDER BY and LIMIT in DELETE
committed := false // rows, err = tx.Query(ctx, `
defer func() { // DELETE FROM objects
if !committed { // WHERE
err = errs.Combine(err, Error.Wrap(tx.Rollback())) // project_id = $1 AND
} // bucket_name = $2 AND
}() // object_key = $3 AND
// status = 1
// ORDER BY version DESC
// LIMIT 1
// RETURNING stream_id;
// `, opts.ProjectID, opts.BucketName, opts.ObjectKey)
// TODO different sql for Postgres and CockroachDB // version for Postgres and Cockroachdb (but slow for Cockroachdb)
// version ONLY for cockroachdb rows, err := tx.Query(ctx, `
// Postgres doesn't support ORDER BY and LIMIT in DELETE DELETE FROM objects
// rows, err = tx.Query(ctx, ` WHERE
// DELETE FROM objects
// WHERE
// project_id = $1 AND
// bucket_name = $2 AND
// object_key = $3 AND
// status = 1
// ORDER BY version DESC
// LIMIT 1
// RETURNING stream_id;
// `, opts.ProjectID, opts.BucketName, opts.ObjectKey)
// version for Postgres and Cockroachdb (but slow for Cockroachdb)
rows, err := tx.Query(ctx, `
DELETE FROM objects
WHERE
project_id = $1 AND
bucket_name = $2 AND
object_key = $3 AND
version = (SELECT version FROM objects WHERE
project_id = $1 AND project_id = $1 AND
bucket_name = $2 AND bucket_name = $2 AND
object_key = $3 AND object_key = $3 AND
version = (SELECT version FROM objects WHERE
project_id = $1 AND
bucket_name = $2 AND
object_key = $3 AND
status = 1
ORDER BY version DESC LIMIT 1
) AND
status = 1 status = 1
ORDER BY version DESC LIMIT 1 RETURNING
) AND version, stream_id,
status = 1 created_at, expires_at,
RETURNING status, segment_count,
version, stream_id, encrypted_metadata_nonce, encrypted_metadata,
created_at, expires_at, total_encrypted_size, fixed_segment_size,
status, segment_count, encryption;
encrypted_metadata_nonce, encrypted_metadata, `, opts.ProjectID, opts.BucketName, []byte(opts.ObjectKey))
total_encrypted_size, fixed_segment_size, if err != nil {
encryption; if errors.Is(err, sql.ErrNoRows) {
`, opts.ProjectID, opts.BucketName, []byte(opts.ObjectKey)) return storj.ErrObjectNotFound.Wrap(Error.Wrap(err))
if err != nil { }
if errors.Is(err, sql.ErrNoRows) { return Error.New("unable to delete object: %w", err)
return DeleteObjectResult{}, storj.ErrObjectNotFound.Wrap(Error.Wrap(err))
} }
return DeleteObjectResult{}, Error.New("unable to delete object: %w", err)
}
result.Objects, err = scanObjectDeletion(opts.ObjectLocation, rows) result.Objects, err = scanObjectDeletion(opts.ObjectLocation, rows)
if err != nil {
return err
}
if len(result.Objects) == 0 {
return storj.ErrObjectNotFound.Wrap(Error.New("no rows deleted"))
}
segmentInfos, err := deleteSegments(ctx, tx, result.Objects)
if err != nil {
return err
}
if len(segmentInfos) != 0 {
result.Segments = segmentInfos
}
return nil
})
if err != nil { if err != nil {
return DeleteObjectResult{}, err return DeleteObjectResult{}, err
} }
if len(result.Objects) == 0 {
return DeleteObjectResult{}, storj.ErrObjectNotFound.Wrap(Error.New("no rows deleted"))
}
segmentInfos, err := deleteSegments(ctx, tx, result.Objects)
if err != nil {
return DeleteObjectResult{}, err
}
if len(segmentInfos) != 0 {
result.Segments = segmentInfos
}
err, committed = tx.Commit(), true
if err != nil {
return DeleteObjectResult{}, Error.New("unable to commit tx: %w", err)
}
return result, nil return result, nil
} }
@ -258,63 +238,52 @@ func (db *DB) DeleteObjectAllVersions(ctx context.Context, opts DeleteObjectAllV
if err := opts.Verify(); err != nil { if err := opts.Verify(); err != nil {
return DeleteObjectResult{}, err return DeleteObjectResult{}, err
} }
err = txutil.WithTx(ctx, db.db, nil, func(ctx context.Context, tx tagsql.Tx) error {
tx, err := db.db.BeginTx(ctx, nil) rows, err := tx.Query(ctx, `
if err != nil { DELETE FROM objects
return DeleteObjectResult{}, Error.New("failed BeginTx: %w", err) WHERE
} project_id = $1 AND
committed := false bucket_name = $2 AND
defer func() { object_key = $3 AND
if !committed { status = 1
err = errs.Combine(err, Error.Wrap(tx.Rollback())) RETURNING
version, stream_id,
created_at, expires_at,
status, segment_count,
encrypted_metadata_nonce, encrypted_metadata,
total_encrypted_size, fixed_segment_size,
encryption;
`, opts.ProjectID, opts.BucketName, []byte(opts.ObjectKey))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return storj.ErrObjectNotFound.Wrap(Error.Wrap(err))
}
return Error.New("unable to delete object: %w", err)
} }
}()
rows, err := tx.Query(ctx, ` result.Objects, err = scanObjectDeletion(opts.ObjectLocation, rows)
DELETE FROM objects if err != nil {
WHERE return err
project_id = $1 AND
bucket_name = $2 AND
object_key = $3 AND
status = 1
RETURNING
version, stream_id,
created_at, expires_at,
status, segment_count,
encrypted_metadata_nonce, encrypted_metadata,
total_encrypted_size, fixed_segment_size,
encryption;
`, opts.ProjectID, opts.BucketName, []byte(opts.ObjectKey))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return DeleteObjectResult{}, storj.ErrObjectNotFound.Wrap(Error.Wrap(err))
} }
return DeleteObjectResult{}, Error.New("unable to delete object: %w", err)
}
result.Objects, err = scanObjectDeletion(opts.ObjectLocation, rows) if len(result.Objects) == 0 {
return storj.ErrObjectNotFound.Wrap(Error.New("no rows deleted"))
}
segmentInfos, err := deleteSegments(ctx, tx, result.Objects)
if err != nil {
return err
}
if len(segmentInfos) != 0 {
result.Segments = segmentInfos
}
return nil
})
if err != nil { if err != nil {
return DeleteObjectResult{}, err return DeleteObjectResult{}, err
} }
if len(result.Objects) == 0 {
return DeleteObjectResult{}, storj.ErrObjectNotFound.Wrap(Error.New("no rows deleted"))
}
segmentInfos, err := deleteSegments(ctx, tx, result.Objects)
if err != nil {
return DeleteObjectResult{}, err
}
if len(segmentInfos) != 0 {
result.Segments = segmentInfos
}
err, committed = tx.Commit(), true
if err != nil {
return DeleteObjectResult{}, Error.New("unable to commit tx: %w", err)
}
return result, nil return result, nil
} }
@ -331,79 +300,68 @@ func (db *DB) DeleteObjectsAllVersions(ctx context.Context, opts DeleteObjectsAl
return DeleteObjectResult{}, err return DeleteObjectResult{}, err
} }
tx, err := db.db.BeginTx(ctx, nil) err = txutil.WithTx(ctx, db.db, nil, func(ctx context.Context, tx tagsql.Tx) error {
if err != nil { // It is aleady verified that all object locations are in the same bucket
return DeleteObjectResult{}, Error.New("failed BeginTx: %w", err) projectID := opts.Locations[0].ProjectID
} bucketName := opts.Locations[0].BucketName
committed := false
defer func() { objectKeys := make([][]byte, len(opts.Locations))
if !committed { for i := range opts.Locations {
err = errs.Combine(err, Error.Wrap(tx.Rollback())) objectKeys[i] = []byte(opts.Locations[i].ObjectKey)
} }
}()
// It is aleady verified that all object locations are in the same bucket // Sorting the object keys just in case.
projectID := opts.Locations[0].ProjectID // TODO: Check if this is really necessary for the SQL query.
bucketName := opts.Locations[0].BucketName sort.Slice(objectKeys, func(i, j int) bool {
return bytes.Compare(objectKeys[i], objectKeys[j]) < 0
})
objectKeys := make([][]byte, len(opts.Locations)) rows, err := tx.Query(ctx, `
for i := range opts.Locations { DELETE FROM objects
objectKeys[i] = []byte(opts.Locations[i].ObjectKey) WHERE
} project_id = $1 AND
bucket_name = $2 AND
object_key = ANY ($3) AND
status = 1
RETURNING
project_id, bucket_name,
object_key, version, stream_id,
created_at, expires_at,
status, segment_count,
encrypted_metadata_nonce, encrypted_metadata,
total_encrypted_size, fixed_segment_size,
encryption;
`, projectID, bucketName, pgutil.ByteaArray(objectKeys))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return storj.ErrObjectNotFound.Wrap(Error.Wrap(err))
}
return Error.New("unable to delete object: %w", err)
}
// Sorting the object keys just in case. result.Objects, err = scanMultipleObjectsDeletion(rows)
// TODO: Check if this is really necessary for the SQL query. if err != nil {
sort.Slice(objectKeys, func(i, j int) bool { return err
return bytes.Compare(objectKeys[i], objectKeys[j]) < 0 }
if len(result.Objects) == 0 {
// nothing was delete, no error
return nil
}
segmentInfos, err := deleteSegments(ctx, tx, result.Objects)
if err != nil {
return err
}
if len(segmentInfos) != 0 {
result.Segments = segmentInfos
}
return nil
}) })
rows, err := tx.Query(ctx, `
DELETE FROM objects
WHERE
project_id = $1 AND
bucket_name = $2 AND
object_key = ANY ($3) AND
status = 1
RETURNING
project_id, bucket_name,
object_key, version, stream_id,
created_at, expires_at,
status, segment_count,
encrypted_metadata_nonce, encrypted_metadata,
total_encrypted_size, fixed_segment_size,
encryption;
`, projectID, bucketName, pgutil.ByteaArray(objectKeys))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return DeleteObjectResult{}, storj.ErrObjectNotFound.Wrap(Error.Wrap(err))
}
return DeleteObjectResult{}, Error.New("unable to delete object: %w", err)
}
result.Objects, err = scanMultipleObjectsDeletion(rows)
if err != nil { if err != nil {
return DeleteObjectResult{}, err return DeleteObjectResult{}, err
} }
if len(result.Objects) == 0 {
// nothing was delete, no error
return DeleteObjectResult{}, nil
}
segmentInfos, err := deleteSegments(ctx, tx, result.Objects)
if err != nil {
return DeleteObjectResult{}, err
}
if len(segmentInfos) != 0 {
result.Segments = segmentInfos
}
err, committed = tx.Commit(), true
if err != nil {
return DeleteObjectResult{}, Error.New("unable to commit tx: %w", err)
}
return result, nil return result, nil
} }
@ -460,6 +418,10 @@ func scanMultipleObjectsDeletion(rows tagsql.Rows) (objects []Object, err error)
return nil, Error.New("unable to delete object: %w", err) return nil, Error.New("unable to delete object: %w", err)
} }
if len(objects) == 0 {
objects = nil
}
return objects, nil return objects, nil
} }