cmd/tools: remove nullify-bad-user-agents tool

This tool is being removed because it has served its purpose and was blocking another removal from being verified.

Change-Id: Ie888aa7ae1b153a34210af3a5d5a3682b381ba82
This commit is contained in:
Wilfred Asomani 2023-01-30 15:57:33 +00:00
parent 2f04e20627
commit 73ffa0827f
4 changed files with 0 additions and 2120 deletions

View File

@ -1,308 +0,0 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"context"
"github.com/jackc/pgtype"
pgx "github.com/jackc/pgx/v4"
"github.com/zeebo/errs"
"go.uber.org/zap"
"storj.io/private/dbutil/pgutil"
)
// MigrateTablesLimited runs the migration for each table with update count limits.
func MigrateTablesLimited(ctx context.Context, log *zap.Logger, conn *pgx.Conn, config Config) (err error) {
err = MigrateUsersLimited(ctx, log, conn, config)
if err != nil {
return errs.New("error migrating users: %w", err)
}
err = MigrateProjectsLimited(ctx, log, conn, config)
if err != nil {
return errs.New("error migrating projects: %w", err)
}
err = MigrateAPIKeysLimited(ctx, log, conn, config)
if err != nil {
return errs.New("error migrating api_keys: %w", err)
}
err = MigrateBucketMetainfosLimited(ctx, log, conn, config)
if err != nil {
return errs.New("error migrating bucket_metainfos: %w", err)
}
err = MigrateValueAttributionsLimited(ctx, log, conn, config)
if err != nil {
return errs.New("error migrating value_attributions: %w", err)
}
return nil
}
// MigrateUsersLimited updates the user_agent column to corresponding Partners.Names or partner_id if applicable for a limited number
// of rows.
func MigrateUsersLimited(ctx context.Context, log *zap.Logger, conn *pgx.Conn, config Config) (err error) {
defer mon.Task()(&ctx)(&err)
log.Info("beginning users migration", zap.Int("max updates", config.MaxUpdates))
// wrap select in anonymous function for deferred rows.Close()
selected, err := func() (ids [][]byte, err error) {
rows, err := conn.Query(ctx, `
SELECT id FROM users
WHERE user_agent = partner_id
LIMIT $1
`, config.MaxUpdates)
if err != nil {
return nil, errs.New("selecting ids for update: %w", err)
}
defer rows.Close()
for rows.Next() {
var id pgtype.GenericBinary
err = rows.Scan(&id)
if err != nil {
return nil, errs.New("scanning row: %w", err)
}
ids = append(ids, id.Bytes)
}
return ids, rows.Err()
}()
if err != nil {
return err
}
row := conn.QueryRow(ctx, `
WITH updated as (
UPDATE users
SET user_agent = NULL
WHERE users.id IN (SELECT unnest($1::bytea[]))
RETURNING 1
)
SELECT count(*)
FROM updated
`, pgutil.ByteaArray(selected),
)
var updated int
err = row.Scan(&updated)
if err != nil {
return errs.New("error scanning results: %w", err)
}
log.Info("updated rows", zap.Int("count", updated))
return nil
}
// MigrateProjectsLimited updates the user_agent column to corresponding Partners.Names or partner_id if applicable for a limited number
// of rows.
func MigrateProjectsLimited(ctx context.Context, log *zap.Logger, conn *pgx.Conn, config Config) (err error) {
defer mon.Task()(&ctx)(&err)
log.Info("beginning projects migration", zap.Int("max updates", config.MaxUpdates))
// wrap select in anonymous function for deferred rows.Close()
selected, err := func() (ids [][]byte, err error) {
rows, err := conn.Query(ctx, `
SELECT id FROM projects
WHERE user_agent = partner_id
LIMIT $1
`, config.MaxUpdates)
if err != nil {
return nil, errs.New("selecting ids for update: %w", err)
}
defer rows.Close()
for rows.Next() {
var id pgtype.GenericBinary
err = rows.Scan(&id)
if err != nil {
return nil, errs.New("scanning row: %w", err)
}
ids = append(ids, id.Bytes)
}
return ids, rows.Err()
}()
if err != nil {
return err
}
row := conn.QueryRow(ctx, `
WITH updated as (
UPDATE projects
SET user_agent = NULL
WHERE projects.id IN (SELECT unnest($1::bytea[]))
RETURNING 1
)
SELECT count(*)
FROM updated
`, pgutil.ByteaArray(selected),
)
var updated int
err = row.Scan(&updated)
if err != nil {
return errs.New("error scanning results: %w", err)
}
log.Info("updated rows", zap.Int("count", updated))
return nil
}
// MigrateAPIKeysLimited updates the user_agent column to corresponding Partners.Names or partner_id if applicable for a limited number
// of rows.
func MigrateAPIKeysLimited(ctx context.Context, log *zap.Logger, conn *pgx.Conn, config Config) (err error) {
defer mon.Task()(&ctx)(&err)
log.Info("beginning api_keys migration", zap.Int("max updates", config.MaxUpdates))
// wrap select in anonymous function for deferred rows.Close()
selected, err := func() (ids [][]byte, err error) {
rows, err := conn.Query(ctx, `
SELECT id FROM api_keys
WHERE user_agent = partner_id
LIMIT $1
`, config.MaxUpdates)
if err != nil {
return nil, errs.New("selecting ids for update: %w", err)
}
defer rows.Close()
for rows.Next() {
var id pgtype.GenericBinary
err = rows.Scan(&id)
if err != nil {
return nil, errs.New("scanning row: %w", err)
}
ids = append(ids, id.Bytes)
}
return ids, rows.Err()
}()
if err != nil {
return err
}
row := conn.QueryRow(ctx, `
WITH updated as (
UPDATE api_keys
SET user_agent = NULL
WHERE api_keys.id IN (SELECT unnest($1::bytea[]))
RETURNING 1
)
SELECT count(*)
FROM updated
`, pgutil.ByteaArray(selected),
)
var updated int
err = row.Scan(&updated)
if err != nil {
return errs.New("error scanning results: %w", err)
}
log.Info("updated rows", zap.Int("count", updated))
return nil
}
// MigrateBucketMetainfosLimited updates the user_agent column to corresponding Partners.Names or partner_id if applicable for a limited number
// of rows.
func MigrateBucketMetainfosLimited(ctx context.Context, log *zap.Logger, conn *pgx.Conn, config Config) (err error) {
defer mon.Task()(&ctx)(&err)
log.Info("beginning bucket_metainfos migration", zap.Int("max updates", config.MaxUpdates))
// wrap select in anonymous function for deferred rows.Close()
selected, err := func() (ids [][]byte, err error) {
rows, err := conn.Query(ctx, `
SELECT id FROM bucket_metainfos
WHERE user_agent = partner_id
LIMIT $1
`, config.MaxUpdates)
if err != nil {
return nil, errs.New("selecting ids for update: %w", err)
}
defer rows.Close()
for rows.Next() {
var id pgtype.GenericBinary
err = rows.Scan(&id)
if err != nil {
return nil, errs.New("scanning row: %w", err)
}
ids = append(ids, id.Bytes)
}
return ids, rows.Err()
}()
if err != nil {
return err
}
row := conn.QueryRow(ctx, `
WITH updated as (
UPDATE bucket_metainfos
SET user_agent = NULL
WHERE bucket_metainfos.id IN (SELECT unnest($1::bytea[]))
RETURNING 1
)
SELECT count(*)
FROM updated
`, pgutil.ByteaArray(selected),
)
var updated int
err = row.Scan(&updated)
if err != nil {
return errs.New("error scanning results: %w", err)
}
log.Info("updated rows", zap.Int("count", updated))
return nil
}
// MigrateValueAttributionsLimited updates the user_agent column to corresponding Partners.Names or partner_id if applicable for a limited number
// of rows.
func MigrateValueAttributionsLimited(ctx context.Context, log *zap.Logger, conn *pgx.Conn, config Config) (err error) {
defer mon.Task()(&ctx)(&err)
log.Info("beginning value_attributions migration", zap.Int("max updates", config.MaxUpdates))
// wrap select in anonymous function for deferred rows.Close()
projects, buckets, err := func() (projectIDs [][]byte, buckets [][]byte, err error) {
rows, err := conn.Query(ctx, `
SELECT project_id, bucket_name FROM value_attributions
WHERE user_agent = partner_id
LIMIT $1
`, config.MaxUpdates)
if err != nil {
return nil, nil, errs.New("selecting rows for update: %w", err)
}
defer rows.Close()
for rows.Next() {
var projectID, bucketName pgtype.GenericBinary
err = rows.Scan(&projectID, &bucketName)
if err != nil {
return nil, nil, errs.New("scanning row: %w", err)
}
projectIDs = append(projectIDs, projectID.Bytes)
buckets = append(buckets, bucketName.Bytes)
}
return projectIDs, buckets, rows.Err()
}()
if err != nil {
return err
}
row := conn.QueryRow(ctx, `
WITH updated as (
UPDATE value_attributions
SET user_agent = NULL
WHERE value_attributions.project_id IN (SELECT unnest($1::bytea[]))
AND value_attributions.bucket_name IN (SELECT unnest($2::bytea[]))
AND user_agent = partner_id
RETURNING 1
)
SELECT count(*)
FROM updated
`, pgutil.ByteaArray(projects), pgutil.ByteaArray(buckets),
)
var updated int
err = row.Scan(&updated)
if err != nil {
return errs.New("error scanning results: %w", err)
}
log.Info("updated rows", zap.Int("count", updated))
return nil
}

