mirror of
https://github.com/vale981/grapher
synced 2025-03-04 17:11:38 -05:00
Merge pull request #294 from bhunjadi/support-projection-operators
Support for query projection operators
This commit is contained in:
commit
77702acd84
6 changed files with 96 additions and 10 deletions
0
lib/query/lib/applyProps.js
Normal file → Executable file
0
lib/query/lib/applyProps.js
Normal file → Executable file
21
lib/query/lib/createGraph.js
Normal file → Executable file
21
lib/query/lib/createGraph.js
Normal file → Executable file
|
@ -81,6 +81,14 @@ export function createNodes(root) {
|
|||
}
|
||||
}
|
||||
|
||||
function isProjectionOperatorExpression(body) {
|
||||
if (_.isObject(body)) {
|
||||
const keys = _.keys(body);
|
||||
return keys.length === 1 && _.contains(['$elemMatch', '$meta', '$slice'], keys[0]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param body
|
||||
* @param fieldName
|
||||
|
@ -89,10 +97,15 @@ export function createNodes(root) {
|
|||
export function addFieldNode(body, fieldName, root) {
|
||||
// it's not a link and not a special variable => we assume it's a field
|
||||
if (_.isObject(body)) {
|
||||
let dotted = dotize.convert({[fieldName]: body});
|
||||
_.each(dotted, (value, key) => {
|
||||
root.add(new FieldNode(key, value));
|
||||
});
|
||||
if (!isProjectionOperatorExpression(body)) {
|
||||
let dotted = dotize.convert({[fieldName]: body});
|
||||
_.each(dotted, (value, key) => {
|
||||
root.add(new FieldNode(key, value));
|
||||
});
|
||||
}
|
||||
else {
|
||||
root.add(new FieldNode(fieldName, body, true));
|
||||
}
|
||||
} else {
|
||||
let fieldNode = new FieldNode(fieldName, body);
|
||||
root.add(fieldNode);
|
||||
|
|
22
lib/query/nodes/collectionNode.js
Normal file → Executable file
22
lib/query/nodes/collectionNode.js
Normal file → Executable file
|
@ -87,7 +87,17 @@ export default class CollectionNode {
|
|||
let hasAddedAnyField = false;
|
||||
|
||||
_.each(this.fieldNodes, n => {
|
||||
hasAddedAnyField = true;
|
||||
/**
|
||||
* $meta field should be added to the options.fields, but MongoDB does not exclude other fields.
|
||||
* Therefore, we do not count this as a field addition.
|
||||
*
|
||||
* See: https://docs.mongodb.com/manual/reference/operator/projection/meta/
|
||||
* The $meta expression specifies the inclusion of the field to the result set
|
||||
* and does not specify the exclusion of the other fields.
|
||||
*/
|
||||
if (n.projectionOperator !== '$meta') {
|
||||
hasAddedAnyField = true;
|
||||
}
|
||||
n.applyFields(options.fields)
|
||||
});
|
||||
|
||||
|
@ -103,8 +113,8 @@ export default class CollectionNode {
|
|||
|
||||
// if he selected filters, we should automatically add those fields
|
||||
_.each(filters, (value, field) => {
|
||||
// special handling for the $meta filter and conditional operators
|
||||
if (!_.contains(['$or', '$nor', '$not', '$and', '$meta'], field)) {
|
||||
// special handling for the $meta filter, conditional operators and text search
|
||||
if (!_.contains(['$or', '$nor', '$not', '$and', '$meta', '$text'], field)) {
|
||||
// if the field or the parent of the field already exists, don't add it
|
||||
if (!_.has(options.fields, field.split('.')[0])){
|
||||
hasAddedAnyField = true;
|
||||
|
@ -114,7 +124,11 @@ export default class CollectionNode {
|
|||
});
|
||||
|
||||
if (!hasAddedAnyField) {
|
||||
options.fields = {_id: 1};
|
||||
options.fields = {
|
||||
_id: 1,
|
||||
// fields might contain $meta expression, so it should be added here,
|
||||
...options.fields,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
5
lib/query/nodes/fieldNode.js
Normal file → Executable file
5
lib/query/nodes/fieldNode.js
Normal file → Executable file
|
@ -1,7 +1,8 @@
|
|||
export default class FieldNode {
|
||||
constructor(name, body) {
|
||||
constructor(name, body, isProjectionOperator = false) {
|
||||
this.name = name;
|
||||
this.body = _.isObject(body) ? 1 : body;
|
||||
this.projectionOperator = isProjectionOperator ? _.keys(body)[0] : null;
|
||||
this.body = !_.isObject(body) || isProjectionOperator ? body : 1;
|
||||
this.scheduledForDeletion = false;
|
||||
}
|
||||
|
||||
|
|
0
lib/query/testing/client.test.js
Normal file → Executable file
0
lib/query/testing/client.test.js
Normal file → Executable file
|
@ -10,8 +10,66 @@ import './link-cache/server.test';
|
|||
// Used in some tests below
|
||||
const Users = new Mongo.Collection('__many_inversed_users');
|
||||
const Restaurants = new Mongo.Collection('__many_inversed_restaurants');
|
||||
const ShoppingCart = new Mongo.Collection('__projection_operators_cart');
|
||||
const Clients = new Mongo.Collection('__text_search_clients');
|
||||
Clients._ensureIndex({name: 'text'});
|
||||
|
||||
describe('Hypernova', function() {
|
||||
it('Should support projection operators', () => {
|
||||
ShoppingCart.remove({});
|
||||
ShoppingCart.insert({
|
||||
date: new Date(),
|
||||
items: [{
|
||||
title: 'Item 1',
|
||||
price: 30,
|
||||
}, {
|
||||
title: 'Item 2',
|
||||
price: 50,
|
||||
}],
|
||||
})
|
||||
|
||||
const data = ShoppingCart.createQuery({
|
||||
items: {$elemMatch: {price: {$gt: 40}}},
|
||||
}).fetch();
|
||||
|
||||
assert.lengthOf(data, 1);
|
||||
assert.lengthOf(data[0].items, 1);
|
||||
});
|
||||
|
||||
it('Should properly handle text search with sorting and score value projection', () => {
|
||||
Clients.remove({});
|
||||
Clients.insert({name: 'John Doe', age: 23});
|
||||
Clients.insert({name: 'John F McNull', age: 23});
|
||||
Clients.insert({name: 'Mary Smith', age: 40});
|
||||
|
||||
const data = Clients.createQuery({
|
||||
$filters: {
|
||||
$text: {$search: 'john'},
|
||||
},
|
||||
$options: {
|
||||
sort: {
|
||||
score: {$meta: 'textScore'}
|
||||
}
|
||||
},
|
||||
score: {$meta: 'textScore'},
|
||||
}).fetch();
|
||||
|
||||
assert.lengthOf(data, 2);
|
||||
data.forEach(client => {
|
||||
// unspecified fields must be excluded
|
||||
assert.isUndefined(client.name);
|
||||
assert.isUndefined(client.age);
|
||||
|
||||
// _id and score should be included
|
||||
assert.isString(client._id);
|
||||
assert.isNumber(client.score);
|
||||
});
|
||||
|
||||
// sort check
|
||||
const [client1, client2] = data;
|
||||
assert.isTrue(client1.score > client2.score);
|
||||
});
|
||||
|
||||
it('Should fetch One links correctly', function() {
|
||||
const data = createQuery({
|
||||
comments: {
|
||||
|
|
Loading…
Add table
Reference in a new issue