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

[Cosmos] Return undefined for sum aggregate when one partition is undefined #12988

Merged
merged 12 commits into from
Jan 5, 2021
4 changes: 4 additions & 0 deletions sdk/cosmosdb/cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Release History

## 3.9.4 (2021-01-04)

- BUGFIX: Sums group by operations for cross-partition queries correctly with null values

## 3.9.3 (2020-10-19)

- BUGFIX: Fixes bulk operations with top level partitionKey values that are undefined or null.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ export class GroupByValueEndpointComponent implements ExecutionContext {

if (this.aggregateType) {
const aggregateResult = extractAggregateResult(payload[0]);
// if aggregate result is null, we need to short circuit aggregation and return undefined
if (aggregateResult === null) {
this.completed = true;
}
this.aggregators.get(grouping).aggregate(aggregateResult);
} else {
// Queries with no aggregates pass the payload directly to the aggregator
Expand All @@ -75,7 +79,11 @@ export class GroupByValueEndpointComponent implements ExecutionContext {
}
}

// It no results are left in the underling execution context, convert our aggregate results to an array
// We bail early since we got an undefined result back `[{}]`
if (this.completed) {
return { result: undefined, headers: aggregateHeaders };
}
// If no results are left in the underlying execution context, convert our aggregate results to an array
for (const aggregator of this.aggregators.values()) {
this.aggregateResultArray.push(aggregator.getResult());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ export const emptyGroup = "__empty__";
// Newer API versions rewrite the query to return `item2`. It fixes some legacy issues with the original `item` result
// Aggregator code should use item2 when available
export const extractAggregateResult = (payload: any) =>
payload.item2 ? payload.item2 : payload.item;
Object.keys(payload).length > 0 ? (payload.item2 ? payload.item2 : payload.item) : null;
86 changes: 86 additions & 0 deletions sdk/cosmosdb/cosmos/test/public/functional/query.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,91 @@ describe("Queries", function() {
"second batch element should be doc3"
);
});

describe("SUM query iterator", function() {
this.timeout(process.env.MOCHA_TIMEOUT || 30000);

it("returns undefined sum with null value in aggregator", async function() {
const container = await getTestContainer(
"Validate QueryIterator Functionality",
undefined,
{
throughput: 10100,
partitionKey: "/id"
}
);
await container.items.create({ id: "5eded6f8asdfasdfasdfaa21be0109ae34e29", age: 22 });
await container.items.create({ id: "5eded6f8a21be0109ae34e29", age: 22 });
await container.items.create({ id: "5edasdfasdfed6f8a21be0109ae34e29", age: null });
await container.items.create({ id: "5eded6f8a2dd1be0109ae34e29", age: 22 });
await container.items.create({ id: "AndersenFamily" });
await container.items.create({ id: "1" });

const queryIterator = container.items.query("SELECT VALUE SUM(c.age) FROM c");
const { resources: sum } = await queryIterator.fetchAll();
assert.equal(sum.length, 0);
});
it("returns undefined sum with false value in aggregator", async function() {
const container = await getTestContainer(
"Validate QueryIterator Functionality",
undefined,
{
throughput: 10100,
partitionKey: "/id"
}
);
await container.items.create({ id: "5eded6f8asdfasdfasdfaa21be0109ae34e29", age: 22 });
await container.items.create({ id: "5eded6f8a21be0109ae34e29", age: 22 });
await container.items.create({ id: "5edasdfasdfed6f8a21be0109ae34e29", age: false });
await container.items.create({ id: "5eded6f8a2dd1be0109ae34e29", age: 22 });
await container.items.create({ id: "AndersenFamily" });
await container.items.create({ id: "1" });

const queryIterator = container.items.query("SELECT VALUE SUM(c.age) FROM c");
const { resources: sum } = await queryIterator.fetchAll();
assert.equal(sum.length, 0);
});
it("returns undefined sum with empty array value in aggregator", async function() {
const container = await getTestContainer(
"Validate QueryIterator Functionality",
undefined,
{
throughput: 10100,
partitionKey: "/id"
}
);
await container.items.create({ id: "5eded6f8asdfasdfasdfaa21be0109ae34e29", age: 22 });
await container.items.create({ id: "5eded6f8a21be0109ae34e29", age: 22 });
await container.items.create({ id: "5edasdfasdfed6f8a21be0109ae34e29", age: [] });
await container.items.create({ id: "5eded6f8a2dd1be0109ae34e29", age: 22 });
await container.items.create({ id: "AndersenFamily" });
await container.items.create({ id: "1" });

const queryIterator = container.items.query("SELECT VALUE SUM(c.age) FROM c");
const { resources: sum } = await queryIterator.fetchAll();
assert.equal(sum.length, 0);
});
it("returns a valid sum with undefined value in aggregator", async function() {
const container = await getTestContainer(
"Validate QueryIterator Functionality",
undefined,
{
throughput: 10100,
partitionKey: "/id"
}
);
await container.items.create({ id: "5eded6f8asdfasdfasdfaa21be0109ae34e29", age: 22 });
await container.items.create({ id: "5eded6f8a21be0109ae34e29", age: 22 });
await container.items.create({ id: "5edasdfasdfed6f8a21be0109ae34e29", age: undefined });
await container.items.create({ id: "5eded6f8a2dd1be0109ae34e29", age: 22 });
await container.items.create({ id: "AndersenFamily" });
await container.items.create({ id: "1" });

const queryIterator = container.items.query("SELECT VALUE SUM(c.age) FROM c");
const { resources: sum } = await queryIterator.fetchAll();
assert.equal(sum.length, 1);
assert.equal(sum[0], 66);
});
});
});
});