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

feat: Secondary index reader #829

Merged
merged 6 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions internal/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"bytes"
"time"

jsoniter "github.com/json-iterator/go"
"github.com/tigrisdata/tigris/errors"
ulog "github.com/tigrisdata/tigris/util/log"
"github.com/ugorji/go/codec"
Expand Down Expand Up @@ -129,6 +130,23 @@ func (x *TableData) UpdatedToProtoTS() *timestamppb.Timestamp {
return nil
}

func (x *TableData) TimestampsToJSON() ([]byte, error) {
data := map[string]jsoniter.RawMessage{
"_tigris_created_at": nil,
"_tigris_updated_at": nil,
}

if x.CreatedAt != nil {
data["_tigris_created_at"] = jsoniter.RawMessage(x.CreatedAt.ToRFC3339())
}

if x.UpdatedAt != nil {
data["_tigris_updated_at"] = jsoniter.RawMessage(x.CreatedAt.ToRFC3339())
}

return jsoniter.Marshal(data)
}

// Encode is used to encode data to the raw bytes which is used to store in storage as value. The first byte is storing
// the type corresponding to this Data. This is important and used by the decoder later to decode back.
func Encode(data *TableData) ([]byte, error) {
Expand Down
32 changes: 27 additions & 5 deletions query/filter/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,25 @@ func None(reqFilter []byte) bool {
type Factory struct {
fields []*schema.QueryableField
collation *value.Collation
// For secondary Indexes do the following:
// 1. Reject Case insensitive queries
// 2. Always use Factory Top level collation because it will be a sort key collation
buildForSecondaryIndex bool
}

func NewFactory(fields []*schema.QueryableField, collation *value.Collation) *Factory {
return &Factory{
fields: fields,
collation: collation,
fields: fields,
collation: collation,
buildForSecondaryIndex: false,
}
}

func NewFactoryForSecondaryIndex(fields []*schema.QueryableField) *Factory {
return &Factory{
fields: fields,
collation: value.NewSortKeyCollation(),
buildForSecondaryIndex: true,
}
}

Expand Down Expand Up @@ -268,7 +281,7 @@ func (factory *Factory) ParseSelector(k []byte, v []byte, dataType jsonparser.Va

return NewSelector(field, NewEqualityMatcher(val), factory.collation), nil
case jsonparser.Object:
valueMatcher, collation, err := buildValueMatcher(v, field)
valueMatcher, collation, err := buildValueMatcher(v, field, factory.collation, factory.buildForSecondaryIndex)
if err != nil {
return nil, err
}
Expand All @@ -286,7 +299,7 @@ func (factory *Factory) ParseSelector(k []byte, v []byte, dataType jsonparser.Va
// instead of a simple JSON value. Apart from comparison operators, this object can have its own collation, which
// needs to be honored at the field level. Therefore, the caller needs to check if the collation returned by the
// method is not nil and if yes, use this collation..
func buildValueMatcher(input jsoniter.RawMessage, field *schema.QueryableField) (ValueMatcher, *value.Collation, error) {
func buildValueMatcher(input jsoniter.RawMessage, field *schema.QueryableField, factoryCollation *value.Collation, buildForSecondaryIndex bool) (ValueMatcher, *value.Collation, error) {
if len(input) == 0 {
return nil, nil, errors.InvalidArgument("empty object")
}
Expand All @@ -303,6 +316,12 @@ func buildValueMatcher(input jsoniter.RawMessage, field *schema.QueryableField)
return nil, nil, err
}
collation = value.NewCollationFrom(apiCollation)

if buildForSecondaryIndex && collation.IsCaseInsensitive() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

So we won't be supporting case-insensitive queries on secondary indexes fields?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can support it in the in-memory filter part but not part of what we use to build the range keys. Or at least I can't think of a way of making that work.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sure, we can figure it out iteratively.

return nil, nil, errors.InvalidArgument("found case insensitive collation")
}
} else {
collation = factoryCollation
}

var err error
Expand All @@ -323,7 +342,10 @@ func buildValueMatcher(input jsoniter.RawMessage, field *schema.QueryableField)
}

var val value.Value
if collation != nil {
//nolint:gocritic
if buildForSecondaryIndex {
val, err = value.NewValueUsingCollation(tigrisType, v, factoryCollation)
} else if collation != nil {
val, err = value.NewValueUsingCollation(tigrisType, v, collation)
} else {
val, err = value.NewValue(tigrisType, v)
Expand Down
Loading