Skip to content

Commit 9199757

Browse files
kidkidkidCoda-bot
andcommitted
feat(backend): add annotation feature support with ClickHouse table and DAO implementation
Co-Authored-By: Coda <coda@bytedance.com>
1 parent 5a30a06 commit 9199757

12 files changed

Lines changed: 1162 additions & 134 deletions

File tree

backend/go.mod

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,19 @@ require (
9696
gorm.io/plugin/soft_delete v1.2.1
9797
)
9898

99-
require github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
99+
require (
100+
github.com/gopherjs/gopherjs v1.17.2 // indirect
101+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
102+
github.com/jtolds/gls v4.20.0+incompatible // indirect
103+
github.com/smarty/assertions v1.16.0 // indirect
104+
)
100105

101106
require (
102107
github.com/brianvoe/gofakeit/v6 v6.28.0
108+
github.com/bytedance/mockey v1.2.14
103109
github.com/coze-dev/coze-loop/backend/modules/observability/lib v0.0.0-00010101000000-000000000000
104110
github.com/coze-dev/cozeloop-go v0.1.16
111+
github.com/smartystreets/goconvey v1.8.1
105112
)
106113

107114
require (

backend/modules/observability/infra/repo/ck/annotation.go

Lines changed: 148 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,18 @@ package ck
55

66
import (
77
"context"
8+
"fmt"
9+
"strings"
810

911
"github.com/coze-dev/coze-loop/backend/infra/ck"
12+
"github.com/coze-dev/coze-loop/backend/modules/observability/infra/repo/ck/convertor"
13+
"github.com/coze-dev/coze-loop/backend/modules/observability/infra/repo/ck/gorm_gen/model"
1014
"github.com/coze-dev/coze-loop/backend/modules/observability/infra/repo/dao"
15+
obErrorx "github.com/coze-dev/coze-loop/backend/modules/observability/pkg/errno"
16+
"github.com/coze-dev/coze-loop/backend/pkg/errorx"
17+
"github.com/coze-dev/coze-loop/backend/pkg/logs"
18+
"gorm.io/gorm"
19+
"gorm.io/gorm/clause"
1120
)
1221

1322
func NewAnnotationCkDaoImpl(db ck.Provider) (dao.IAnnotationDao, error) {
@@ -21,13 +30,149 @@ type AnnotationCkDaoImpl struct {
2130
}
2231

2332
func (a *AnnotationCkDaoImpl) Insert(ctx context.Context, params *dao.InsertAnnotationParam) error {
24-
return nil
33+
if params == nil || len(params.Annotations) == 0 {
34+
return errorx.NewByCode(obErrorx.CommercialCommonInvalidParamCodeCode)
35+
}
36+
db := a.db.NewSession(ctx)
37+
retryTimes := 3
38+
var lastErr error
39+
for i := 0; i < retryTimes; i++ {
40+
if err := db.Table(params.Table).Create(params.Annotations).Error; err != nil {
41+
lastErr = err
42+
} else {
43+
logs.CtxInfo(ctx, "insert annotations successfully")
44+
return nil
45+
}
46+
}
47+
logs.CtxError(ctx, "fail to insert annotations: %v", lastErr)
48+
return errorx.WrapByCode(lastErr, obErrorx.CommercialCommonInternalErrorCodeCode)
2549
}
2650

2751
func (a *AnnotationCkDaoImpl) Get(ctx context.Context, params *dao.GetAnnotationParam) (*dao.Annotation, error) {
28-
return nil, nil
52+
if params == nil || params.ID == "" {
53+
return nil, errorx.NewByCode(obErrorx.CommercialCommonInvalidParamCodeCode)
54+
}
55+
db, err := a.buildSql(ctx, &annoSqlParam{
56+
Tables: params.Tables,
57+
StartTime: params.StartTime,
58+
EndTime: params.EndTime,
59+
ID: params.ID,
60+
Limit: 1,
61+
})
62+
if err != nil {
63+
return nil, err
64+
}
65+
logs.CtxInfo(ctx, "Get Annotation SQL: %s", db.ToSQL(func(tx *gorm.DB) *gorm.DB {
66+
return tx.Find(nil)
67+
}))
68+
var annotations []*model.ObservabilityAnnotation
69+
if err := db.Find(&annotations).Error; err != nil {
70+
return nil, err
71+
}
72+
if len(annotations) == 0 {
73+
return nil, nil
74+
} else if len(annotations) > 1 {
75+
logs.CtxWarn(ctx, "multiple annotations found")
76+
}
77+
return convertor.AnnotationCKModel2PO(annotations[0]), nil
2978
}
3079

3180
func (a *AnnotationCkDaoImpl) List(ctx context.Context, params *dao.ListAnnotationsParam) ([]*dao.Annotation, error) {
32-
return nil, nil
81+
if params == nil || len(params.SpanIDs) == 0 {
82+
return nil, nil
83+
}
84+
db, err := a.buildSql(ctx, &annoSqlParam{
85+
Tables: params.Tables,
86+
StartTime: params.StartTime,
87+
EndTime: params.EndTime,
88+
SpanIDs: params.SpanIDs,
89+
DescByUpdatedAt: params.DescByUpdatedAt,
90+
Limit: params.Limit,
91+
})
92+
if err != nil {
93+
return nil, err
94+
}
95+
logs.CtxInfo(ctx, "List Annotations SQL: %s", db.ToSQL(func(tx *gorm.DB) *gorm.DB {
96+
return tx.Find(nil)
97+
}))
98+
var annotations []*model.ObservabilityAnnotation
99+
if err := db.Find(&annotations).Error; err != nil {
100+
return nil, err
101+
}
102+
return convertor.AnnotationListCKModels2PO(annotations), nil
103+
}
104+
105+
type annoSqlParam struct {
106+
Tables []string
107+
StartTime int64
108+
EndTime int64
109+
ID string
110+
SpanIDs []string
111+
DescByUpdatedAt bool
112+
Limit int32
113+
}
114+
115+
func (a *AnnotationCkDaoImpl) buildSql(ctx context.Context, param *annoSqlParam) (*gorm.DB, error) {
116+
db := a.db.NewSession(ctx)
117+
var tableQueries []*gorm.DB
118+
for _, table := range param.Tables {
119+
query, err := a.buildSingleSql(ctx, db, table, param)
120+
if err != nil {
121+
return nil, err
122+
}
123+
tableQueries = append(tableQueries, query)
124+
}
125+
if len(tableQueries) == 0 {
126+
return nil, fmt.Errorf("no table configured")
127+
} else if len(tableQueries) == 1 {
128+
query := tableQueries[0].ToSQL(func(tx *gorm.DB) *gorm.DB {
129+
return tx.Find(nil)
130+
})
131+
query += " SETTINGS final = 1"
132+
return db.Raw(query), nil
133+
} else {
134+
queries := make([]string, 0)
135+
for i := 0; i < len(tableQueries); i++ {
136+
query := tableQueries[i].ToSQL(func(tx *gorm.DB) *gorm.DB {
137+
return tx.Find(nil)
138+
})
139+
queries = append(queries, "("+query+")")
140+
}
141+
sql := fmt.Sprintf("SELECT * FROM (%s)", strings.Join(queries, " UNION ALL "))
142+
if param.DescByUpdatedAt {
143+
sql += " ORDER BY updated_at DESC"
144+
} else {
145+
sql += " ORDER BY created_at ASC"
146+
}
147+
sql += fmt.Sprintf(" LIMIT %d SETTINGS final = 1", param.Limit)
148+
return db.Raw(sql), nil
149+
}
150+
}
151+
152+
// buildSingleSql 构建单表查询SQL
153+
func (a *AnnotationCkDaoImpl) buildSingleSql(ctx context.Context, db *gorm.DB, tableName string, param *annoSqlParam) (*gorm.DB, error) {
154+
sqlQuery := db.
155+
Table(tableName).
156+
Where("deleted_at = 0")
157+
158+
if param.ID != "" {
159+
sqlQuery = sqlQuery.Where("id = ?", param.ID)
160+
}
161+
if len(param.SpanIDs) > 0 {
162+
sqlQuery = sqlQuery.Where("span_id IN (?)", param.SpanIDs)
163+
}
164+
sqlQuery = sqlQuery.
165+
Where("start_time >= ?", param.StartTime).
166+
Where("start_time <= ?", param.EndTime)
167+
if param.DescByUpdatedAt {
168+
sqlQuery = sqlQuery.Order(clause.OrderBy{Columns: []clause.OrderByColumn{
169+
{Column: clause.Column{Name: "updated_at"}, Desc: true},
170+
}})
171+
} else {
172+
sqlQuery = sqlQuery.Order(clause.OrderBy{Columns: []clause.OrderByColumn{
173+
{Column: clause.Column{Name: "created_at"}, Desc: false},
174+
}})
175+
}
176+
sqlQuery = sqlQuery.Limit(int(param.Limit))
177+
return sqlQuery, nil
33178
}

0 commit comments

Comments
 (0)