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