go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/db/migration/guard_predicates.go (about)

     1  /*
     2  
     3  Copyright (c) 2023 - Present. Will Charczuk. All rights reserved.
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository.
     5  
     6  */
     7  
     8  package migration
     9  
    10  import (
    11  	"context"
    12  	"database/sql"
    13  	"fmt"
    14  
    15  	"go.charczuk.com/sdk/db"
    16  )
    17  
    18  // Always always runs a step.
    19  func Always() GuardFunc {
    20  	return Guard("always run", func(_ context.Context, _ *db.Connection, _ *sql.Tx) (bool, error) { return true, nil })
    21  }
    22  
    23  // TableExists returns a guard that ensures a table exists
    24  func TableExists(tableName string) GuardFunc {
    25  	return guardPredicate(fmt.Sprintf("Check table exists: %s", tableName), PredicateTableExists, tableName)
    26  }
    27  
    28  // TableNotExists returns a guard that ensures a table does not exist
    29  func TableNotExists(tableName string) GuardFunc {
    30  	return guardNotPredicate(fmt.Sprintf("Check table does not exist: %s", tableName), PredicateTableExists, tableName)
    31  }
    32  
    33  // ColumnExists returns a guard that ensures a column exists
    34  func ColumnExists(tableName, columnName string) GuardFunc {
    35  	return guardPredicate2(fmt.Sprintf("Check column exists: %s.%s", tableName, columnName),
    36  		PredicateColumnExists, tableName, columnName)
    37  }
    38  
    39  // ColumnNotExists returns a guard that ensures a column does not exist
    40  func ColumnNotExists(tableName, columnName string) GuardFunc {
    41  	return guardNotPredicate2(fmt.Sprintf("Check column does not exist: %s.%s", tableName, columnName),
    42  		PredicateColumnExists, tableName, columnName)
    43  }
    44  
    45  // ConstraintExists returns a guard that ensures a constraint exists
    46  func ConstraintExists(tableName, constraintName string) GuardFunc {
    47  	return guardPredicate2(fmt.Sprintf("Check constraint %s exists on table %s", constraintName, tableName),
    48  		PredicateConstraintExists, tableName, constraintName)
    49  }
    50  
    51  // ConstraintNotExists returns a guard that ensures a constraint does not exist
    52  func ConstraintNotExists(tableName, constraintName string) GuardFunc {
    53  	return guardNotPredicate2(fmt.Sprintf("Check constraint %s does not exist on table %s", constraintName, tableName),
    54  		PredicateConstraintExists, tableName, constraintName)
    55  }
    56  
    57  // IndexExists returns a guard that ensures an index exists
    58  func IndexExists(tableName, indexName string) GuardFunc {
    59  	return guardPredicate2(fmt.Sprintf("Check index %s exists on table %s", indexName, tableName),
    60  		PredicateIndexExists, tableName, indexName)
    61  }
    62  
    63  // IndexNotExists returns a guard that ensures an index does not exist
    64  func IndexNotExists(tableName, indexName string) GuardFunc {
    65  	return guardNotPredicate2(fmt.Sprintf("Check index %s does not exist on table %s", indexName, tableName),
    66  		PredicateIndexExists, tableName, indexName)
    67  }
    68  
    69  // RoleExists returns a guard that ensures a role (user) exists
    70  func RoleExists(roleName string) GuardFunc {
    71  	return guardPredicate(fmt.Sprintf("Check Role Exists: %s", roleName), PredicateRoleExists, roleName)
    72  }
    73  
    74  // RoleNotExists returns a guard that ensures a role (user) does not exist
    75  func RoleNotExists(roleName string) GuardFunc {
    76  	return guardNotPredicate(fmt.Sprintf("Check Role Not Exists: %s", roleName), PredicateRoleExists, roleName)
    77  }
    78  
    79  // IfExists only runs the statement if the given item exists.
    80  func IfExists(statement string, args ...interface{}) GuardFunc {
    81  	return Guard("if exists run", func(ctx context.Context, c *db.Connection, tx *sql.Tx) (bool, error) {
    82  		return PredicateAny(ctx, c, tx, statement, args...)
    83  	})
    84  }
    85  
    86  // IfNotExists only runs the statement if the given item doesn't exist.
    87  func IfNotExists(statement string, args ...interface{}) GuardFunc {
    88  	return Guard("if not exists run", func(ctx context.Context, c *db.Connection, tx *sql.Tx) (bool, error) {
    89  		return PredicateNone(ctx, c, tx, statement, args...)
    90  	})
    91  }
    92  
    93  // GuardFunc is a control for migration steps.
    94  // It should internally evaluate if the action should be called.
    95  // The action is typically given separately so these two components can be composed.
    96  type GuardFunc func(context.Context, *db.Connection, *sql.Tx, Action) error
    97  
    98  // GuardPredicateFunc is a function that can act as a guard
    99  type GuardPredicateFunc func(context.Context, *db.Connection, *sql.Tx) (bool, error)
   100  
   101  // --------------------------------------------------------------------------------
   102  // Guards
   103  // --------------------------------------------------------------------------------
   104  
   105  // Guard returns a function that determines if a step in a group should run.
   106  func Guard(description string, predicate GuardPredicateFunc) GuardFunc {
   107  	return func(ctx context.Context, c *db.Connection, tx *sql.Tx, step Action) error {
   108  		proceed, err := predicate(ctx, c, tx)
   109  		if err != nil {
   110  			if suite := GetContextSuite(ctx); suite != nil {
   111  				return suite.WriteError(WithLabel(ctx, description), err)
   112  			}
   113  			return err
   114  		}
   115  
   116  		if !proceed {
   117  			if suite := GetContextSuite(ctx); suite != nil {
   118  				suite.WriteSkipf(ctx, description)
   119  			}
   120  			return nil
   121  		}
   122  
   123  		err = step.Action(ctx, c, tx)
   124  		if err != nil {
   125  			if suite := GetContextSuite(ctx); suite != nil {
   126  				return suite.WriteError(WithLabel(ctx, description), err)
   127  			}
   128  			return err
   129  		}
   130  		if suite := GetContextSuite(ctx); suite != nil {
   131  			suite.WriteApplyf(ctx, description)
   132  		}
   133  		return nil
   134  	}
   135  }
   136  
   137  // guardPredicate wraps a predicate in a GuardFunc
   138  func guardPredicate(description string, p predicate, arg1 string) GuardFunc {
   139  	return Guard(description, func(ctx context.Context, c *db.Connection, tx *sql.Tx) (bool, error) {
   140  		return p(ctx, c, tx, arg1)
   141  	})
   142  }
   143  
   144  // guardNotPredicate inverts a predicate, and wraps that in a GuardFunc
   145  func guardNotPredicate(description string, p predicate, arg1 string) GuardFunc {
   146  	return Guard(description, func(ctx context.Context, c *db.Connection, tx *sql.Tx) (bool, error) {
   147  		return Not(p(ctx, c, tx, arg1))
   148  	})
   149  }
   150  
   151  // guardPredicate2 wraps a predicate2 in a GuardFunc
   152  func guardPredicate2(description string, p predicate2, arg1, arg2 string) GuardFunc {
   153  	return Guard(description, func(ctx context.Context, c *db.Connection, tx *sql.Tx) (bool, error) {
   154  		return p(ctx, c, tx, arg1, arg2)
   155  	})
   156  }
   157  
   158  // guardNotPredicate2 inverts a predicate2, and wraps that in a GuardFunc
   159  func guardNotPredicate2(description string, p predicate2, arg1, arg2 string) GuardFunc {
   160  	return Guard(description, func(ctx context.Context, c *db.Connection, tx *sql.Tx) (bool, error) {
   161  		return Not(p(ctx, c, tx, arg1, arg2))
   162  	})
   163  }
   164  
   165  // guardPredicate3 wraps a predicate3 in a GuardFunc
   166  func guardPredicate3(description string, p predicate3, arg1, arg2, arg3 string) GuardFunc {
   167  	return Guard(description, func(ctx context.Context, c *db.Connection, tx *sql.Tx) (bool, error) {
   168  		return p(ctx, c, tx, arg1, arg2, arg3)
   169  	})
   170  }
   171  
   172  // guardNotPredicate3 inverts a predicate3, and wraps that in a GuardFunc
   173  func guardNotPredicate3(description string, p predicate3, arg1, arg2, arg3 string) GuardFunc {
   174  	return Guard(description, func(ctx context.Context, c *db.Connection, tx *sql.Tx) (bool, error) {
   175  		return Not(p(ctx, c, tx, arg1, arg2, arg3))
   176  	})
   177  }
   178  
   179  // predicate is a function that evaluates based on a string param.
   180  type predicate func(context.Context, *db.Connection, *sql.Tx, string) (bool, error)
   181  
   182  // predicate2 is a function that evaluates based on two string params.
   183  type predicate2 func(context.Context, *db.Connection, *sql.Tx, string, string) (bool, error)
   184  
   185  // predicate3 is a function that evaluates based on three string params.
   186  type predicate3 func(context.Context, *db.Connection, *sql.Tx, string, string, string) (bool, error)