* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * Update copyright file headers to BUS-1.1 * Rerun make gen This will pick up the last of the license changes * Revert "Rerun make gen" This reverts commit dbe0968bffd92745d8663c6940debae098395a6b. * Remove copywrite from generation script The current version of copywrite is not sophisticated enough to only apply the copywrite changes to certain subdirectories, so to avoid accidentally relicensing our api and sdk modules, we will stop running the copywrite script until such time that it can do the right thing. * License protobuf API as MPL The protobuf API files are used to generate the SDK, since we want the SDK to be MPL, the protobuf files must also be MPL. This extends to all protobuf files whose sources end up in the SDK directory, including: - internal/proto/controller/api - internal/proto/controller/custom_options - internal/proto/plugin It also includes the generated service directory in internal/gen/controller/api/services, since they are generated from the protobuf sources used for the SDK. * Change website license file name This makes the naming scheme consistent with the rest of the repository. * Add note on licensing to README * Set copywrite license to BUSL * Revert "Add note on licensing to README" This reverts commit 4d865a91a7a4f11605f0dc4609355d7e3c3cfffe. --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
365 lines
10 KiB
Go
365 lines
10 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
"github.com/hashicorp/boundary/internal/oplog"
|
|
"github.com/hashicorp/go-dbw"
|
|
"github.com/hashicorp/go-hclog"
|
|
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
|
)
|
|
|
|
// GetOpts - iterate the inbound Options and return a struct.
|
|
func GetOpts(opt ...Option) Options {
|
|
opts := getDefaultOptions()
|
|
for _, o := range opt {
|
|
o(&opts)
|
|
}
|
|
return opts
|
|
}
|
|
|
|
func getDbwOptions(ctx context.Context, rw *Db, i any, opType OpType, opt ...Option) ([]dbw.Option, error) {
|
|
const op = "db.getDbwOptions"
|
|
opts := GetOpts(opt...)
|
|
dbwOpts := make([]dbw.Option, 0, len(opt))
|
|
oplogBefore, after, err := rw.generateOplogBeforeAfterOpts(ctx, i, opType, opts)
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
}
|
|
before := oplogBefore
|
|
if !opts.withSkipVetForWrite && (opType != DeleteOp && opType != DeleteItemsOp) {
|
|
if vetter, ok := i.(VetForWriter); ok {
|
|
before = func(i any) error {
|
|
if err := vetter.VetForWrite(ctx, rw, opType, opt...); err != nil {
|
|
return err
|
|
}
|
|
if oplogBefore != nil {
|
|
if err := oplogBefore(i); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
if before != nil {
|
|
dbwOpts = append(dbwOpts, dbw.WithBeforeWrite(before))
|
|
}
|
|
if after != nil {
|
|
dbwOpts = append(dbwOpts, dbw.WithAfterWrite(after))
|
|
}
|
|
if opts.withOnConflict != nil {
|
|
c := &dbw.OnConflict{}
|
|
switch target := opts.withOnConflict.Target.(type) {
|
|
case Constraint:
|
|
c.Target = dbw.Constraint(target)
|
|
case Columns:
|
|
c.Target = dbw.Columns(target)
|
|
default:
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("not a supported target type: %T", target))
|
|
}
|
|
switch action := opts.withOnConflict.Action.(type) {
|
|
case DoNothing:
|
|
c.Action = dbw.DoNothing(bool(action))
|
|
case UpdateAll:
|
|
c.Action = dbw.UpdateAll(bool(action))
|
|
case []ColumnValue:
|
|
colVals := make([]dbw.ColumnValue, 0, len(action))
|
|
for _, cv := range action {
|
|
dbwColVal := dbw.ColumnValue{
|
|
Column: cv.column,
|
|
}
|
|
switch cvVal := cv.value.(type) {
|
|
case ExprValue:
|
|
dbwColVal.Value = dbw.ExprValue{
|
|
Sql: cvVal.sql,
|
|
Vars: cvVal.vars,
|
|
}
|
|
case column:
|
|
dbwColVal.Column = cvVal.name
|
|
dbwColVal.Value = dbw.Column{
|
|
Table: cvVal.table,
|
|
Name: cvVal.name,
|
|
}
|
|
default:
|
|
dbwColVal.Value = cv.value
|
|
}
|
|
colVals = append(colVals, dbwColVal)
|
|
}
|
|
c.Action = colVals
|
|
default:
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("not a valid supported action: %T", action))
|
|
}
|
|
dbwOpts = append(dbwOpts, dbw.WithOnConflict(c))
|
|
}
|
|
if opts.withLookup {
|
|
dbwOpts = append(dbwOpts, dbw.WithLookup(opts.withLookup))
|
|
}
|
|
if opts.WithLimit != 0 {
|
|
dbwOpts = append(dbwOpts, dbw.WithLimit(opts.WithLimit))
|
|
}
|
|
if len(opts.WithFieldMaskPaths) > 0 {
|
|
dbwOpts = append(dbwOpts, dbw.WithFieldMaskPaths(opts.WithFieldMaskPaths))
|
|
}
|
|
if len(opts.WithNullPaths) > 0 {
|
|
dbwOpts = append(dbwOpts, dbw.WithNullPaths(opts.WithNullPaths))
|
|
}
|
|
if opts.WithVersion != nil {
|
|
dbwOpts = append(dbwOpts, dbw.WithVersion(opts.WithVersion))
|
|
}
|
|
if opts.withSkipVetForWrite {
|
|
dbwOpts = append(dbwOpts, dbw.WithSkipVetForWrite(opts.withSkipVetForWrite))
|
|
}
|
|
if opts.withWhereClause != "" {
|
|
dbwOpts = append(dbwOpts, dbw.WithWhere(opts.withWhereClause, opts.withWhereClauseArgs...))
|
|
}
|
|
if opts.withOrder != "" {
|
|
dbwOpts = append(dbwOpts, dbw.WithOrder(opts.withOrder))
|
|
}
|
|
if len(opts.withPrngValues) > 0 {
|
|
dbwOpts = append(dbwOpts, dbw.WithPrngValues(opts.withPrngValues))
|
|
}
|
|
if opts.withGormFormatter != nil {
|
|
dbwOpts = append(dbwOpts, dbw.WithLogger(opts.withGormFormatter))
|
|
}
|
|
if opts.withMaxOpenConnections > 0 {
|
|
dbwOpts = append(dbwOpts, dbw.WithMaxOpenConnections(opts.withMaxOpenConnections))
|
|
}
|
|
if opts.withDebug {
|
|
dbwOpts = append(dbwOpts, dbw.WithDebug(opts.withDebug))
|
|
}
|
|
if opts.withRowsAffected != nil {
|
|
dbwOpts = append(dbwOpts, dbw.WithReturnRowsAffected(opts.withRowsAffected))
|
|
}
|
|
return dbwOpts, nil
|
|
}
|
|
|
|
// Option - how Options are passed as arguments.
|
|
type Option func(*Options)
|
|
|
|
// Options - how Options are represented.
|
|
type Options struct {
|
|
withOplog bool
|
|
oplogOpts oplogOpts
|
|
withLookup bool
|
|
// WithLimit must be accessible in other packages.
|
|
WithLimit int
|
|
// WithFieldMaskPaths must be accessible from other packages.
|
|
WithFieldMaskPaths []string
|
|
// WithNullPaths must be accessible from other packages.
|
|
WithNullPaths []string
|
|
|
|
newOplogMsg *oplog.Message
|
|
newOplogMsgs *[]*oplog.Message
|
|
|
|
// WithVersion must be accessible from other packages.
|
|
WithVersion *uint32
|
|
|
|
withSkipVetForWrite bool
|
|
|
|
withWhereClause string
|
|
withWhereClauseArgs []any
|
|
withOrder string
|
|
|
|
// withPrngValues is used to switch the ID generation to a pseudo-random mode
|
|
withPrngValues []string
|
|
|
|
withGormFormatter hclog.Logger
|
|
withMaxOpenConnections int
|
|
withMaxIdleConnections *int
|
|
withConnMaxIdleTimeDuration *time.Duration
|
|
|
|
// withDebug indicates that the given operation should invoke Gorm's debug
|
|
// mode
|
|
withDebug bool
|
|
|
|
withOnConflict *OnConflict
|
|
withRowsAffected *int64
|
|
}
|
|
|
|
type oplogOpts struct {
|
|
wrapper wrapping.Wrapper
|
|
metadata oplog.Metadata
|
|
}
|
|
|
|
func getDefaultOptions() Options {
|
|
return Options{
|
|
withOplog: false,
|
|
oplogOpts: oplogOpts{
|
|
wrapper: nil,
|
|
metadata: oplog.Metadata{},
|
|
},
|
|
withLookup: false,
|
|
WithFieldMaskPaths: []string{},
|
|
WithNullPaths: []string{},
|
|
WithLimit: 0,
|
|
WithVersion: nil,
|
|
withMaxIdleConnections: nil,
|
|
withConnMaxIdleTimeDuration: nil,
|
|
}
|
|
}
|
|
|
|
// WithLookup enables a lookup.
|
|
func WithLookup(enable bool) Option {
|
|
return func(o *Options) {
|
|
o.withLookup = enable
|
|
}
|
|
}
|
|
|
|
// WithOplog provides an option to write an oplog entry. WithOplog and
|
|
// NewOplogMsg cannot be used together.
|
|
func WithOplog(wrapper wrapping.Wrapper, md oplog.Metadata) Option {
|
|
return func(o *Options) {
|
|
o.withOplog = true
|
|
o.oplogOpts = oplogOpts{
|
|
wrapper: wrapper,
|
|
metadata: md,
|
|
}
|
|
}
|
|
}
|
|
|
|
// NewOplogMsg provides an option to ask for a new in-memory oplog message. The
|
|
// new msg will be returned in the provided *oplog.Message parameter. WithOplog
|
|
// and NewOplogMsg cannot be used together.
|
|
func NewOplogMsg(msg *oplog.Message) Option {
|
|
return func(o *Options) {
|
|
o.newOplogMsg = msg
|
|
}
|
|
}
|
|
|
|
// NewOplogMsgs provides an option to ask for multiple new in-memory oplog
|
|
// messages. The new msgs will be returned in the provided *[]oplog.Message
|
|
// parameter. NewOplogMsgs can only be used with write functions that operate on
|
|
// multiple items(CreateItems, DeleteItems). WithOplog and NewOplogMsgs cannot
|
|
// be used together.
|
|
func NewOplogMsgs(msgs *[]*oplog.Message) Option {
|
|
return func(o *Options) {
|
|
o.newOplogMsgs = msgs
|
|
}
|
|
}
|
|
|
|
// WithFieldMaskPaths provides an option to provide field mask paths.
|
|
func WithFieldMaskPaths(paths []string) Option {
|
|
return func(o *Options) {
|
|
o.WithFieldMaskPaths = paths
|
|
}
|
|
}
|
|
|
|
// WithNullPaths provides an option to provide null paths.
|
|
func WithNullPaths(paths []string) Option {
|
|
return func(o *Options) {
|
|
o.WithNullPaths = paths
|
|
}
|
|
}
|
|
|
|
// WithLimit provides an option to provide a limit. Intentionally allowing
|
|
// negative integers. If WithLimit < 0, then unlimited results are returned.
|
|
// If WithLimit == 0, then default limits are used for results.
|
|
func WithLimit(limit int) Option {
|
|
return func(o *Options) {
|
|
o.WithLimit = limit
|
|
}
|
|
}
|
|
|
|
// WithVersion provides an option version number for update operations.
|
|
func WithVersion(version *uint32) Option {
|
|
return func(o *Options) {
|
|
o.WithVersion = version
|
|
}
|
|
}
|
|
|
|
// WithSkipVetForWrite provides an option to allow skipping vet checks to allow
|
|
// testing lower-level SQL triggers and constraints
|
|
func WithSkipVetForWrite(enable bool) Option {
|
|
return func(o *Options) {
|
|
o.withSkipVetForWrite = enable
|
|
}
|
|
}
|
|
|
|
// WithWhere provides an option to provide a where clause with arguments for an
|
|
// operation.
|
|
func WithWhere(whereClause string, args ...any) Option {
|
|
return func(o *Options) {
|
|
o.withWhereClause = whereClause
|
|
o.withWhereClauseArgs = append(o.withWhereClauseArgs, args...)
|
|
}
|
|
}
|
|
|
|
// WithOrder provides an option to provide an order when searching and looking
|
|
// up.
|
|
func WithOrder(withOrder string) Option {
|
|
return func(o *Options) {
|
|
o.withOrder = withOrder
|
|
}
|
|
}
|
|
|
|
// WithPrngValues provides an option to provide values to seed an PRNG when generating IDs
|
|
func WithPrngValues(withPrngValues []string) Option {
|
|
return func(o *Options) {
|
|
o.withPrngValues = withPrngValues
|
|
}
|
|
}
|
|
|
|
// WithGormFormatter specifies an optional hclog to use for gorm's log
|
|
// formmater
|
|
func WithGormFormatter(l hclog.Logger) Option {
|
|
return func(o *Options) {
|
|
o.withGormFormatter = l
|
|
}
|
|
}
|
|
|
|
// WithMaxOpenConnections specifies an optional max open connections for the
|
|
// database
|
|
func WithMaxOpenConnections(max int) Option {
|
|
return func(o *Options) {
|
|
o.withMaxOpenConnections = max
|
|
}
|
|
}
|
|
|
|
// WithMaxIdleConnections specifies an optional max idle connections for the
|
|
// database.
|
|
// This corresponds with: https://pkg.go.dev/database/sql#DB.SetMaxIdleConns
|
|
func WithMaxIdleConnections(max *int) Option {
|
|
return func(o *Options) {
|
|
o.withMaxIdleConnections = max
|
|
}
|
|
}
|
|
|
|
// WithConnMaxIdleTimeDuration specifies an optional connection max idle time
|
|
// for the database.
|
|
// This corresponds with: https://pkg.go.dev/database/sql#DB.SetConnMaxIdleTime
|
|
func WithConnMaxIdleTimeDuration(max *time.Duration) Option {
|
|
return func(o *Options) {
|
|
o.withConnMaxIdleTimeDuration = max
|
|
}
|
|
}
|
|
|
|
// WithDebug specifies the given operation should invoke debug mode in Gorm
|
|
func WithDebug(with bool) Option {
|
|
return func(o *Options) {
|
|
o.withDebug = with
|
|
}
|
|
}
|
|
|
|
// WithOnConflict specifies an optional on conflict criteria which specify
|
|
// alternative actions to take when an insert results in a unique constraint or
|
|
// exclusion constraint error
|
|
func WithOnConflict(onConflict *OnConflict) Option {
|
|
return func(o *Options) {
|
|
o.withOnConflict = onConflict
|
|
}
|
|
}
|
|
|
|
// WithReturnRowsAffected specifies an option for returning the rows affected
|
|
func WithReturnRowsAffected(rowsAffected *int64) Option {
|
|
return func(o *Options) {
|
|
o.withRowsAffected = rowsAffected
|
|
}
|
|
}
|