github.com/paweljw/pop/v5@v5.4.6/executors.go (about)

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