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(aggregation): add $fill pipeline stage #12545

Merged
merged 15 commits into from Oct 17, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/bug.yml
Expand Up @@ -37,6 +37,14 @@ body:
validations:
required: true

- type: input
id: typescript-version
attributes:
label: Typescript version (if applicable)
placeholder: 4.8.x
validations:
required: false

- type: textarea
id: description
attributes:
Expand Down
4 changes: 2 additions & 2 deletions docs/guide.md
Expand Up @@ -744,9 +744,9 @@ The read option also allows us to specify _tag sets_. These tell the
[driver](https://github.com/mongodb/node-mongodb-native/) from which members
of the replica-set it should attempt to read. Read more about tag sets
[here](http://docs.mongodb.org/manual/applications/replication/#tag-sets) and
[here](http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences).
[here](https://www.mongodb.com/docs/manual/core/read-preference).

_NOTE: you may also specify the driver read pref [strategy](http://mongodb.github.com/node-mongodb-native/api-generated/replset.html?highlight=strategy)
_NOTE: you may also specify the driver read preference [strategy](https://www.mongodb.com/docs/manual/core/read-preference/#read-preference-modes)
option when connecting:_

```javascript
Expand Down
36 changes: 28 additions & 8 deletions lib/aggregate.js
Expand Up @@ -111,11 +111,11 @@ Aggregate.prototype.model = function(model) {
this._model = model;
if (model.schema != null) {
if (this.options.readPreference == null &&
model.schema.options.read != null) {
model.schema.options.read != null) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

For the future, please avoid making these sort of minor style changes. They make it harder to review your code.

this.options.readPreference = model.schema.options.read;
}
if (this.options.collation == null &&
model.schema.options.collation != null) {
model.schema.options.collation != null) {
this.options.collation = model.schema.options.collation;
}
}
Expand Down Expand Up @@ -158,7 +158,7 @@ Aggregate.prototype.append = function() {
* Requires MongoDB v3.4+ to work
*
* #### Example:
*
*
* // adding new fields based on existing fields
* aggregate.addFields({
* newField: '$b.nested'
Expand Down Expand Up @@ -328,6 +328,28 @@ Aggregate.prototype.project = function(arg) {
* @api public
*/

/**
* Appends a new $fill operator to this aggregate pipeline.
*
* #### Example:
*
* aggregate.fill({
* output: {
* bootsSold: { value: 0 },
* sandalsSold: { value: 0 },
* sneakersSold: { value: 0 }
* }
* });
*
* @see $fill https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/
* @method fill
* @memberOf Aggregate
* @instance
* @param {Object} arg $fill operator contents
* @return {Aggregate}
* @api public
*/

/**
* Appends a new $geoNear operator to this aggregate pipeline.
*
Expand Down Expand Up @@ -366,7 +388,7 @@ Aggregate.prototype.near = function(arg) {
* define methods
*/

'group match skip limit out densify'.split(' ').forEach(function($operator) {
'group match skip limit out densify fill'.split(' ').forEach(function($operator) {
Aggregate.prototype[$operator] = function(arg) {
const op = {};
op['$' + $operator] = arg;
Expand Down Expand Up @@ -702,7 +724,7 @@ Aggregate.prototype.readConcern = function(level) {
Aggregate.prototype.redact = function(expression, thenExpr, elseExpr) {
if (arguments.length === 3) {
if ((typeof thenExpr === 'string' && !validRedactStringValues.has(thenExpr)) ||
(typeof elseExpr === 'string' && !validRedactStringValues.has(elseExpr))) {
(typeof elseExpr === 'string' && !validRedactStringValues.has(elseExpr))) {
throw new Error('If thenExpr or elseExpr is string, it must be either $$DESCEND, $$PRUNE or $$KEEP');
}

Expand Down Expand Up @@ -1099,9 +1121,7 @@ Aggregate.prototype.catch = function(reject) {

if (Symbol.asyncIterator != null) {
Aggregate.prototype[Symbol.asyncIterator] = function() {
return this.cursor({ useMongooseAggCursor: true }).
transformNull().
_transformForAsyncIterator();
return this.cursor({ useMongooseAggCursor: true }).transformNull()._transformForAsyncIterator();
};
}

Expand Down
11 changes: 11 additions & 0 deletions lib/connection.js
Expand Up @@ -1534,6 +1534,17 @@ Connection.prototype.syncIndexes = async function syncIndexes(options = {}) {
*
* Returns a new connection object, with the new db.
*
* #### Example:
*
* // Connect to `initialdb` first
* const conn = await mongoose.createConnection('mongodb://localhost:27017/initialdb').asPromise();
*
* // Creates an un-cached connection to `mydb`
* const db = conn.useDb('mydb');
* // Creates a cached connection to `mydb2`. All calls to `conn.useDb('mydb2', { useCache: true })` will return the same
* // connection instance as opposed to creating a new connection instance
* const db2 = conn.useDb('mydb2', { useCache: true });
*
* @method useDb
* @memberOf Connection
* @param {String} name The database name
Expand Down
19 changes: 19 additions & 0 deletions test/aggregate.test.js
Expand Up @@ -389,6 +389,25 @@ describe('aggregate: ', function() {
});
});

describe('fill', function() {
it('works', function() {
const aggregate = new Aggregate();
const obj = {
output:
{
bootsSold: { value: 0 },
sandalsSold: { value: 0 },
sneakersSold: { value: 0 }
}
};

aggregate.fill(obj);

assert.equal(aggregate._pipeline.length, 1);
assert.deepEqual(aggregate._pipeline[0].$fill, obj);
});
});

describe('model()', function() {
it('works', function() {
const aggregate = new Aggregate();
Expand Down
79 changes: 79 additions & 0 deletions test/types/PipelineStage.test.ts
Expand Up @@ -311,6 +311,85 @@ const setWindowFields4: PipelineStage = {
}
};

const setWindowFieldsLinearFill: PipelineStage = {
$setWindowFields: {
partitionBy: '$stock',
sortBy: { date: 1 },
output: {
price: { $linearFill: '$price' }
}
}
};

const setWindowFieldsLocf: PipelineStage = {
$setWindowFields: {
partitionBy: '$stock',
sortBy: { date: 1 },
output: {
price: { $locf: '$price' }
}
}
};

const fillWithOutput: PipelineStage = {
$fill: {
output: {
bootsSold: { value: 0 }
}
}
};

const fillWithPartitionBy: PipelineStage = {
$fill: {
partitionBy: 'date',
output: {
bootsSold: { value: 0 }
}
}
};

const fillWithPartitionByFields: PipelineStage = {
$fill: {
partitionByFields: ['date'],
output: {
bootsSold: { value: 0 }
}
}
};

const fillWithSortBy: PipelineStage = {
$fill: {
sortBy: {
date: -1
},
output: {
bootsSold: { value: 0 }
}
}
};

const fillWithOutputMethodLinear: PipelineStage = {
$fill: {
sortBy: {
date: -1
},
output: {
bootsSold: { method: 'linear' }
}
}
};

const fillWithOutputMethodLocf: PipelineStage = {
$fill: {
sortBy: {
date: -1
},
output: {
bootsSold: { method: 'locf' }
}
}
};

const group1: PipelineStage = { $group: { _id: null, ageStdDev: { $stdDevSamp: '$age' } } };
const group2: PipelineStage = {
$group: {
Expand Down
3 changes: 3 additions & 0 deletions types/aggregate.d.ts
Expand Up @@ -133,6 +133,9 @@ declare module 'mongoose' {
/** Combines multiple aggregation pipelines. */
facet(options: PipelineStage.Facet['$facet']): this;

/** Appends a new $fill operator to this aggregate pipeline */
fill(arg: PipelineStage.Fill['$fill']): this;

/** Appends new custom $graphLookup operator(s) to this aggregate pipeline, performing a recursive search on a collection. */
graphLookup(options: PipelineStage.GraphLookup['$graphLookup']): this;

Expand Down
22 changes: 22 additions & 0 deletions types/expressions.d.ts
Expand Up @@ -1180,6 +1180,26 @@ declare module 'mongoose' {
$last: Expression;
}

export interface LinearFill {
/**
* Fills null and missing fields in a window using linear interpolation based on surrounding field values.
*
* @version 5.3
* @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/linearFill
*/
$linearFill: Expression
}

export interface Locf {
/**
* Last observation carried forward. Sets values for null and missing fields in a window to the last non-null value for the field.
*
* @version 5.2
* @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/locf
*/
$locf: Expression
}

export interface Map {
/**
* Applies a subexpression to each element of an array and returns the array of resulting values in order. Accepts named parameters.
Expand Down Expand Up @@ -2735,6 +2755,8 @@ declare module 'mongoose' {
Expression.First |
Expression.Integral |
Expression.Last |
Expression.LinearFill |
Expression.Locf |
Expression.Max |
Expression.Min |
Expression.Push |
Expand Down
14 changes: 13 additions & 1 deletion types/pipelinestage.d.ts
Expand Up @@ -8,7 +8,9 @@ declare module 'mongoose' {
| PipelineStage.BucketAuto
| PipelineStage.CollStats
| PipelineStage.Count
| PipelineStage.Densify
| PipelineStage.Facet
| PipelineStage.Fill
| PipelineStage.GeoNear
| PipelineStage.GraphLookup
| PipelineStage.Group
Expand Down Expand Up @@ -89,6 +91,16 @@ declare module 'mongoose' {
}
}

export interface Fill {
/** [`$fill` reference](https://docs.mongodb.com/manual/reference/operator/aggregation/fill/) */
$fill: {
partitionBy?: Expression,
partitionByFields?: string[],
sortBy?: Record<string, 1 | -1>,
output: Record<string, { value: Expression } | { method: 'linear' | 'locf' }>
}
}

export interface Facet {
/** [`$facet` reference](https://docs.mongodb.com/manual/reference/operator/aggregation/facet/) */
$facet: Record<string, FacetPipelineStage[]>;
Expand Down Expand Up @@ -201,7 +213,7 @@ declare module 'mongoose' {

export interface ReplaceWith {
/** [`$replaceWith` reference](https://docs.mongodb.com/manual/reference/operator/aggregation/replaceWith/) */
$replaceWith: ObjectExpressionOperator;
$replaceWith: ObjectExpressionOperator | { [field: string]: Expression };
}

export interface Sample {
Expand Down