func (b *Endpoint) ListCollections(ctx context.Context, prefix string) (chan string, chan error) {
	rv, err := b.g.ListCollections(ctx, &pb.ListCollectionsParams{
		Prefix: prefix,
	})
	rvc := make(chan string, 100)
	rve := make(chan error, 1)
	if err != nil {
		close(rvc)
		rve <- err
		close(rve)
		return rvc, rve
	}
	go func() {
		for {
			cols, err := rv.Recv()
			if err == io.EOF {
				close(rvc)
				close(rve)
				return
			}
			if err != nil {
				close(rvc)
				rve <- err
				close(rve)
				return
			}
			if cols.Stat != nil {
				close(rvc)
				rve <- &CodedError{cols.Stat}
				close(rve)
				return
			}
			for _, r := range cols.Collections {
				rvc <- r
			}
		}
	}()
	return rvc, rve
}
ListCollections is a low level function, and in particular has complex constraints. Rather use BTrDB.ListCollections()
package btrdb
import (
	"context"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"strings"
	"time"
	"github.com/BTrDB/btrdb/v5/bte"
	pb "github.com/BTrDB/btrdb/v5/v5api"
	"github.com/pborman/uuid"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)
type PropertyVersion uint64
const EndpointTimeout = 5 * time.Second
type Endpoint struct {
	g    pb.BTrDBClient
	conn *grpc.ClientConn
}
type RawPoint struct {
	Time int64
	Value float64
}
type InsertParams struct {
	RoundBits   *int
	MergePolicy MergePolicy
}
type MergePolicy = int
const (
	MPNever = MergePolicy(iota)
	MPEqual
	MPRetain
	MPReplace
)
var forceEp = errors.New("Not really an error, you should not see this")
type apikeyCred string
func (a apikeyCred) GetRequestMetadata(ctx context.Context, uris ...string) (map[string]string, error) {
	return map[string]string{
		"authorization": fmt.Sprintf("bearer %s", a),
	}, nil
}
func (a apikeyCred) RequireTransportSecurity() bool {
	return false
}
func ConnectEndpoint(ctx context.Context, addresses ...string) (*Endpoint, error) {
	return ConnectEndpointAuth(ctx, "", addresses...)
}
func ConnectEndpointAuth(ctx context.Context, apikey string, addresses ...string) (*Endpoint, error) {
	if len(addresses) == 0 {
		return nil, fmt.Errorf("No addresses provided")
	}
	ep_errors := ""
	for _, a := range addresses {
		if ctx.Err() != nil {
			return nil, ctx.Err()
		}
		dl, ok := ctx.Deadline()
		var tmt time.Duration
		if ok {
			tmt = dl.Sub(time.Now())
			if tmt > EndpointTimeout {
				tmt = EndpointTimeout
			}
		} else {
			tmt = EndpointTimeout
		}
		addrport := strings.SplitN(a, ":", 2)
		if len(addrport) != 2 {
			fmt.Printf("invalid address:port %q\n", a)
			continue
		}
		secure := false
		switch {
		case os.Getenv("BTRDB_FORCE_SECURE") == "YES":
			secure = true
		case addrport[1] == "4411" && os.Getenv("BTRDB_FORCE_INSECURE") != "YES":
			secure = true
		default:
			secure = false
		}
		dc := grpc.NewGZIPDecompressor()
		dialopts := []grpc.DialOption{
			grpc.WithTimeout(tmt),
			grpc.FailOnNonTempDialError(true),
			grpc.WithBlock(),
			grpc.WithDecompressor(dc),
			grpc.WithInitialWindowSize(1 * 1024 * 1024),
			grpc.WithInitialConnWindowSize(1 * 1024 * 1024)}
		if secure {
			dialopts = append(dialopts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})))
		} else {
			dialopts = append(dialopts, grpc.WithInsecure())
		}
		if apikey != "" {
			dialopts = append(dialopts, grpc.WithPerRPCCredentials(apikeyCred(apikey)))
		}
		conn, err := grpc.Dial(a, dialopts...)
		if err != nil {
			if ctx.Err() != nil {
				return nil, ctx.Err()
			}
			ep_errors += fmt.Sprintf("endpoint error: err=%v a=%v\n", err, a)
			continue
		}
		client := pb.NewBTrDBClient(conn)
		inf, err := client.Info(ctx, &pb.InfoParams{})
		if err != nil {
			ep_errors += fmt.Sprintf("endpoint error: err=%v a=%v\n", err, a)
			continue
		}
		if inf.MajorVersion != 5 {
			lg.Errorf("BTrDB server is the wrong version (expecting v5.x, got v%d.%d)", inf.MajorVersion, inf.MinorVersion)
			return nil, fmt.Errorf("Endpoint is the wrong version")
		}
		rv := &Endpoint{g: client, conn: conn}
		return rv, nil
	}
	fmt.Printf(ep_errors)
	return nil, fmt.Errorf("Endpoint is unreachable on all addresses")
}
func (b *Endpoint) GetGRPC() pb.BTrDBClient {
	return b.g
}
func (b *Endpoint) GetClientConnection() *grpc.ClientConn {
	return b.conn
}
func (b *Endpoint) Disconnect() error {
	return b.conn.Close()
}
func (b *Endpoint) InsertGeneric(ctx context.Context, uu uuid.UUID, values []*pb.RawPoint, p *InsertParams) error {
	policy := pb.MergePolicy_NEVER
	rounding := (*pb.RoundSpec)(nil)
	if p != nil {
		if p.RoundBits != nil {
			rounding = &pb.RoundSpec{
				Spec: &pb.RoundSpec_Bits{int32(*p.RoundBits)},
			}
		}
		switch p.MergePolicy {
		case MPNever:
			policy = pb.MergePolicy_NEVER
		case MPEqual:
			policy = pb.MergePolicy_EQUAL
		case MPRetain:
			policy = pb.MergePolicy_RETAIN
		case MPReplace:
			policy = pb.MergePolicy_REPLACE
		}
	}
	rv, err := b.g.Insert(ctx, &pb.InsertParams{
		Uuid:        uu,
		Sync:        false,
		Values:      values,
		MergePolicy: policy,
		Rounding:    rounding,
	})
	if err != nil {
		return err
	}
	if rv.GetStat() != nil {
		return &CodedError{rv.GetStat()}
	}
	return nil
}
func (b *Endpoint) InsertUnique(ctx context.Context, uu uuid.UUID, values []*pb.RawPoint, mp MergePolicy) error {
	return b.InsertGeneric(ctx, uu, values, &InsertParams{MergePolicy: mp})
}
func (b *Endpoint) Insert(ctx context.Context, uu uuid.UUID, values []*pb.RawPoint) error {
	return b.InsertGeneric(ctx, uu, values, nil)
}
func (b *Endpoint) FaultInject(ctx context.Context, typ uint64, args []byte) ([]byte, error) {
	rv, err := b.g.FaultInject(ctx, &pb.FaultInjectParams{
		Type:   typ,
		Params: args,
	})
	if err != nil {
		return nil, err
	}
	if rv.GetStat() != nil {
		return nil, &CodedError{rv.GetStat()}
	}
	return rv.Rv, nil
}
func (b *Endpoint) Create(ctx context.Context, uu uuid.UUID, collection string, tags map[string]*string, annotations map[string]*string) error {
	taglist := []*pb.KeyOptValue{}
	for k, v := range tags {
		if v == nil {
			taglist = append(taglist, &pb.KeyOptValue{Key: k})
		} else {
			taglist = append(taglist, &pb.KeyOptValue{Key: k, Val: &pb.OptValue{Value: *v}})
		}
	}
	annlist := []*pb.KeyOptValue{}
	for k, v := range annotations {
		if v == nil {
			annlist = append(annlist, &pb.KeyOptValue{Key: k})
		} else {
			annlist = append(annlist, &pb.KeyOptValue{Key: k, Val: &pb.OptValue{Value: *v}})
		}
	}
	rv, err := b.g.Create(ctx, &pb.CreateParams{
		Uuid:        uu,
		Collection:  collection,
		Tags:        taglist,
		Annotations: annlist,
	})
	if err != nil {
		return err
	}
	if rv.GetStat() != nil {
		return &CodedError{rv.GetStat()}
	}
	return nil
}
func (b *Endpoint) ListAllCollections(ctx context.Context) (chan string, chan error) {
	return b.ListCollections(ctx, "")
}
func (b *Endpoint) StreamInfo(ctx context.Context, uu uuid.UUID, omitDescriptor bool, omitVersion bool) (
	collection string,
	pver PropertyVersion,
	tags map[string]*string,
	anns map[string]*string,
	version uint64, err error) {
	rv, err := b.g.StreamInfo(ctx, &pb.StreamInfoParams{
		Uuid:           uu,
		OmitDescriptor: omitDescriptor,
		OmitVersion:    omitVersion})
	if err != nil {
		return "", 0, nil, nil, 0, err
	}
	if rv.GetStat() != nil {
		return "", 0, nil, nil, 0, &CodedError{rv.GetStat()}
	}
	if !omitDescriptor {
		tags = make(map[string]*string)
		for _, kv := range rv.Descriptor_.Tags {
			if kv.Val == nil {
				tags[kv.Key] = nil
			} else {
				vc := kv.Val.Value
				tags[kv.Key] = &vc
			}
		}
		anns = make(map[string]*string)
		for _, kv := range rv.Descriptor_.Annotations {
			if kv.Val == nil {
				anns[kv.Key] = nil
			} else {
				vc := kv.Val.Value
				anns[kv.Key] = &vc
			}
		}
		pver = PropertyVersion(rv.Descriptor_.PropertyVersion)
		collection = rv.Descriptor_.Collection
	}
	return collection, pver, tags, anns, rv.VersionMajor, nil
}
func (b *Endpoint) SetStreamAnnotations(ctx context.Context, uu uuid.UUID, expected PropertyVersion, changes map[string]*string, remove []string) error {
	ch := []*pb.KeyOptValue{}
	for k, v := range changes {
		kop := &pb.KeyOptValue{
			Key: k,
		}
		if v != nil {
			kop.Val = &pb.OptValue{Value: *v}
		}
		ch = append(ch, kop)
	}
	rv, err := b.g.SetStreamAnnotations(ctx, &pb.SetStreamAnnotationsParams{Uuid: uu, ExpectedPropertyVersion: uint64(expected), Changes: ch, Removals: remove})
	if err != nil {
		return err
	}
	if rv.GetStat() != nil {
		return &CodedError{rv.GetStat()}
	}
	return nil
}
func (b *Endpoint) SetStreamTags(ctx context.Context, uu uuid.UUID, expected PropertyVersion, collection string, changes map[string]*string) error {
	ch := []*pb.KeyOptValue{}
	for k, v := range changes {
		if v == nil {
			ch = append(ch, &pb.KeyOptValue{Key: k})
		} else {
			ch = append(ch, &pb.KeyOptValue{Key: k, Val: &pb.OptValue{Value: *v}})
		}
	}
	rv, err := b.g.SetStreamTags(ctx, &pb.SetStreamTagsParams{Uuid: uu, ExpectedPropertyVersion: uint64(expected), Tags: ch, Collection: collection})
	if err != nil {
		return err
	}
	if rv.GetStat() != nil {
		return &CodedError{rv.GetStat()}
	}
	return nil
}
func (b *Endpoint) GetMetadataUsage(ctx context.Context, prefix string) (tags map[string]int, annotations map[string]int, err error) {
	rv, err := b.g.GetMetadataUsage(ctx, &pb.MetadataUsageParams{
		Prefix: prefix,
	})
	if err != nil {
		return nil, nil, err
	}
	if rv.Stat != nil {
		return nil, nil, &CodedError{rv.GetStat()}
	}
	tags = make(map[string]int)
	annotations = make(map[string]int)
	for _, kv := range rv.Tags {
		tags[kv.Key] = int(kv.Count)
	}
	for _, kv := range rv.Annotations {
		annotations[kv.Key] = int(kv.Count)
	}
	return tags, annotations, nil
}