diff --git a/deploy/datastore/index.yaml b/deploy/datastore/index.yaml index fb110b86..c08483d2 100644 --- a/deploy/datastore/index.yaml +++ b/deploy/datastore/index.yaml @@ -13,3 +13,8 @@ indexes: direction: asc - name: Timestamps.UpdatedAt direction: asc + +- kind: record + ancestor: yes + properties: + - name: Properties.prop1 diff --git a/deploy/terraform/gcp/datastore.tf b/deploy/terraform/gcp/datastore.tf index 4c8292ea..cfc4b7b7 100644 --- a/deploy/terraform/gcp/datastore.tf +++ b/deploy/terraform/gcp/datastore.tf @@ -17,11 +17,11 @@ resource "google_datastore_index" "blob_status_updated_at" { kind = "blob" properties { - name = "Status" + name = "Status" direction = "ASCENDING" } properties { - name = "Timestamps.UpdatedAt" + name = "Timestamps.UpdatedAt" direction = "ASCENDING" } } @@ -29,11 +29,24 @@ resource "google_datastore_index" "blob_status_updated_at" { resource "google_datastore_index" "chunk_status_updated_at" { kind = "chunk" properties { - name = "Status" + name = "Status" direction = "ASCENDING" } properties { - name = "Timestamps.UpdatedAt" + name = "Timestamps.UpdatedAt" direction = "ASCENDING" } } + +resource "google_datastore_index" "default_indexed_properties" { + kind = "record" + ancestor = "ALL_ANCESTORS" + properties { + name = "Properties.prop1" + direction = "ASCENDING" + } + properties { + name = "Properties.prop1" + direction = "DESCENDING" + } +} diff --git a/internal/app/server/open_saves_test.go b/internal/app/server/open_saves_test.go index 4b3818d5..1924b41b 100644 --- a/internal/app/server/open_saves_test.go +++ b/internal/app/server/open_saves_test.go @@ -769,7 +769,7 @@ func TestOpenSaves_ExternalBlobSimple(t *testing.T) { verifyBlob(ctx, t, client, store.Key, record.Key, make([]byte, 0)) } -func TestOpenSaves_QueryRecords_Filter(t *testing.T) { +func TestOpenSaves_QueryRecords_EqualityFilter(t *testing.T) { ctx := context.Background() _, listener := getOpenSavesServer(ctx, t, "gcp") _, client := getTestClient(ctx, t, listener) @@ -824,6 +824,74 @@ func TestOpenSaves_QueryRecords_Filter(t *testing.T) { assert.Equal(t, resp.Records[0].Properties["prop1"].Value, stringVal1) } +func TestOpenSaves_QueryRecords_InequalityFilter(t *testing.T) { + ctx := context.Background() + _, listener := getOpenSavesServer(ctx, t, "gcp") + _, client := getTestClient(ctx, t, listener) + storeKey := uuid.NewString() + store := &pb.Store{Key: storeKey} + setupTestStore(ctx, t, client, store) + + recordKey1 := uuid.NewString() + intVal1 := &pb.Property_IntegerValue{IntegerValue: 10} + setupTestRecord(ctx, t, client, storeKey, &pb.Record{ + Key: recordKey1, + Properties: map[string]*pb.Property{ + "prop1": { + Type: pb.Property_INTEGER, + Value: intVal1, + }, + }, + }) + + recordKey2 := uuid.NewString() + intVal2 := &pb.Property_IntegerValue{IntegerValue: 20} + setupTestRecord(ctx, t, client, storeKey, &pb.Record{ + Key: recordKey2, + Properties: map[string]*pb.Property{ + "prop1": { + Type: pb.Property_INTEGER, + Value: intVal2, + }, + }, + }) + + intVal3 := &pb.Property_IntegerValue{IntegerValue: 0} + queryReq := &pb.QueryRecordsRequest{ + StoreKey: storeKey, + Filters: []*pb.QueryFilter{ + { + PropertyName: "prop1", + Operator: pb.FilterOperator_GREATER, + Value: &pb.Property{ + Type: pb.Property_INTEGER, + Value: intVal3, + }, + }, + }, + } + resp, err := client.QueryRecords(ctx, queryReq) + require.NoError(t, err) + + // Both records match the query. + require.Equal(t, 2, len(resp.Records)) + require.Equal(t, 2, len(resp.StoreKeys)) + + assert.Equal(t, storeKey, resp.StoreKeys[0]) + assert.Equal(t, resp.Records[0].Properties["prop1"].Value, intVal1) + assert.Equal(t, resp.Records[1].Properties["prop1"].Value, intVal2) + + // Run a new query that matches only one record. + queryReq.Filters[0].Value.Value = &pb.Property_IntegerValue{IntegerValue: 15} + + resp, err = client.QueryRecords(ctx, queryReq) + require.NoError(t, err) + + require.Equal(t, 1, len(resp.Records)) + + assert.Equal(t, resp.Records[0].Properties["prop1"].Value, intVal2) +} + func TestOpenSaves_QueryRecords_Owner(t *testing.T) { ctx := context.Background() _, listener := getOpenSavesServer(ctx, t, "gcp") diff --git a/internal/pkg/metadb/metadb.go b/internal/pkg/metadb/metadb.go index 4f1a05b5..2c405b1e 100644 --- a/internal/pkg/metadb/metadb.go +++ b/internal/pkg/metadb/metadb.go @@ -685,14 +685,24 @@ func (m *MetaDB) GetChildChunkRefs(ctx context.Context, blobKey uuid.UUID) *chun return chunkref.NewCursor(m.client.Run(ctx, query)) } +// addPropertyFilter augments a query with the QueryFilter operations. func addPropertyFilter(q *ds.Query, f *pb.QueryFilter) (*ds.Query, error) { + filter := propertiesField + "." + f.PropertyName switch f.Operator { case pb.FilterOperator_EQUAL: - return q.Filter(propertiesField+"."+f.PropertyName+"=", record.ExtractValue(f.Value)), nil + filter += "=" + case pb.FilterOperator_GREATER: + filter += ">" + case pb.FilterOperator_LESS: + filter += "<" + case pb.FilterOperator_GREATER_OR_EQUAL: + filter += ">=" + case pb.FilterOperator_LESS_OR_EQUAL: + filter += "<=" default: - // TODO(hongalex): implement inequality filters - return nil, status.Errorf(codes.Unimplemented, "only the equality operator is supported currently") + return nil, status.Errorf(codes.Unimplemented, "unknown filter operator detected: %+v", f.Operator) } + return q.Filter(filter, record.ExtractValue(f.Value)), nil } // QueryRecords returns a list of records that match the given filters and their stores.