github.com/pjdufour-truss/pop@v4.11.2-0.20190705085848-4c90b0ff4d5a+incompatible/executors.go (about)

     1  package pop
     2  
     3  import (
     4  	"reflect"
     5  
     6  	"github.com/gobuffalo/pop/associations"
     7  	"github.com/gobuffalo/pop/columns"
     8  	"github.com/gobuffalo/pop/logging"
     9  	"github.com/gobuffalo/validate"
    10  	"github.com/gofrs/uuid"
    11  	"github.com/pkg/errors"
    12  )
    13  
    14  // Reload fetch fresh data for a given model, using its ID.
    15  func (c *Connection) Reload(model interface{}) error {
    16  	sm := Model{Value: model}
    17  	return sm.iterate(func(m *Model) error {
    18  		return c.Find(m.Value, m.ID())
    19  	})
    20  }
    21  
    22  // Exec runs the given query.
    23  func (q *Query) Exec() error {
    24  	return q.Connection.timeFunc("Exec", func() error {
    25  		sql, args := q.ToSQL(nil)
    26  		log(logging.SQL, sql, args...)
    27  		_, err := q.Connection.Store.Exec(sql, args...)
    28  		return err
    29  	})
    30  }
    31  
    32  // ExecWithCount runs the given query, and returns the amount of
    33  // affected rows.
    34  func (q *Query) ExecWithCount() (int, error) {
    35  	count := int64(0)
    36  	return int(count), q.Connection.timeFunc("Exec", func() error {
    37  		sql, args := q.ToSQL(nil)
    38  		log(logging.SQL, sql, args...)
    39  		result, err := q.Connection.Store.Exec(sql, args...)
    40  		if err != nil {
    41  			return err
    42  		}
    43  
    44  		count, err = result.RowsAffected()
    45  		return err
    46  	})
    47  }
    48  
    49  // ValidateAndSave applies validation rules on the given entry, then save it
    50  // if the validation succeed, excluding the given columns.
    51  //
    52  // If model is a slice, each item of the slice is validated then saved in the database.
    53  func (c *Connection) ValidateAndSave(model interface{}, excludeColumns ...string) (*validate.Errors, error) {
    54  	sm := &Model{Value: model}
    55  	verrs, err := sm.validateSave(c)
    56  	if err != nil {
    57  		return verrs, err
    58  	}
    59  	if verrs.HasAny() {
    60  		return verrs, nil
    61  	}
    62  	return verrs, c.Save(model, excludeColumns...)
    63  }
    64  
    65  var emptyUUID = uuid.Nil.String()
    66  
    67  // IsZeroOfUnderlyingType will check if the value of anything is the equal to the Zero value of that type.
    68  func IsZeroOfUnderlyingType(x interface{}) bool {
    69  	return reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface())
    70  }
    71  
    72  // Save wraps the Create and Update methods. It executes a Create if no ID is provided with the entry;
    73  // or issues an Update otherwise.
    74  //
    75  // If model is a slice, each item of the slice is saved in the database.
    76  func (c *Connection) Save(model interface{}, excludeColumns ...string) error {
    77  	sm := &Model{Value: model}
    78  	return sm.iterate(func(m *Model) error {
    79  		id, err := m.fieldByName("ID")
    80  		if err != nil {
    81  			return err
    82  		}
    83  		if IsZeroOfUnderlyingType(id.Interface()) {
    84  			return c.Create(m.Value, excludeColumns...)
    85  		}
    86  		return c.Update(m.Value, excludeColumns...)
    87  	})
    88  }
    89  
    90  // ValidateAndCreate applies validation rules on the given entry, then creates it
    91  // if the validation succeed, excluding the given columns.
    92  //
    93  // If model is a slice, each item of the slice is validated then created in the database.
    94  func (c *Connection) ValidateAndCreate(model interface{}, excludeColumns ...string) (*validate.Errors, error) {
    95  	sm := &Model{Value: model}
    96  	verrs, err := sm.validateCreate(c)
    97  	if err != nil {
    98  		return verrs, err
    99  	}
   100  	if verrs.HasAny() {
   101  		return verrs, nil
   102  	}
   103  
   104  	if c.eager {
   105  		asos, err := associations.ForStruct(model, c.eagerFields...)
   106  		if err != nil {
   107  			return verrs, errors.Wrap(err, "could not retrieve associations")
   108  		}
   109  
   110  		if len(asos) == 0 {
   111  			log(logging.Debug, "no associations found for given struct, disable eager mode")
   112  			c.disableEager()
   113  			return verrs, c.Create(model, excludeColumns...)
   114  		}
   115  
   116  		before := asos.AssociationsBeforeCreatable()
   117  		for index := range before {
   118  			i := before[index].BeforeInterface()
   119  			if i == nil {
   120  				continue
   121  			}
   122  
   123  			sm := &Model{Value: i}
   124  			verrs, err := sm.validateAndOnlyCreate(c)
   125  			if err != nil || verrs.HasAny() {
   126  				return verrs, err
   127  			}
   128  		}
   129  
   130  		after := asos.AssociationsAfterCreatable()
   131  		for index := range after {
   132  			i := after[index].AfterInterface()
   133  			if i == nil {
   134  				continue
   135  			}
   136  
   137  			sm := &Model{Value: i}
   138  			verrs, err := sm.validateAndOnlyCreate(c)
   139  			if err != nil || verrs.HasAny() {
   140  				return verrs, err
   141  			}
   142  		}
   143  
   144  		sm := &Model{Value: model}
   145  		verrs, err = sm.validateCreate(c)
   146  		if err != nil || verrs.HasAny() {
   147  			return verrs, err
   148  		}
   149  	}
   150  
   151  	return verrs, c.Create(model, excludeColumns...)
   152  }
   153  
   154  // Create add a new given entry to the database, excluding the given columns.
   155  // It updates `created_at` and `updated_at` columns automatically.
   156  //
   157  // If model is a slice, each item of the slice is created in the database.
   158  //
   159  // Create support two modes:
   160  // * Flat (default): Associate existing nested objects only. NO creation or update of nested objects.
   161  // * Eager: Associate existing nested objects and create non-existent objects. NO change to existing objects.
   162  func (c *Connection) Create(model interface{}, excludeColumns ...string) error {
   163  	var isEager = c.eager
   164  
   165  	c.disableEager()
   166  
   167  	sm := &Model{Value: model}
   168  	return sm.iterate(func(m *Model) error {
   169  		return c.timeFunc("Create", func() error {
   170  			var localIsEager = isEager
   171  			asos, err := associations.ForStruct(m.Value, c.eagerFields...)
   172  			if err != nil {
   173  				return errors.Wrap(err, "could not retrieve associations")
   174  			}
   175  
   176  			if localIsEager && len(asos) == 0 {
   177  				// No association, fallback to non-eager mode.
   178  				localIsEager = false
   179  			}
   180  
   181  			if err = m.beforeSave(c); err != nil {
   182  				return err
   183  			}
   184  
   185  			if err = m.beforeCreate(c); err != nil {
   186  				return err
   187  			}
   188  
   189  			processAssoc := len(asos) > 0
   190  
   191  			if processAssoc {
   192  				before := asos.AssociationsBeforeCreatable()
   193  				for index := range before {
   194  					i := before[index].BeforeInterface()
   195  					if i == nil {
   196  						continue
   197  					}
   198  
   199  					if localIsEager {
   200  						sm := &Model{Value: i}
   201  						err = sm.iterate(func(m *Model) error {
   202  							id, err := m.fieldByName("ID")
   203  							if err != nil {
   204  								return err
   205  							}
   206  							if IsZeroOfUnderlyingType(id.Interface()) {
   207  								return c.Create(m.Value)
   208  							}
   209  							return nil
   210  						})
   211  
   212  						if err != nil {
   213  							return err
   214  						}
   215  					}
   216  
   217  					err = before[index].BeforeSetup()
   218  					if err != nil {
   219  						return err
   220  					}
   221  				}
   222  			}
   223  
   224  			tn := m.TableName()
   225  			cols := columns.ForStructWithAlias(m.Value, tn, m.As)
   226  
   227  			if tn == sm.TableName() {
   228  				cols.Remove(excludeColumns...)
   229  			}
   230  
   231  			m.touchCreatedAt()
   232  			m.touchUpdatedAt()
   233  
   234  			if err = c.Dialect.Create(c.Store, m, cols); err != nil {
   235  				return err
   236  			}
   237  
   238  			if processAssoc {
   239  				after := asos.AssociationsAfterCreatable()
   240  				for index := range after {
   241  					if localIsEager {
   242  						err = after[index].AfterSetup()
   243  						if err != nil {
   244  							return err
   245  						}
   246  
   247  						i := after[index].AfterInterface()
   248  						if i == nil {
   249  							continue
   250  						}
   251  
   252  						sm := &Model{Value: i}
   253  						err = sm.iterate(func(m *Model) error {
   254  							fbn, err := m.fieldByName("ID")
   255  							if err != nil {
   256  								return err
   257  							}
   258  							id := fbn.Interface()
   259  							if IsZeroOfUnderlyingType(id) {
   260  								return c.Create(m.Value)
   261  							}
   262  							exists, errE := Q(c).Exists(i)
   263  							if errE != nil || !exists {
   264  								return c.Create(m.Value)
   265  							}
   266  							return nil
   267  						})
   268  
   269  						if err != nil {
   270  							return err
   271  						}
   272  					}
   273  					stm := after[index].AfterProcess()
   274  					if c.TX != nil && !stm.Empty() {
   275  						_, err := c.TX.Exec(c.Dialect.TranslateSQL(stm.Statement), stm.Args...)
   276  						if err != nil {
   277  							return err
   278  						}
   279  					}
   280  				}
   281  
   282  				stms := asos.AssociationsCreatableStatement()
   283  				for index := range stms {
   284  					statements := stms[index].Statements()
   285  					for _, stm := range statements {
   286  						if c.TX != nil {
   287  							_, err := c.TX.Exec(c.Dialect.TranslateSQL(stm.Statement), stm.Args...)
   288  							if err != nil {
   289  								return err
   290  							}
   291  							continue
   292  						}
   293  						_, err = c.Store.Exec(c.Dialect.TranslateSQL(stm.Statement), stm.Args...)
   294  						if err != nil {
   295  							return err
   296  						}
   297  					}
   298  				}
   299  			}
   300  
   301  			if err = m.afterCreate(c); err != nil {
   302  				return err
   303  			}
   304  
   305  			return m.afterSave(c)
   306  		})
   307  	})
   308  }
   309  
   310  // ValidateAndUpdate applies validation rules on the given entry, then update it
   311  // if the validation succeed, excluding the given columns.
   312  //
   313  // If model is a slice, each item of the slice is validated then updated in the database.
   314  func (c *Connection) ValidateAndUpdate(model interface{}, excludeColumns ...string) (*validate.Errors, error) {
   315  	sm := &Model{Value: model}
   316  	verrs, err := sm.validateUpdate(c)
   317  	if err != nil {
   318  		return verrs, err
   319  	}
   320  	if verrs.HasAny() {
   321  		return verrs, nil
   322  	}
   323  	return verrs, c.Update(model, excludeColumns...)
   324  }
   325  
   326  // Update writes changes from an entry to the database, excluding the given columns.
   327  // It updates the `updated_at` column automatically.
   328  //
   329  // If model is a slice, each item of the slice is updated in the database.
   330  func (c *Connection) Update(model interface{}, excludeColumns ...string) error {
   331  	sm := &Model{Value: model}
   332  	return sm.iterate(func(m *Model) error {
   333  		return c.timeFunc("Update", func() error {
   334  			var err error
   335  
   336  			if err = m.beforeSave(c); err != nil {
   337  				return err
   338  			}
   339  			if err = m.beforeUpdate(c); err != nil {
   340  				return err
   341  			}
   342  
   343  			tn := m.TableName()
   344  			cols := columns.ForStructWithAlias(model, tn, m.As)
   345  			cols.Remove("id", "created_at")
   346  
   347  			if tn == sm.TableName() {
   348  				cols.Remove(excludeColumns...)
   349  			}
   350  
   351  			m.touchUpdatedAt()
   352  
   353  			if err = c.Dialect.Update(c.Store, m, cols); err != nil {
   354  				return err
   355  			}
   356  			if err = m.afterUpdate(c); err != nil {
   357  				return err
   358  			}
   359  
   360  			return m.afterSave(c)
   361  		})
   362  	})
   363  }
   364  
   365  // Destroy deletes a given entry from the database.
   366  //
   367  // If model is a slice, each item of the slice is deleted from the database.
   368  func (c *Connection) Destroy(model interface{}) error {
   369  	sm := &Model{Value: model}
   370  	return sm.iterate(func(m *Model) error {
   371  		return c.timeFunc("Destroy", func() error {
   372  			var err error
   373  
   374  			if err = m.beforeDestroy(c); err != nil {
   375  				return err
   376  			}
   377  			if err = c.Dialect.Destroy(c.Store, m); err != nil {
   378  				return err
   379  			}
   380  
   381  			return m.afterDestroy(c)
   382  		})
   383  	})
   384  }