feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
435
backend/domain/workflow/internal/nodes/database/common.go
Normal file
435
backend/domain/workflow/internal/nodes/database/common.go
Normal file
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/eino/compose"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/crossdomain/database"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/sonic"
|
||||
)
|
||||
|
||||
const rowNum = "rowNum"
|
||||
const outputList = "outputList"
|
||||
const TimeFormat = "2006-01-02 15:04:05 -0700 MST"
|
||||
|
||||
func toString(in any) (any, error) {
|
||||
switch in := in.(type) {
|
||||
case []byte:
|
||||
return string(in), nil
|
||||
case string:
|
||||
return in, nil
|
||||
case int64:
|
||||
return strconv.FormatInt(in, 10), nil
|
||||
case float64:
|
||||
return strconv.FormatFloat(in, 'f', -1, 64), nil
|
||||
case time.Time:
|
||||
return in.Format(TimeFormat), nil
|
||||
case bool:
|
||||
return strconv.FormatBool(in), nil
|
||||
case map[string]any, []any:
|
||||
return sonic.MarshalString(in)
|
||||
default:
|
||||
return "", fmt.Errorf("unknown type: %T", in)
|
||||
}
|
||||
}
|
||||
|
||||
func toInteger(in any) (any, error) {
|
||||
switch in := in.(type) {
|
||||
case []byte:
|
||||
return strconv.ParseInt(string(in), 10, 64)
|
||||
case string:
|
||||
return strconv.ParseInt(in, 10, 64)
|
||||
case int64:
|
||||
return in, nil
|
||||
case float64:
|
||||
return int64(in), nil
|
||||
case time.Time, bool:
|
||||
return nil, fmt.Errorf(`type '%T' can't convert to int64'`, in)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown type: %T", in)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func toNumber(in any) (any, error) {
|
||||
switch in := in.(type) {
|
||||
case []byte:
|
||||
i, err := strconv.ParseFloat(string(in), 64)
|
||||
return i, err
|
||||
case string:
|
||||
return strconv.ParseFloat(in, 64)
|
||||
case int64:
|
||||
return float64(in), nil
|
||||
case float64:
|
||||
return in, nil
|
||||
case time.Time, bool:
|
||||
return nil, fmt.Errorf(`type '%T' can't convert to float64'`, in)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown type: %T", in)
|
||||
}
|
||||
}
|
||||
|
||||
func toTime(in any) (any, error) {
|
||||
switch in := in.(type) {
|
||||
case []byte:
|
||||
return string(in), nil
|
||||
case string:
|
||||
return in, nil
|
||||
case int64:
|
||||
return strconv.FormatInt(in, 10), nil
|
||||
case float64:
|
||||
return strconv.FormatFloat(in, 'f', -1, 64), nil
|
||||
case time.Time:
|
||||
return in.Format(TimeFormat), nil
|
||||
case bool:
|
||||
if in {
|
||||
return "1", nil
|
||||
}
|
||||
return "0", nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown type: %T", in)
|
||||
}
|
||||
}
|
||||
|
||||
func toBool(in any) (any, error) {
|
||||
switch in := in.(type) {
|
||||
case []byte:
|
||||
return strconv.ParseBool(string(in))
|
||||
case string:
|
||||
return strconv.ParseBool(in)
|
||||
case int64:
|
||||
return strconv.ParseBool(strconv.FormatInt(in, 10))
|
||||
case float64:
|
||||
return strconv.ParseBool(strconv.FormatFloat(in, 'f', -1, 64))
|
||||
case time.Time:
|
||||
return strconv.ParseBool(in.Format(TimeFormat))
|
||||
case bool:
|
||||
return in, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown type: %T", in)
|
||||
}
|
||||
}
|
||||
|
||||
// formatted convert the interface type according to the datatype type.
|
||||
// notice: object is currently not supported by database, and ignore it.
|
||||
func formatted(in any, ty *vo.TypeInfo) any {
|
||||
switch ty.Type {
|
||||
case vo.DataTypeString:
|
||||
r, err := toString(in)
|
||||
if err != nil {
|
||||
logs.Warnf("formatted string error: %v", err)
|
||||
return nil
|
||||
}
|
||||
return r
|
||||
case vo.DataTypeNumber:
|
||||
r, err := toNumber(in)
|
||||
if err != nil {
|
||||
logs.Warnf("formatted number error: %v", err)
|
||||
return nil
|
||||
}
|
||||
return r
|
||||
case vo.DataTypeInteger:
|
||||
r, err := toInteger(in)
|
||||
if err != nil {
|
||||
logs.Warnf("formatted integer error: %v", err)
|
||||
return nil
|
||||
}
|
||||
return r
|
||||
case vo.DataTypeBoolean:
|
||||
r, err := toBool(in)
|
||||
if err != nil {
|
||||
logs.Warnf("formatted boolean error: %v", err)
|
||||
}
|
||||
return r
|
||||
case vo.DataTypeTime:
|
||||
r, err := toTime(in)
|
||||
if err != nil {
|
||||
logs.Warnf("formatted time error: %v", err)
|
||||
return nil
|
||||
}
|
||||
return r
|
||||
case vo.DataTypeArray:
|
||||
arrayIn := make([]any, 0)
|
||||
inStr, err := toString(in)
|
||||
if err != nil {
|
||||
logs.Warnf("formatted array error: %v", err)
|
||||
return []any{}
|
||||
}
|
||||
|
||||
err = sonic.UnmarshalString(inStr.(string), &arrayIn)
|
||||
if err != nil {
|
||||
logs.Warnf("formatted array unmarshal error: %v", err)
|
||||
return []any{}
|
||||
}
|
||||
result := make([]any, 0)
|
||||
switch ty.ElemTypeInfo.Type {
|
||||
case vo.DataTypeTime:
|
||||
for _, in := range arrayIn {
|
||||
r, err := toTime(in)
|
||||
if err != nil {
|
||||
logs.Warnf("formatted time: %v", err)
|
||||
continue
|
||||
}
|
||||
result = append(result, r)
|
||||
}
|
||||
return result
|
||||
case vo.DataTypeString:
|
||||
for _, in := range arrayIn {
|
||||
r, err := toString(in)
|
||||
if err != nil {
|
||||
logs.Warnf("formatted string failed: %v", err)
|
||||
continue
|
||||
}
|
||||
result = append(result, r)
|
||||
}
|
||||
return result
|
||||
case vo.DataTypeInteger:
|
||||
for _, in := range arrayIn {
|
||||
r, err := toInteger(in)
|
||||
if err != nil {
|
||||
logs.Warnf("formatted interger failed: %v", err)
|
||||
continue
|
||||
}
|
||||
result = append(result, r)
|
||||
}
|
||||
return result
|
||||
case vo.DataTypeBoolean:
|
||||
for _, in := range arrayIn {
|
||||
r, err := toBool(in)
|
||||
if err != nil {
|
||||
logs.Warnf("formatted bool failed: %v", err)
|
||||
continue
|
||||
}
|
||||
result = append(result, r)
|
||||
}
|
||||
return result
|
||||
case vo.DataTypeNumber:
|
||||
for _, in := range arrayIn {
|
||||
r, err := toNumber(in)
|
||||
if err != nil {
|
||||
logs.Warnf("formatted number failed: %v", err)
|
||||
continue
|
||||
}
|
||||
result = append(result, r)
|
||||
}
|
||||
return result
|
||||
case vo.DataTypeObject:
|
||||
properties := ty.ElemTypeInfo.Properties
|
||||
if len(properties) == 0 {
|
||||
for idx := range arrayIn {
|
||||
in := arrayIn[idx]
|
||||
if _, ok := in.(database.Object); ok {
|
||||
result = append(result, in)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
for idx := range arrayIn {
|
||||
in := arrayIn[idx]
|
||||
object, ok := in.(database.Object)
|
||||
if !ok {
|
||||
object = make(database.Object)
|
||||
for key := range properties {
|
||||
object[key] = nil
|
||||
}
|
||||
result = append(result, object)
|
||||
} else {
|
||||
result = append(result, objectFormatted(ty.ElemTypeInfo.Properties, object))
|
||||
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func objectFormatted(props map[string]*vo.TypeInfo, object database.Object) map[string]any {
|
||||
ret := make(map[string]any)
|
||||
|
||||
// if config is nil, it agrees to convert to string type as the default value
|
||||
if len(props) == 0 {
|
||||
for k, v := range object {
|
||||
val, err := toString(v)
|
||||
if err != nil {
|
||||
logs.Warnf("formatted string error: %v", err)
|
||||
continue
|
||||
}
|
||||
ret[k] = val
|
||||
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
for k, v := range props {
|
||||
if r, ok := object[k]; ok && r != nil {
|
||||
formattedValue := formatted(r, v)
|
||||
ret[k] = formattedValue
|
||||
} else {
|
||||
// if key not existed, assign nil
|
||||
ret[k] = nil
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// responseFormatted convert the object list returned by "response" into the field mapping of the "config output" configuration,
|
||||
// If the conversion fail, set the output list to null. If there are missing fields, set the missing fields to null.
|
||||
func responseFormatted(configOutput map[string]*vo.TypeInfo, response *database.Response) (map[string]any, error) {
|
||||
ret := make(map[string]any)
|
||||
list := make([]any, 0, len(configOutput))
|
||||
|
||||
outputListTypeInfo, ok := configOutput["outputList"]
|
||||
if !ok {
|
||||
return ret, fmt.Errorf("outputList key is required")
|
||||
}
|
||||
if outputListTypeInfo.Type != vo.DataTypeArray {
|
||||
return nil, fmt.Errorf("output list type info must array,but got %v", outputListTypeInfo.Type)
|
||||
}
|
||||
if outputListTypeInfo.ElemTypeInfo == nil {
|
||||
return nil, fmt.Errorf("output list must be an array and the array must contain element type info")
|
||||
}
|
||||
if outputListTypeInfo.ElemTypeInfo.Type != vo.DataTypeObject {
|
||||
return nil, fmt.Errorf("output list must be an array and element must object, but got %v", outputListTypeInfo.ElemTypeInfo.Type)
|
||||
}
|
||||
|
||||
props := outputListTypeInfo.ElemTypeInfo.Properties
|
||||
|
||||
for _, object := range response.Objects {
|
||||
list = append(list, objectFormatted(props, object))
|
||||
}
|
||||
|
||||
ret[outputList] = list
|
||||
if response.RowNumber != nil {
|
||||
ret[rowNum] = *response.RowNumber
|
||||
} else {
|
||||
ret[rowNum] = nil
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func convertClauseGroupToConditionGroup(ctx context.Context, clauseGroup *database.ClauseGroup, input map[string]any) (*database.ConditionGroup, error) {
|
||||
var (
|
||||
rightValue any
|
||||
ok bool
|
||||
)
|
||||
|
||||
conditionGroup := &database.ConditionGroup{
|
||||
Conditions: make([]*database.Condition, 0),
|
||||
Relation: database.ClauseRelationAND,
|
||||
}
|
||||
|
||||
if clauseGroup.Single != nil {
|
||||
clause := clauseGroup.Single
|
||||
if !notNeedTakeMapValue(clause.Operator) {
|
||||
rightValue, ok = nodes.TakeMapValue(input, compose.FieldPath{"__condition_right_0"})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot take single clause from input")
|
||||
}
|
||||
}
|
||||
|
||||
conditionGroup.Conditions = append(conditionGroup.Conditions, &database.Condition{
|
||||
Left: clause.Left,
|
||||
Operator: clause.Operator,
|
||||
Right: rightValue,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
if clauseGroup.Multi != nil {
|
||||
conditionGroup.Relation = clauseGroup.Multi.Relation
|
||||
|
||||
conditionGroup.Conditions = make([]*database.Condition, len(clauseGroup.Multi.Clauses))
|
||||
multiSelect := clauseGroup.Multi
|
||||
for idx, clause := range multiSelect.Clauses {
|
||||
if !notNeedTakeMapValue(clause.Operator) {
|
||||
rightValue, ok = nodes.TakeMapValue(input, compose.FieldPath{fmt.Sprintf("__condition_right_%d", idx)})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot take multi clause from input")
|
||||
}
|
||||
}
|
||||
conditionGroup.Conditions[idx] = &database.Condition{
|
||||
Left: clause.Left,
|
||||
Operator: clause.Operator,
|
||||
Right: rightValue,
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return conditionGroup, nil
|
||||
}
|
||||
|
||||
func convertClauseGroupToUpdateInventory(ctx context.Context, clauseGroup *database.ClauseGroup, input map[string]any) (*UpdateInventory, error) {
|
||||
conditionGroup, err := convertClauseGroupToConditionGroup(ctx, clauseGroup, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields := parseToInput(input)
|
||||
inventory := &UpdateInventory{
|
||||
ConditionGroup: conditionGroup,
|
||||
Fields: fields,
|
||||
}
|
||||
return inventory, nil
|
||||
}
|
||||
|
||||
func isDebugExecute(ctx context.Context) bool {
|
||||
execCtx := execute.GetExeCtx(ctx)
|
||||
if execCtx == nil {
|
||||
panic(fmt.Errorf("unable to get exe context"))
|
||||
}
|
||||
return execCtx.RootCtx.ExeCfg.Mode == vo.ExecuteModeDebug || execCtx.RootCtx.ExeCfg.Mode == vo.ExecuteModeNodeDebug
|
||||
}
|
||||
|
||||
func getExecUserID(ctx context.Context) int64 {
|
||||
execCtx := execute.GetExeCtx(ctx)
|
||||
if execCtx == nil {
|
||||
panic(fmt.Errorf("unable to get exe context"))
|
||||
}
|
||||
return execCtx.RootCtx.ExeCfg.Operator
|
||||
}
|
||||
|
||||
func parseToInput(input map[string]any) map[string]any {
|
||||
result := make(map[string]any, len(input))
|
||||
for key, value := range input {
|
||||
if strings.HasPrefix(key, "__setting_field_") {
|
||||
key = strings.TrimPrefix(key, "__setting_field_")
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user