github.com/eden-framework/sqlx@v0.0.2/generator/model_crud.go (about) 1 package generator 2 3 import ( 4 "bytes" 5 "fmt" 6 "github.com/eden-framework/codegen" 7 "github.com/eden-framework/packagex" 8 "strings" 9 10 "github.com/eden-framework/sqlx/builder" 11 ) 12 13 func (m *Model) snippetSetDeletedAtIfNeedForFieldValues(file *codegen.File) codegen.Snippet { 14 if m.HasDeletedAt { 15 return codegen.Expr(`if _, ok := fieldValues["`+m.FieldKeyDeletedAt+`"]; !ok { 16 fieldValues["`+m.FieldKeyDeletedAt+`"] = ?(`+file.Use("time", "Now")+`()) 17 }`, 18 m.FieldType(file, m.FieldKeyDeletedAt), 19 ) 20 } 21 return nil 22 } 23 24 func (m *Model) snippetSetCreatedAtIfNeed(file *codegen.File) codegen.Snippet { 25 if m.HasCreatedAt { 26 return codegen.Expr(` 27 if m.`+m.FieldKeyCreatedAt+`.IsZero() { 28 m.`+m.FieldKeyCreatedAt+` = ?(`+file.Use("time", "Now")+`()) 29 } 30 `, 31 m.FieldType(file, m.FieldKeyCreatedAt), 32 ) 33 } 34 35 return nil 36 } 37 38 func (m *Model) snippetSetLastInsertIdIfNeed(file *codegen.File) codegen.Snippet { 39 if m.HasAutoIncrement { 40 return codegen.Expr(` 41 if err == nil { 42 lastInsertID, _ := result.LastInsertId() 43 m.? = ?(lastInsertID) 44 } 45 `, 46 codegen.Id(m.FieldKeyAutoIncrement), 47 m.FieldType(file, m.FieldKeyAutoIncrement), 48 ) 49 } 50 51 return codegen.Expr(` 52 _ = result 53 `) 54 } 55 56 func (m *Model) snippetSetUpdatedAtIfNeed(file *codegen.File) codegen.Snippet { 57 if m.HasUpdatedAt { 58 return codegen.Expr(` 59 if m.`+m.FieldKeyUpdatedAt+`.IsZero() { 60 m.`+m.FieldKeyUpdatedAt+` = ?(`+file.Use("time", "Now")+`()) 61 } 62 `, 63 m.FieldType(file, m.FieldKeyUpdatedAt), 64 ) 65 } 66 67 return codegen.Expr("") 68 } 69 70 func (m *Model) snippetSetUpdatedAtIfNeedForFieldValues(file *codegen.File) codegen.Snippet { 71 if m.HasUpdatedAt { 72 return codegen.Expr(` 73 if _, ok := fieldValues[?]; !ok { 74 fieldValues[?] = ?(?()) 75 } 76 `, 77 codegen.Val(m.FieldKeyUpdatedAt), 78 codegen.Val(m.FieldKeyUpdatedAt), 79 m.FieldType(file, m.FieldKeyUpdatedAt), 80 codegen.Id(file.Use("time", "Now"))) 81 } 82 return nil 83 } 84 85 func (m *Model) WriteCreate(file *codegen.File) { 86 file.WriteBlock( 87 codegen.Func(codegen.Var( 88 codegen.Type(file.Use("github.com/eden-framework/sqlx", "DBExecutor")), "db")). 89 Named("Create"). 90 MethodOf(codegen.Var(m.PtrType(), "m")). 91 Return(codegen.Var(codegen.Error)). 92 Do( 93 m.snippetSetCreatedAtIfNeed(file), 94 m.snippetSetUpdatedAtIfNeed(file), 95 96 codegen.Expr(` 97 _, err := db.ExecExpr(?(db, m, nil)) 98 return err 99 `, 100 codegen.Id(file.Use("github.com/eden-framework/sqlx", "InsertToDB")), 101 ), 102 ), 103 ) 104 105 if len(m.Keys.UniqueIndexes) > 0 { 106 107 file.WriteBlock( 108 codegen.Func( 109 codegen.Var(codegen.Type(file.Use("github.com/eden-framework/sqlx", "DBExecutor")), "db"), 110 codegen.Var(codegen.Slice(codegen.String), "updateFields"), 111 ). 112 Named("CreateOnDuplicateWithUpdateFields"). 113 MethodOf(codegen.Var(m.PtrType(), "m")). 114 Return(codegen.Var(codegen.Error)). 115 Do( 116 codegen.Expr(` 117 if len(updateFields) == 0 { 118 panic(`+file.Use("fmt", "Errorf")+`("must have update fields")) 119 } 120 `), 121 122 m.snippetSetCreatedAtIfNeed(file), 123 m.snippetSetUpdatedAtIfNeed(file), 124 125 codegen.Expr(` 126 fieldValues := `+file.Use("github.com/eden-framework/sqlx/builder", "FieldValuesFromStructByNonZero")+`(m, updateFields...) 127 `), 128 func() codegen.Snippet { 129 if m.HasAutoIncrement { 130 return codegen.Expr( 131 `delete(fieldValues, ?)`, 132 file.Val(m.FieldKeyAutoIncrement), 133 ) 134 } 135 return nil 136 }(), 137 138 codegen.Expr(` 139 table := db.T(m) 140 141 cols, vals := table.ColumnsAndValuesByFieldValues(fieldValues) 142 143 fields := make(map[string]bool, len(updateFields)) 144 for _, field := range updateFields { 145 fields[field] = true 146 } 147 `), 148 codegen.Expr(` 149 for _, fieldNames := range m.UniqueIndexes() { 150 for _, field := range fieldNames { 151 delete(fields, field) 152 } 153 } 154 155 if len(fields) == 0 { 156 panic(`+file.Use("fmt", "Errorf")+`("no fields for updates")) 157 } 158 159 for field := range fieldValues { 160 if !fields[field] { 161 delete(fieldValues, field) 162 } 163 } 164 165 additions := `+file.Use("github.com/eden-framework/sqlx/builder", "Additions")+`{} 166 167 switch db.Dialect().DriverName() { 168 case "mysql": 169 additions = append(additions, `+file.Use("github.com/eden-framework/sqlx/builder", "OnDuplicateKeyUpdate")+`(table.AssignmentsByFieldValues(fieldValues)...)) 170 case "postgres": 171 indexes := m.UniqueIndexes() 172 fields := make([]string, 0) 173 for _, fs := range indexes { 174 fields = append(fields, fs...) 175 } 176 indexFields, _ := db.T(m).Fields(fields...) 177 178 additions = append(additions, 179 `+file.Use("github.com/eden-framework/sqlx/builder", "OnConflict")+`(indexFields). 180 DoUpdateSet(table.AssignmentsByFieldValues(fieldValues)...)) 181 } 182 183 additions = append(additions, `+file.Use("github.com/eden-framework/sqlx/builder", "Comment")+`("User.CreateOnDuplicateWithUpdateFields")) 184 185 expr := `+file.Use("github.com/eden-framework/sqlx/builder", "Insert")+`().Into(table, additions...).Values(cols, vals...) 186 187 _, err := db.ExecExpr(expr) 188 return err 189 `, 190 file.Val(m.StructName+".CreateOnDuplicateWithUpdateFields"), 191 file.Val(m.StructName+".CreateOnDuplicateWithUpdateFields"), 192 ), 193 ), 194 ) 195 } 196 } 197 198 func (m *Model) WriteDelete(file *codegen.File) { 199 file.WriteBlock( 200 codegen.Func(codegen.Var( 201 codegen.Type(file.Use("github.com/eden-framework/sqlx", "DBExecutor")), "db")). 202 Named("DeleteByStruct"). 203 MethodOf(codegen.Var(m.PtrType(), "m")). 204 Return(codegen.Var(codegen.Error)). 205 Do( 206 codegen.Expr(` 207 _, err := db.ExecExpr( 208 `+file.Use("github.com/eden-framework/sqlx/builder", "Delete")+`(). 209 From( 210 db.T(m), 211 `+file.Use("github.com/eden-framework/sqlx/builder", "Where")+`(m.ConditionByStruct(db)), 212 `+file.Use("github.com/eden-framework/sqlx/builder", "Comment")+`(?), 213 ), 214 ) 215 216 `, file.Val(m.StructName+".DeleteByStruct")), 217 218 codegen.Return(codegen.Expr("err")), 219 ), 220 ) 221 } 222 223 func (m *Model) WriteByKey(file *codegen.File) { 224 m.Table.Keys.Range(func(key *builder.Key, idx int) { 225 fieldNames := key.Columns.FieldNames() 226 227 fieldNamesWithoutEnabled := stringFilter(fieldNames, func(item string, i int) bool { 228 if m.HasDeletedAt { 229 return item != m.FieldKeyDeletedAt 230 } 231 return true 232 }) 233 234 if m.HasDeletedAt && key.IsPrimary() { 235 fieldNames = append(fieldNames, m.FieldKeyDeletedAt) 236 } 237 238 if key.IsUnique { 239 { 240 methodForFetch := createMethod("FetchBy%s", fieldNamesWithoutEnabled...) 241 242 file.WriteBlock( 243 codegen.Func(codegen.Var( 244 codegen.Type(file.Use("github.com/eden-framework/sqlx", "DBExecutor")), "db")). 245 Named(methodForFetch). 246 MethodOf(codegen.Var(m.PtrType(), "m")). 247 Return(codegen.Var(codegen.Error)). 248 Do( 249 codegen.Expr(` 250 table := db.T(m) 251 252 err := db.QueryExprAndScan( 253 `+file.Use("github.com/eden-framework/sqlx/builder", "Select")+`(nil). 254 From( 255 db.T(m), 256 `+file.Use("github.com/eden-framework/sqlx/builder", "Where")+`(`+toExactlyConditionFrom(file, fieldNames...)+`), 257 `+file.Use("github.com/eden-framework/sqlx/builder", "Comment")+`(?), 258 ), 259 m, 260 ) 261 `, 262 file.Val(m.StructName+"."+methodForFetch), 263 ), 264 265 codegen.Return(codegen.Expr("err")), 266 ), 267 ) 268 269 methodForUpdateWithMap := createMethod("UpdateBy%sWithMap", fieldNamesWithoutEnabled...) 270 271 file.WriteBlock( 272 codegen.Func( 273 codegen.Var(codegen.Type(file.Use("github.com/eden-framework/sqlx", "DBExecutor")), "db"), 274 codegen.Var(codegen.Type(file.Use("github.com/eden-framework/sqlx/builder", "FieldValues")), "fieldValues"), 275 ). 276 Named(methodForUpdateWithMap). 277 MethodOf(codegen.Var(m.PtrType(), "m")). 278 Return(codegen.Var(codegen.Error)). 279 Do( 280 m.snippetSetUpdatedAtIfNeedForFieldValues(file), 281 codegen.Expr(` 282 table := db.T(m) 283 284 result, err := db.ExecExpr( 285 `+file.Use("github.com/eden-framework/sqlx/builder", "Update")+`(db.T(m)). 286 Where( 287 `+toExactlyConditionFrom(file, fieldNames...)+`, 288 `+file.Use("github.com/eden-framework/sqlx/builder", "Comment")+`(?), 289 ). 290 Set(table.AssignmentsByFieldValues(fieldValues)...), 291 ) 292 293 if err != nil { 294 return err 295 } 296 297 rowsAffected, _ := result.RowsAffected() 298 if rowsAffected == 0 { 299 return m.`+methodForFetch+`(db) 300 } 301 302 return nil 303 `, 304 file.Val(m.StructName+"."+methodForUpdateWithMap), 305 ), 306 ), 307 ) 308 309 methodForUpdateWithStruct := createMethod("UpdateBy%sWithStruct", fieldNamesWithoutEnabled...) 310 311 file.WriteBlock( 312 codegen.Func( 313 codegen.Var(codegen.Type(file.Use("github.com/eden-framework/sqlx", "DBExecutor")), "db"), 314 codegen.Var(codegen.Ellipsis(codegen.String), "zeroFields"), 315 ). 316 Named(methodForUpdateWithStruct). 317 MethodOf(codegen.Var(m.PtrType(), "m")). 318 Return(codegen.Var(codegen.Error)). 319 Do( 320 codegen.Expr(` 321 fieldValues := ` + file.Use("github.com/eden-framework/sqlx/builder", "FieldValuesFromStructByNonZero") + `(m, zeroFields...) 322 return m.` + methodForUpdateWithMap + `(db, fieldValues) 323 `), 324 ), 325 ) 326 } 327 328 { 329 330 method := createMethod("FetchBy%sForUpdate", fieldNamesWithoutEnabled...) 331 332 file.WriteBlock( 333 codegen.Func(codegen.Var( 334 codegen.Type(file.Use("github.com/eden-framework/sqlx", "DBExecutor")), "db")). 335 Named(method). 336 MethodOf(codegen.Var(m.PtrType(), "m")). 337 Return(codegen.Var(codegen.Error)). 338 Do( 339 codegen.Expr(` 340 table := db.T(m) 341 342 err := db.QueryExprAndScan( 343 `+file.Use("github.com/eden-framework/sqlx/builder", "Select")+`(nil). 344 From( 345 db.T(m), 346 `+file.Use("github.com/eden-framework/sqlx/builder", "Where")+`(`+toExactlyConditionFrom(file, fieldNames...)+`), 347 `+file.Use("github.com/eden-framework/sqlx/builder", "ForUpdate")+`(), 348 `+file.Use("github.com/eden-framework/sqlx/builder", "Comment")+`(?), 349 ), 350 m, 351 ) 352 `, 353 file.Val(m.StructName+"."+method), 354 ), 355 356 codegen.Return(codegen.Expr("err")), 357 ), 358 ) 359 } 360 361 { 362 methodForDelete := createMethod("DeleteBy%s", fieldNamesWithoutEnabled...) 363 364 file.WriteBlock( 365 codegen.Func(codegen.Var( 366 codegen.Type(file.Use("github.com/eden-framework/sqlx", "DBExecutor")), "db")). 367 Named(methodForDelete). 368 MethodOf(codegen.Var(m.PtrType(), "m")). 369 Return(codegen.Var(codegen.Error)). 370 Do( 371 codegen.Expr(` 372 table := db.T(m) 373 374 _, err := db.ExecExpr( 375 `+file.Use("github.com/eden-framework/sqlx/builder", "Delete")+`(). 376 From(db.T(m), 377 `+file.Use("github.com/eden-framework/sqlx/builder", "Where")+`(`+toExactlyConditionFrom(file, fieldNames...)+`), 378 `+file.Use("github.com/eden-framework/sqlx/builder", "Comment")+`(`+string(file.Val(m.StructName+"."+methodForDelete).Bytes())+`), 379 )) 380 `, 381 file.Val(m.StructName+"."+methodForDelete), 382 ), 383 384 codegen.Return(codegen.Expr("err")), 385 ), 386 ) 387 388 if m.HasDeletedAt { 389 390 methodForSoftDelete := createMethod("SoftDeleteBy%s", fieldNamesWithoutEnabled...) 391 392 file.WriteBlock( 393 codegen.Func(codegen.Var( 394 codegen.Type(file.Use("github.com/eden-framework/sqlx", "DBExecutor")), "db")). 395 Named(methodForSoftDelete). 396 MethodOf(codegen.Var(m.PtrType(), "m")). 397 Return(codegen.Var(codegen.Error)). 398 Do( 399 codegen.Expr(` 400 table := db.T(m) 401 402 fieldValues := `+file.Use("github.com/eden-framework/sqlx/builder", "FieldValues")+`{}`), 403 404 m.snippetSetDeletedAtIfNeedForFieldValues(file), 405 m.snippetSetUpdatedAtIfNeedForFieldValues(file), 406 407 codegen.Expr(` 408 _, err := db.ExecExpr( 409 `+file.Use("github.com/eden-framework/sqlx/builder", "Update")+`(db.T(m)). 410 Where( 411 `+toExactlyConditionFrom(file, fieldNames...)+`, 412 `+file.Use("github.com/eden-framework/sqlx/builder", "Comment")+`(`+string(file.Val(m.StructName+"."+methodForSoftDelete).Bytes())+`), 413 ). 414 Set(table.AssignmentsByFieldValues(fieldValues)...), 415 ) 416 417 return err 418 `), 419 ), 420 ) 421 } 422 } 423 } 424 }) 425 } 426 427 func (m *Model) WriteCRUD(file *codegen.File) { 428 m.WriteCreate(file) 429 m.WriteDelete(file) 430 m.WriteByKey(file) 431 } 432 433 func toExactlyConditionFrom(file *codegen.File, fieldNames ...string) string { 434 buf := &bytes.Buffer{} 435 buf.WriteString(file.Use("github.com/eden-framework/sqlx/builder", "And")) 436 buf.WriteString(`( 437 `) 438 439 for _, fieldName := range fieldNames { 440 buf.WriteString(fmt.Sprintf(`table.F("%s").Eq(m.%s), 441 `, fieldName, fieldName)) 442 } 443 444 buf.WriteString(` 445 )`) 446 447 return buf.String() 448 } 449 450 func createMethod(method string, fieldNames ...string) string { 451 return fmt.Sprintf(method, strings.Join(fieldNames, "And")) 452 } 453 454 func (m *Model) FieldType(file *codegen.File, fieldName string) codegen.SnippetType { 455 if field, ok := m.Fields[fieldName]; ok { 456 typ := field.Type().String() 457 if strings.Index(typ, ".") > -1 { 458 importPath, name := packagex.GetPkgImportPathAndExpose(typ) 459 if importPath != m.TypeName.Pkg().Path() { 460 return codegen.Type(file.Use(importPath, name)) 461 } 462 return codegen.Type(name) 463 } 464 return codegen.BuiltInType(typ) 465 } 466 return nil 467 }