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