View File

@ -1,113 +0,0 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"context"
"errors"
pgx "github.com/jackc/pgx/v4"
"github.com/spacemonkeygo/monkit/v3"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"github.com/zeebo/errs"
"go.uber.org/zap"
"storj.io/private/process"
)
var mon = monkit.Package()
var (
rootCmd = &cobra.Command{
Use: "nullify-bad-user-agents",
Short: "nullify-bad-user-agents",
}
runCmd = &cobra.Command{
Use: "run",
Short: "run nullify-bad-user-agents",
RunE: run,
}
config Config
)
func init() {
rootCmd.AddCommand(runCmd)
config.BindFlags(runCmd.Flags())
}
// Config defines configuration for migration.
type Config struct {
SatelliteDB string
Limit int
MaxUpdates int
}
// BindFlags adds bench flags to the flagset.
func (config *Config) BindFlags(flag *flag.FlagSet) {
flag.StringVar(&config.SatelliteDB, "satellitedb", "", "connection URL for satelliteDB")
flag.IntVar(&config.Limit, "limit", 1000, "number of updates to perform at once")
flag.IntVar(&config.MaxUpdates, "max-updates", 0, "max number of updates to perform on each table")
}
// VerifyFlags verifies whether the values provided are valid.
func (config *Config) VerifyFlags() error {
var errlist errs.Group
if config.SatelliteDB == "" {
errlist.Add(errors.New("flag '--satellitedb' is not set"))
}
return errlist.Err()
}
func run(cmd *cobra.Command, args []string) error {
if err := config.VerifyFlags(); err != nil {
return err
}
ctx, _ := process.Ctx(cmd)
log := zap.L()
return Migrate(ctx, log, config)
}
func main() {
process.Exec(rootCmd)
}
// Migrate updates the user_agent column if user_agent = '\x00000000000000000000000000000000' and sets
// it to NULL.
// Affected tables:
//
// users
// projects
// api_keys
// bucket_metainfos
// value_attributions.
func Migrate(ctx context.Context, log *zap.Logger, config Config) (err error) {
defer mon.Task()(&ctx)(&err)
conn, err := pgx.Connect(ctx, config.SatelliteDB)
if err != nil {
return errs.New("unable to connect %q: %w", config.SatelliteDB, err)
}
defer func() {
err = errs.Combine(err, conn.Close(ctx))
}()
// The original migrations are already somewhat complex and in my opinion,
// trying to edit them to be able to handle conditionally limiting updates increased the
// complexity. While I think splitting out the limited update migrations isn't the
// most ideal solution, since this code is temporary we don't need to worry about
// maintenance concerns with having multiple queries.
if config.MaxUpdates > 1000 {
return errs.New("When running limited migration, set --max-updates to something less than 1000")
}
if config.MaxUpdates > 0 {
return MigrateTablesLimited(ctx, log, conn, config)
}
return MigrateTables(ctx, log, conn, config)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,511 +0,0 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"context"
pgx "github.com/jackc/pgx/v4"
"github.com/zeebo/errs"
"go.uber.org/zap"
"storj.io/private/dbutil/cockroachutil"
)
// MigrateTables runs the migration for each table.
func MigrateTables(ctx context.Context, log *zap.Logger, conn *pgx.Conn, config Config) (err error) {
err = MigrateUsers(ctx, log, conn, config)
if err != nil {
return errs.New("error migrating users: %w", err)
}
err = MigrateProjects(ctx, log, conn, config)
if err != nil {
return errs.New("error migrating projects: %w", err)
}
err = MigrateAPIKeys(ctx, log, conn, config)
if err != nil {
return errs.New("error migrating api_keys: %w", err)
}
err = MigrateBucketMetainfos(ctx, log, conn, config)
if err != nil {
return errs.New("error migrating bucket_metainfos: %w", err)
}
err = MigrateValueAttributions(ctx, log, conn, config)
if err != nil {
return errs.New("error migrating value_attributions: %w", err)
}
return nil
}
// MigrateUsers updates the user_agent column to corresponding Partners.Names or partner_id if applicable.
func MigrateUsers(ctx context.Context, log *zap.Logger, conn *pgx.Conn, config Config) (err error) {
defer mon.Task()(&ctx)(&err)
// We select the next id then use limit as an offset which actually gives us limit+1 rows.
offset := config.Limit - 1
startID := []byte{}
nextID := []byte{}
var total int
more := true
// select the next ID after startID and offset the result. We will update relevant rows from the range of startID to nextID.
_, err = conn.Prepare(ctx, "select-for-update-users", "SELECT id FROM users WHERE id > $1 ORDER BY id OFFSET $2 LIMIT 1")
if err != nil {
return errs.New("could not prepare select query")
}
// update range from startID to nextID. Return count of updated rows to log progress.
_, err = conn.Prepare(ctx, "update-limited-users", `
WITH updated as (
UPDATE users
SET user_agent = NULL
WHERE users.id > $1 AND users.id <= $2
AND user_agent = partner_id
RETURNING 1
)
SELECT count(*)
FROM updated;
`)
if err != nil {
return errs.New("could not prepare update statement: %w", err)
}
for {
row := conn.QueryRow(ctx, "select-for-update-users", startID, offset)
err = row.Scan(&nextID)
if err != nil {
if errs.Is(err, pgx.ErrNoRows) {
more = false
} else {
return errs.New("unable to select row for update from users: %w", err)
}
}
var updated int
for {
var row pgx.Row
if more {
row = conn.QueryRow(ctx, "update-limited-users", startID, nextID)
} else {
// if !more then the select statement reached the end of the table. Update to the end of the table.
row = conn.QueryRow(ctx, `
WITH updated as (
UPDATE users
SET user_agent = NULL
WHERE users.id > $1
AND user_agent = partner_id
RETURNING 1
)
SELECT count(*)
FROM updated;
`, startID,
)
}
err := row.Scan(&updated)
if err != nil {
if cockroachutil.NeedsRetry(err) {
continue
} else if errs.Is(err, pgx.ErrNoRows) {
break
}
return errs.New("updating users %w", err)
}
break
}
total += updated
if !more {
log.Info("batch update complete", zap.Int("rows updated", updated))
break
}
log.Info("batch update complete", zap.Int("rows updated", updated), zap.Binary("last id", nextID))
startID = nextID
}
log.Info("users migration complete", zap.Int("total rows updated", total))
return nil
}
// MigrateProjects updates the user_agent column to corresponding PartnerInfo.Names or partner_id if applicable.
func MigrateProjects(ctx context.Context, log *zap.Logger, conn *pgx.Conn, config Config) (err error) {
defer mon.Task()(&ctx)(&err)
// We select the next id then use limit as an offset which actually gives us limit+1 rows.
offset := config.Limit - 1
startID := []byte{}
nextID := []byte{}
var total int
more := true
// select the next ID after startID and offset the result. We will update relevant rows from the range of startID to nextID.
_, err = conn.Prepare(ctx, "select-for-update-projects", "SELECT id FROM projects WHERE id > $1 ORDER BY id OFFSET $2 LIMIT 1")
if err != nil {
return errs.New("could not prepare select query: %w", err)
}
// update range from startID to nextID. Return count of updated rows to log progress.
_, err = conn.Prepare(ctx, "update-limited-projects", `
WITH updated as (
UPDATE projects
SET user_agent = NULL
WHERE projects.id > $1 AND projects.id <= $2
AND user_agent = partner_id
RETURNING 1
)
SELECT count(*)
FROM updated;
`)
if err != nil {
return errs.New("could not prepare update statement")
}
for {
row := conn.QueryRow(ctx, "select-for-update-projects", startID, offset)
err = row.Scan(&nextID)
if err != nil {
if errs.Is(err, pgx.ErrNoRows) {
more = false
} else {
return errs.New("unable to select row for update from projects: %w", err)
}
}
var updated int
for {
var row pgx.Row
if more {
row = conn.QueryRow(ctx, "update-limited-projects", startID, nextID)
} else {
// if !more then the select statement reached the end of the table. Update to the end of the table.
row = conn.QueryRow(ctx, `
WITH updated as (
UPDATE projects
SET user_agent = NULL
WHERE projects.id > $1
AND user_agent = partner_id
RETURNING 1
)
SELECT count(*)
FROM updated;
`, startID,
)
}
err := row.Scan(&updated)
if err != nil {
if cockroachutil.NeedsRetry(err) {
continue
} else if errs.Is(err, pgx.ErrNoRows) {
break
}
return errs.New("updating projects %w", err)
}
break
}
total += updated
if !more {
log.Info("batch update complete", zap.Int("rows updated", updated))
break
}
log.Info("batch update complete", zap.Int("rows updated", updated), zap.Binary("last id", nextID))
startID = nextID
}
log.Info("projects migration complete", zap.Int("total rows updated", total))
return nil
}
// MigrateAPIKeys updates the user_agent column to corresponding PartnerInfo.Names or partner_id if applicable.
func MigrateAPIKeys(ctx context.Context, log *zap.Logger, conn *pgx.Conn, config Config) (err error) {
defer mon.Task()(&ctx)(&err)
// We select the next id then use limit as an offset which actually gives us limit+1 rows.
offset := config.Limit - 1
startID := []byte{}
nextID := []byte{}
var total int
more := true
// select the next ID after startID and offset the result. We will update relevant rows from the range of startID to nextID.
_, err = conn.Prepare(ctx, "select-for-update-api-keys", "SELECT id FROM api_keys WHERE id > $1 ORDER BY id OFFSET $2 LIMIT 1")
if err != nil {
return errs.New("could not prepare select query: %w", err)
}
// update range from startID to nextID. Return count of updated rows to log progress.
_, err = conn.Prepare(ctx, "update-limited-api-keys", `
WITH updated as (
UPDATE api_keys
SET user_agent = NULL
WHERE api_keys.id > $1 AND api_keys.id <= $2
AND user_agent = partner_id
RETURNING 1
)
SELECT count(*)
FROM updated;
`)
if err != nil {
return errs.New("could not prepare update statement")
}
for {
row := conn.QueryRow(ctx, "select-for-update-api-keys", startID, offset)
err = row.Scan(&nextID)
if err != nil {
if errs.Is(err, pgx.ErrNoRows) {
more = false
} else {
return errs.New("unable to select row for update from api_keys: %w", err)
}
}
var updated int
for {
var row pgx.Row
if more {
row = conn.QueryRow(ctx, "update-limited-api-keys", startID, nextID)
} else {
// if !more then the select statement reached the end of the table. Update to the end of the table.
row = conn.QueryRow(ctx, `
WITH updated as (
UPDATE api_keys
SET user_agent = NULL
WHERE api_keys.id > $1
AND user_agent = partner_id
RETURNING 1
)
SELECT count(*)
FROM updated;
`, startID,
)
}
err := row.Scan(&updated)
if err != nil {
if cockroachutil.NeedsRetry(err) {
continue
} else if errs.Is(err, pgx.ErrNoRows) {
break
}
return errs.New("updating api_keys %w", err)
}
break
}
total += updated
if !more {
log.Info("batch update complete", zap.Int("rows updated", updated))
break
}
log.Info("batch update complete", zap.Int("rows updated", updated), zap.Binary("last id", nextID))
startID = nextID
}
log.Info("api_keys migration complete", zap.Int("total rows updated", total))
return nil
}
// MigrateBucketMetainfos updates the user_agent column to corresponding Partners.Names or partner_id if applicable.
func MigrateBucketMetainfos(ctx context.Context, log *zap.Logger, conn *pgx.Conn, config Config) (err error) {
defer mon.Task()(&ctx)(&err)
// We select the next id then use limit as an offset which actually gives us limit+1 rows.
offset := config.Limit - 1
startID := []byte{}
nextID := []byte{}
var total int
more := true
// select the next ID after startID and offset the result. We will update relevant rows from the range of startID to nextID.
_, err = conn.Prepare(ctx, "select-for-update-bucket-metainfos", "SELECT id FROM bucket_metainfos WHERE id > $1 ORDER BY id OFFSET $2 LIMIT 1")
if err != nil {
return errs.New("could not prepare select query: %w", err)
}
// update range from startID to nextID. Return count of updated rows to log progress.
_, err = conn.Prepare(ctx, "update-limited-bucket-metainfos", `
WITH updated as (
UPDATE bucket_metainfos
SET user_agent = NULL
WHERE bucket_metainfos.id > $1 AND bucket_metainfos.id <= $2
AND user_agent = partner_id
RETURNING 1
)
SELECT count(*)
FROM updated;
`)
if err != nil {
return errs.New("could not prepare update statement")
}
for {
row := conn.QueryRow(ctx, "select-for-update-bucket-metainfos", startID, offset)
err = row.Scan(&nextID)
if err != nil {
if errs.Is(err, pgx.ErrNoRows) {
more = false
} else {
return errs.New("unable to select row for update from bucket_metainfos: %w", err)
}
}
var updated int
for {
var row pgx.Row
if more {
row = conn.QueryRow(ctx, "update-limited-bucket-metainfos", startID, nextID)
} else {
// if !more then the select statement reached the end of the table. Update to the end of the table.
row = conn.QueryRow(ctx, `
WITH updated as (
UPDATE bucket_metainfos
SET user_agent = NULL
WHERE bucket_metainfos.id > $1
AND user_agent = partner_id
RETURNING 1
)
SELECT count(*)
FROM updated;
`, startID,
)
}
err := row.Scan(&updated)
if err != nil {
if cockroachutil.NeedsRetry(err) {
continue
} else if errs.Is(err, pgx.ErrNoRows) {
break
}
return errs.New("updating bucket_metainfos %w", err)
}
break
}
total += updated
if !more {
log.Info("batch update complete", zap.Int("rows updated", updated))
break
}
log.Info("batch update complete", zap.Int("rows updated", updated), zap.Binary("last id", nextID))
startID = nextID
}
log.Info("bucket_metainfos migration complete", zap.Int("total rows updated", total))
return nil
}
// MigrateValueAttributions updates the user_agent column to corresponding Partners.Names or partner_id if applicable.
func MigrateValueAttributions(ctx context.Context, log *zap.Logger, conn *pgx.Conn, config Config) (err error) {
defer mon.Task()(&ctx)(&err)
// We select the next id then use limit as an offset which actually gives us limit+1 rows.
offset := config.Limit - 1
startProjectID := []byte{}
startBucket := []byte{}
nextProjectID := []byte{}
nextBucket := []byte{}
var total int
more := true
// select the next ID after startID and offset the result. We will update relevant rows from the range of startID to nextID.
_, err = conn.Prepare(ctx, "select-for-update-value-attributions", `
SELECT project_id, bucket_name
FROM value_attributions
WHERE project_id > $1
OR (
project_id = $1 AND bucket_name > $2
)
ORDER BY project_id, bucket_name
OFFSET $3
LIMIT 1
`)
if err != nil {
return errs.New("could not prepare select query: %w", err)
}
// update range from startID to nextID. Return count of updated rows to log progress.
_, err = conn.Prepare(ctx, "update-limited-value-attributions", `
WITH updated as (
UPDATE value_attributions
SET user_agent = NULL
WHERE user_agent = partner_id
AND (
value_attributions.project_id > $1
OR (
value_attributions.project_id = $1 AND value_attributions.bucket_name > $3
)
)
AND (
value_attributions.project_id < $2
OR (
value_attributions.project_id = $2 AND value_attributions.bucket_name <= $4
)
)
RETURNING 1
)
SELECT count(*)
FROM updated;
`)
if err != nil {
return errs.New("could not prepare update statement")
}
for {
row := conn.QueryRow(ctx, "select-for-update-value-attributions", startProjectID, startBucket, offset)
err = row.Scan(&nextProjectID, &nextBucket)
if err != nil {
if errs.Is(err, pgx.ErrNoRows) {
more = false
} else {
return errs.New("unable to select row for update from value_attributions: %w", err)
}
}
var updated int
for {
var row pgx.Row
if more {
row = conn.QueryRow(ctx, "update-limited-value-attributions", startProjectID, nextProjectID, startBucket, nextBucket)
} else {
// if !more then the select statement reached the end of the table. Update to the end of the table.
row = conn.QueryRow(ctx, `
WITH updated as (
UPDATE value_attributions
SET user_agent = NULL
WHERE user_agent = partner_id
AND (
value_attributions.project_id > $1
OR (
value_attributions.project_id = $1 AND value_attributions.bucket_name > $2
)
)
RETURNING 1
)
SELECT count(*)
FROM updated;
`, startProjectID, startBucket,
)
}
err := row.Scan(&updated)
if err != nil {
if cockroachutil.NeedsRetry(err) {
continue
} else if errs.Is(err, pgx.ErrNoRows) {
break
}
return errs.New("updating value_attributions %w", err)
}
break
}
total += updated
if !more {
log.Info("batch update complete", zap.Int("rows updated", updated))
break
}
log.Info("batch update complete", zap.Int("rows updated", updated), zap.Binary("last project id", nextProjectID), zap.String("last bucket name", string(nextBucket)))
startProjectID = nextProjectID
startBucket = nextBucket
}
log.Info("value_attributions migration complete", zap.Int("total rows updated", total))
return nil
}