Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
77 lines
2.3 KiB
Go
77 lines
2.3 KiB
Go
// Copyright © 2023 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package update
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/pkg/errors"
|
|
"go.opentelemetry.io/otel/trace"
|
|
|
|
"github.com/ory/pop/v6"
|
|
"github.com/ory/pop/v6/columns"
|
|
"github.com/ory/x/otelx"
|
|
"github.com/ory/x/sqlcon"
|
|
)
|
|
|
|
func Generic(ctx context.Context, c *pop.Connection, tracer trace.Tracer, v any, columnNames ...string) (err error) {
|
|
ctx, span := tracer.Start(ctx, "persistence.sql.update")
|
|
defer otelx.End(span, &err)
|
|
|
|
quoter, ok := c.Dialect.(interface{ Quote(key string) string })
|
|
if !ok {
|
|
return errors.Errorf("store is not a quoter: %T", c.Store)
|
|
}
|
|
|
|
model := pop.NewModel(v, ctx)
|
|
tn := model.TableName()
|
|
|
|
cols := columns.Columns{}
|
|
if len(columnNames) > 0 && tn == model.TableName() {
|
|
cols = columns.NewColumnsWithAlias(tn, model.As, model.IDField())
|
|
cols.Add(columnNames...)
|
|
} else {
|
|
cols = columns.ForStructWithAlias(v, tn, model.As, model.IDField())
|
|
}
|
|
|
|
//#nosec G201 -- TableName is static
|
|
stmt := fmt.Sprintf("UPDATE %s AS %s SET %s WHERE (%s) AND %s.nid = :nid",
|
|
quoter.Quote(model.TableName()),
|
|
model.Alias(),
|
|
cols.Writeable().QuotedUpdateString(quoter),
|
|
model.WhereNamedID(),
|
|
model.Alias(),
|
|
)
|
|
|
|
if result, err := c.Store.NamedExecContext(ctx, stmt, v); err != nil {
|
|
return sqlcon.HandleError(err)
|
|
} else if affected, err := result.RowsAffected(); err != nil {
|
|
return sqlcon.HandleError(err)
|
|
} else if affected == 0 {
|
|
return errors.WithStack(sqlcon.ErrNoRows())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GenericExcept updates v like Generic, but omits excludeColumns from the
|
|
// UPDATE ... SET list (every other writeable column is still written).
|
|
//
|
|
// Leaving a column that backs a unique index out of the SET list — when its
|
|
// value has not changed — lets the database skip the uniqueness check it would
|
|
// otherwise plan for that index, which can be expensive.
|
|
func GenericExcept(ctx context.Context, c *pop.Connection, tracer trace.Tracer, v any, excludeColumns ...string) error {
|
|
model := pop.NewModel(v, ctx)
|
|
cols := columns.ForStructWithAlias(v, model.TableName(), model.As, model.IDField())
|
|
cols.Remove(excludeColumns...)
|
|
|
|
writeable := cols.Writeable().Cols
|
|
names := make([]string, 0, len(writeable))
|
|
for name := range writeable {
|
|
names = append(names, name)
|
|
}
|
|
return Generic(ctx, c, tracer, v, names...)
|
|
}
|