CosmosDBState - Add, as option, the partitionKey in QueryMethod (#3227)
Signed-off-by: luigirende <luigirende@gmail.com> Signed-off-by: Luigi Rende <luigirende@gmail.com> Signed-off-by: Bernd Verst <github@bernd.dev> Co-authored-by: Bernd Verst <github@bernd.dev>
This commit is contained in:
parent
805c1f8423
commit
120a649caa
|
@ -92,8 +92,11 @@ func (p crossPartitionQueryPolicy) Do(req *policy.Request) (*http.Response, erro
|
||||||
// Check if we're performing a query
|
// Check if we're performing a query
|
||||||
// In that case, remove the partitionkey header and enable cross-partition queries
|
// In that case, remove the partitionkey header and enable cross-partition queries
|
||||||
if strings.ToLower(raw.Header.Get("x-ms-documentdb-query")) == "true" {
|
if strings.ToLower(raw.Header.Get("x-ms-documentdb-query")) == "true" {
|
||||||
raw.Header.Add("x-ms-documentdb-query-enablecrosspartition", "true")
|
// Only when the partitionKey is fake (true), it will be removed amd enabled the cross partition
|
||||||
raw.Header.Del("x-ms-documentdb-partitionkey")
|
if strings.ToLower(raw.Header.Get("x-ms-documentdb-partitionkey")) == "[true]" {
|
||||||
|
raw.Header.Add("x-ms-documentdb-query-enablecrosspartition", "true")
|
||||||
|
raw.Header.Del("x-ms-documentdb-partitionkey")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return req.Next()
|
return req.Next()
|
||||||
}
|
}
|
||||||
|
@ -206,6 +209,7 @@ func (c *StateStore) Features() []state.Feature {
|
||||||
state.FeatureTransactional,
|
state.FeatureTransactional,
|
||||||
state.FeatureQueryAPI,
|
state.FeatureQueryAPI,
|
||||||
state.FeatureTTL,
|
state.FeatureTTL,
|
||||||
|
state.FeaturePartitionKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -594,6 +598,12 @@ func (c *StateStore) Query(ctx context.Context, req *state.QueryRequest) (*state
|
||||||
return &state.QueryResponse{}, err
|
return &state.QueryResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If present partitionKey, the value will be used in the query disabling the cross partition
|
||||||
|
q.partitionKey = ""
|
||||||
|
if val, found := req.Metadata[metadataPartitionKey]; found {
|
||||||
|
q.partitionKey = val
|
||||||
|
}
|
||||||
|
|
||||||
data, token, err := q.execute(ctx, c.client)
|
data, token, err := q.execute(ctx, c.client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -34,9 +34,10 @@ type InternalQuery struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
query InternalQuery
|
query InternalQuery
|
||||||
limit int
|
limit int
|
||||||
token string
|
token string
|
||||||
|
partitionKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) VisitEQ(f *query.EQ) (string, error) {
|
func (q *Query) VisitEQ(f *query.EQ) (string, error) {
|
||||||
|
@ -264,7 +265,14 @@ func (q *Query) execute(ctx context.Context, client *azcosmos.ContainerClient) (
|
||||||
}
|
}
|
||||||
|
|
||||||
items := []CosmosItem{}
|
items := []CosmosItem{}
|
||||||
pk := azcosmos.NewPartitionKeyBool(true)
|
|
||||||
|
var pk azcosmos.PartitionKey
|
||||||
|
if q.partitionKey != "" {
|
||||||
|
pk = azcosmos.NewPartitionKeyString(q.partitionKey)
|
||||||
|
} else {
|
||||||
|
pk = azcosmos.NewPartitionKeyBool(true)
|
||||||
|
}
|
||||||
|
|
||||||
queryPager := client.NewQueryItemsPager(q.query.query, pk, opts)
|
queryPager := client.NewQueryItemsPager(q.query.query, pk, opts)
|
||||||
|
|
||||||
token := ""
|
token := ""
|
||||||
|
|
|
@ -28,6 +28,8 @@ const (
|
||||||
FeatureTTL Feature = "TTL"
|
FeatureTTL Feature = "TTL"
|
||||||
// FeatureDeleteWithPrefix is the feature that supports deleting with prefix.
|
// FeatureDeleteWithPrefix is the feature that supports deleting with prefix.
|
||||||
FeatureDeleteWithPrefix Feature = "DELETE_WITH_PREFIX"
|
FeatureDeleteWithPrefix Feature = "DELETE_WITH_PREFIX"
|
||||||
|
// FeaturePartitionKey is the feature that supports the partition
|
||||||
|
FeaturePartitionKey Feature = "PARTITION_KEY"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Feature names a feature that can be implemented by state store components.
|
// Feature names a feature that can be implemented by state store components.
|
||||||
|
|
|
@ -44,7 +44,8 @@ type StructType struct {
|
||||||
Product struct {
|
Product struct {
|
||||||
Value int `json:"value"`
|
Value int `json:"value"`
|
||||||
} `json:"product"`
|
} `json:"product"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type intValueType struct {
|
type intValueType struct {
|
||||||
|
@ -62,8 +63,10 @@ type scenario struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type queryScenario struct {
|
type queryScenario struct {
|
||||||
query string
|
query string
|
||||||
results []state.QueryItem
|
results []state.QueryItem
|
||||||
|
metadata map[string]string
|
||||||
|
partitionOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestConfig struct {
|
type TestConfig struct {
|
||||||
|
@ -130,14 +133,19 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
||||||
key: fmt.Sprintf("%s-struct-operations", key),
|
key: fmt.Sprintf("%s-struct-operations", key),
|
||||||
value: StructType{Product: struct {
|
value: StructType{Product: struct {
|
||||||
Value int `json:"value"`
|
Value int `json:"value"`
|
||||||
}{Value: 15}, Status: "ACTIVE"},
|
}{Value: 15}, Status: "ACTIVE", Message: fmt.Sprintf("%smessage", key)},
|
||||||
contentType: contenttype.JSONContentType,
|
contentType: contenttype.JSONContentType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: fmt.Sprintf("%s-struct-operations-inactive", key),
|
key: fmt.Sprintf("%s-struct-operations-inactive", key),
|
||||||
value: StructType{Product: struct {
|
value: StructType{Product: struct {
|
||||||
Value int `json:"value"`
|
Value int `json:"value"`
|
||||||
}{Value: 12}, Status: "INACTIVE"},
|
}{Value: 12}, Status: "INACTIVE", Message: fmt.Sprintf("%smessage", key)},
|
||||||
|
contentType: contenttype.JSONContentType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: fmt.Sprintf("%s-struct-2", key),
|
||||||
|
value: ValueType{Message: fmt.Sprintf("%stest", key)},
|
||||||
contentType: contenttype.JSONContentType,
|
contentType: contenttype.JSONContentType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -261,6 +269,9 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
||||||
{
|
{
|
||||||
"filter": {
|
"filter": {
|
||||||
"AND": [
|
"AND": [
|
||||||
|
{
|
||||||
|
"EQ": {"message": "` + key + `message"}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"GTE": {"product.value": 10}
|
"GTE": {"product.value": 10}
|
||||||
},
|
},
|
||||||
|
@ -277,7 +288,7 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
||||||
results: []state.QueryItem{
|
results: []state.QueryItem{
|
||||||
{
|
{
|
||||||
Key: fmt.Sprintf("%s-struct-operations", key),
|
Key: fmt.Sprintf("%s-struct-operations", key),
|
||||||
Data: []byte(fmt.Sprintf(`{"product":{"value":15}, "status":"ACTIVE"}`)),
|
Data: []byte(fmt.Sprintf(`{"product":{"value":15}, "status":"ACTIVE","message":"%smessage"}`, key)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -288,6 +299,9 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
||||||
"OR": [
|
"OR": [
|
||||||
{
|
{
|
||||||
"AND": [
|
"AND": [
|
||||||
|
{
|
||||||
|
"EQ": {"message": "` + key + `message"}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"GT": {"product.value": 11.1}
|
"GT": {"product.value": 11.1}
|
||||||
},
|
},
|
||||||
|
@ -298,6 +312,9 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"AND": [
|
"AND": [
|
||||||
|
{
|
||||||
|
"EQ": {"message": "` + key + `message"}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"LTE": {"product.value": 0.5}
|
"LTE": {"product.value": 0.5}
|
||||||
},
|
},
|
||||||
|
@ -313,7 +330,65 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
||||||
results: []state.QueryItem{
|
results: []state.QueryItem{
|
||||||
{
|
{
|
||||||
Key: fmt.Sprintf("%s-struct-operations-inactive", key),
|
Key: fmt.Sprintf("%s-struct-operations-inactive", key),
|
||||||
Data: []byte(fmt.Sprintf(`{"product":{"value":12}, "status":"INACTIVE"}`)),
|
Data: []byte(fmt.Sprintf(`{"product":{"value":12}, "status":"INACTIVE","message":"%smessage"}`, key)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// In CosmosDB this query uses the cross partition ( value with 2 different partitionKey <key>-struct and <key>-struct-2)
|
||||||
|
{
|
||||||
|
query: `
|
||||||
|
{
|
||||||
|
"filter": {
|
||||||
|
"OR": [
|
||||||
|
{
|
||||||
|
"EQ": {"message": "` + key + `test"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IN": {"message": ["test` + key + `", "dummy"]}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
// Return 2 item from 2 different partitionKey (<key>-struct and <key>-struct-2), for default the partitionKey is equals to key
|
||||||
|
results: []state.QueryItem{
|
||||||
|
{
|
||||||
|
Key: fmt.Sprintf("%s-struct", key),
|
||||||
|
Data: []byte(fmt.Sprintf(`{"message":"test%s"}`, key)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: fmt.Sprintf("%s-struct-2", key),
|
||||||
|
Data: []byte(fmt.Sprintf(`{"message":"%stest"}`, key)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Test for CosmosDB (filter test with partitionOnly) this query doesn't use the cross partition ( value from 2 different partitionKey %s-struct and %s-struct-2)
|
||||||
|
{
|
||||||
|
query: `
|
||||||
|
{
|
||||||
|
"filter": {
|
||||||
|
"OR": [
|
||||||
|
{
|
||||||
|
"EQ": {"message": "` + key + `test"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IN": {"message": ["test` + key + `", "dummy"]}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
metadata: map[string]string{
|
||||||
|
"partitionKey": fmt.Sprintf("%s-struct-2", key),
|
||||||
|
},
|
||||||
|
partitionOnly: true,
|
||||||
|
// The same query from previous test but return only item having the same partitionKey value (%s-struct-2) given in the metadata
|
||||||
|
results: []state.QueryItem{
|
||||||
|
{
|
||||||
|
Key: fmt.Sprintf("%s-struct-2", key),
|
||||||
|
Data: []byte(fmt.Sprintf(`{"message":"%stest"}`, key)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -382,10 +457,12 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
||||||
// Check if query feature is listed
|
// Check if query feature is listed
|
||||||
features := statestore.Features()
|
features := statestore.Features()
|
||||||
require.True(t, state.FeatureQueryAPI.IsPresent(features))
|
require.True(t, state.FeatureQueryAPI.IsPresent(features))
|
||||||
|
|
||||||
querier, ok := statestore.(state.Querier)
|
querier, ok := statestore.(state.Querier)
|
||||||
assert.True(t, ok, "Querier interface is not implemented")
|
assert.True(t, ok, "Querier interface is not implemented")
|
||||||
for _, scenario := range queryScenarios {
|
for _, scenario := range queryScenarios {
|
||||||
|
if (scenario.partitionOnly) && (!state.FeaturePartitionKey.IsPresent(features)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
t.Logf("Querying value presence for %s", scenario.query)
|
t.Logf("Querying value presence for %s", scenario.query)
|
||||||
var req state.QueryRequest
|
var req state.QueryRequest
|
||||||
err := json.Unmarshal([]byte(scenario.query), &req.Query)
|
err := json.Unmarshal([]byte(scenario.query), &req.Query)
|
||||||
|
@ -395,6 +472,10 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
||||||
metadata.QueryIndexName: "qIndx",
|
metadata.QueryIndexName: "qIndx",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if val, found := scenario.metadata["partitionKey"]; found {
|
||||||
|
req.Metadata["partitionKey"] = val
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := querier.Query(context.Background(), &req)
|
resp, err := querier.Query(context.Background(), &req)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, len(scenario.results), len(resp.Results))
|
assert.Equal(t, len(scenario.results), len(resp.Results))
|
||||||
|
|
Loading…
Reference in New Issue