diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8f1c84b19..8b3e796f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,5 +13,5 @@ jobs: - uses: ncipollo/release-action@v1 with: body: - Check [CHANGELOG.md](https://github.com/uptrace/bun/blob/master/CHANGELOG.md) for - details + Please refer to [CHANGELOG.md](https://github.com/uptrace/bun/blob/master/CHANGELOG.md) + for details diff --git a/extra/bunotel/go.mod b/extra/bunotel/go.mod index 9ece8fc2f..034599335 100644 --- a/extra/bunotel/go.mod +++ b/extra/bunotel/go.mod @@ -6,6 +6,7 @@ replace github.com/uptrace/bun => ../.. require ( github.com/uptrace/bun v1.0.8 - go.opentelemetry.io/otel v1.0.0-RC1 - go.opentelemetry.io/otel/trace v1.0.0-RC1 + go.opentelemetry.io/otel v1.0.0-RC3 + go.opentelemetry.io/otel/metric v0.23.0 + go.opentelemetry.io/otel/trace v1.0.0-RC3 ) diff --git a/extra/bunotel/go.sum b/extra/bunotel/go.sum index a9a0d577b..1413ed3db 100644 --- a/extra/bunotel/go.sum +++ b/extra/bunotel/go.sum @@ -17,12 +17,14 @@ github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2el github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -go.opentelemetry.io/otel v1.0.0-RC1 h1:4CeoX93DNTWt8awGK9JmNXzF9j7TyOu9upscEdtcdXc= -go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I= -go.opentelemetry.io/otel/oteltest v1.0.0-RC1 h1:G685iP3XiskCwk/z0eIabL55XUl2gk0cljhGk9sB0Yk= -go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4= -go.opentelemetry.io/otel/trace v1.0.0-RC1 h1:jrjqKJZEibFrDz+umEASeU3LvdVyWKlnTh7XEfwrT58= -go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg= +go.opentelemetry.io/otel v1.0.0-RC3 h1:kvwiyEkiUT/JaadXzVLI/R1wDO934A7r3Bs2wEe6wqA= +go.opentelemetry.io/otel v1.0.0-RC3/go.mod h1:Ka5j3ua8tZs4Rkq4Ex3hwgBgOchyPVq5S6P2lz//nKQ= +go.opentelemetry.io/otel/internal/metric v0.23.0 h1:mPfzm9Iqhw7G2nDBmUAjFTfPqLZPbOW2k7QI57ITbaI= +go.opentelemetry.io/otel/internal/metric v0.23.0/go.mod h1:z+RPiDJe30YnCrOhFGivwBS+DU1JU/PiLKkk4re2DNY= +go.opentelemetry.io/otel/metric v0.23.0 h1:mYCcDxi60P4T27/0jchIDFa1WHEfQeU3zH9UEMpnj2c= +go.opentelemetry.io/otel/metric v0.23.0/go.mod h1:G/Nn9InyNnIv7J6YVkQfpc0JCfKBNJaERBGw08nqmVQ= +go.opentelemetry.io/otel/trace v1.0.0-RC3 h1:9F0ayEvlxv8BmNmPbU005WK7hC+7KbOazCPZjNa1yME= +go.opentelemetry.io/otel/trace v1.0.0-RC3/go.mod h1:VUt2TUYd8S2/ZRX09ZDFZQwn2RqfMB5MzO17jBojGxo= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/extra/bunotel/otel.go b/extra/bunotel/otel.go index 805bfbde9..63604452b 100644 --- a/extra/bunotel/otel.go +++ b/extra/bunotel/otel.go @@ -5,10 +5,13 @@ import ( "database/sql" "runtime" "strings" + "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/global" "go.opentelemetry.io/otel/trace" "github.com/uptrace/bun" @@ -16,7 +19,15 @@ import ( "github.com/uptrace/bun/schema" ) -var tracer = otel.Tracer("github.com/uptrace/bun") +var ( + tracer = otel.Tracer("github.com/uptrace/bun") + meter = metric.Must(global.Meter("github.com/uptrace/bun")) + queryHistogram = meter.NewInt64Histogram( + "bun.query.timing", + metric.WithDescription("Timing of processed queries"), + metric.WithUnit("milliseconds"), + ) +) type ConfigOption func(*QueryHook) @@ -37,18 +48,27 @@ func (h *QueryHook) BeforeQuery(ctx context.Context, event *bun.QueryEvent) cont return ctx } - operation := event.Operation() - ctx, span := tracer.Start(ctx, operation) - span.SetAttributes(attribute.String("db.operation", operation)) - + ctx, _ = tracer.Start(ctx, "") return ctx } func (h *QueryHook) AfterQuery(ctx context.Context, event *bun.QueryEvent) { + operation := event.Operation() + dbOperation := attribute.String("db.operation", operation) + + labels := []attribute.KeyValue{dbOperation} + if tableName := tableName(event.QueryAppender); tableName != "" { + labels = append(labels, attribute.String("db.table", tableName)) + } + + queryHistogram.Record(ctx, time.Since(event.StartTime).Milliseconds(), labels...) + span := trace.SpanFromContext(ctx) if !span.IsRecording() { return } + + span.SetName(operation) defer span.End() query := eventQuery(event) @@ -56,6 +76,7 @@ func (h *QueryHook) AfterQuery(ctx context.Context, event *bun.QueryEvent) { attrs := make([]attribute.KeyValue, 0, 10) attrs = append(attrs, + dbOperation, attribute.String("db.statement", query), attribute.String("code.function", fn), attribute.String("code.filepath", file), @@ -110,8 +131,8 @@ func funcFileLine(pkg string) (string, string, int) { } func eventQuery(event *bun.QueryEvent) string { - const softQueryLimit = 5000 - const hardQueryLimit = 10000 + const softQueryLimit = 8000 + const hardQueryLimit = 16000 var query string @@ -147,3 +168,12 @@ func dbSystem(db *bun.DB) string { return "" } } + +func tableName(query schema.Query) string { + if v, ok := query.(interface { + GetTableName() string + }); ok { + return v.GetTableName() + } + return "" +} diff --git a/join.go b/join.go index 42bf800d6..7d5ce06fc 100644 --- a/join.go +++ b/join.go @@ -10,8 +10,8 @@ import ( type relationJoin struct { Parent *relationJoin - BaseModel tableModel - JoinModel tableModel + BaseModel TableModel + JoinModel TableModel Relation *schema.Relation apply func(*SelectQuery) *SelectQuery @@ -71,8 +71,8 @@ func (j *relationJoin) manyQuery(q *SelectQuery) *SelectQuery { where = appendChildValues( q.db.Formatter(), where, - j.JoinModel.Root(), - j.JoinModel.ParentIndex(), + j.JoinModel.rootValue(), + j.JoinModel.parentIndex(), j.Relation.BaseFields, ) where = append(where, ")"...) @@ -135,7 +135,7 @@ func (j *relationJoin) m2mQuery(q *SelectQuery) *SelectQuery { } q = q.Model(m2mModel) - index := j.JoinModel.ParentIndex() + index := j.JoinModel.parentIndex() baseTable := j.BaseModel.Table() //nolint @@ -154,7 +154,7 @@ func (j *relationJoin) m2mQuery(q *SelectQuery) *SelectQuery { join = append(join, col.SQLName...) } join = append(join, ") IN ("...) - join = appendChildValues(fmter, join, j.BaseModel.Root(), index, baseTable.PKs) + join = appendChildValues(fmter, join, j.BaseModel.rootValue(), index, baseTable.PKs) join = append(join, ")"...) q = q.Join(internal.String(join)) diff --git a/model.go b/model.go index d392608c4..71a3a1e60 100644 --- a/model.go +++ b/model.go @@ -24,12 +24,8 @@ type rowScanner interface { ScanRow(ctx context.Context, rows *sql.Rows) error } -type model interface { +type TableModel interface { Model -} - -type tableModel interface { - model schema.BeforeScanHook schema.AfterScanHook @@ -38,19 +34,19 @@ type tableModel interface { Table() *schema.Table Relation() *schema.Relation - Join(string) *relationJoin - GetJoin(string) *relationJoin - GetJoins() []relationJoin - AddJoin(relationJoin) *relationJoin + join(string) *relationJoin + getJoin(string) *relationJoin + getJoins() []relationJoin + addJoin(relationJoin) *relationJoin - Root() reflect.Value - ParentIndex() []int - Mount(reflect.Value) + rootValue() reflect.Value + parentIndex() []int + mount(reflect.Value) updateSoftDeleteField(time.Time) error } -func newModel(db *DB, dest []interface{}) (model, error) { +func newModel(db *DB, dest []interface{}) (Model, error) { if len(dest) == 1 { return _newModel(db, dest[0], true) } @@ -74,11 +70,11 @@ func newModel(db *DB, dest []interface{}) (model, error) { return newSliceModel(db, dest, values), nil } -func newSingleModel(db *DB, dest interface{}) (model, error) { +func newSingleModel(db *DB, dest interface{}) (Model, error) { return _newModel(db, dest, false) } -func _newModel(db *DB, dest interface{}, scan bool) (model, error) { +func _newModel(db *DB, dest interface{}, scan bool) (Model, error) { switch dest := dest.(type) { case nil: return nil, errNilModel @@ -150,7 +146,7 @@ func newTableModelIndex( root reflect.Value, index []int, rel *schema.Relation, -) (tableModel, error) { +) (TableModel, error) { typ := typeByIndex(table.Type, index) if typ.Kind() == reflect.Struct { @@ -195,7 +191,7 @@ func validMap(typ reflect.Type) error { //------------------------------------------------------------------------------ -func isSingleRowModel(m model) bool { +func isSingleRowModel(m Model) bool { switch m.(type) { case *mapModel, *structTableModel, diff --git a/model_map.go b/model_map.go index 81c1a4a3b..7262ddbc1 100644 --- a/model_map.go +++ b/model_map.go @@ -21,7 +21,7 @@ type mapModel struct { scanIndex int } -var _ model = (*mapModel)(nil) +var _ Model = (*mapModel)(nil) func newMapModel(db *DB, dest *map[string]interface{}) *mapModel { m := &mapModel{ diff --git a/model_map_slice.go b/model_map_slice.go index 5c6f48e44..1e578db50 100644 --- a/model_map_slice.go +++ b/model_map_slice.go @@ -17,7 +17,7 @@ type mapSliceModel struct { keys []string } -var _ model = (*mapSliceModel)(nil) +var _ Model = (*mapSliceModel)(nil) func newMapSliceModel(db *DB, dest *[]map[string]interface{}) *mapSliceModel { return &mapSliceModel{ diff --git a/model_scan.go b/model_scan.go index 6dd061fb2..2ac22fbb0 100644 --- a/model_scan.go +++ b/model_scan.go @@ -13,7 +13,7 @@ type scanModel struct { scanIndex int } -var _ model = (*scanModel)(nil) +var _ Model = (*scanModel)(nil) func newScanModel(db *DB, dest []interface{}) *scanModel { return &scanModel{ diff --git a/model_slice.go b/model_slice.go index afe804382..bc29db41f 100644 --- a/model_slice.go +++ b/model_slice.go @@ -21,7 +21,7 @@ type sliceModel struct { info []sliceInfo } -var _ model = (*sliceModel)(nil) +var _ Model = (*sliceModel)(nil) func newSliceModel(db *DB, dest []interface{}, values []reflect.Value) *sliceModel { return &sliceModel{ diff --git a/model_table_has_many.go b/model_table_has_many.go index 6b29fa5da..4db3ec121 100644 --- a/model_table_has_many.go +++ b/model_table_has_many.go @@ -19,7 +19,7 @@ type hasManyModel struct { structKey []interface{} } -var _ tableModel = (*hasManyModel)(nil) +var _ TableModel = (*hasManyModel)(nil) func newHasManyModel(j *relationJoin) *hasManyModel { baseTable := j.BaseModel.Table() @@ -129,11 +129,11 @@ func (m *hasManyModel) parkStruct() error { return nil } -func baseValues(model tableModel, fields []*schema.Field) map[internal.MapKey][]reflect.Value { +func baseValues(model TableModel, fields []*schema.Field) map[internal.MapKey][]reflect.Value { fieldIndex := model.Relation().Field.Index m := make(map[internal.MapKey][]reflect.Value) key := make([]interface{}, 0, len(fields)) - walk(model.Root(), model.ParentIndex(), func(v reflect.Value) { + walk(model.rootValue(), model.parentIndex(), func(v reflect.Value) { key = modelKey(key[:0], v, fields) mapKey := internal.NewMapKey(key) m[mapKey] = append(m[mapKey], v.FieldByIndex(fieldIndex)) diff --git a/model_table_m2m.go b/model_table_m2m.go index d82bc7b8a..88d8a1268 100644 --- a/model_table_m2m.go +++ b/model_table_m2m.go @@ -19,7 +19,7 @@ type m2mModel struct { structKey []interface{} } -var _ tableModel = (*m2mModel)(nil) +var _ TableModel = (*m2mModel)(nil) func newM2MModel(j *relationJoin) *m2mModel { baseTable := j.BaseModel.Table() diff --git a/model_table_slice.go b/model_table_slice.go index 2fccaa5e7..b312b663d 100644 --- a/model_table_slice.go +++ b/model_table_slice.go @@ -18,7 +18,7 @@ type sliceTableModel struct { nextElem func() reflect.Value } -var _ tableModel = (*sliceTableModel)(nil) +var _ TableModel = (*sliceTableModel)(nil) func newSliceTableModel( db *DB, dest interface{}, slice reflect.Value, elemType reflect.Type, @@ -46,19 +46,15 @@ func (m *sliceTableModel) init(sliceType reflect.Type) { } } -func (m *sliceTableModel) Join(name string) *relationJoin { - return m.join(m.slice, name) -} - -func (m *sliceTableModel) Bind(bind reflect.Value) { - m.slice = bind.Field(m.index[len(m.index)-1]) +func (m *sliceTableModel) join(name string) *relationJoin { + return m._join(m.slice, name) } func (m *sliceTableModel) SetCap(cap int) { if cap > 100 { cap = 100 } - if m.slice.Cap() < cap { + if m.slice.Cap() == 0 { m.slice.Set(reflect.MakeSlice(m.slice.Type(), 0, cap)) } } diff --git a/model_table_struct.go b/model_table_struct.go index 409d54326..fba17f42a 100644 --- a/model_table_struct.go +++ b/model_table_struct.go @@ -30,7 +30,7 @@ type structTableModel struct { scanIndex int } -var _ tableModel = (*structTableModel)(nil) +var _ TableModel = (*structTableModel)(nil) func newStructTableModel(db *DB, dest interface{}, table *schema.Table) *structTableModel { return &structTableModel{ @@ -62,23 +62,6 @@ func (m *structTableModel) Relation() *schema.Relation { return m.rel } -func (m *structTableModel) Root() reflect.Value { - return m.root -} - -func (m *structTableModel) Index() []int { - return m.index -} - -func (m *structTableModel) ParentIndex() []int { - return m.index[:len(m.index)-len(m.rel.Field.Index)] -} - -func (m *structTableModel) Mount(host reflect.Value) { - m.strct = host.FieldByIndex(m.rel.Field.Index) - m.structInited = false -} - func (m *structTableModel) initStruct() error { if m.structInited { return m.structInitErr @@ -112,7 +95,7 @@ func (m *structTableModel) mountJoins() { j := &m.joins[i] switch j.Relation.Type { case schema.HasOneRelation, schema.BelongsToRelation: - j.JoinModel.Mount(m.strct) + j.JoinModel.mount(m.strct) } } } @@ -151,7 +134,7 @@ func (m *structTableModel) AfterScan(ctx context.Context) error { return firstErr } -func (m *structTableModel) GetJoin(name string) *relationJoin { +func (m *structTableModel) getJoin(name string) *relationJoin { for i := range m.joins { j := &m.joins[i] if j.Relation.Field.Name == name || j.Relation.Field.GoName == name { @@ -161,20 +144,20 @@ func (m *structTableModel) GetJoin(name string) *relationJoin { return nil } -func (m *structTableModel) GetJoins() []relationJoin { +func (m *structTableModel) getJoins() []relationJoin { return m.joins } -func (m *structTableModel) AddJoin(j relationJoin) *relationJoin { +func (m *structTableModel) addJoin(j relationJoin) *relationJoin { m.joins = append(m.joins, j) return &m.joins[len(m.joins)-1] } -func (m *structTableModel) Join(name string) *relationJoin { - return m.join(m.strct, name) +func (m *structTableModel) join(name string) *relationJoin { + return m._join(m.strct, name) } -func (m *structTableModel) join(bind reflect.Value, name string) *relationJoin { +func (m *structTableModel) _join(bind reflect.Value, name string) *relationJoin { path := strings.Split(name, ".") index := make([]int, 0, len(path)) @@ -193,7 +176,7 @@ func (m *structTableModel) join(bind reflect.Value, name string) *relationJoin { currJoin.Relation = relation index = append(index, relation.Field.Index...) - if j := currJoin.JoinModel.GetJoin(name); j != nil { + if j := currJoin.JoinModel.getJoin(name); j != nil { currJoin.BaseModel = j.BaseModel currJoin.JoinModel = j.JoinModel @@ -208,13 +191,26 @@ func (m *structTableModel) join(bind reflect.Value, name string) *relationJoin { currJoin.BaseModel = currJoin.JoinModel currJoin.JoinModel = model - lastJoin = currJoin.BaseModel.AddJoin(currJoin) + lastJoin = currJoin.BaseModel.addJoin(currJoin) } } return lastJoin } +func (m *structTableModel) rootValue() reflect.Value { + return m.root +} + +func (m *structTableModel) parentIndex() []int { + return m.index[:len(m.index)-len(m.rel.Field.Index)] +} + +func (m *structTableModel) mount(host reflect.Value) { + m.strct = host.FieldByIndex(m.rel.Field.Index) + m.structInited = false +} + func (m *structTableModel) updateSoftDeleteField(tm time.Time) error { if !m.strct.IsValid() { return nil @@ -309,7 +305,7 @@ func (m *structTableModel) scanColumn(column string, src interface{}) (bool, err } if joinName, column := splitColumn(column); joinName != "" { - if join := m.GetJoin(joinName); join != nil { + if join := m.getJoin(joinName); join != nil { return true, join.JoinModel.ScanColumn(column, src) } diff --git a/query_base.go b/query_base.go index 4e1151dbe..bb90de6d1 100644 --- a/query_base.go +++ b/query_base.go @@ -68,10 +68,10 @@ type baseQuery struct { db *DB conn IConn - model model + model Model err error - tableModel tableModel + tableModel TableModel table *schema.Table with []withQuery @@ -86,10 +86,39 @@ func (q *baseQuery) DB() *DB { return q.db } +type query interface { + GetModel() Model + GetTableName() string +} + +var _ query = (*baseQuery)(nil) + func (q *baseQuery) GetModel() Model { return q.model } +func (q *baseQuery) GetTableName() string { + if q.table != nil { + return q.table.Name + } + + for _, wq := range q.with { + if v, ok := wq.query.(query); ok { + if model := v.GetModel(); model != nil { + return v.GetTableName() + } + } + } + + if q.modelTable.Query != "" { + return q.modelTable.Query + } + if len(q.tables) > 0 { + return q.tables[0].Query + } + return "" +} + func (q *baseQuery) setConn(db IConn) { // Unwrap Bun wrappers to not call query hooks twice. switch db := db.(type) { @@ -113,7 +142,7 @@ func (q *baseQuery) setTableModel(modeli interface{}) { } q.model = model - if tm, ok := model.(tableModel); ok { + if tm, ok := model.(TableModel); ok { q.tableModel = tm q.table = tm.Table() } @@ -125,7 +154,7 @@ func (q *baseQuery) setErr(err error) { } } -func (q *baseQuery) getModel(dest []interface{}) (model, error) { +func (q *baseQuery) getModel(dest []interface{}) (Model, error) { if len(dest) == 0 { if q.model != nil { return q.model, nil @@ -429,7 +458,7 @@ func (q *baseQuery) scan( ctx context.Context, queryApp schema.Query, query string, - model model, + model Model, hasDest bool, ) (sql.Result, error) { ctx, event := q.db.beforeQuery(ctx, queryApp, query, nil) diff --git a/query_select.go b/query_select.go index 03f4bc04b..4c29b1863 100644 --- a/query_select.go +++ b/query_select.go @@ -297,7 +297,7 @@ func (q *SelectQuery) Relation(name string, apply ...func(*SelectQuery) *SelectQ return q } - join := q.tableModel.Join(name) + join := q.tableModel.join(name) if join == nil { q.setErr(fmt.Errorf("%s does not have relation=%q", q.table, name)) return q @@ -314,7 +314,7 @@ func (q *SelectQuery) forEachHasOneJoin(fn func(*relationJoin) error) error { if q.tableModel == nil { return nil } - return q._forEachHasOneJoin(fn, q.tableModel.GetJoins()) + return q._forEachHasOneJoin(fn, q.tableModel.getJoins()) } func (q *SelectQuery) _forEachHasOneJoin(fn func(*relationJoin) error, joins []relationJoin) error { @@ -325,7 +325,7 @@ func (q *SelectQuery) _forEachHasOneJoin(fn func(*relationJoin) error, joins []r if err := fn(j); err != nil { return err } - if err := q._forEachHasOneJoin(fn, j.JoinModel.GetJoins()); err != nil { + if err := q._forEachHasOneJoin(fn, j.JoinModel.getJoins()); err != nil { return err } } @@ -341,7 +341,7 @@ func (q *SelectQuery) selectJoins(ctx context.Context, joins []relationJoin) err switch j.Relation.Type { case schema.HasOneRelation, schema.BelongsToRelation: - err = q.selectJoins(ctx, j.JoinModel.GetJoins()) + err = q.selectJoins(ctx, j.JoinModel.getJoins()) case schema.HasManyRelation: err = j.selectMany(ctx, q.db.NewSelect()) case schema.ManyToManyRelation: @@ -701,8 +701,8 @@ func (q *SelectQuery) Scan(ctx context.Context, dest ...interface{}) error { } if n, _ := res.RowsAffected(); n > 0 { - if tableModel, ok := model.(tableModel); ok { - if err := q.selectJoins(ctx, tableModel.GetJoins()); err != nil { + if tableModel, ok := model.(TableModel); ok { + if err := q.selectJoins(ctx, tableModel.getJoins()); err != nil { return err } }