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 }