diff --git a/internal/badgerd/replication/gossip/pb/gossip.pb.go b/internal/badgerd/replication/gossip/pb/gossip.pb.go index 647f715..ec6d6d8 100644 --- a/internal/badgerd/replication/gossip/pb/gossip.pb.go +++ b/internal/badgerd/replication/gossip/pb/gossip.pb.go @@ -14,7 +14,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.7 +// protoc-gen-go v1.36.6 // protoc v4.23.0 // source: internal/badgerd/replication/gossip/pb/gossip.proto diff --git a/internal/client/client.go b/internal/client/client.go index 3ba286a..2002f85 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -96,7 +96,7 @@ func (c *client) Get(ctx context.Context, m proto.Message, opts ...protodb.GetOp if o.Filter != nil { f = o.Filter.Expr() } - res, err := c.c.Get(ctx, &pb.GetRequest{Search: a, Filter: f, Paging: o.Paging, FieldMask: o.FieldMask, Reverse: o.Reverse, One: o.One}) + res, err := c.c.Get(ctx, &pb.GetRequest{Search: a, Filter: f, Paging: o.Paging, FieldMask: o.FieldMask, Reverse: o.Reverse, One: o.One, OrderBy: o.OrderBy}) if err != nil { return nil, nil, err } @@ -264,7 +264,7 @@ func (t *txc) Get(ctx context.Context, m proto.Message, opts ...protodb.GetOptio } if err := t.txn.Send(&pb.TxRequest{ Request: &pb.TxRequest_Get{ - Get: &pb.GetRequest{Search: a, Filter: f, Paging: o.Paging, FieldMask: o.FieldMask, One: o.One}, + Get: &pb.GetRequest{Search: a, Filter: f, Paging: o.Paging, FieldMask: o.FieldMask, Reverse: o.Reverse, One: o.One, OrderBy: o.OrderBy}, }, }); err != nil { return nil, nil, err diff --git a/internal/db/index_test.go b/internal/db/index_test.go index f19a14f..35b6568 100644 --- a/internal/db/index_test.go +++ b/internal/db/index_test.go @@ -357,6 +357,110 @@ func TestIndexReversePaging(t *testing.T) { require.Equal(t, "k2", keyFromMessage(t, res[0])) } +func TestOrderByIndexedPagingAndKeyTieBreak(t *testing.T) { + ctx := context.Background() + path := t.TempDir() + + dbif, err := Open(ctx, WithPath(path), WithApplyDefaults(true)) + require.NoError(t, err) + defer dbif.Close() + + db := dbif.(*db) + fd := buildIndexedFileDescriptor(t) + require.NoError(t, db.RegisterProto(ctx, fd)) + + md, err := lookupMessage(db, "tests.index.Indexed") + require.NoError(t, err) + + _, err = db.Set(ctx, newIndexedMessage(md, "k2", "up", nil, "admin", "a")) + require.NoError(t, err) + _, err = db.Set(ctx, newIndexedMessage(md, "k1", "up", nil, "admin", "b")) + require.NoError(t, err) + _, err = db.Set(ctx, newIndexedMessage(md, "k0", "down", nil, "admin", "c")) + require.NoError(t, err) + + paging := &protodb.Paging{Limit: 2} + res, pi, err := db.Get(ctx, dynamicpb.NewMessage(md), protodb.WithOrderByAsc("status"), protodb.WithPaging(paging)) + require.NoError(t, err) + require.Len(t, res, 2) + require.True(t, pi.GetHasNext()) + require.Equal(t, []string{"k0", "k1"}, keysFromMessages(t, res)) + + paging = &protodb.Paging{Limit: 2, Token: pi.GetToken()} + res, pi, err = db.Get(ctx, dynamicpb.NewMessage(md), protodb.WithOrderByAsc("status"), protodb.WithPaging(paging)) + require.NoError(t, err) + require.Len(t, res, 1) + require.False(t, pi.GetHasNext()) + require.Equal(t, []string{"k2"}, keysFromMessages(t, res)) +} + +func TestOrderByKeyDesc(t *testing.T) { + ctx := context.Background() + path := t.TempDir() + + dbif, err := Open(ctx, WithPath(path), WithApplyDefaults(true)) + require.NoError(t, err) + defer dbif.Close() + + db := dbif.(*db) + fd := buildIndexedFileDescriptor(t) + require.NoError(t, db.RegisterProto(ctx, fd)) + + md, err := lookupMessage(db, "tests.index.Indexed") + require.NoError(t, err) + + _, err = db.Set(ctx, newIndexedMessage(md, "k1", "up", nil, "admin", "a")) + require.NoError(t, err) + _, err = db.Set(ctx, newIndexedMessage(md, "k3", "up", nil, "admin", "b")) + require.NoError(t, err) + _, err = db.Set(ctx, newIndexedMessage(md, "k2", "up", nil, "admin", "c")) + require.NoError(t, err) + + res, _, err := db.Get(ctx, dynamicpb.NewMessage(md), protodb.WithOrderByDesc("key")) + require.NoError(t, err) + require.Equal(t, []string{"k3", "k2", "k1"}, keysFromMessages(t, res)) +} + +func TestOrderByRejectsNonIndexedField(t *testing.T) { + ctx := context.Background() + path := t.TempDir() + + dbif, err := Open(ctx, WithPath(path), WithApplyDefaults(true)) + require.NoError(t, err) + defer dbif.Close() + + db := dbif.(*db) + fd := buildIndexedFileDescriptor(t) + require.NoError(t, db.RegisterProto(ctx, fd)) + + md, err := lookupMessage(db, "tests.index.Indexed") + require.NoError(t, err) + + _, _, err = db.Get(ctx, dynamicpb.NewMessage(md), protodb.WithOrderByAsc("description")) + require.Error(t, err) + require.ErrorContains(t, err, "must be indexed") +} + +func TestOrderByRejectsReverseCombination(t *testing.T) { + ctx := context.Background() + path := t.TempDir() + + dbif, err := Open(ctx, WithPath(path), WithApplyDefaults(true)) + require.NoError(t, err) + defer dbif.Close() + + db := dbif.(*db) + fd := buildIndexedFileDescriptor(t) + require.NoError(t, db.RegisterProto(ctx, fd)) + + md, err := lookupMessage(db, "tests.index.Indexed") + require.NoError(t, err) + + _, _, err = db.Get(ctx, dynamicpb.NewMessage(md), protodb.WithOrderByAsc("status"), protodb.WithReverse()) + require.Error(t, err) + require.ErrorContains(t, err, "cannot be combined") +} + func TestIndexAndOr(t *testing.T) { ctx := context.Background() path := t.TempDir() @@ -740,6 +844,14 @@ func keyFromMessage(t *testing.T, msg proto.Message) string { return pm.Get(fd).String() } +func keysFromMessages(t *testing.T, msgs []proto.Message) []string { + out := make([]string, 0, len(msgs)) + for _, msg := range msgs { + out = append(out, keyFromMessage(t, msg)) + } + return out +} + func countIndexFieldKeys(db *db, md protoreflect.MessageDescriptor, fieldPath string) (int, error) { count := 0 path, err := fieldNumberPath(md, fieldPath) diff --git a/internal/db/tx.go b/internal/db/tx.go index f130772..1a3454b 100644 --- a/internal/db/tx.go +++ b/internal/db/tx.go @@ -16,11 +16,13 @@ package db import ( "bytes" + "cmp" "context" "crypto/sha512" "encoding/base64" "errors" "fmt" + "slices" "strings" "sync" "time" @@ -28,15 +30,20 @@ import ( "github.com/dgraph-io/badger/v3" "github.com/dgraph-io/badger/v3/y" + pfreflect "go.linka.cloud/protofilters/reflect" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" + "go.linka.cloud/protofilters/index/bitmap" + "go.linka.cloud/protodb/internal/badgerd" idxstore "go.linka.cloud/protodb/internal/index" "go.linka.cloud/protodb/internal/protodb" "go.linka.cloud/protodb/internal/token" + "go.linka.cloud/protodb/pb" + protopts "go.linka.cloud/protodb/protodb" ) func newTx(ctx context.Context, db *db, opts ...protodb.TxOption) (*tx, error) { @@ -126,6 +133,12 @@ func (tx *tx) get(ctx context.Context, m proto.Message, opts ...protodb.GetOptio return nil, nil, badger.ErrDBClosed } o := makeGetOpts(opts...) + if o.OrderBy != nil { + if o.Reverse { + return nil, nil, errors.New("reverse and order_by cannot be combined") + } + return tx.getOrdered(ctx, m, o) + } if o.One { out = make([]proto.Message, 0, 1) } else if lim := o.Paging.GetLimit(); lim > 0 { @@ -840,6 +853,461 @@ func stringToBytesNoCopy(s string) []byte { return unsafe.Slice(unsafe.StringData(s), len(s)) } +type orderField struct { + path []protoreflect.FieldDescriptor + fieldPath string + direction pb.OrderDirection + key bool + seconds protoreflect.FieldDescriptor + nanos protoreflect.FieldDescriptor +} + +type orderedResult struct { + msg proto.Message + key []byte +} + +func (tx *tx) getOrdered(ctx context.Context, m proto.Message, o protodb.GetOpts) ([]proto.Message, *protodb.PagingInfo, error) { + span := trace.SpanFromContext(ctx) + if span.IsRecording() { + ctx, span = tracer.Start(ctx, "Tx.getOrdered") + span.SetAttributes( + attribute.String("message", string(m.ProtoReflect().Descriptor().FullName())), + attribute.Bool("filtered", o.Filter != nil), + attribute.Bool("continuation", o.Paging.GetToken() != ""), + ) + defer span.End() + } + plan, err := buildOrderPlan(m.ProtoReflect().New(), o.OrderBy) + if err != nil { + return nil, nil, err + } + hasContinuationToken := o.Paging.GetToken() != "" + inToken := &token.Token{} + if err := inToken.Decode(o.Paging.GetToken()); err != nil { + return nil, nil, err + } + fhash, err := hash(o.Filter) + if err != nil { + return nil, nil, fmt.Errorf("hash filter: %w", err) + } + ohash := orderHash(plan) + outToken := &token.Token{ + Ts: tx.txn.ReadTs(), + Type: string(m.ProtoReflect().Descriptor().FullName()), + FiltersHash: fhash, + Reverse: false, + OrderHash: ohash, + } + if err := outToken.ValidateFor(inToken); err != nil { + return nil, nil, err + } + out, info, ok, err := tx.getOrderedIndexed(ctx, m, o, plan, inToken, outToken, hasContinuationToken) + if err != nil { + return nil, nil, err + } + if ok { + return out, info, nil + } + return tx.getOrderedFallbackScanSort(ctx, m, o, plan, inToken, outToken, hasContinuationToken) +} + +func (tx *tx) getOrderedIndexed(ctx context.Context, m proto.Message, o protodb.GetOpts, plan orderField, inToken, outToken *token.Token, hasContinuationToken bool) ([]proto.Message, *protodb.PagingInfo, bool, error) { + span := trace.SpanFromContext(ctx) + if span.IsRecording() { + ctx, span = tracer.Start(ctx, "Tx.getOrderedIndexed") + span.SetAttributes( + attribute.String("message", string(m.ProtoReflect().Descriptor().FullName())), + attribute.String("order_field", plan.fieldPath), + attribute.Bool("order_desc", plan.direction == pb.OrderDirectionDesc), + attribute.Bool("filtered", o.Filter != nil), + attribute.Bool("continuation", hasContinuationToken), + ) + defer span.End() + } + if plan.key { + span.SetAttributes(attribute.Bool("fallback", true), attribute.String("fallback_reason", "key_order")) + return nil, nil, false, nil + } + prefix, _, _, _ := protodb.DataPrefix(m) + if o.Filter != nil { + ok, err := tx.db.idx.IndexableFilter(m, o.Filter) + if err != nil { + return nil, nil, false, err + } + if !ok { + span.SetAttributes(attribute.Bool("fallback", true), attribute.String("fallback_reason", "non_indexable_filter")) + return nil, nil, false, nil + } + } + var allowed bitmap.Bitmap + if o.Filter != nil { + uids, err := tx.db.idx.FindUIDs(ctx, tx, m.ProtoReflect().Descriptor().FullName(), o.Filter, idxstore.FindOpts{}) + if err != nil { + return nil, nil, false, err + } + allowed = bitmap.NewWith(len(uids)) + for _, uid := range uids { + allowed.Set(uid) + } + } + windowLimit := o.Paging.GetLimit() + if o.One && (windowLimit == 0 || windowLimit > 1) { + windowLimit = 1 + } + off := o.Paging.GetOffset() + count := uint64(0) + selectedKeys := make([][]byte, 0) + hasNext := false + continuationFound := !hasContinuationToken + seq, err := tx.db.idx.OrderedUIDGroupsSeq(ctx, tx, m.ProtoReflect().Descriptor().FullName(), plan.path, plan.direction == pb.OrderDirectionDesc) + if err != nil { + return nil, nil, false, err + } + groupsSeen := 0 + for uids, err := range seq { + if err != nil { + return nil, nil, false, err + } + groupsSeen++ + keys := make([][]byte, 0, len(uids)) + for _, uid := range uids { + if err := ctx.Err(); err != nil { + return nil, nil, false, err + } + if allowed != nil && !allowed.Contains(uid) { + continue + } + uidItem, err := tx.txn.Get(ctx, protodb.UIDKey(uid)) + if err != nil { + if errors.Is(err, badger.ErrKeyNotFound) { + continue + } + return nil, nil, false, err + } + var fullKey []byte + if err := uidItem.Value(func(val []byte) error { + fullKey = append(fullKey[:0], val...) + return nil + }); err != nil { + return nil, nil, false, err + } + if !bytes.HasPrefix(fullKey, prefix) { + continue + } + keys = append(keys, fullKey) + } + slices.SortStableFunc(keys, func(a, b []byte) int { return bytes.Compare(a, b) }) + for _, key := range keys { + if hasContinuationToken && !continuationFound { + if bytes.Equal(key, inToken.GetLastPrefix()) { + continuationFound = true + } + continue + } + count++ + if !hasContinuationToken && off > 0 && count <= off { + continue + } + if windowLimit > 0 && uint64(len(selectedKeys)) >= windowLimit { + hasNext = true + break + } + selectedKeys = append(selectedKeys, key) + } + if hasNext || (o.One && len(selectedKeys) > 0) { + break + } + } + if hasContinuationToken && !continuationFound { + return nil, nil, false, fmt.Errorf("%w: continuation key not found", token.ErrInvalid) + } + out := make([]proto.Message, 0, len(selectedKeys)) + for _, key := range selectedKeys { + item, err := tx.txn.Get(ctx, key) + if err != nil { + if errors.Is(err, badger.ErrKeyNotFound) { + continue + } + return nil, nil, false, err + } + v := m.ProtoReflect().New().Interface() + if err := item.Value(func(val []byte) error { return tx.db.unmarshal(val, v) }); err != nil { + return nil, nil, false, err + } + if o.FieldMask != nil { + if err := FilterFieldMask(v, o.FieldMask); err != nil { + return nil, nil, false, err + } + } + tx.txn.AddReadKey(key) + out = append(out, v) + outToken.LastPrefix = append(outToken.LastPrefix[:0], key...) + } + tks, err := outToken.Encode() + if err != nil { + return nil, nil, false, err + } + span.SetAttributes( + attribute.Bool("fallback", false), + attribute.Int("groups_seen", groupsSeen), + attribute.Int("selected_keys", len(selectedKeys)), + attribute.Bool("has_next", hasNext), + ) + return out, &protodb.PagingInfo{HasNext: hasNext, Token: tks}, true, nil +} + +func (tx *tx) getOrderedFallbackScanSort(ctx context.Context, m proto.Message, o protodb.GetOpts, plan orderField, inToken, outToken *token.Token, hasContinuationToken bool) ([]proto.Message, *protodb.PagingInfo, error) { + span := trace.SpanFromContext(ctx) + if span.IsRecording() { + ctx, span = tracer.Start(ctx, "Tx.getOrderedFallbackScanSort") + span.SetAttributes( + attribute.String("message", string(m.ProtoReflect().Descriptor().FullName())), + attribute.String("order_field", plan.fieldPath), + attribute.Bool("order_desc", plan.direction == pb.OrderDirectionDesc), + attribute.Bool("filtered", o.Filter != nil), + attribute.Bool("continuation", hasContinuationToken), + ) + defer span.End() + } + + baseOpts := make([]protodb.GetOption, 0, 1) + if o.Filter != nil { + baseOpts = append(baseOpts, protodb.WithFilter(o.Filter)) + } + all, _, err := tx.get(ctx, m, baseOpts...) + if err != nil { + return nil, nil, err + } + + ordered := make([]orderedResult, 0, len(all)) + for _, item := range all { + key, _, _, err := protodb.DataPrefix(item) + if err != nil { + return nil, nil, err + } + ordered = append(ordered, orderedResult{msg: item, key: append([]byte(nil), key...)}) + } + + slices.SortStableFunc(ordered, func(a, b orderedResult) int { + av, aok := fieldValue(a.msg.ProtoReflect(), plan.path) + bv, bok := fieldValue(b.msg.ProtoReflect(), plan.path) + c := compareMaybeValue(plan, av, aok, bv, bok) + if c == 0 { + return bytes.Compare(a.key, b.key) + } + if plan.direction == pb.OrderDirectionDesc { + return -c + } + return c + }) + + start := 0 + if hasContinuationToken { + if len(inToken.GetLastPrefix()) == 0 { + return nil, nil, fmt.Errorf("%w: missing continuation key", token.ErrInvalid) + } + found := false + for i, item := range ordered { + if bytes.Equal(item.key, inToken.GetLastPrefix()) { + start = i + 1 + found = true + break + } + } + if !found { + return nil, nil, fmt.Errorf("%w: continuation key not found", token.ErrInvalid) + } + } else if off := o.Paging.GetOffset(); off > 0 { + start = int(min(off, uint64(len(ordered)))) + } + + windowLimit := o.Paging.GetLimit() + if o.One && (windowLimit == 0 || windowLimit > 1) { + windowLimit = 1 + } + + end := len(ordered) + if windowLimit > 0 { + end = min(end, start+int(windowLimit)) + } + hasNext := end < len(ordered) + + out := make([]proto.Message, 0, end-start) + for _, item := range ordered[start:end] { + v := item.msg + if o.FieldMask != nil { + if err := FilterFieldMask(v, o.FieldMask); err != nil { + return nil, nil, err + } + } + tx.txn.AddReadKey(item.key) + out = append(out, v) + outToken.LastPrefix = append(outToken.LastPrefix[:0], item.key...) + } + tks, err := outToken.Encode() + if err != nil { + return nil, nil, err + } + span.SetAttributes( + attribute.Int("ordered_candidates", len(ordered)), + attribute.Int("selected", len(out)), + attribute.Bool("has_next", hasNext), + ) + return out, &protodb.PagingInfo{HasNext: hasNext, Token: tks}, nil +} + +func buildOrderPlan(msg protoreflect.Message, orderBy *pb.OrderBy) (orderField, error) { + if orderBy == nil { + return orderField{}, errors.New("order_by cannot be empty") + } + md := msg.Descriptor() + keyField, hasKey := protodb.KeyFieldName(md) + fieldPath := strings.TrimSpace(orderBy.GetField()) + if fieldPath == "" { + return orderField{}, errors.New("order_by field cannot be empty") + } + fds, err := pfreflect.Lookup(msg, fieldPath) + if err != nil { + return orderField{}, fmt.Errorf("invalid order_by field %q: %w", fieldPath, err) + } + if !isOrderableFieldPath(fds) { + return orderField{}, fmt.Errorf("order_by field %q is not sortable", fieldPath) + } + isKey := hasKey && len(fds) == 1 && string(fds[0].Name()) == keyField + if !isKey { + fd := fds[len(fds)-1] + if !proto.HasExtension(fd.Options(), protopts.E_Index) { + return orderField{}, fmt.Errorf("order_by field %q must be indexed", fieldPath) + } + } + direction := orderBy.GetDirection() + if direction == pb.OrderDirectionUnspecified { + direction = pb.OrderDirectionAsc + } + if direction != pb.OrderDirectionAsc && direction != pb.OrderDirectionDesc { + return orderField{}, fmt.Errorf("order_by field %q has invalid direction %v", fieldPath, orderBy.GetDirection()) + } + pl := orderField{path: fds, fieldPath: fieldPath, direction: direction, key: isKey} + if fds[len(fds)-1].Kind() == protoreflect.MessageKind { + mfd := fds[len(fds)-1] + if n := mfd.Message().FullName(); n == "google.protobuf.Timestamp" || n == "google.protobuf.Duration" { + pl.seconds = mfd.Message().Fields().Get(0) + pl.nanos = mfd.Message().Fields().Get(1) + } + } + return pl, nil +} + +func isOrderableFieldPath(fds []protoreflect.FieldDescriptor) bool { + if len(fds) == 0 { + return false + } + for i, fd := range fds { + if fd.IsMap() || fd.IsList() { + return false + } + if i == len(fds)-1 { + break + } + if fd.Kind() != protoreflect.MessageKind || pfreflect.IsWKType(fd.Message().FullName()) { + return false + } + } + return isOrderableLeaf(fds[len(fds)-1]) +} + +func isOrderableLeaf(fd protoreflect.FieldDescriptor) bool { + return idxstore.IsIndexableLeaf(fd) +} + +func fieldValue(msg protoreflect.Message, path []protoreflect.FieldDescriptor) (protoreflect.Value, bool) { + cur := msg + for i, fd := range path { + v := cur.Get(fd) + if i == len(path)-1 { + if fd.Kind() == protoreflect.MessageKind && !v.Message().IsValid() { + return protoreflect.Value{}, false + } + return v, true + } + if fd.Kind() != protoreflect.MessageKind || !v.Message().IsValid() { + return protoreflect.Value{}, false + } + cur = v.Message() + } + return protoreflect.Value{}, false +} + +func compareMaybeValue(of orderField, av protoreflect.Value, aok bool, bv protoreflect.Value, bok bool) int { + if !aok && !bok { + return 0 + } + if !aok { + return -1 + } + if !bok { + return 1 + } + return compareValue(of, av, bv) +} + +func compareValue(of orderField, av, bv protoreflect.Value) int { + fd := of.path[len(of.path)-1] + switch fd.Kind() { + case protoreflect.BoolKind: + ab, bb := av.Bool(), bv.Bool() + switch { + case ab == bb: + return 0 + case !ab && bb: + return -1 + default: + return 1 + } + case protoreflect.StringKind: + return strings.Compare(av.String(), bv.String()) + case protoreflect.BytesKind: + return bytes.Compare(av.Bytes(), bv.Bytes()) + case protoreflect.EnumKind: + return cmp.Compare(int32(av.Enum()), int32(bv.Enum())) + case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind, + protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: + return cmp.Compare(av.Int(), bv.Int()) + case protoreflect.Uint32Kind, protoreflect.Fixed32Kind, + protoreflect.Uint64Kind, protoreflect.Fixed64Kind: + return cmp.Compare(av.Uint(), bv.Uint()) + case protoreflect.FloatKind, protoreflect.DoubleKind: + return cmp.Compare(av.Float(), bv.Float()) + case protoreflect.MessageKind: + am := av.Message() + bm := bv.Message() + asec := am.Get(of.seconds).Int() + bsec := bm.Get(of.seconds).Int() + if c := cmp.Compare(asec, bsec); c != 0 { + return c + } + ananos := am.Get(of.nanos).Int() + bnanos := bm.Get(of.nanos).Int() + return cmp.Compare(ananos, bnanos) + default: + return 0 + } +} + +func orderHash(orderBy orderField) string { + if orderBy.fieldPath == "" { + return "" + } + var b strings.Builder + b.WriteString(orderBy.fieldPath) + b.WriteByte(':') + b.WriteString(fmt.Sprintf("%d", orderBy.direction)) + b.WriteByte(';') + h := sha512.Sum512([]byte(b.String())) + return base64.StdEncoding.EncodeToString(h[:]) +} + func hash(f protodb.Filter) (hash string, err error) { var b []byte if f != nil { diff --git a/internal/db/tx_bench_test.go b/internal/db/tx_bench_test.go index 3d49542..0694f68 100644 --- a/internal/db/tx_bench_test.go +++ b/internal/db/tx_bench_test.go @@ -136,6 +136,55 @@ func BenchmarkTxGet(b *testing.B) { require.NoError(b, err) } }) + + b.Run("ordered_indexed", func(b *testing.B) { + query := dynamicpb.NewMessage(md) + opts := []iprotodb.GetOption{ + iprotodb.WithOrderByAsc("status"), + iprotodb.WithPaging(&pb.Paging{Limit: 50}), + } + b.ReportAllocs() + for i := 0; i < b.N; i++ { + tx, err := newTx(ctx, db, iprotodb.WithReadOnly()) + require.NoError(b, err) + _, _, err = tx.get(ctx, query, opts...) + tx.Close() + require.NoError(b, err) + } + }) + + b.Run("ordered_fallback_scan_sort", func(b *testing.B) { + query := dynamicpb.NewMessage(md) + opts := []iprotodb.GetOption{ + iprotodb.WithFilter(filters.Where("payload").StringHasPrefix("seed-payload")), + iprotodb.WithOrderByAsc("status"), + iprotodb.WithPaging(&pb.Paging{Limit: 50}), + } + b.ReportAllocs() + for i := 0; i < b.N; i++ { + tx, err := newTx(ctx, db, iprotodb.WithReadOnly()) + require.NoError(b, err) + _, _, err = tx.get(ctx, query, opts...) + tx.Close() + require.NoError(b, err) + } + }) + + b.Run("ordered_key_desc_fallback", func(b *testing.B) { + query := dynamicpb.NewMessage(md) + opts := []iprotodb.GetOption{ + iprotodb.WithOrderByDesc("key"), + iprotodb.WithPaging(&pb.Paging{Limit: 50}), + } + b.ReportAllocs() + for i := 0; i < b.N; i++ { + tx, err := newTx(ctx, db, iprotodb.WithReadOnly()) + require.NoError(b, err) + _, _, err = tx.get(ctx, query, opts...) + tx.Close() + require.NoError(b, err) + } + }) } func openBenchDB(b *testing.B) (*db, protoreflect.MessageDescriptor) { diff --git a/internal/index/indexer.go b/internal/index/indexer.go index d189497..59daa8b 100644 --- a/internal/index/indexer.go +++ b/internal/index/indexer.go @@ -20,6 +20,7 @@ import ( "encoding/binary" "errors" "fmt" + "iter" "sort" "strconv" "strings" @@ -315,6 +316,144 @@ func (idx *Indexer) FindUIDs(ctx context.Context, tx Tx, name protoreflect.FullN return out, nil } +func (idx *Indexer) OrderedUIDGroupsSeq(ctx context.Context, tx Tx, name protoreflect.FullName, fds []protoreflect.FieldDescriptor, reverse bool) (iter.Seq2[[]uint64, error], error) { + if len(fds) == 0 { + return nil, errors.New("empty order field descriptors") + } + fieldPath := fieldPathFromNumbers(fds) + deltaValues, err := collectOrderedDeltaValues(ctx, tx.Txn(), name, fieldPath, reverse) + if err != nil { + return nil, err + } + return func(yield func([]uint64, error) bool) { + basePrefix := typePrefix(name) + baseIt := tx.Txn().Iterator(badger.IteratorOptions{Prefix: basePrefix, PrefetchValues: false, Reverse: reverse}) + defer baseIt.Close() + var streamErr error + + nextBaseValue := func(last []byte) ([]byte, bool) { + for ; baseIt.Valid(); baseIt.Next() { + if err := ctx.Err(); err != nil { + streamErr = err + return nil, false + } + k := baseIt.Item().Key() + fp, val, ok := parseFieldValue(basePrefix, k) + if !ok || fp != fieldPath { + continue + } + if len(last) != 0 && bytes.Equal(last, val) { + continue + } + return append([]byte(nil), val...), true + } + return nil, false + } + + baseIt.Rewind() + deltaIdx := 0 + var lastBase []byte + baseVal, hasBase := nextBaseValue(nil) + + for { + if streamErr != nil { + yield(nil, streamErr) + return + } + if err := ctx.Err(); err != nil { + yield(nil, err) + return + } + hasDelta := deltaIdx < len(deltaValues) + if !hasBase && !hasDelta { + return + } + + var val []byte + takeBase := false + switch { + case hasBase && !hasDelta: + takeBase = true + case !hasBase && hasDelta: + takeBase = false + default: + cmp := bytes.Compare(baseVal, deltaValues[deltaIdx]) + if cmp == 0 { + val = baseVal + lastBase = baseVal + baseIt.Next() + baseVal, hasBase = nextBaseValue(lastBase) + deltaIdx++ + } else { + if reverse { + takeBase = cmp > 0 + } else { + takeBase = cmp < 0 + } + } + } + + if val == nil { + if takeBase { + val = baseVal + lastBase = baseVal + baseIt.Next() + baseVal, hasBase = nextBaseValue(lastBase) + } else { + val = deltaValues[deltaIdx] + deltaIdx++ + } + } + + bm, err := readValueBitmap(tx.Txn(), valuePrefix(name, fieldPath, val)) + if err != nil { + yield(nil, err) + return + } + if bm.Cardinality() == 0 { + continue + } + uids := make([]uint64, 0, bm.Cardinality()) + for uid := range bm.Iter() { + uids = append(uids, uid) + } + sort.Slice(uids, func(i, j int) bool { return uids[i] < uids[j] }) + if !yield(uids, nil) { + return + } + } + }, nil +} + +func collectOrderedDeltaValues(ctx context.Context, txn badgerd.Tx, name protoreflect.FullName, fieldPath string, reverse bool) ([][]byte, error) { + deltaPrefix := deltaTypePrefix(name) + it := txn.Iterator(badger.IteratorOptions{Prefix: deltaPrefix, PrefetchValues: false}) + defer it.Close() + seen := make(map[string]struct{}) + for it.Rewind(); it.Valid(); it.Next() { + if err := ctx.Err(); err != nil { + return nil, err + } + k := it.Item().Key() + fp, val, ok := parseFieldValueWithSuffix(deltaPrefix, k, uidShardSize+uidSize) + if !ok || fp != fieldPath { + continue + } + seen[string(val)] = struct{}{} + } + out := make([][]byte, 0, len(seen)) + for v := range seen { + out = append(out, []byte(v)) + } + sort.Slice(out, func(i, j int) bool { + if reverse { + return bytes.Compare(out[i], out[j]) > 0 + } + return bytes.Compare(out[i], out[j]) < 0 + }) + return out, nil +} + func (idx *Indexer) Insert(ctx context.Context, tx Tx, key []byte, m proto.Message) error { ctx, span := idxTracer.Start(ctx, "Indexer.Insert") defer span.End() @@ -873,14 +1012,15 @@ func isIndexableFieldPathDescriptors(fds []protoreflect.FieldDescriptor) bool { } } } - return isIndexableLeaf(last) + return IsIndexableLeaf(last) } func isIndexableField(fd protoreflect.FieldDescriptor) bool { - return isIndexableLeaf(fd) + return IsIndexableLeaf(fd) } -func isIndexableLeaf(fd protoreflect.FieldDescriptor) bool { +// IsIndexableLeaf reports whether a field descriptor supports index ordering/lookup. +func IsIndexableLeaf(fd protoreflect.FieldDescriptor) bool { switch fd.Kind() { case protoreflect.BoolKind, protoreflect.Int32Kind, diff --git a/internal/protodb/options.go b/internal/protodb/options.go index bfbfc21..adc27cb 100644 --- a/internal/protodb/options.go +++ b/internal/protodb/options.go @@ -53,6 +53,20 @@ func WithReverse() GetOption { } } +func WithOrderBy(field string, direction pb.OrderDirection) GetOption { + return func(o *GetOpts) { + o.OrderBy = &pb.OrderBy{Field: field, Direction: direction} + } +} + +func WithOrderByAsc(field string) GetOption { + return WithOrderBy(field, pb.OrderDirectionAsc) +} + +func WithOrderByDesc(field string) GetOption { + return WithOrderBy(field, pb.OrderDirectionDesc) +} + func WithReadFieldMaskPaths(paths ...string) GetOption { return func(o *GetOpts) { o.FieldMask = &fieldmaskpb.FieldMask{Paths: paths} @@ -89,6 +103,7 @@ type GetOpts struct { FieldMask *fieldmaskpb.FieldMask Reverse bool One bool + OrderBy *pb.OrderBy } type SetOption func(o *SetOpts) diff --git a/internal/registry/pb/test.pb.go b/internal/registry/pb/test.pb.go index 7c55a6e..0dbbec4 100644 --- a/internal/registry/pb/test.pb.go +++ b/internal/registry/pb/test.pb.go @@ -6,7 +6,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.7 +// protoc-gen-go v1.36.6 // protoc v4.23.0 // source: internal/registry/pb/test.proto diff --git a/internal/server/server.go b/internal/server/server.go index 1daf074..98b5599 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -403,6 +403,12 @@ func getOpts(r *pb.GetRequest) (opts []protodb.GetOption) { if r.One { opts = append(opts, protodb.WithOne()) } + if r.Reverse { + opts = append(opts, protodb.WithReverse()) + } + if r.GetOrderBy() != nil { + opts = append(opts, protodb.WithOrderBy(r.GetOrderBy().GetField(), r.GetOrderBy().GetDirection())) + } return append(opts, protodb.WithFilter(r.Filter), protodb.WithPaging(r.Paging), protodb.WithReadFieldMask(r.FieldMask)) } diff --git a/internal/token/token.extensions.go b/internal/token/token.extensions.go index 938a1e4..8fea1c7 100644 --- a/internal/token/token.extensions.go +++ b/internal/token/token.extensions.go @@ -63,5 +63,8 @@ func (x *Token) ValidateFor(prev *Token) error { if x.Reverse != prev.Reverse { return fmt.Errorf("%w: reverse mismatch", ErrInvalid) } + if prev.GetOrderHash() != "" && x.GetOrderHash() != prev.GetOrderHash() { + return fmt.Errorf("%w: order mismatch", ErrInvalid) + } return nil } diff --git a/internal/token/token.pb.fields.go b/internal/token/token.pb.fields.go index 47f5063..fdcc2e1 100644 --- a/internal/token/token.pb.fields.go +++ b/internal/token/token.pb.fields.go @@ -22,10 +22,12 @@ var TokenFields = struct { LastPrefix string FiltersHash string Reverse string + OrderHash string }{ Ts: "ts", Type: "type", LastPrefix: "last_prefix", FiltersHash: "filters_hash", Reverse: "reverse", + OrderHash: "order_hash", } diff --git a/internal/token/token.pb.go b/internal/token/token.pb.go index d561711..78d1d6b 100644 --- a/internal/token/token.pb.go +++ b/internal/token/token.pb.go @@ -14,7 +14,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.7 +// protoc-gen-go v1.36.6 // protoc v4.23.0 // source: internal/token/token.proto @@ -42,6 +42,7 @@ type Token struct { LastPrefix []byte `protobuf:"bytes,3,opt,name=last_prefix,json=lastPrefix,proto3" json:"last_prefix,omitempty"` FiltersHash string `protobuf:"bytes,4,opt,name=filters_hash,json=filtersHash,proto3" json:"filters_hash,omitempty"` Reverse bool `protobuf:"varint,5,opt,name=reverse,proto3" json:"reverse,omitempty"` + OrderHash string `protobuf:"bytes,6,opt,name=order_hash,json=orderHash,proto3" json:"order_hash,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -111,18 +112,27 @@ func (x *Token) GetReverse() bool { return false } +func (x *Token) GetOrderHash() string { + if x != nil { + return x.OrderHash + } + return "" +} + var File_internal_token_token_proto protoreflect.FileDescriptor const file_internal_token_token_proto_rawDesc = "" + "\n" + - "\x1ainternal/token/token.proto\x12\"linka.cloud.protodb.internal.token\"\x89\x01\n" + + "\x1ainternal/token/token.proto\x12\"linka.cloud.protodb.internal.token\"\xa8\x01\n" + "\x05Token\x12\x0e\n" + "\x02ts\x18\x01 \x01(\x04R\x02ts\x12\x12\n" + "\x04type\x18\x02 \x01(\tR\x04type\x12\x1f\n" + "\vlast_prefix\x18\x03 \x01(\fR\n" + "lastPrefix\x12!\n" + "\ffilters_hash\x18\x04 \x01(\tR\vfiltersHash\x12\x18\n" + - "\areverse\x18\x05 \x01(\bR\areverseB-Z+go.linka.cloud/protodb/internal/token;tokenb\x06proto3" + "\areverse\x18\x05 \x01(\bR\areverse\x12\x1d\n" + + "\n" + + "order_hash\x18\x06 \x01(\tR\torderHashB-Z+go.linka.cloud/protodb/internal/token;tokenb\x06proto3" var ( file_internal_token_token_proto_rawDescOnce sync.Once diff --git a/internal/token/token.pb.validate.go b/internal/token/token.pb.validate.go index 910e2cf..81641c5 100644 --- a/internal/token/token.pb.validate.go +++ b/internal/token/token.pb.validate.go @@ -66,6 +66,8 @@ func (m *Token) validate(all bool) error { // no validation rules for Reverse + // no validation rules for OrderHash + if len(errors) > 0 { return TokenMultiError(errors) } diff --git a/internal/token/token.proto b/internal/token/token.proto index 69c5908..be27697 100644 --- a/internal/token/token.proto +++ b/internal/token/token.proto @@ -24,4 +24,5 @@ message Token { bytes last_prefix = 3; string filters_hash = 4; bool reverse = 5; + string order_hash = 6; } diff --git a/internal/token/token_vtproto.pb.go b/internal/token/token_vtproto.pb.go index c347a82..321a9d1 100644 --- a/internal/token/token_vtproto.pb.go +++ b/internal/token/token_vtproto.pb.go @@ -28,6 +28,7 @@ func (m *Token) CloneVT() *Token { r.Type = m.Type r.FiltersHash = m.FiltersHash r.Reverse = m.Reverse + r.OrderHash = m.OrderHash if rhs := m.LastPrefix; rhs != nil { tmpBytes := make([]byte, len(rhs)) copy(tmpBytes, rhs) @@ -65,6 +66,9 @@ func (this *Token) EqualVT(that *Token) bool { if this.Reverse != that.Reverse { return false } + if this.OrderHash != that.OrderHash { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -105,6 +109,13 @@ func (m *Token) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.OrderHash) > 0 { + i -= len(m.OrderHash) + copy(dAtA[i:], m.OrderHash) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.OrderHash))) + i-- + dAtA[i] = 0x32 + } if m.Reverse { i-- if m.Reverse { @@ -168,6 +179,10 @@ func (m *Token) SizeVT() (n int) { if m.Reverse { n += 2 } + l = len(m.OrderHash) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } n += len(m.unknownFields) return n } @@ -338,6 +353,38 @@ func (m *Token) UnmarshalVT(dAtA []byte) error { } } m.Reverse = bool(v != 0) + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OrderHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.OrderHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/pb/protodb.pb.defaults.go b/pb/protodb.pb.defaults.go index 657e137..e51358f 100644 --- a/pb/protodb.pb.defaults.go +++ b/pb/protodb.pb.defaults.go @@ -43,6 +43,9 @@ func (x *DeleteResponse) Default() { func (x *GetRequest) Default() { } +func (x *OrderBy) Default() { +} + func (x *GetResponse) Default() { } diff --git a/pb/protodb.pb.fields.go b/pb/protodb.pb.fields.go index 3b3cb65..f2a5b5c 100644 --- a/pb/protodb.pb.fields.go +++ b/pb/protodb.pb.fields.go @@ -72,6 +72,7 @@ var GetRequestFields = struct { FieldMask string Reverse string One string + OrderBy string }{ Search: "search", Filter: "filter", @@ -79,6 +80,15 @@ var GetRequestFields = struct { FieldMask: "field_mask", Reverse: "reverse", One: "one", + OrderBy: "order_by", +} + +var OrderByFields = struct { + Field string + Direction string +}{ + Field: "field", + Direction: "direction", } var GetResponseFields = struct { diff --git a/pb/protodb.pb.go b/pb/protodb.pb.go index 8aacc2b..7312fe6 100644 --- a/pb/protodb.pb.go +++ b/pb/protodb.pb.go @@ -14,7 +14,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.7 +// protoc-gen-go v1.36.6 // protoc v4.23.0 // source: pb/protodb.proto @@ -44,6 +44,55 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type OrderDirection int32 + +const ( + OrderDirectionUnspecified OrderDirection = 0 + OrderDirectionAsc OrderDirection = 1 + OrderDirectionDesc OrderDirection = 2 +) + +// Enum value maps for OrderDirection. +var ( + OrderDirection_name = map[int32]string{ + 0: "ORDER_DIRECTION_UNSPECIFIED", + 1: "ORDER_DIRECTION_ASC", + 2: "ORDER_DIRECTION_DESC", + } + OrderDirection_value = map[string]int32{ + "ORDER_DIRECTION_UNSPECIFIED": 0, + "ORDER_DIRECTION_ASC": 1, + "ORDER_DIRECTION_DESC": 2, + } +) + +func (x OrderDirection) Enum() *OrderDirection { + p := new(OrderDirection) + *p = x + return p +} + +func (x OrderDirection) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (OrderDirection) Descriptor() protoreflect.EnumDescriptor { + return file_pb_protodb_proto_enumTypes[0].Descriptor() +} + +func (OrderDirection) Type() protoreflect.EnumType { + return &file_pb_protodb_proto_enumTypes[0] +} + +func (x OrderDirection) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use OrderDirection.Descriptor instead. +func (OrderDirection) EnumDescriptor() ([]byte, []int) { + return file_pb_protodb_proto_rawDescGZIP(), []int{0} +} + type WatchEventType int32 const ( @@ -80,11 +129,11 @@ func (x WatchEventType) String() string { } func (WatchEventType) Descriptor() protoreflect.EnumDescriptor { - return file_pb_protodb_proto_enumTypes[0].Descriptor() + return file_pb_protodb_proto_enumTypes[1].Descriptor() } func (WatchEventType) Type() protoreflect.EnumType { - return &file_pb_protodb_proto_enumTypes[0] + return &file_pb_protodb_proto_enumTypes[1] } func (x WatchEventType) Number() protoreflect.EnumNumber { @@ -93,7 +142,7 @@ func (x WatchEventType) Number() protoreflect.EnumNumber { // Deprecated: Use WatchEvent_Type.Descriptor instead. func (WatchEventType) EnumDescriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{16, 0} + return file_pb_protodb_proto_rawDescGZIP(), []int{17, 0} } type SetRequest struct { @@ -288,7 +337,8 @@ type GetRequest struct { FieldMask *fieldmaskpb.FieldMask `protobuf:"bytes,4,opt,name=field_mask,json=fieldMask,proto3" json:"field_mask,omitempty"` Reverse bool `protobuf:"varint,5,opt,name=reverse,proto3" json:"reverse,omitempty"` // one is a flag to indicate that request must stop after the first result - One bool `protobuf:"varint,6,opt,name=one,proto3" json:"one,omitempty"` + One bool `protobuf:"varint,6,opt,name=one,proto3" json:"one,omitempty"` + OrderBy *OrderBy `protobuf:"bytes,7,opt,name=order_by,json=orderBy,proto3" json:"order_by,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -365,6 +415,65 @@ func (x *GetRequest) GetOne() bool { return false } +func (x *GetRequest) GetOrderBy() *OrderBy { + if x != nil { + return x.OrderBy + } + return nil +} + +type OrderBy struct { + state protoimpl.MessageState `protogen:"open.v1"` + Field string `protobuf:"bytes,1,opt,name=field,proto3" json:"field,omitempty"` + Direction OrderDirection `protobuf:"varint,2,opt,name=direction,proto3,enum=linka.cloud.protodb.OrderDirection" json:"direction,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OrderBy) Reset() { + *x = OrderBy{} + mi := &file_pb_protodb_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OrderBy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderBy) ProtoMessage() {} + +func (x *OrderBy) ProtoReflect() protoreflect.Message { + mi := &file_pb_protodb_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderBy.ProtoReflect.Descriptor instead. +func (*OrderBy) Descriptor() ([]byte, []int) { + return file_pb_protodb_proto_rawDescGZIP(), []int{5} +} + +func (x *OrderBy) GetField() string { + if x != nil { + return x.Field + } + return "" +} + +func (x *OrderBy) GetDirection() OrderDirection { + if x != nil { + return x.Direction + } + return OrderDirectionUnspecified +} + type GetResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Results []*anypb.Any `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` @@ -375,7 +484,7 @@ type GetResponse struct { func (x *GetResponse) Reset() { *x = GetResponse{} - mi := &file_pb_protodb_proto_msgTypes[5] + mi := &file_pb_protodb_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -387,7 +496,7 @@ func (x *GetResponse) String() string { func (*GetResponse) ProtoMessage() {} func (x *GetResponse) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[5] + mi := &file_pb_protodb_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -400,7 +509,7 @@ func (x *GetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetResponse.ProtoReflect.Descriptor instead. func (*GetResponse) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{5} + return file_pb_protodb_proto_rawDescGZIP(), []int{6} } func (x *GetResponse) GetResults() []*anypb.Any { @@ -432,7 +541,7 @@ type TxRequest struct { func (x *TxRequest) Reset() { *x = TxRequest{} - mi := &file_pb_protodb_proto_msgTypes[6] + mi := &file_pb_protodb_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -444,7 +553,7 @@ func (x *TxRequest) String() string { func (*TxRequest) ProtoMessage() {} func (x *TxRequest) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[6] + mi := &file_pb_protodb_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -457,7 +566,7 @@ func (x *TxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TxRequest.ProtoReflect.Descriptor instead. func (*TxRequest) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{6} + return file_pb_protodb_proto_rawDescGZIP(), []int{7} } func (x *TxRequest) GetRequest() isTxRequest_Request { @@ -546,7 +655,7 @@ type TxResponse struct { func (x *TxResponse) Reset() { *x = TxResponse{} - mi := &file_pb_protodb_proto_msgTypes[7] + mi := &file_pb_protodb_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -558,7 +667,7 @@ func (x *TxResponse) String() string { func (*TxResponse) ProtoMessage() {} func (x *TxResponse) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[7] + mi := &file_pb_protodb_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -571,7 +680,7 @@ func (x *TxResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use TxResponse.ProtoReflect.Descriptor instead. func (*TxResponse) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{7} + return file_pb_protodb_proto_rawDescGZIP(), []int{8} } func (x *TxResponse) GetResponse() isTxResponse_Response { @@ -654,7 +763,7 @@ type NextSeqRequest struct { func (x *NextSeqRequest) Reset() { *x = NextSeqRequest{} - mi := &file_pb_protodb_proto_msgTypes[8] + mi := &file_pb_protodb_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -666,7 +775,7 @@ func (x *NextSeqRequest) String() string { func (*NextSeqRequest) ProtoMessage() {} func (x *NextSeqRequest) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[8] + mi := &file_pb_protodb_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -679,7 +788,7 @@ func (x *NextSeqRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NextSeqRequest.ProtoReflect.Descriptor instead. func (*NextSeqRequest) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{8} + return file_pb_protodb_proto_rawDescGZIP(), []int{9} } func (x *NextSeqRequest) GetKey() string { @@ -698,7 +807,7 @@ type NextSeqResponse struct { func (x *NextSeqResponse) Reset() { *x = NextSeqResponse{} - mi := &file_pb_protodb_proto_msgTypes[9] + mi := &file_pb_protodb_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -710,7 +819,7 @@ func (x *NextSeqResponse) String() string { func (*NextSeqResponse) ProtoMessage() {} func (x *NextSeqResponse) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[9] + mi := &file_pb_protodb_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -723,7 +832,7 @@ func (x *NextSeqResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use NextSeqResponse.ProtoReflect.Descriptor instead. func (*NextSeqResponse) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{9} + return file_pb_protodb_proto_rawDescGZIP(), []int{10} } func (x *NextSeqResponse) GetSeq() uint64 { @@ -742,7 +851,7 @@ type LockRequest struct { func (x *LockRequest) Reset() { *x = LockRequest{} - mi := &file_pb_protodb_proto_msgTypes[10] + mi := &file_pb_protodb_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -754,7 +863,7 @@ func (x *LockRequest) String() string { func (*LockRequest) ProtoMessage() {} func (x *LockRequest) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[10] + mi := &file_pb_protodb_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -767,7 +876,7 @@ func (x *LockRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LockRequest.ProtoReflect.Descriptor instead. func (*LockRequest) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{10} + return file_pb_protodb_proto_rawDescGZIP(), []int{11} } func (x *LockRequest) GetKey() string { @@ -785,7 +894,7 @@ type LockResponse struct { func (x *LockResponse) Reset() { *x = LockResponse{} - mi := &file_pb_protodb_proto_msgTypes[11] + mi := &file_pb_protodb_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -797,7 +906,7 @@ func (x *LockResponse) String() string { func (*LockResponse) ProtoMessage() {} func (x *LockResponse) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[11] + mi := &file_pb_protodb_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -810,7 +919,7 @@ func (x *LockResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LockResponse.ProtoReflect.Descriptor instead. func (*LockResponse) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{11} + return file_pb_protodb_proto_rawDescGZIP(), []int{12} } type CommitResponse struct { @@ -822,7 +931,7 @@ type CommitResponse struct { func (x *CommitResponse) Reset() { *x = CommitResponse{} - mi := &file_pb_protodb_proto_msgTypes[12] + mi := &file_pb_protodb_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -834,7 +943,7 @@ func (x *CommitResponse) String() string { func (*CommitResponse) ProtoMessage() {} func (x *CommitResponse) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[12] + mi := &file_pb_protodb_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -847,7 +956,7 @@ func (x *CommitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CommitResponse.ProtoReflect.Descriptor instead. func (*CommitResponse) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{12} + return file_pb_protodb_proto_rawDescGZIP(), []int{13} } func (x *CommitResponse) GetError() *wrapperspb.StringValue { @@ -869,7 +978,7 @@ type Paging struct { func (x *Paging) Reset() { *x = Paging{} - mi := &file_pb_protodb_proto_msgTypes[13] + mi := &file_pb_protodb_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -881,7 +990,7 @@ func (x *Paging) String() string { func (*Paging) ProtoMessage() {} func (x *Paging) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[13] + mi := &file_pb_protodb_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -894,7 +1003,7 @@ func (x *Paging) ProtoReflect() protoreflect.Message { // Deprecated: Use Paging.ProtoReflect.Descriptor instead. func (*Paging) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{13} + return file_pb_protodb_proto_rawDescGZIP(), []int{14} } func (x *Paging) GetLimit() uint64 { @@ -928,7 +1037,7 @@ type PagingInfo struct { func (x *PagingInfo) Reset() { *x = PagingInfo{} - mi := &file_pb_protodb_proto_msgTypes[14] + mi := &file_pb_protodb_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -940,7 +1049,7 @@ func (x *PagingInfo) String() string { func (*PagingInfo) ProtoMessage() {} func (x *PagingInfo) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[14] + mi := &file_pb_protodb_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -953,7 +1062,7 @@ func (x *PagingInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use PagingInfo.ProtoReflect.Descriptor instead. func (*PagingInfo) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{14} + return file_pb_protodb_proto_rawDescGZIP(), []int{15} } func (x *PagingInfo) GetHasNext() bool { @@ -980,7 +1089,7 @@ type WatchRequest struct { func (x *WatchRequest) Reset() { *x = WatchRequest{} - mi := &file_pb_protodb_proto_msgTypes[15] + mi := &file_pb_protodb_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -992,7 +1101,7 @@ func (x *WatchRequest) String() string { func (*WatchRequest) ProtoMessage() {} func (x *WatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[15] + mi := &file_pb_protodb_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1005,7 +1114,7 @@ func (x *WatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchRequest.ProtoReflect.Descriptor instead. func (*WatchRequest) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{15} + return file_pb_protodb_proto_rawDescGZIP(), []int{16} } func (x *WatchRequest) GetSearch() *anypb.Any { @@ -1033,7 +1142,7 @@ type WatchEvent struct { func (x *WatchEvent) Reset() { *x = WatchEvent{} - mi := &file_pb_protodb_proto_msgTypes[16] + mi := &file_pb_protodb_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1045,7 +1154,7 @@ func (x *WatchEvent) String() string { func (*WatchEvent) ProtoMessage() {} func (x *WatchEvent) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[16] + mi := &file_pb_protodb_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1058,7 +1167,7 @@ func (x *WatchEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchEvent.ProtoReflect.Descriptor instead. func (*WatchEvent) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{16} + return file_pb_protodb_proto_rawDescGZIP(), []int{17} } func (x *WatchEvent) GetType() WatchEventType { @@ -1091,7 +1200,7 @@ type RegisterRequest struct { func (x *RegisterRequest) Reset() { *x = RegisterRequest{} - mi := &file_pb_protodb_proto_msgTypes[17] + mi := &file_pb_protodb_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1103,7 +1212,7 @@ func (x *RegisterRequest) String() string { func (*RegisterRequest) ProtoMessage() {} func (x *RegisterRequest) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[17] + mi := &file_pb_protodb_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1116,7 +1225,7 @@ func (x *RegisterRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterRequest.ProtoReflect.Descriptor instead. func (*RegisterRequest) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{17} + return file_pb_protodb_proto_rawDescGZIP(), []int{18} } func (x *RegisterRequest) GetFile() *descriptorpb.FileDescriptorProto { @@ -1134,7 +1243,7 @@ type RegisterResponse struct { func (x *RegisterResponse) Reset() { *x = RegisterResponse{} - mi := &file_pb_protodb_proto_msgTypes[18] + mi := &file_pb_protodb_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1146,7 +1255,7 @@ func (x *RegisterResponse) String() string { func (*RegisterResponse) ProtoMessage() {} func (x *RegisterResponse) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[18] + mi := &file_pb_protodb_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1159,7 +1268,7 @@ func (x *RegisterResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterResponse.ProtoReflect.Descriptor instead. func (*RegisterResponse) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{18} + return file_pb_protodb_proto_rawDescGZIP(), []int{19} } type DescriptorsRequest struct { @@ -1170,7 +1279,7 @@ type DescriptorsRequest struct { func (x *DescriptorsRequest) Reset() { *x = DescriptorsRequest{} - mi := &file_pb_protodb_proto_msgTypes[19] + mi := &file_pb_protodb_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1182,7 +1291,7 @@ func (x *DescriptorsRequest) String() string { func (*DescriptorsRequest) ProtoMessage() {} func (x *DescriptorsRequest) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[19] + mi := &file_pb_protodb_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1195,7 +1304,7 @@ func (x *DescriptorsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DescriptorsRequest.ProtoReflect.Descriptor instead. func (*DescriptorsRequest) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{19} + return file_pb_protodb_proto_rawDescGZIP(), []int{20} } type DescriptorsResponse struct { @@ -1207,7 +1316,7 @@ type DescriptorsResponse struct { func (x *DescriptorsResponse) Reset() { *x = DescriptorsResponse{} - mi := &file_pb_protodb_proto_msgTypes[20] + mi := &file_pb_protodb_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1219,7 +1328,7 @@ func (x *DescriptorsResponse) String() string { func (*DescriptorsResponse) ProtoMessage() {} func (x *DescriptorsResponse) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[20] + mi := &file_pb_protodb_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1232,7 +1341,7 @@ func (x *DescriptorsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DescriptorsResponse.ProtoReflect.Descriptor instead. func (*DescriptorsResponse) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{20} + return file_pb_protodb_proto_rawDescGZIP(), []int{21} } func (x *DescriptorsResponse) GetResults() []*descriptorpb.DescriptorProto { @@ -1250,7 +1359,7 @@ type FileDescriptorsRequest struct { func (x *FileDescriptorsRequest) Reset() { *x = FileDescriptorsRequest{} - mi := &file_pb_protodb_proto_msgTypes[21] + mi := &file_pb_protodb_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1262,7 +1371,7 @@ func (x *FileDescriptorsRequest) String() string { func (*FileDescriptorsRequest) ProtoMessage() {} func (x *FileDescriptorsRequest) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[21] + mi := &file_pb_protodb_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1275,7 +1384,7 @@ func (x *FileDescriptorsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FileDescriptorsRequest.ProtoReflect.Descriptor instead. func (*FileDescriptorsRequest) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{21} + return file_pb_protodb_proto_rawDescGZIP(), []int{22} } type FileDescriptorsResponse struct { @@ -1287,7 +1396,7 @@ type FileDescriptorsResponse struct { func (x *FileDescriptorsResponse) Reset() { *x = FileDescriptorsResponse{} - mi := &file_pb_protodb_proto_msgTypes[22] + mi := &file_pb_protodb_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1299,7 +1408,7 @@ func (x *FileDescriptorsResponse) String() string { func (*FileDescriptorsResponse) ProtoMessage() {} func (x *FileDescriptorsResponse) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[22] + mi := &file_pb_protodb_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1312,7 +1421,7 @@ func (x *FileDescriptorsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FileDescriptorsResponse.ProtoReflect.Descriptor instead. func (*FileDescriptorsResponse) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{22} + return file_pb_protodb_proto_rawDescGZIP(), []int{23} } func (x *FileDescriptorsResponse) GetResults() []*descriptorpb.FileDescriptorProto { @@ -1331,7 +1440,7 @@ type MessageDiff struct { func (x *MessageDiff) Reset() { *x = MessageDiff{} - mi := &file_pb_protodb_proto_msgTypes[23] + mi := &file_pb_protodb_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1343,7 +1452,7 @@ func (x *MessageDiff) String() string { func (*MessageDiff) ProtoMessage() {} func (x *MessageDiff) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[23] + mi := &file_pb_protodb_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1356,7 +1465,7 @@ func (x *MessageDiff) ProtoReflect() protoreflect.Message { // Deprecated: Use MessageDiff.ProtoReflect.Descriptor instead. func (*MessageDiff) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{23} + return file_pb_protodb_proto_rawDescGZIP(), []int{24} } func (x *MessageDiff) GetFields() map[string]*FieldDiff { @@ -1376,7 +1485,7 @@ type FieldDiff struct { func (x *FieldDiff) Reset() { *x = FieldDiff{} - mi := &file_pb_protodb_proto_msgTypes[24] + mi := &file_pb_protodb_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1388,7 +1497,7 @@ func (x *FieldDiff) String() string { func (*FieldDiff) ProtoMessage() {} func (x *FieldDiff) ProtoReflect() protoreflect.Message { - mi := &file_pb_protodb_proto_msgTypes[24] + mi := &file_pb_protodb_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1401,7 +1510,7 @@ func (x *FieldDiff) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldDiff.ProtoReflect.Descriptor instead. func (*FieldDiff) Descriptor() ([]byte, []int) { - return file_pb_protodb_proto_rawDescGZIP(), []int{24} + return file_pb_protodb_proto_rawDescGZIP(), []int{25} } func (x *FieldDiff) GetFrom() *structpb.Value { @@ -1433,7 +1542,7 @@ const file_pb_protodb_proto_rawDesc = "" + "\x06result\x18\x01 \x01(\v2\x14.google.protobuf.AnyR\x06result\"I\n" + "\rDeleteRequest\x128\n" + "\apayload\x18\x01 \x01(\v2\x14.google.protobuf.AnyB\b\xfaB\x05\xa2\x01\x02\b\x01R\apayload\"\x10\n" + - "\x0eDeleteResponse\"\x9e\x02\n" + + "\x0eDeleteResponse\"\xd7\x02\n" + "\n" + "GetRequest\x126\n" + "\x06search\x18\x01 \x01(\v2\x14.google.protobuf.AnyB\b\xfaB\x05\xa2\x01\x02\b\x01R\x06search\x12<\n" + @@ -1442,7 +1551,11 @@ const file_pb_protodb_proto_rawDesc = "" + "\n" + "field_mask\x18\x04 \x01(\v2\x1a.google.protobuf.FieldMaskR\tfieldMask\x12\x18\n" + "\areverse\x18\x05 \x01(\bR\areverse\x12\x10\n" + - "\x03one\x18\x06 \x01(\bR\x03one\"v\n" + + "\x03one\x18\x06 \x01(\bR\x03one\x127\n" + + "\border_by\x18\a \x01(\v2\x1c.linka.cloud.protodb.OrderByR\aorderBy\"b\n" + + "\aOrderBy\x12\x14\n" + + "\x05field\x18\x01 \x01(\tR\x05field\x12A\n" + + "\tdirection\x18\x02 \x01(\x0e2#.linka.cloud.protodb.OrderDirectionR\tdirection\"v\n" + "\vGetResponse\x12.\n" + "\aresults\x18\x01 \x03(\v2\x14.google.protobuf.AnyR\aresults\x127\n" + "\x06paging\x18\x02 \x01(\v2\x1f.linka.cloud.protodb.PagingInfoR\x06paging\"\xf9\x01\n" + @@ -1507,7 +1620,11 @@ const file_pb_protodb_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\v2\x1e.linka.cloud.protodb.FieldDiffR\x05value:\x028\x01\"_\n" + "\tFieldDiff\x12*\n" + "\x04from\x18\x01 \x01(\v2\x16.google.protobuf.ValueR\x04from\x12&\n" + - "\x02to\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x02to2\xda\x06\n" + + "\x02to\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x02to*d\n" + + "\x0eOrderDirection\x12\x1f\n" + + "\x1bORDER_DIRECTION_UNSPECIFIED\x10\x00\x12\x17\n" + + "\x13ORDER_DIRECTION_ASC\x10\x01\x12\x18\n" + + "\x14ORDER_DIRECTION_DESC\x10\x022\xda\x06\n" + "\aProtoDB\x12H\n" + "\x03Get\x12\x1f.linka.cloud.protodb.GetRequest\x1a .linka.cloud.protodb.GetResponse\x12H\n" + "\x03Set\x12\x1f.linka.cloud.protodb.SetRequest\x1a .linka.cloud.protodb.SetResponse\x12Q\n" + @@ -1533,104 +1650,108 @@ func file_pb_protodb_proto_rawDescGZIP() []byte { return file_pb_protodb_proto_rawDescData } -var file_pb_protodb_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_pb_protodb_proto_msgTypes = make([]protoimpl.MessageInfo, 26) +var file_pb_protodb_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_pb_protodb_proto_msgTypes = make([]protoimpl.MessageInfo, 27) var file_pb_protodb_proto_goTypes = []any{ - (WatchEventType)(0), // 0: linka.cloud.protodb.WatchEvent.Type - (*SetRequest)(nil), // 1: linka.cloud.protodb.SetRequest - (*SetResponse)(nil), // 2: linka.cloud.protodb.SetResponse - (*DeleteRequest)(nil), // 3: linka.cloud.protodb.DeleteRequest - (*DeleteResponse)(nil), // 4: linka.cloud.protodb.DeleteResponse - (*GetRequest)(nil), // 5: linka.cloud.protodb.GetRequest - (*GetResponse)(nil), // 6: linka.cloud.protodb.GetResponse - (*TxRequest)(nil), // 7: linka.cloud.protodb.TxRequest - (*TxResponse)(nil), // 8: linka.cloud.protodb.TxResponse - (*NextSeqRequest)(nil), // 9: linka.cloud.protodb.NextSeqRequest - (*NextSeqResponse)(nil), // 10: linka.cloud.protodb.NextSeqResponse - (*LockRequest)(nil), // 11: linka.cloud.protodb.LockRequest - (*LockResponse)(nil), // 12: linka.cloud.protodb.LockResponse - (*CommitResponse)(nil), // 13: linka.cloud.protodb.CommitResponse - (*Paging)(nil), // 14: linka.cloud.protodb.Paging - (*PagingInfo)(nil), // 15: linka.cloud.protodb.PagingInfo - (*WatchRequest)(nil), // 16: linka.cloud.protodb.WatchRequest - (*WatchEvent)(nil), // 17: linka.cloud.protodb.WatchEvent - (*RegisterRequest)(nil), // 18: linka.cloud.protodb.RegisterRequest - (*RegisterResponse)(nil), // 19: linka.cloud.protodb.RegisterResponse - (*DescriptorsRequest)(nil), // 20: linka.cloud.protodb.DescriptorsRequest - (*DescriptorsResponse)(nil), // 21: linka.cloud.protodb.DescriptorsResponse - (*FileDescriptorsRequest)(nil), // 22: linka.cloud.protodb.FileDescriptorsRequest - (*FileDescriptorsResponse)(nil), // 23: linka.cloud.protodb.FileDescriptorsResponse - (*MessageDiff)(nil), // 24: linka.cloud.protodb.MessageDiff - (*FieldDiff)(nil), // 25: linka.cloud.protodb.FieldDiff - nil, // 26: linka.cloud.protodb.MessageDiff.FieldsEntry - (*anypb.Any)(nil), // 27: google.protobuf.Any - (*durationpb.Duration)(nil), // 28: google.protobuf.Duration - (*fieldmaskpb.FieldMask)(nil), // 29: google.protobuf.FieldMask - (*filters.Expression)(nil), // 30: linka.cloud.protofilters.Expression - (*wrapperspb.BoolValue)(nil), // 31: google.protobuf.BoolValue - (*wrapperspb.StringValue)(nil), // 32: google.protobuf.StringValue - (*descriptorpb.FileDescriptorProto)(nil), // 33: google.protobuf.FileDescriptorProto - (*descriptorpb.DescriptorProto)(nil), // 34: google.protobuf.DescriptorProto - (*structpb.Value)(nil), // 35: google.protobuf.Value + (OrderDirection)(0), // 0: linka.cloud.protodb.OrderDirection + (WatchEventType)(0), // 1: linka.cloud.protodb.WatchEvent.Type + (*SetRequest)(nil), // 2: linka.cloud.protodb.SetRequest + (*SetResponse)(nil), // 3: linka.cloud.protodb.SetResponse + (*DeleteRequest)(nil), // 4: linka.cloud.protodb.DeleteRequest + (*DeleteResponse)(nil), // 5: linka.cloud.protodb.DeleteResponse + (*GetRequest)(nil), // 6: linka.cloud.protodb.GetRequest + (*OrderBy)(nil), // 7: linka.cloud.protodb.OrderBy + (*GetResponse)(nil), // 8: linka.cloud.protodb.GetResponse + (*TxRequest)(nil), // 9: linka.cloud.protodb.TxRequest + (*TxResponse)(nil), // 10: linka.cloud.protodb.TxResponse + (*NextSeqRequest)(nil), // 11: linka.cloud.protodb.NextSeqRequest + (*NextSeqResponse)(nil), // 12: linka.cloud.protodb.NextSeqResponse + (*LockRequest)(nil), // 13: linka.cloud.protodb.LockRequest + (*LockResponse)(nil), // 14: linka.cloud.protodb.LockResponse + (*CommitResponse)(nil), // 15: linka.cloud.protodb.CommitResponse + (*Paging)(nil), // 16: linka.cloud.protodb.Paging + (*PagingInfo)(nil), // 17: linka.cloud.protodb.PagingInfo + (*WatchRequest)(nil), // 18: linka.cloud.protodb.WatchRequest + (*WatchEvent)(nil), // 19: linka.cloud.protodb.WatchEvent + (*RegisterRequest)(nil), // 20: linka.cloud.protodb.RegisterRequest + (*RegisterResponse)(nil), // 21: linka.cloud.protodb.RegisterResponse + (*DescriptorsRequest)(nil), // 22: linka.cloud.protodb.DescriptorsRequest + (*DescriptorsResponse)(nil), // 23: linka.cloud.protodb.DescriptorsResponse + (*FileDescriptorsRequest)(nil), // 24: linka.cloud.protodb.FileDescriptorsRequest + (*FileDescriptorsResponse)(nil), // 25: linka.cloud.protodb.FileDescriptorsResponse + (*MessageDiff)(nil), // 26: linka.cloud.protodb.MessageDiff + (*FieldDiff)(nil), // 27: linka.cloud.protodb.FieldDiff + nil, // 28: linka.cloud.protodb.MessageDiff.FieldsEntry + (*anypb.Any)(nil), // 29: google.protobuf.Any + (*durationpb.Duration)(nil), // 30: google.protobuf.Duration + (*fieldmaskpb.FieldMask)(nil), // 31: google.protobuf.FieldMask + (*filters.Expression)(nil), // 32: linka.cloud.protofilters.Expression + (*wrapperspb.BoolValue)(nil), // 33: google.protobuf.BoolValue + (*wrapperspb.StringValue)(nil), // 34: google.protobuf.StringValue + (*descriptorpb.FileDescriptorProto)(nil), // 35: google.protobuf.FileDescriptorProto + (*descriptorpb.DescriptorProto)(nil), // 36: google.protobuf.DescriptorProto + (*structpb.Value)(nil), // 37: google.protobuf.Value } var file_pb_protodb_proto_depIdxs = []int32{ - 27, // 0: linka.cloud.protodb.SetRequest.payload:type_name -> google.protobuf.Any - 28, // 1: linka.cloud.protodb.SetRequest.ttl:type_name -> google.protobuf.Duration - 29, // 2: linka.cloud.protodb.SetRequest.field_mask:type_name -> google.protobuf.FieldMask - 27, // 3: linka.cloud.protodb.SetResponse.result:type_name -> google.protobuf.Any - 27, // 4: linka.cloud.protodb.DeleteRequest.payload:type_name -> google.protobuf.Any - 27, // 5: linka.cloud.protodb.GetRequest.search:type_name -> google.protobuf.Any - 30, // 6: linka.cloud.protodb.GetRequest.filter:type_name -> linka.cloud.protofilters.Expression - 14, // 7: linka.cloud.protodb.GetRequest.paging:type_name -> linka.cloud.protodb.Paging - 29, // 8: linka.cloud.protodb.GetRequest.field_mask:type_name -> google.protobuf.FieldMask - 27, // 9: linka.cloud.protodb.GetResponse.results:type_name -> google.protobuf.Any - 15, // 10: linka.cloud.protodb.GetResponse.paging:type_name -> linka.cloud.protodb.PagingInfo - 5, // 11: linka.cloud.protodb.TxRequest.get:type_name -> linka.cloud.protodb.GetRequest - 1, // 12: linka.cloud.protodb.TxRequest.set:type_name -> linka.cloud.protodb.SetRequest - 3, // 13: linka.cloud.protodb.TxRequest.delete:type_name -> linka.cloud.protodb.DeleteRequest - 31, // 14: linka.cloud.protodb.TxRequest.commit:type_name -> google.protobuf.BoolValue - 6, // 15: linka.cloud.protodb.TxResponse.get:type_name -> linka.cloud.protodb.GetResponse - 2, // 16: linka.cloud.protodb.TxResponse.set:type_name -> linka.cloud.protodb.SetResponse - 4, // 17: linka.cloud.protodb.TxResponse.delete:type_name -> linka.cloud.protodb.DeleteResponse - 13, // 18: linka.cloud.protodb.TxResponse.commit:type_name -> linka.cloud.protodb.CommitResponse - 32, // 19: linka.cloud.protodb.CommitResponse.error:type_name -> google.protobuf.StringValue - 27, // 20: linka.cloud.protodb.WatchRequest.search:type_name -> google.protobuf.Any - 30, // 21: linka.cloud.protodb.WatchRequest.filter:type_name -> linka.cloud.protofilters.Expression - 0, // 22: linka.cloud.protodb.WatchEvent.type:type_name -> linka.cloud.protodb.WatchEvent.Type - 27, // 23: linka.cloud.protodb.WatchEvent.old:type_name -> google.protobuf.Any - 27, // 24: linka.cloud.protodb.WatchEvent.new:type_name -> google.protobuf.Any - 33, // 25: linka.cloud.protodb.RegisterRequest.file:type_name -> google.protobuf.FileDescriptorProto - 34, // 26: linka.cloud.protodb.DescriptorsResponse.results:type_name -> google.protobuf.DescriptorProto - 33, // 27: linka.cloud.protodb.FileDescriptorsResponse.results:type_name -> google.protobuf.FileDescriptorProto - 26, // 28: linka.cloud.protodb.MessageDiff.fields:type_name -> linka.cloud.protodb.MessageDiff.FieldsEntry - 35, // 29: linka.cloud.protodb.FieldDiff.from:type_name -> google.protobuf.Value - 35, // 30: linka.cloud.protodb.FieldDiff.to:type_name -> google.protobuf.Value - 25, // 31: linka.cloud.protodb.MessageDiff.FieldsEntry.value:type_name -> linka.cloud.protodb.FieldDiff - 5, // 32: linka.cloud.protodb.ProtoDB.Get:input_type -> linka.cloud.protodb.GetRequest - 1, // 33: linka.cloud.protodb.ProtoDB.Set:input_type -> linka.cloud.protodb.SetRequest - 3, // 34: linka.cloud.protodb.ProtoDB.Delete:input_type -> linka.cloud.protodb.DeleteRequest - 7, // 35: linka.cloud.protodb.ProtoDB.Tx:input_type -> linka.cloud.protodb.TxRequest - 9, // 36: linka.cloud.protodb.ProtoDB.NextSeq:input_type -> linka.cloud.protodb.NextSeqRequest - 11, // 37: linka.cloud.protodb.ProtoDB.Lock:input_type -> linka.cloud.protodb.LockRequest - 16, // 38: linka.cloud.protodb.ProtoDB.Watch:input_type -> linka.cloud.protodb.WatchRequest - 18, // 39: linka.cloud.protodb.ProtoDB.Register:input_type -> linka.cloud.protodb.RegisterRequest - 20, // 40: linka.cloud.protodb.ProtoDB.Descriptors:input_type -> linka.cloud.protodb.DescriptorsRequest - 22, // 41: linka.cloud.protodb.ProtoDB.FileDescriptors:input_type -> linka.cloud.protodb.FileDescriptorsRequest - 6, // 42: linka.cloud.protodb.ProtoDB.Get:output_type -> linka.cloud.protodb.GetResponse - 2, // 43: linka.cloud.protodb.ProtoDB.Set:output_type -> linka.cloud.protodb.SetResponse - 4, // 44: linka.cloud.protodb.ProtoDB.Delete:output_type -> linka.cloud.protodb.DeleteResponse - 8, // 45: linka.cloud.protodb.ProtoDB.Tx:output_type -> linka.cloud.protodb.TxResponse - 10, // 46: linka.cloud.protodb.ProtoDB.NextSeq:output_type -> linka.cloud.protodb.NextSeqResponse - 12, // 47: linka.cloud.protodb.ProtoDB.Lock:output_type -> linka.cloud.protodb.LockResponse - 17, // 48: linka.cloud.protodb.ProtoDB.Watch:output_type -> linka.cloud.protodb.WatchEvent - 19, // 49: linka.cloud.protodb.ProtoDB.Register:output_type -> linka.cloud.protodb.RegisterResponse - 21, // 50: linka.cloud.protodb.ProtoDB.Descriptors:output_type -> linka.cloud.protodb.DescriptorsResponse - 23, // 51: linka.cloud.protodb.ProtoDB.FileDescriptors:output_type -> linka.cloud.protodb.FileDescriptorsResponse - 42, // [42:52] is the sub-list for method output_type - 32, // [32:42] is the sub-list for method input_type - 32, // [32:32] is the sub-list for extension type_name - 32, // [32:32] is the sub-list for extension extendee - 0, // [0:32] is the sub-list for field type_name + 29, // 0: linka.cloud.protodb.SetRequest.payload:type_name -> google.protobuf.Any + 30, // 1: linka.cloud.protodb.SetRequest.ttl:type_name -> google.protobuf.Duration + 31, // 2: linka.cloud.protodb.SetRequest.field_mask:type_name -> google.protobuf.FieldMask + 29, // 3: linka.cloud.protodb.SetResponse.result:type_name -> google.protobuf.Any + 29, // 4: linka.cloud.protodb.DeleteRequest.payload:type_name -> google.protobuf.Any + 29, // 5: linka.cloud.protodb.GetRequest.search:type_name -> google.protobuf.Any + 32, // 6: linka.cloud.protodb.GetRequest.filter:type_name -> linka.cloud.protofilters.Expression + 16, // 7: linka.cloud.protodb.GetRequest.paging:type_name -> linka.cloud.protodb.Paging + 31, // 8: linka.cloud.protodb.GetRequest.field_mask:type_name -> google.protobuf.FieldMask + 7, // 9: linka.cloud.protodb.GetRequest.order_by:type_name -> linka.cloud.protodb.OrderBy + 0, // 10: linka.cloud.protodb.OrderBy.direction:type_name -> linka.cloud.protodb.OrderDirection + 29, // 11: linka.cloud.protodb.GetResponse.results:type_name -> google.protobuf.Any + 17, // 12: linka.cloud.protodb.GetResponse.paging:type_name -> linka.cloud.protodb.PagingInfo + 6, // 13: linka.cloud.protodb.TxRequest.get:type_name -> linka.cloud.protodb.GetRequest + 2, // 14: linka.cloud.protodb.TxRequest.set:type_name -> linka.cloud.protodb.SetRequest + 4, // 15: linka.cloud.protodb.TxRequest.delete:type_name -> linka.cloud.protodb.DeleteRequest + 33, // 16: linka.cloud.protodb.TxRequest.commit:type_name -> google.protobuf.BoolValue + 8, // 17: linka.cloud.protodb.TxResponse.get:type_name -> linka.cloud.protodb.GetResponse + 3, // 18: linka.cloud.protodb.TxResponse.set:type_name -> linka.cloud.protodb.SetResponse + 5, // 19: linka.cloud.protodb.TxResponse.delete:type_name -> linka.cloud.protodb.DeleteResponse + 15, // 20: linka.cloud.protodb.TxResponse.commit:type_name -> linka.cloud.protodb.CommitResponse + 34, // 21: linka.cloud.protodb.CommitResponse.error:type_name -> google.protobuf.StringValue + 29, // 22: linka.cloud.protodb.WatchRequest.search:type_name -> google.protobuf.Any + 32, // 23: linka.cloud.protodb.WatchRequest.filter:type_name -> linka.cloud.protofilters.Expression + 1, // 24: linka.cloud.protodb.WatchEvent.type:type_name -> linka.cloud.protodb.WatchEvent.Type + 29, // 25: linka.cloud.protodb.WatchEvent.old:type_name -> google.protobuf.Any + 29, // 26: linka.cloud.protodb.WatchEvent.new:type_name -> google.protobuf.Any + 35, // 27: linka.cloud.protodb.RegisterRequest.file:type_name -> google.protobuf.FileDescriptorProto + 36, // 28: linka.cloud.protodb.DescriptorsResponse.results:type_name -> google.protobuf.DescriptorProto + 35, // 29: linka.cloud.protodb.FileDescriptorsResponse.results:type_name -> google.protobuf.FileDescriptorProto + 28, // 30: linka.cloud.protodb.MessageDiff.fields:type_name -> linka.cloud.protodb.MessageDiff.FieldsEntry + 37, // 31: linka.cloud.protodb.FieldDiff.from:type_name -> google.protobuf.Value + 37, // 32: linka.cloud.protodb.FieldDiff.to:type_name -> google.protobuf.Value + 27, // 33: linka.cloud.protodb.MessageDiff.FieldsEntry.value:type_name -> linka.cloud.protodb.FieldDiff + 6, // 34: linka.cloud.protodb.ProtoDB.Get:input_type -> linka.cloud.protodb.GetRequest + 2, // 35: linka.cloud.protodb.ProtoDB.Set:input_type -> linka.cloud.protodb.SetRequest + 4, // 36: linka.cloud.protodb.ProtoDB.Delete:input_type -> linka.cloud.protodb.DeleteRequest + 9, // 37: linka.cloud.protodb.ProtoDB.Tx:input_type -> linka.cloud.protodb.TxRequest + 11, // 38: linka.cloud.protodb.ProtoDB.NextSeq:input_type -> linka.cloud.protodb.NextSeqRequest + 13, // 39: linka.cloud.protodb.ProtoDB.Lock:input_type -> linka.cloud.protodb.LockRequest + 18, // 40: linka.cloud.protodb.ProtoDB.Watch:input_type -> linka.cloud.protodb.WatchRequest + 20, // 41: linka.cloud.protodb.ProtoDB.Register:input_type -> linka.cloud.protodb.RegisterRequest + 22, // 42: linka.cloud.protodb.ProtoDB.Descriptors:input_type -> linka.cloud.protodb.DescriptorsRequest + 24, // 43: linka.cloud.protodb.ProtoDB.FileDescriptors:input_type -> linka.cloud.protodb.FileDescriptorsRequest + 8, // 44: linka.cloud.protodb.ProtoDB.Get:output_type -> linka.cloud.protodb.GetResponse + 3, // 45: linka.cloud.protodb.ProtoDB.Set:output_type -> linka.cloud.protodb.SetResponse + 5, // 46: linka.cloud.protodb.ProtoDB.Delete:output_type -> linka.cloud.protodb.DeleteResponse + 10, // 47: linka.cloud.protodb.ProtoDB.Tx:output_type -> linka.cloud.protodb.TxResponse + 12, // 48: linka.cloud.protodb.ProtoDB.NextSeq:output_type -> linka.cloud.protodb.NextSeqResponse + 14, // 49: linka.cloud.protodb.ProtoDB.Lock:output_type -> linka.cloud.protodb.LockResponse + 19, // 50: linka.cloud.protodb.ProtoDB.Watch:output_type -> linka.cloud.protodb.WatchEvent + 21, // 51: linka.cloud.protodb.ProtoDB.Register:output_type -> linka.cloud.protodb.RegisterResponse + 23, // 52: linka.cloud.protodb.ProtoDB.Descriptors:output_type -> linka.cloud.protodb.DescriptorsResponse + 25, // 53: linka.cloud.protodb.ProtoDB.FileDescriptors:output_type -> linka.cloud.protodb.FileDescriptorsResponse + 44, // [44:54] is the sub-list for method output_type + 34, // [34:44] is the sub-list for method input_type + 34, // [34:34] is the sub-list for extension type_name + 34, // [34:34] is the sub-list for extension extendee + 0, // [0:34] is the sub-list for field type_name } func init() { file_pb_protodb_proto_init() } @@ -1638,13 +1759,13 @@ func file_pb_protodb_proto_init() { if File_pb_protodb_proto != nil { return } - file_pb_protodb_proto_msgTypes[6].OneofWrappers = []any{ + file_pb_protodb_proto_msgTypes[7].OneofWrappers = []any{ (*TxRequest_Get)(nil), (*TxRequest_Set)(nil), (*TxRequest_Delete)(nil), (*TxRequest_Commit)(nil), } - file_pb_protodb_proto_msgTypes[7].OneofWrappers = []any{ + file_pb_protodb_proto_msgTypes[8].OneofWrappers = []any{ (*TxResponse_Get)(nil), (*TxResponse_Set)(nil), (*TxResponse_Delete)(nil), @@ -1655,8 +1776,8 @@ func file_pb_protodb_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_pb_protodb_proto_rawDesc), len(file_pb_protodb_proto_rawDesc)), - NumEnums: 1, - NumMessages: 26, + NumEnums: 2, + NumMessages: 27, NumExtensions: 0, NumServices: 1, }, diff --git a/pb/protodb.pb.validate.go b/pb/protodb.pb.validate.go index e944211..c94d173 100644 --- a/pb/protodb.pb.validate.go +++ b/pb/protodb.pb.validate.go @@ -678,6 +678,35 @@ func (m *GetRequest) validate(all bool) error { // no validation rules for One + if all { + switch v := interface{}(m.GetOrderBy()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, GetRequestValidationError{ + field: "OrderBy", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, GetRequestValidationError{ + field: "OrderBy", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetOrderBy()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return GetRequestValidationError{ + field: "OrderBy", + reason: "embedded message failed validation", + cause: err, + } + } + } + if len(errors) > 0 { return GetRequestMultiError(errors) } @@ -755,6 +784,108 @@ var _ interface { ErrorName() string } = GetRequestValidationError{} +// Validate checks the field values on OrderBy with the rules defined in the +// proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *OrderBy) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on OrderBy with the rules defined in the +// proto definition for this message. If any rules are violated, the result is +// a list of violation errors wrapped in OrderByMultiError, or nil if none found. +func (m *OrderBy) ValidateAll() error { + return m.validate(true) +} + +func (m *OrderBy) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Field + + // no validation rules for Direction + + if len(errors) > 0 { + return OrderByMultiError(errors) + } + + return nil +} + +// OrderByMultiError is an error wrapping multiple validation errors returned +// by OrderBy.ValidateAll() if the designated constraints aren't met. +type OrderByMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m OrderByMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m OrderByMultiError) AllErrors() []error { return m } + +// OrderByValidationError is the validation error returned by OrderBy.Validate +// if the designated constraints aren't met. +type OrderByValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e OrderByValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e OrderByValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e OrderByValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e OrderByValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e OrderByValidationError) ErrorName() string { return "OrderByValidationError" } + +// Error satisfies the builtin error interface +func (e OrderByValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sOrderBy.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = OrderByValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = OrderByValidationError{} + // Validate checks the field values on GetResponse with the rules defined in // the proto definition for this message. If any rules are violated, the first // error encountered is returned, or nil if there are no violations. diff --git a/pb/protodb.proto b/pb/protodb.proto index 40c1088..2e2d62c 100644 --- a/pb/protodb.proto +++ b/pb/protodb.proto @@ -93,6 +93,18 @@ message GetRequest { bool reverse = 5; // one is a flag to indicate that request must stop after the first result bool one = 6; + OrderBy order_by = 7; +} + +enum OrderDirection { + ORDER_DIRECTION_UNSPECIFIED = 0; + ORDER_DIRECTION_ASC = 1; + ORDER_DIRECTION_DESC = 2; +} + +message OrderBy { + string field = 1; + OrderDirection direction = 2; } message GetResponse { diff --git a/pb/protodb_vtproto.pb.go b/pb/protodb_vtproto.pb.go index 08ea290..9dff03e 100644 --- a/pb/protodb_vtproto.pb.go +++ b/pb/protodb_vtproto.pb.go @@ -110,6 +110,7 @@ func (m *GetRequest) CloneVT() *GetRequest { r.FieldMask = (*fieldmaskpb.FieldMask)((*fieldmaskpb1.FieldMask)(m.FieldMask).CloneVT()) r.Reverse = m.Reverse r.One = m.One + r.OrderBy = m.OrderBy.CloneVT() if rhs := m.Filter; rhs != nil { if vtpb, ok := interface{}(rhs).(interface{ CloneVT() *filters.Expression }); ok { r.Filter = vtpb.CloneVT() @@ -128,6 +129,24 @@ func (m *GetRequest) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *OrderBy) CloneVT() *OrderBy { + if m == nil { + return (*OrderBy)(nil) + } + r := new(OrderBy) + r.Field = m.Field + r.Direction = m.Direction + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *OrderBy) CloneMessageVT() proto.Message { + return m.CloneVT() +} + func (m *GetResponse) CloneVT() *GetResponse { if m == nil { return (*GetResponse)(nil) @@ -707,6 +726,9 @@ func (this *GetRequest) EqualVT(that *GetRequest) bool { if this.One != that.One { return false } + if !this.OrderBy.EqualVT(that.OrderBy) { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -717,6 +739,28 @@ func (this *GetRequest) EqualMessageVT(thatMsg proto.Message) bool { } return this.EqualVT(that) } +func (this *OrderBy) EqualVT(that *OrderBy) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Field != that.Field { + return false + } + if this.Direction != that.Direction { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *OrderBy) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*OrderBy) + if !ok { + return false + } + return this.EqualVT(that) +} func (this *GetResponse) EqualVT(that *GetResponse) bool { if this == that { return true @@ -1622,6 +1666,16 @@ func (m *GetRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.OrderBy != nil { + size, err := m.OrderBy.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x3a + } if m.One { i-- if m.One { @@ -1697,6 +1751,51 @@ func (m *GetRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *OrderBy) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *OrderBy) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *OrderBy) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Direction != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Direction)) + i-- + dAtA[i] = 0x10 + } + if len(m.Field) > 0 { + i -= len(m.Field) + copy(dAtA[i:], m.Field) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Field))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *GetResponse) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -2907,6 +3006,27 @@ func (m *GetRequest) SizeVT() (n int) { if m.One { n += 2 } + if m.OrderBy != nil { + l = m.OrderBy.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *OrderBy) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Field) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Direction != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Direction)) + } n += len(m.unknownFields) return n } @@ -3956,6 +4076,144 @@ func (m *GetRequest) UnmarshalVT(dAtA []byte) error { } } m.One = bool(v != 0) + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OrderBy", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.OrderBy == nil { + m.OrderBy = &OrderBy{} + } + if err := m.OrderBy.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *OrderBy) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: OrderBy: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: OrderBy: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Field", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Field = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Direction", wireType) + } + m.Direction = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Direction |= OrderDirection(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/protodb.go b/protodb.go index e57d91e..557aa1f 100644 --- a/protodb.go +++ b/protodb.go @@ -92,6 +92,9 @@ var ( WithReadFieldMask = protodb.WithReadFieldMask WithReverse = protodb.WithReverse WithOne = protodb.WithOne + WithOrderBy = protodb.WithOrderBy + WithOrderByAsc = protodb.WithOrderByAsc + WithOrderByDesc = protodb.WithOrderByDesc ) type TxOption = protodb.TxOption @@ -138,6 +141,15 @@ type ( PagingInfo = pb.PagingInfo FilterExpr = filters.Expression Filter = filters.FieldFilterer + OrderBy = pb.OrderBy +) + +type OrderDirection = pb.OrderDirection + +const ( + OrderDirectionUnspecified = pb.OrderDirectionUnspecified + OrderDirectionAsc = pb.OrderDirectionAsc + OrderDirectionDesc = pb.OrderDirectionDesc ) type Client = client.Client diff --git a/protodb/protodb.pb.go b/protodb/protodb.pb.go index de04027..fb0a61c 100644 --- a/protodb/protodb.pb.go +++ b/protodb/protodb.pb.go @@ -14,7 +14,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.7 +// protoc-gen-go v1.36.6 // protoc v4.23.0 // source: protodb/protodb.proto diff --git a/tests/index.go b/tests/index.go index 4d1a384..ea75bc1 100644 --- a/tests/index.go +++ b/tests/index.go @@ -105,6 +105,53 @@ func TestIndexCRUD(t *testing.T, db protodb.Client) { }) }) + t.Run("order-by", func(t *testing.T) { + m0 := newIndexCRUDMessage(md, "k0", "down") + m1 := newIndexCRUDMessage(md, "k2", "up") + m2 := newIndexCRUDMessage(md, "k1", "up") + tx, err := db.Tx(ctx) + require.NoError(err) + defer tx.Close() + _, err = tx.Set(ctx, m0) + require.NoError(err) + _, err = tx.Set(ctx, m1) + require.NoError(err) + _, err = tx.Set(ctx, m2) + require.NoError(err) + require.NoError(tx.Commit(ctx)) + + res, pi, err := db.Get(ctx, dynamicpb.NewMessage(md), protodb.WithOrderByAsc("status"), protodb.WithPaging(&protodb.Paging{Limit: 2})) + require.NoError(err) + require.Len(res, 2) + require.True(pi.GetHasNext()) + require.Equal("k0", keyFromDynamic(t, res[0])) + require.Equal("k1", keyFromDynamic(t, res[1])) + + res, pi, err = db.Get(ctx, dynamicpb.NewMessage(md), protodb.WithOrderByAsc("status"), protodb.WithPaging(&protodb.Paging{Limit: 2, Token: pi.GetToken()})) + require.NoError(err) + require.Len(res, 1) + require.False(pi.GetHasNext()) + require.Equal("k2", keyFromDynamic(t, res[0])) + + res, _, err = db.Get(ctx, dynamicpb.NewMessage(md), protodb.WithOrderByDesc("key")) + require.NoError(err) + require.Len(res, 3) + require.Equal("k2", keyFromDynamic(t, res[0])) + require.Equal("k1", keyFromDynamic(t, res[1])) + require.Equal("k0", keyFromDynamic(t, res[2])) + + _, _, err = db.Get(ctx, dynamicpb.NewMessage(md), protodb.WithOrderByAsc("status"), protodb.WithReverse()) + require.Error(err) + + tx, err = db.Tx(ctx) + require.NoError(err) + defer tx.Close() + require.NoError(tx.Delete(ctx, m0)) + require.NoError(tx.Delete(ctx, m1)) + require.NoError(tx.Delete(ctx, m2)) + require.NoError(tx.Commit(ctx)) + }) + t.Run("unique", func(t *testing.T) { ufd := buildIndexCRUDUniqueFile(t) require.NoError(db.Register(ctx, ufd)) diff --git a/tests/pb/test.pb.go b/tests/pb/test.pb.go index 1f384a3..b4060eb 100644 --- a/tests/pb/test.pb.go +++ b/tests/pb/test.pb.go @@ -14,7 +14,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.7 +// protoc-gen-go v1.36.6 // protoc v4.23.0 // source: tests/pb/test.proto