github.com/aacfactory/fns-contrib/databases/sql@v1.2.84/dac/specifications/build.go (about)

     1  package specifications
     2  
     3  import (
     4  	stdsql "database/sql"
     5  	"fmt"
     6  	"github.com/aacfactory/errors"
     7  	"github.com/aacfactory/fns/commons/times"
     8  	"github.com/aacfactory/fns/context"
     9  	"github.com/aacfactory/fns/services/authorizations"
    10  	"github.com/aacfactory/json"
    11  	"reflect"
    12  	"time"
    13  )
    14  
    15  func BuildInsert[T any](ctx context.Context, entries []T) (method Method, query []byte, arguments []any, returning []string, err error) {
    16  	dialect, dialectErr := LoadDialect(ctx)
    17  	if dialectErr != nil {
    18  		err = dialectErr
    19  		return
    20  	}
    21  	spec, specErr := GetSpecification(ctx, entries[0])
    22  	if specErr != nil {
    23  		err = specErr
    24  		return
    25  	}
    26  	if spec.View {
    27  		err = errors.Warning(fmt.Sprintf("sql: %s is view", spec.Key))
    28  		return
    29  	}
    30  
    31  	var fields []string
    32  	method, query, fields, returning, err = dialect.Insert(Todo(ctx, entries[0], dialect), spec, len(entries))
    33  	if err != nil {
    34  		return
    35  	}
    36  	// audit
    37  	auditErr := TrySetupAuditCreation[T](ctx, spec, entries)
    38  	if auditErr != nil {
    39  		err = auditErr
    40  		return
    41  	}
    42  	for _, entry := range entries {
    43  		args, argsErr := spec.Arguments(entry, fields)
    44  		if argsErr != nil {
    45  			err = argsErr
    46  			return
    47  		}
    48  		arguments = append(arguments, args...)
    49  	}
    50  	return
    51  }
    52  
    53  func BuildInsertOrUpdate[T any](ctx context.Context, entries []T) (method Method, query []byte, arguments []any, returning []string, err error) {
    54  	dialect, dialectErr := LoadDialect(ctx)
    55  	if dialectErr != nil {
    56  		err = dialectErr
    57  		return
    58  	}
    59  	spec, specErr := GetSpecification(ctx, entries[0])
    60  	if specErr != nil {
    61  		err = specErr
    62  		return
    63  	}
    64  	if spec.View {
    65  		err = errors.Warning(fmt.Sprintf("sql: %s is view", spec.Key))
    66  		return
    67  	}
    68  
    69  	var fields []string
    70  	method, query, fields, returning, err = dialect.InsertOrUpdate(Todo(ctx, entries[0], dialect), spec)
    71  	if err != nil {
    72  		return
    73  	}
    74  	// audit
    75  	auditErr := TrySetupAuditCreation[T](ctx, spec, entries)
    76  	if auditErr != nil {
    77  		err = auditErr
    78  		return
    79  	}
    80  	auditErr = TrySetupAuditModification[T](ctx, spec, entries)
    81  	if auditErr != nil {
    82  		err = auditErr
    83  		return
    84  	}
    85  	arguments, err = spec.Arguments(entries[0], fields)
    86  	return
    87  }
    88  
    89  func BuildInsertWhenExist[T any](ctx context.Context, entries []T, src QueryExpr) (method Method, query []byte, arguments []any, returning []string, err error) {
    90  	dialect, dialectErr := LoadDialect(ctx)
    91  	if dialectErr != nil {
    92  		err = dialectErr
    93  		return
    94  	}
    95  	spec, specErr := GetSpecification(ctx, entries[0])
    96  	if specErr != nil {
    97  		err = specErr
    98  		return
    99  	}
   100  	if spec.View {
   101  		err = errors.Warning(fmt.Sprintf("sql: %s is view", spec.Key))
   102  		return
   103  	}
   104  
   105  	var fields []string
   106  	var srcArguments []any
   107  	method, query, fields, srcArguments, returning, err = dialect.InsertWhenExist(Todo(ctx, entries[0], dialect), spec, src)
   108  	if err != nil {
   109  		return
   110  	}
   111  	// audit
   112  	auditErr := TrySetupAuditCreation[T](ctx, spec, entries)
   113  	if auditErr != nil {
   114  		err = auditErr
   115  		return
   116  	}
   117  	arguments, err = spec.Arguments(entries[0], fields)
   118  	if err != nil {
   119  		return
   120  	}
   121  	arguments = append(arguments, srcArguments...)
   122  	return
   123  }
   124  
   125  func BuildInsertWhenNotExist[T any](ctx context.Context, entries []T, src QueryExpr) (method Method, query []byte, arguments []any, returning []string, err error) {
   126  	dialect, dialectErr := LoadDialect(ctx)
   127  	if dialectErr != nil {
   128  		err = dialectErr
   129  		return
   130  	}
   131  	spec, specErr := GetSpecification(ctx, entries[0])
   132  	if specErr != nil {
   133  		err = specErr
   134  		return
   135  	}
   136  	if spec.View {
   137  		err = errors.Warning(fmt.Sprintf("sql: %s is view", spec.Key))
   138  		return
   139  	}
   140  
   141  	var fields []string
   142  	var srcArguments []any
   143  	method, query, fields, srcArguments, returning, err = dialect.InsertWhenNotExist(Todo(ctx, entries[0], dialect), spec, src)
   144  	if err != nil {
   145  		return
   146  	}
   147  	// audit
   148  	auditErr := TrySetupAuditCreation[T](ctx, spec, entries)
   149  	if auditErr != nil {
   150  		err = auditErr
   151  		return
   152  	}
   153  	arguments, err = spec.Arguments(entries[0], fields)
   154  	if err != nil {
   155  		return
   156  	}
   157  	arguments = append(arguments, srcArguments...)
   158  	return
   159  }
   160  
   161  func BuildUpdate[T any](ctx context.Context, entries []T) (method Method, query []byte, arguments []any, err error) {
   162  	dialect, dialectErr := LoadDialect(ctx)
   163  	if dialectErr != nil {
   164  		err = dialectErr
   165  		return
   166  	}
   167  	spec, specErr := GetSpecification(ctx, entries[0])
   168  	if specErr != nil {
   169  		err = specErr
   170  		return
   171  	}
   172  	if spec.View {
   173  		err = errors.Warning(fmt.Sprintf("sql: %s is view", spec.Key))
   174  		return
   175  	}
   176  
   177  	var fields []string
   178  	method, query, fields, err = dialect.Update(Todo(ctx, entries[0], dialect), spec)
   179  	if err != nil {
   180  		return
   181  	}
   182  	// audit
   183  	auditErr := TrySetupAuditModification[T](ctx, spec, entries)
   184  	if auditErr != nil {
   185  		err = auditErr
   186  		return
   187  	}
   188  	arguments, err = spec.Arguments(entries[0], fields)
   189  	return
   190  }
   191  
   192  func BuildUpdateFields[T any](ctx context.Context, fields []FieldValue, cond Condition) (method Method, query []byte, arguments []any, err error) {
   193  	dialect, dialectErr := LoadDialect(ctx)
   194  	if dialectErr != nil {
   195  		err = dialectErr
   196  		return
   197  	}
   198  	t := Instance[T]()
   199  	spec, specErr := GetSpecification(ctx, t)
   200  	if specErr != nil {
   201  		err = specErr
   202  		return
   203  	}
   204  	if spec.View {
   205  		err = errors.Warning(fmt.Sprintf("sql: %s is view", spec.Key))
   206  		return
   207  	}
   208  	// audit
   209  	by, at, hasAm := spec.AuditModification()
   210  	if hasAm {
   211  		auth, hasAuth, loadErr := authorizations.Load(ctx)
   212  		if loadErr != nil {
   213  			err = errors.Warning(fmt.Sprintf("sql: %s need audit deletion", spec.Key)).WithCause(loadErr)
   214  			return
   215  		}
   216  		if !hasAuth {
   217  			err = errors.Warning(fmt.Sprintf("sql: %s need audit deletion", spec.Key)).WithCause(fmt.Errorf("authorization was not found"))
   218  			return
   219  		}
   220  		if !auth.Exist() {
   221  			err = errors.Warning(fmt.Sprintf("sql: %s need audit deletion", spec.Key)).WithCause(authorizations.ErrUnauthorized)
   222  			return
   223  		}
   224  		if by != nil {
   225  			exist := false
   226  			for _, field := range fields {
   227  				if field.Name == by.Field {
   228  					exist = true
   229  					break
   230  				}
   231  			}
   232  			if !exist {
   233  				if by.Type.Name == StringType {
   234  					fields = append(fields, FieldValue{
   235  						Name:  by.Field,
   236  						Value: auth.Id.String(),
   237  					})
   238  				} else if by.Type.Name == IntType {
   239  					fields = append(fields, FieldValue{
   240  						Name:  by.Field,
   241  						Value: auth.Id.Int(),
   242  					})
   243  				}
   244  			}
   245  		}
   246  		if at != nil {
   247  			exist := false
   248  			for _, field := range fields {
   249  				if field.Name == at.Field {
   250  					exist = true
   251  					break
   252  				}
   253  			}
   254  			if !exist {
   255  				if at.Type.Value.ConvertibleTo(datetimeType) {
   256  					fields = append(fields, FieldValue{
   257  						Name:  at.Field,
   258  						Value: time.Now(),
   259  					})
   260  				} else if at.Type.Value.ConvertibleTo(nullTimeType) {
   261  					fields = append(fields, FieldValue{
   262  						Name: at.Field,
   263  						Value: stdsql.NullTime{
   264  							Time:  time.Now(),
   265  							Valid: true,
   266  						},
   267  					})
   268  				} else if at.Type.Value.ConvertibleTo(intType) {
   269  					fields = append(fields, FieldValue{
   270  						Name:  at.Field,
   271  						Value: time.Now().UnixMilli(),
   272  					})
   273  				} else if at.Type.Value.ConvertibleTo(nullInt64Type) {
   274  					fields = append(fields, FieldValue{
   275  						Name: at.Field,
   276  						Value: stdsql.NullInt64{
   277  							Int64: time.Now().UnixMilli(),
   278  							Valid: true,
   279  						},
   280  					})
   281  				}
   282  			}
   283  		}
   284  	}
   285  	for i, field := range fields {
   286  		column, hasColumn := spec.ColumnByField(field.Name)
   287  		if !hasColumn {
   288  			err = errors.Warning(fmt.Sprintf("sql: %s field was not found", field.Name)).WithMeta("table", spec.Key)
   289  			return
   290  		}
   291  		switch column.Type.Name {
   292  		case DateType:
   293  			fv, ok := field.Value.(times.Date)
   294  			if !ok {
   295  				err = errors.Warning(fmt.Sprintf("sql: %s field value type must be times.Date", field.Name)).WithMeta("table", spec.Key)
   296  				return
   297  			}
   298  			field.Value = fv.ToTime()
   299  			fields[i] = field
   300  			break
   301  		case TimeType:
   302  			fv, ok := field.Value.(times.Time)
   303  			if !ok {
   304  				err = errors.Warning(fmt.Sprintf("sql: %s field value type must be times.Time", field.Name)).WithMeta("table", spec.Key)
   305  				return
   306  			}
   307  			field.Value = fv.ToTime()
   308  			fields[i] = field
   309  			break
   310  		case JsonType:
   311  			p, encodeErr := json.Marshal(field.Value)
   312  			if encodeErr != nil {
   313  				err = errors.Warning(fmt.Sprintf("sql: encode %s field value failed", field.Name)).WithMeta("table", spec.Key)
   314  				return
   315  			}
   316  			field.Value = p
   317  			fields[i] = field
   318  			break
   319  		case MappingType:
   320  			if column.Kind != Reference {
   321  				err = errors.Warning(fmt.Sprintf("sql: kind %s field value type can not be updated", field.Name)).WithMeta("table", spec.Key)
   322  				return
   323  			}
   324  			rv := reflect.Indirect(reflect.ValueOf(field.Value))
   325  			if rv.Type().Kind() == reflect.Struct {
   326  				awayField, mapping, _ := column.Reference()
   327  				refArg, refArgErr := mapping.ArgumentByField(field.Value, awayField)
   328  				if refArgErr != nil {
   329  					err = errors.Warning(fmt.Sprintf("sql: scan reference %s field value faield", field.Name)).WithCause(refArgErr).WithMeta("table", spec.Key)
   330  					return
   331  				}
   332  				field.Value = refArg
   333  				fields[i] = field
   334  			}
   335  			break
   336  		default:
   337  			break
   338  		}
   339  	}
   340  	method, query, arguments, err = dialect.UpdateFields(Todo(ctx, t, dialect), spec, fields, cond)
   341  	if err != nil {
   342  		return
   343  	}
   344  	return
   345  }
   346  
   347  func BuildDelete[T any](ctx context.Context, entries []T) (method Method, query []byte, arguments []any, err error) {
   348  	dialect, dialectErr := LoadDialect(ctx)
   349  	if dialectErr != nil {
   350  		err = dialectErr
   351  		return
   352  	}
   353  	spec, specErr := GetSpecification(ctx, entries[0])
   354  	if specErr != nil {
   355  		err = specErr
   356  		return
   357  	}
   358  	if spec.View {
   359  		err = errors.Warning(fmt.Sprintf("sql: %s is view", spec.Key))
   360  		return
   361  	}
   362  
   363  	var fields []string
   364  	method, query, fields, err = dialect.Delete(Todo(ctx, entries[0], dialect), spec)
   365  	if err != nil {
   366  		return
   367  	}
   368  	// audit
   369  	auditErr := TrySetupAuditDeletion[T](ctx, spec, entries)
   370  	if auditErr != nil {
   371  		err = auditErr
   372  		return
   373  	}
   374  	arguments, err = spec.Arguments(entries[0], fields)
   375  	return
   376  }
   377  
   378  func BuildDeleteAnyByCondition(ctx context.Context, entry any, cond Condition) (method Method, query []byte, arguments []any, err error) {
   379  	dialect, dialectErr := LoadDialect(ctx)
   380  	if dialectErr != nil {
   381  		err = dialectErr
   382  		return
   383  	}
   384  	spec, specErr := GetSpecification(ctx, entry)
   385  	if specErr != nil {
   386  		err = specErr
   387  		return
   388  	}
   389  	if spec.View {
   390  		err = errors.Warning(fmt.Sprintf("sql: %s is view", spec.Key))
   391  		return
   392  	}
   393  	var audits []string
   394  	method, query, audits, arguments, err = dialect.DeleteByConditions(Todo(ctx, entry, dialect), spec, cond)
   395  	if err != nil {
   396  		return
   397  	}
   398  	if len(audits) > 0 {
   399  		by, at, hasAd := spec.AuditDeletion()
   400  		if !hasAd {
   401  			err = errors.Warning(fmt.Sprintf("sql: %s need audit deletion", spec.Key)).WithCause(fmt.Errorf("dialect return audits but entry has no audit deletion"))
   402  			return
   403  		}
   404  		auth, hasAuth, loadErr := authorizations.Load(ctx)
   405  		if loadErr != nil {
   406  			err = errors.Warning(fmt.Sprintf("sql: %s need audit deletion", spec.Key)).WithCause(loadErr)
   407  			return
   408  		}
   409  		if !hasAuth {
   410  			err = errors.Warning(fmt.Sprintf("sql: %s need audit deletion", spec.Key)).WithCause(fmt.Errorf("authorization was not found"))
   411  			return
   412  		}
   413  		if !auth.Exist() {
   414  			err = errors.Warning(fmt.Sprintf("sql: %s need audit deletion", spec.Key)).WithCause(authorizations.ErrUnauthorized)
   415  			return
   416  		}
   417  		auditArgs := make([]any, 0, 2)
   418  		for _, auditFieldName := range audits {
   419  			column, hasColumn := spec.ColumnByField(auditFieldName)
   420  			if !hasColumn {
   421  				err = errors.Warning(fmt.Sprintf("sql: %s need audit deletion", spec.Key)).WithCause(fmt.Errorf("column was not found")).WithMeta("field", auditFieldName)
   422  				return
   423  			}
   424  			if by != nil && column.Name == by.Name {
   425  				if by.Type.Name == StringType {
   426  					auditArgs = append(auditArgs, auth.Id.String())
   427  				} else if by.Type.Name == IntType {
   428  					auditArgs = append(auditArgs, auth.Id.Int())
   429  				}
   430  			} else if at != nil && column.Name == at.Name {
   431  				if at.Type.Value.ConvertibleTo(datetimeType) {
   432  					auditArgs = append(auditArgs, time.Now())
   433  				} else if at.Type.Value.ConvertibleTo(nullTimeType) {
   434  					auditArgs = append(auditArgs, stdsql.NullTime{
   435  						Time:  time.Now(),
   436  						Valid: true,
   437  					})
   438  				} else if at.Type.Value.ConvertibleTo(intType) {
   439  					auditArgs = append(auditArgs, time.Now().UnixMilli())
   440  				} else if at.Type.Value.ConvertibleTo(nullInt64Type) {
   441  					auditArgs = append(auditArgs, stdsql.NullInt64{
   442  						Int64: time.Now().UnixMilli(),
   443  						Valid: true,
   444  					})
   445  				}
   446  			}
   447  		}
   448  		arguments = append(auditArgs, arguments...)
   449  	}
   450  	return
   451  }
   452  
   453  func BuildDeleteByCondition[T any](ctx context.Context, cond Condition) (method Method, query []byte, arguments []any, err error) {
   454  	method, query, arguments, err = BuildDeleteAnyByCondition(ctx, Instance[T](), cond)
   455  	if err != nil {
   456  		return
   457  	}
   458  	return
   459  }
   460  
   461  func BuildCount[T any](ctx context.Context, cond Condition) (method Method, query []byte, arguments []any, err error) {
   462  	dialect, dialectErr := LoadDialect(ctx)
   463  	if dialectErr != nil {
   464  		err = dialectErr
   465  		return
   466  	}
   467  	t := Instance[T]()
   468  	spec, specErr := GetSpecification(ctx, t)
   469  	if specErr != nil {
   470  		err = specErr
   471  		return
   472  	}
   473  	method, query, arguments, err = dialect.Count(Todo(ctx, t, dialect), spec, cond)
   474  	if err != nil {
   475  		return
   476  	}
   477  	return
   478  }
   479  
   480  func BuildExist[T any](ctx context.Context, cond Condition) (method Method, query []byte, arguments []any, err error) {
   481  	dialect, dialectErr := LoadDialect(ctx)
   482  	if dialectErr != nil {
   483  		err = dialectErr
   484  		return
   485  	}
   486  	t := Instance[T]()
   487  	spec, specErr := GetSpecification(ctx, t)
   488  	if specErr != nil {
   489  		err = specErr
   490  		return
   491  	}
   492  	method, query, arguments, err = dialect.Exist(Todo(ctx, t, dialect), spec, cond)
   493  	if err != nil {
   494  		return
   495  	}
   496  	return
   497  }
   498  
   499  func BuildQuery[T any](ctx context.Context, cond Condition, orders Orders, offset int, length int) (method Method, query []byte, arguments []any, columns []string, err error) {
   500  	dialect, dialectErr := LoadDialect(ctx)
   501  	if dialectErr != nil {
   502  		err = dialectErr
   503  		return
   504  	}
   505  	t := Instance[T]()
   506  	spec, specErr := GetSpecification(ctx, t)
   507  	if specErr != nil {
   508  		err = specErr
   509  		return
   510  	}
   511  	method, query, arguments, columns, err = dialect.Query(Todo(ctx, t, dialect), spec, cond, orders, offset, length)
   512  	if err != nil {
   513  		return
   514  	}
   515  	if length > 0 {
   516  		arguments = append(arguments, offset, length)
   517  	}
   518  	return
   519  }
   520  
   521  func BuildView[T any](ctx context.Context, cond Condition, orders Orders, groupBy GroupBy, offset int, length int) (method Method, query []byte, arguments []any, columns []string, err error) {
   522  	dialect, dialectErr := LoadDialect(ctx)
   523  	if dialectErr != nil {
   524  		err = dialectErr
   525  		return
   526  	}
   527  	t := Instance[T]()
   528  	spec, specErr := GetSpecification(ctx, t)
   529  	if specErr != nil {
   530  		err = specErr
   531  		return
   532  	}
   533  	method, query, arguments, columns, err = dialect.View(Todo(ctx, t, dialect), spec, cond, orders, groupBy, offset, length)
   534  	if err != nil {
   535  		return
   536  	}
   537  	if length > 0 {
   538  		arguments = append(arguments, offset, length)
   539  	}
   540  	return
   541  }