Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[query] Plumb potential tag completion/aggregate through to m3query endpoints #1481

Merged
merged 2 commits into from
Mar 29, 2019

Conversation

arnikola
Copy link
Collaborator

@arnikola arnikola commented Mar 20, 2019

Adds new aggregate endpoint to Query

@prateek
Copy link
Collaborator

prateek commented Mar 21, 2019

@arnikola I have a branch with some of these types defined too. Lemme put it up and we can merge the best features

@prateek
Copy link
Collaborator

prateek commented Mar 21, 2019

@arnikola take a look at src/dbnode/client/types.go and src/dbnode/storage/index/types.go in #1483

@@ -60,6 +61,13 @@ type Query struct {
idx.Query
}

type AggregateQueryOptions struct {
QueryOptions
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to not embed the type here

type AggregateQueryOptions struct {
QueryOptions

TagNameFilter [][]byte
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be more flexible to inject a lambda here

QueryOptions

TagNameFilter [][]byte
AggregateQueryType rpc.AggregateQueryType
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of using rpc generated types in the exported interface.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, agreed; was just throwing up something quick and dirty

@@ -78,6 +78,10 @@ type Session interface {
// FetchTaggedIDs resolves the provided query to known IDs.
FetchTaggedIDs(namespace ident.ID, q index.Query, opts index.QueryOptions) (iter TaggedIDsIterator, exhaustive bool, err error)

// FetchTags resolves the optionally provided query to any known tag matchers.
// NB: this is a provisional function until the `session` contract is fully defined
FetchTags(namespace ident.ID, q index.Query, opts index.AggregateQueryOptions) (tags FetchedTags, exhaustive bool, err error)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you feel about Aggregate instead?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, re: the return type - would be better to use an Iterator - allows us to avoid a bunch of allocs

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, I like the iterator you added in #1483; re: Aggregate where do you think we change the context of the function from being a generic aggregation vs a tag completion; would that be entirely in query? Maybe meet halfway with AggregateTags 😛 ? We've already got tag specific stuff in this interface so wouldn't be a huge change, right?

// NB: fine to overwrite any tags which were present in the `noChildren` map
for _, tag := range withChildren.CompletedTags {
for _, value := range tag.Values {
tagMap[string(value)] = true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you just create a set here instead of the true/false thing? I think you can just do something like:

tagMap := make(map[string]struct{}, mapLength)

then just set it to an empty struct when you're looping through.

		for _, value := range tag.Values {
			tagMap[string(value)] = struct{}{}
		}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I need to see if the given tag has children here so need the bool

for _, result := range results {
for _, tags := range result {
jw.BeginObject()
for _, tag := range tags.Tags.Tags {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tags.Tags.Tags tags on tags on tags - can we possibly come up with a better naming convention?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's also a list of tags ;)

Agree we could do with a refactor but out of scope here

}
jw.EndObject()
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove newline

// NB: Filter will always be the second last term in the matchers, and the
// matchers should always have a length of at least 2 (term + terminator)
// so this is a sanity check and unexpected in actual execution.
filter := [][]byte{matchers[len(matchers)-2].Name}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you check to make sure len(matchers) > 2?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:| yeah so wrote the comment intending to do the check but ended up forgetting to haha

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coolio

*xhttp.ParseError,
) {
values := r.URL.Query()
now := time.Now()
fromString, untilString := r.FormValue("from"), r.FormValue("until")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we getting rid of the from and until? I thought the new index supports that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good one, for some reason decided these are always from time.Time{} until time.Now(), rather than by default (I think because we drop em later which was wrong too)

func RenderSeriesMatchResultsJSON(
w io.Writer,
results []*storage.CompleteTagsResult,
results []models.Metrics,
dropRole bool,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this coming from? Config?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just auto-enabled for all prom requests, and auto-disabled for graphite.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay cool - maybe add a todo to config it. But at this point maybe removing some config stuff is the way to go..

if renderErr := prometheus.RenderSeriesMatchResultsJSON(w, results); renderErr != nil {
logger.Error("unable to write matched series", zap.Error(renderErr))
xhttp.Error(w, renderErr, http.StatusBadRequest)
if err := prometheus.RenderSeriesMatchResultsJSON(w, results, true); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should drop any tags from the results unless that's considered default for Prom. Especially if there's no way for a user to control this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, reluctantly agree... It's a weird tag that's tacked on by prom but will keep it around for the time being

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// If no matchers provided, explicitly set this to an AllQuery
if len(matchers) == 0 {
return index.Query{
// TODO: change this to an idx.AllQuery: https://github.com/m3db/m3/pull/1478
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This returns every id if no matchers are provided? That seems not safe.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the expected behaviour here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To expound:

If not providing any queries, we want to return the set of all possible tags, and all values that each of those tags can have. We'll be building functionality for this in the index which should actually end up more performant than the new tag completion endpoint (perhaps counterintuitively, but we can get huge gains here when we're not required to filter down to satisfy an arbitrary query)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense

@@ -49,46 +57,64 @@ func parseFindParamsToQuery(r *http.Request) (
untilString = "now"
}

from, err := graphite.ParseTime(
end, err := graphite.ParseTime(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be start? And the other be end?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe just leave it as from and until to be consistent with the URL parameter names.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch; added it to the test.

Copy link
Collaborator

@benraskin92 benraskin92 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

return nil, errNoNamespacesConfigured
}

// TODO: (arnikola): implement a better lifecycle for incoming aggregated tag
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you want to do this in this PR?

super nit: TODO(arnikola): instead of TODO: (arnikola)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's some low hanging fruit gains for query in general, but probably best not to add them here and risk weird pooling issues for now

var mu sync.Mutex
aggIterators := make([]client.AggregatedTagsIterator, 0, initSize)
defer func() {
for _, it := range aggIterators {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't you need to lock here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do indeed 👍

name, values := aggTagIter.Current()
tagValues := make([][]byte, values.Remaining())
for j := 0; values.Next(); j++ {
// TODO: (arnikola) fix the lifecycle here so we don't have to clone
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels like a redundant copy. you're making a local copy into storage.CompletedTag and then convert to models.Metrics above. can you do both in a single pass?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, looking at the tag aggregator again looks like we cast to string, so the copy is unnecessary. Likewise the lifecycle is a bit less janky then I was expecting; there's definitely work to do on pooling/lifecycle management in m3query but that's pretty universally true rather than just in this case

// If no matchers provided, explicitly set this to an AllQuery
if len(matchers) == 0 {
return index.Query{
// TODO: change this to an idx.AllQuery: https://github.com/m3db/m3/pull/1478
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there something left server side before we can do this here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, wrote this before AllQuery was pushed and forgot to update it 👍

"github.com/m3db/m3/src/query/storage"
"github.com/m3db/m3/src/query/util/json"
"github.com/m3db/m3/src/x/net/http"
)

func parseFindParamsToQuery(r *http.Request) (
*storage.FetchQuery,
func parseFindParamsToQueries(r *http.Request) (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could you add a comment w/ an example to explain expected input/output

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, i like it.

if rErr != nil {
xhttp.Error(w, rErr.Inner(), rErr.Code())
return
}

opts := storage.NewFetchOptions()
result, err := h.storage.FetchTags(ctx, query, opts)
noChildrenResult, err := h.storage.CompleteTags(ctx, noChildrenQuery, opts)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, no context - what's the difference between noChildrenResult and childrenResult

Copy link
Collaborator Author

@arnikola arnikola Mar 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably easiest with an example:

Query is: query=foo.*

This will actually call two Aggregate queries on the session:
foo.*.*, which is withChildrenResult, and is translated(-ish) to:
[ { __g0__ =~ foo } , { __g1__ =~ .* }, { __g2__ =~ .* } ]

foo.*, which is noChildrenResult, and is translated(-ish) to:
[ { __g0__ =~ foo } , { __g1__ =~ .* }, { __g2__ !=~ .* } ],

Where =~ is the regex operator, and !=~ is the regex exclusion operator.
Then these two are combined to determine which fields have children, and which are leaf nodes.

Hopefully this makes sense, will add a comment to this effect

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm using your example of query=foo.*, why not just translate into a single aggregate query as follows: [ { __g0__ =~ foo } , { __g1__ =~ .* }, { __g2__ =~ .* } ]

it's exactly the same as the union currently done in a single pass afaict

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, do you even need to use a regex operator for __g0__? that can be a straight up term operator

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea; added an exact matcher case for alphanumeric segments but will look into properly detecting regex-able statements

@codecov
Copy link

codecov bot commented Mar 29, 2019

Codecov Report

Merging #1481 into master will increase coverage by 19.6%.
The diff coverage is 67.9%.

Impacted file tree graph

@@            Coverage Diff            @@
##           master   #1481      +/-   ##
=========================================
+ Coverage    51.4%     71%   +19.6%     
=========================================
  Files         307     852     +545     
  Lines       26019   72877   +46858     
=========================================
+ Hits        13394   51815   +38421     
- Misses      11669   17665    +5996     
- Partials      956    3397    +2441
Flag Coverage Δ
#aggregator 82.3% <ø> (+28.8%) ⬆️
#cluster 85.7% <ø> (-0.6%) ⬇️
#collector 63.7% <ø> (+24.5%) ⬆️
#dbnode 80.7% <ø> (+11.5%) ⬆️
#m3em 73.2% <ø> (-26.8%) ⬇️
#m3ninx 74.3% <ø> (?)
#m3nsch 51.1% <ø> (?)
#metrics 17.5% <ø> (ø) ⬆️
#msg 75% <ø> (?)
#query 66.5% <67.9%> (-8.2%) ⬇️
#x 76.2% <ø> (+35.2%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2427bbb...8a5c8cc. Read the comment docs.

var err error
terminatedResult, err = h.storage.CompleteTags(ctx, terminatedQuery, opts)
if err != nil {
multiErr = multiErr.Add(err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Need to do locking on this multiErr.

Instead perhaps just keep both separate then use a xerrors.FirstError(terminatedErr, childErr) instead of multiErr.FinalError()?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah my bad, we have another multiErr type somewhere which is locking; I like the FirstError suggestion more anyway 👍

@m3db m3db deleted a comment from benraskin92 Mar 29, 2019
@m3db m3db deleted a comment from arnikola Mar 29, 2019
@m3db m3db deleted a comment from benraskin92 Mar 29, 2019
)

// NB: small optimization to use exact matcher rather than regex if only
// alphanumeric characters present.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make more sense to do always try to do a glob and make glob return ([]byte, bool) and if it returns false to imply that it didn't need to turn the glob into a regexp then you can use a MatchEqual matcher?

I'm pretty sure that's how we used to do it (and helps in the case you use a non alphanumeric symbol but turns out that it's still not a regexp? (i.e. using the - or _ character).

return index.Query{Query: val}, nil
}

cache.RUnlock()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you simplify the above to not have to do two RUnlocks?

k := queryKey(matchers)
cache.RLock()
val, ok := cache.get(k)
cache.RUnlock()

if ok { 
  return index.Query{Query: val}, nil
}

//....

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, looks better

}

var mu sync.Mutex
aggIterators := make([]client.AggregatedTagsIterator, 0, initSize)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you make this len(namespaces) since you get back one for each namespace?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, good call

defer func() {
mu.Lock()
for _, it := range aggIterators {
it.Finalize()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this actually finalize all bytes from all the results that came back? If so, we need to copy them all below (right now we're just taking ref's to the bytes).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially was cloning the returned bytes, but if you look into how completed tags builder works, it should already copy the bytes when taking a string

It's a bit janky but should hold up

name, values := aggTagIter.Current()
tagValues := make([][]byte, values.Remaining())
for j := 0; values.Next(); j++ {
tagValues[j] = values.Current().Bytes()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be a copy of the bytes if we're finalizing these results no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same deal here where the accumulator is casting to strings; I'll put up a test to double check though

if !found {
continue
completedTags[i] = storage.CompletedTag{
Name: name.Bytes(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be a copy of the bytes if we're finalizing these results no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same deal

terminatedResult *storage.CompleteTagsResult
tErr error
childResult *storage.CompleteTagsResult
cErr error
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: You could make this (to slightly compact):

var (
  terminateResult, childResult *storage.CompleteTagsResult
  termErr, childErr error
  // ...
)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually not a fan of doing it like that, looks messier to me :(

If it's a standard we're using will update though 👍

}

mapLength := len(terminatedResult.CompletedTags) + len(childResult.CompletedTags)
tagMap := make(map[string]bool, mapLength)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: In future we should use an autogen'd map that can use []byte as the key and we could then use SetUnsafe(key, ..., Options{NoCopyKey: true, NoFinalizeKey: true}).

@arnikola arnikola force-pushed the arnikola/query-tag-completion branch from 8f97936 to 65a5523 Compare March 29, 2019 16:49
@arnikola arnikola changed the base branch from master to prateek/dbnode/aggregate-rpc March 29, 2019 17:55
@arnikola arnikola force-pushed the arnikola/query-tag-completion branch from 65a5523 to 74a34b4 Compare March 29, 2019 18:05
ctx context.Context,
query *storage.FetchQuery,
_ *storage.FetchOptions,
) (*storage.SearchResults, error) {
return nil, errors.New("FetchTags not implemented")
return nil, errors.New("SearchSeries not implemented")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the deal with this storage type?

Copy link
Collaborator Author

@arnikola arnikola Mar 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a weird handrolled mock; we should probably strip it and get a real mock in instead

func parseFindParamsToQueries(r *http.Request) (
*storage.CompleteTagsQuery,
*storage.CompleteTagsQuery,
string,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: what is this string supposed to be?

i've taken to giving returned variables name for this reason in the method spec (but no naked returns in the code itself). i.e. something like:

func parseFindParamsToQueries(r *http.Request) (
terminatedQuery *storage.CompleteTagsQuery,
childQuery *storage.CompleteTagsQuery,
? string,
err *xhttp.ParseError,
) { 
values := r.URL.Query()
	query := values.Get("query")
	if query == "" {
		return nil, nil, "", xhttp.NewParseError(errors.ErrNoQueryFound, http.StatusBadRequest)
		// notice how this isn't just "return"
	}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sgtm

func ParseTagCompletionParamsToQuery(
r *http.Request,
) (*storage.CompleteTagsQuery, *xhttp.ParseError) {
tagQuery := storage.CompleteTagsQuery{}
tagQuery := storage.CompleteTagsQuery{
Start: time.Time{},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just making sure this is the default that can be overriden by the parameters passed over http

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can not... fixing

@@ -191,7 +196,7 @@ func ParseSeriesMatchQuery(
return nil, xhttp.NewParseError(err, http.StatusBadRequest)
}

tagMatchers := make([]models.Matchers, len(matcherValues))
queries := make([]*storage.FetchQuery, len(matcherValues))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on line 189 - what's with the magic numbers? (unrelated to this change)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was defaulting to 40 days as query start for ...reasons; changed to go from 0 until Now by default

@@ -52,14 +53,15 @@ const (

var (
matchValues = []byte(".*")
roleName = []byte("role")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm this feels like a weird thing to modify a metric with. won't it conflict with people's tags?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I've disabled stripping it for now

Prom does some dumb thing where it adds a "role":"remote" tag to each metric we get from the remote write endpoint which is questionable, but probably safer to leave it in.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, i'd avoid doing anything magicy to the metrics. can add down the road behind config if it's really required

func RenderSeriesMatchResultsJSON(
w io.Writer,
results []*storage.CompleteTagsResult,
results []models.Metrics,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason to use custom encoder here? could we get away with just creating the right json object and using the default encoder

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Historical reasons, mostly? We use this hand rolled encoder everywhere at the moment but should definitely look into changing this and using something sane instead

@arnikola arnikola force-pushed the arnikola/query-tag-completion branch from 74a34b4 to dc84075 Compare March 29, 2019 19:16
@arnikola arnikola changed the base branch from prateek/dbnode/aggregate-rpc to master March 29, 2019 20:05
@arnikola arnikola force-pushed the arnikola/query-tag-completion branch from dc84075 to c535a7d Compare March 29, 2019 20:14
@arnikola arnikola merged commit ad005eb into master Mar 29, 2019
@arnikola arnikola deleted the arnikola/query-tag-completion branch March 29, 2019 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants