package utils import ( "fmt" "math" "reflect" "strconv" "strings" "go.signoz.io/signoz/pkg/query-service/constants" v3 "go.signoz.io/signoz/pkg/query-service/model/v3" "go.uber.org/zap" ) // ValidateAndCastValue validates and casts the value of a key to the corresponding data type of the key func ValidateAndCastValue(v interface{}, dataType v3.AttributeKeyDataType) (interface{}, error) { switch dataType { case v3.AttributeKeyDataTypeString: switch x := v.(type) { case string, int, int64, float32, float64, bool: return fmt.Sprintf("%v", x), nil case []interface{}: for i, val := range x { // if val is not string and it is int, int64, float, bool, convert it to string if _, ok := val.(string); ok { continue } else if _, ok := val.(int); ok { x[i] = fmt.Sprintf("%v", val) } else if _, ok := val.(int64); ok { x[i] = fmt.Sprintf("%v", val) } else if _, ok := val.(float32); ok { x[i] = fmt.Sprintf("%v", val) } else if _, ok := val.(float64); ok { x[i] = fmt.Sprintf("%v", val) } else if _, ok := val.(bool); ok { x[i] = fmt.Sprintf("%v", val) } else { return nil, fmt.Errorf("invalid data type, expected string, got %v", reflect.TypeOf(val)) } } return x, nil default: return nil, fmt.Errorf("invalid data type, expected string, got %v", reflect.TypeOf(v)) } case v3.AttributeKeyDataTypeBool: switch x := v.(type) { case []interface{}: for i, val := range x { if _, ok := val.(string); ok { boolean, err := strconv.ParseBool(val.(string)) if err != nil { return nil, fmt.Errorf("invalid data type, expected bool, got %v", reflect.TypeOf(val)) } x[i] = boolean } else if _, ok := val.(bool); !ok { return nil, fmt.Errorf("invalid data type, expected bool, got %v", reflect.TypeOf(val)) } else { x[i] = val.(bool) } } return x, nil case bool: return x, nil case string: boolean, err := strconv.ParseBool(x) if err != nil { return nil, fmt.Errorf("invalid data type, expected bool, got %v", reflect.TypeOf(v)) } return boolean, nil default: return nil, fmt.Errorf("invalid data type, expected bool, got %v", reflect.TypeOf(v)) } case v3.AttributeKeyDataTypeInt64: switch x := v.(type) { case []interface{}: for i, val := range x { if _, ok := val.(string); ok { int64val, err := strconv.ParseInt(val.(string), 10, 64) if err != nil { return nil, fmt.Errorf("invalid data type, expected int, got %v", reflect.TypeOf(val)) } x[i] = int64val } else if _, ok := val.(int); ok { x[i] = int64(val.(int)) } else if _, ok := val.(int64); !ok { return nil, fmt.Errorf("invalid data type, expected int, got %v", reflect.TypeOf(val)) } else { x[i] = val.(int64) } } return x, nil case int, int64: return x, nil case float32: return int64(x), nil case float64: return int64(x), nil case string: int64val, err := strconv.ParseInt(x, 10, 64) if err != nil { return nil, fmt.Errorf("invalid data type, expected int, got %v", reflect.TypeOf(v)) } return int64val, nil default: return nil, fmt.Errorf("invalid data type, expected int, got %v", reflect.TypeOf(v)) } case v3.AttributeKeyDataTypeFloat64: switch x := v.(type) { case []interface{}: for i, val := range x { if _, ok := val.(string); ok { float64val, err := strconv.ParseFloat(val.(string), 64) if err != nil { return nil, fmt.Errorf("invalid data type, expected float, got %v", reflect.TypeOf(val)) } x[i] = float64val } else if _, ok := val.(float32); ok { x[i] = float64(val.(float32)) } else if _, ok := val.(int); ok { x[i] = float64(val.(int)) } else if _, ok := val.(int64); ok { x[i] = float64(val.(int64)) } else if _, ok := val.(float64); !ok { return nil, fmt.Errorf("invalid data type, expected float, got %v", reflect.TypeOf(val)) } else { x[i] = val.(float64) } } return x, nil case float32, float64: return x, nil case string: float64val, err := strconv.ParseFloat(x, 64) if err != nil { return nil, fmt.Errorf("invalid data type, expected float, got %v", reflect.TypeOf(v)) } return float64val, nil case int: return float64(x), nil case int64: return float64(x), nil default: return nil, fmt.Errorf("invalid data type, expected float, got %v", reflect.TypeOf(v)) } default: return nil, fmt.Errorf("invalid data type, expected float, bool, int, string or []interface{} but got %v", dataType) } } func QuoteEscapedString(str string) string { // https://clickhouse.com/docs/en/sql-reference/syntax#string str = strings.ReplaceAll(str, `\`, `\\`) str = strings.ReplaceAll(str, `'`, `\'`) return str } // ClickHouseFormattedValue formats the value to be used in clickhouse query func ClickHouseFormattedValue(v interface{}) string { // if it's pointer convert it to a value v = getPointerValue(v) switch x := v.(type) { case uint8, uint16, uint32, uint64, int, int8, int16, int32, int64: return fmt.Sprintf("%d", x) case float32, float64: return fmt.Sprintf("%f", x) case string: return fmt.Sprintf("'%s'", QuoteEscapedString(x)) case bool: return fmt.Sprintf("%v", x) case []interface{}: if len(x) == 0 { return "[]" } switch x[0].(type) { case string: str := "[" for idx, sVal := range x { str += fmt.Sprintf("'%s'", QuoteEscapedString(sVal.(string))) if idx != len(x)-1 { str += "," } } str += "]" return str case uint8, uint16, uint32, uint64, int, int8, int16, int32, int64, float32, float64, bool: return strings.Join(strings.Fields(fmt.Sprint(x)), ",") default: zap.L().Error("invalid type for formatted value", zap.Any("type", reflect.TypeOf(x[0]))) return "[]" } default: zap.L().Error("invalid type for formatted value", zap.Any("type", reflect.TypeOf(x))) return "" } } func getPointerValue(v interface{}) interface{} { switch x := v.(type) { case *uint8: return *x case *uint16: return *x case *uint32: return *x case *uint64: return *x case *int: return *x case *int8: return *x case *int16: return *x case *int32: return *x case *int64: return *x case *float32: return *x case *float64: return *x case *string: return *x case *bool: return *x case []interface{}: values := []interface{}{} for _, val := range x { values = append(values, getPointerValue(val)) } return values default: return v } } func GetClickhouseColumnName(typeName string, dataType, field string) string { if typeName == string(v3.AttributeKeyTypeTag) { typeName = constants.Attributes } if typeName != string(v3.AttributeKeyTypeResource) { typeName = typeName[:len(typeName)-1] } // if name contains . replace it with `$$` field = strings.ReplaceAll(field, ".", "$$") colName := fmt.Sprintf("`%s_%s_%s`", strings.ToLower(typeName), strings.ToLower(dataType), field) return colName } // GetEpochNanoSecs takes epoch and returns it in ns func GetEpochNanoSecs(epoch int64) int64 { temp := epoch count := 0 if epoch == 0 { count = 1 } else { for epoch != 0 { epoch /= 10 count++ } } return temp * int64(math.Pow(10, float64(19-count))) }