448 lines
11 KiB
Go
448 lines
11 KiB
Go
/*
|
|
* 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(_ 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) string {
|
|
execCtx := execute.GetExeCtx(ctx)
|
|
if execCtx == nil {
|
|
panic(fmt.Errorf("unable to get exe context"))
|
|
}
|
|
if execCtx.RootCtx.ExeCfg.AgentID != nil {
|
|
return execCtx.RootCtx.ExeCfg.ConnectorUID
|
|
}
|
|
uIDStr := strconv.FormatInt(execCtx.RootCtx.ExeCfg.Operator, 10)
|
|
return uIDStr
|
|
}
|
|
|
|
func getConnectorID(ctx context.Context) int64 {
|
|
execCtx := execute.GetExeCtx(ctx)
|
|
if execCtx == nil {
|
|
panic(fmt.Errorf("unable to get exe context"))
|
|
}
|
|
return execCtx.RootCtx.ExeCfg.ConnectorID
|
|
}
|
|
|
|
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
|
|
}
|