github.com/profzone/eden-framework@v1.0.10/pkg/sqlx/generator/model_crud.go (about)

     1  package generator
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"github.com/profzone/eden-framework/pkg/codegen"
     7  	"github.com/profzone/eden-framework/pkg/packagex"
     8  	"strings"
     9  
    10  	"github.com/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/sqlx/builder", "Additions")+`{}
   166  
   167  switch db.Dialect().DriverName() {
   168  case "mysql":
   169  	additions = append(additions, `+file.Use("github.com/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/sqlx/builder", "OnConflict")+`(indexFields).
   180  				DoUpdateSet(table.AssignmentsByFieldValues(fieldValues)...))
   181  }
   182  
   183  additions = append(additions, `+file.Use("github.com/profzone/eden-framework/pkg/sqlx/builder", "Comment")+`("User.CreateOnDuplicateWithUpdateFields"))
   184  
   185  expr := `+file.Use("github.com/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/sqlx/builder", "Delete")+`().
   209  From(
   210  	db.T(m),
   211  `+file.Use("github.com/profzone/eden-framework/pkg/sqlx/builder", "Where")+`(m.ConditionByStruct(db)),
   212  `+file.Use("github.com/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/sqlx/builder", "Select")+`(nil).
   254  From(
   255  	db.T(m),
   256  `+file.Use("github.com/profzone/eden-framework/pkg/sqlx/builder", "Where")+`(`+toExactlyConditionFrom(file, fieldNames...)+`),
   257  `+file.Use("github.com/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/sqlx", "DBExecutor")), "db"),
   274  						codegen.Var(codegen.Type(file.Use("github.com/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/sqlx/builder", "Update")+`(db.T(m)).
   286  		Where(
   287  			`+toExactlyConditionFrom(file, fieldNames...)+`,
   288  			`+file.Use("github.com/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/sqlx/builder", "Select")+`(nil).
   344  From(
   345  	db.T(m),
   346  `+file.Use("github.com/profzone/eden-framework/pkg/sqlx/builder", "Where")+`(`+toExactlyConditionFrom(file, fieldNames...)+`),
   347  `+file.Use("github.com/profzone/eden-framework/pkg/sqlx/builder", "ForUpdate")+`(),
   348  `+file.Use("github.com/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/sqlx/builder", "Delete")+`().
   376  	From(db.T(m),
   377  	`+file.Use("github.com/profzone/eden-framework/pkg/sqlx/builder", "Where")+`(`+toExactlyConditionFrom(file, fieldNames...)+`),
   378  	`+file.Use("github.com/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/sqlx/builder", "Update")+`(db.T(m)).
   410  		Where(
   411  			`+toExactlyConditionFrom(file, fieldNames...)+`,
   412  			`+file.Use("github.com/profzone/eden-framework/pkg/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/profzone/eden-framework/pkg/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  }