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