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 }