Skip to content

Commit 111ebad

Browse files
authored
Merge pull request #51 from canjs/and-all-not
Adds support for $all, $and, and $not
2 parents cf00ac7 + d792e40 commit 111ebad

18 files changed

+568
-58
lines changed

.editorconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@ end_of_line = LF
44
indent_style = tab
55
trim_trailing_whitespace = false
66
insert_final_newline = true
7+
indent_size = 4
8+
9+
[{*.json,*.yml,*.md}]
10+
indent_style = space
11+
indent_size = 2

can-query-logic-test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ require("./src/types/make-real-number-range-inclusive-test");
44
require("./src/types/comparisons-test");
55
require("./src/types/and-or-not-test");
66
require("./src/types/values-or-test");
7+
require("./src/types/basic-query-test");
78
require("./src/types/basic-query-sorting-test");
89
require("./src/types/basic-query-filter-from-test");
910
require("./src/types/basic-query-merge-test");
1011
require("./src/serializers/basic-query-test");
1112
require("./src/serializers/comparisons-test");
1213
require("./src/types/make-maybe-test");
1314
require("./src/types/make-enum-test");
15+
require("./src/types/values-and-test");
1416
require("./compat/compat-test");
1517
require("./test/special-comparison-logic-test");
1618
require("./test/make-enum-logic-test");

package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@
2929
"donejs-plugin"
3030
],
3131
"steal": {
32-
"configDependencies": [
33-
"live-reload"
34-
],
3532
"npmIgnore": [
3633
"testee",
3734
"generator-donejs",

src/serializers/basic-query-test.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var logicTypes = require("../types/and-or-not");
55
var is = require("../types/comparisons");
66
var makeMaybe = require("../types/make-maybe");
77
var testHelpers = require("can-test-helpers");
8+
var ValuesAnd = require("../types/values-and");
89

910
QUnit.module("can-query-logic/serializers/basic-query");
1011

@@ -245,3 +246,72 @@ QUnit.skip("nested properties within ors", function(){
245246
}), "adds nested ands");
246247
});
247248
*/
249+
250+
QUnit.test("Complex queries with nested $not, $all", function(assert) {
251+
var query = {
252+
filter: {
253+
$and: [
254+
{tags: {$all: ['sbux']}},
255+
{tags: {$not: {$all: ['dfw']}}}
256+
]
257+
}
258+
};
259+
260+
var converter = makeBasicQueryConvert(EmptySchema);
261+
var basicQuery = converter.hydrate(query);
262+
263+
assert.ok(basicQuery.filter instanceof ValuesAnd);
264+
265+
var res = converter.serializer.serialize(basicQuery);
266+
assert.deepEqual(res, query);
267+
});
268+
269+
QUnit.test("Inverting $not comparisons", function(assert) {
270+
[
271+
{
272+
query: {filter: {age:{$not: {$lt: 5}}} },
273+
expectedInstance: is.GreaterThanEqual,
274+
expectedValue: 5
275+
},
276+
{
277+
query: {filter: {age:{$not: {$lte: 5}}} },
278+
expectedInstance: is.GreaterThan,
279+
expectedValue: 5
280+
},
281+
{
282+
query: {filter: {age:{$not: {$gt: 5}}}},
283+
expectedInstance: is.LessThanEqual,
284+
expectedValue: 5
285+
},
286+
{
287+
query: {filter: {age:{$not: {$gte: 5}}}},
288+
expectedInstance: is.LessThan,
289+
expectedValue: 5
290+
},
291+
{
292+
query: {filter: {age:{$not: {$in: [2, 3]}}}},
293+
expectedInstance: is.NotIn,
294+
expectedValue: [2, 3],
295+
valueProp: "values"
296+
},
297+
{
298+
query: {filter: {age:{$not: {$nin: [2, 3]}}}},
299+
expectedInstance: is.In,
300+
expectedValue: [2, 3],
301+
valueProp: "values"
302+
}
303+
].forEach(function(options) {
304+
var query = options.query;
305+
var ExpectedInstance = options.expectedInstance;
306+
var expectedValue = options.expectedValue;
307+
var prop = options.valueProp || "value";
308+
309+
var converter = makeBasicQueryConvert(EmptySchema);
310+
var basicQuery = converter.hydrate(query);
311+
312+
assert.ok(basicQuery.filter.values.age instanceof ExpectedInstance, "changed to right instance type");
313+
assert.deepEqual(basicQuery.filter.values.age[prop],
314+
expectedValue, "has the correct value");
315+
316+
});
317+
});

src/serializers/basic-query.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ function getSchemaProperties(value) {
2727
}
2828

2929
function hydrateFilter(values, schemaProperties, hydrateUnknown) {
30-
if (values && typeof values === "object" && ("$or" in values)) {
30+
var valuesIsObject = values && typeof values === "object";
31+
if (valuesIsObject && ("$or" in values)) {
3132
return hydrateOrs(values.$or, schemaProperties, hydrateUnknown);
33+
} else if(valuesIsObject && ("$and" in values)) {
34+
return hydrateAnds(values.$and, schemaProperties, hydrateUnknown);
3235
} else {
3336
return hydrateAndValues(values, schemaProperties, hydrateUnknown);
3437
}
@@ -194,6 +197,13 @@ function hydrateOrs(values, schemaProperties, hydrateUnknown) {
194197
return new BasicQuery.Or(comparisons);
195198
}
196199

200+
function hydrateAnds(values, schemaProperties, hydrateUnknown) {
201+
var comparisons = values.map(function(value) {
202+
return hydrateAndValues(value, schemaProperties, hydrateUnknown);
203+
});
204+
return new BasicQuery.And(comparisons);
205+
}
206+
197207
function recursivelyAddOrs(ors, value, serializer, key){
198208
value.orValues().forEach(function(orValue){
199209
if(typeof orValue.orValues === "function") {
@@ -217,6 +227,14 @@ module.exports = function(schema) {
217227
return serializer(value);
218228
});
219229
}],
230+
[BasicQuery.And, function(and, serializer) {
231+
return { $and: and.values.map(function(value) {
232+
return serializer(value);
233+
}) };
234+
}],
235+
[BasicQuery.Not, function(nots, serializer) {
236+
return { $not: serializer(nots.value) };
237+
}],
220238
// this destructures ANDs with OR-like clauses
221239
[BasicQuery.KeysAnd, function(and, serializer) {
222240
var ors = [];
@@ -230,6 +248,7 @@ module.exports = function(schema) {
230248
result[key] = serializer(value);
231249
}
232250
});
251+
233252
if (ors.length) {
234253
if (ors.length === 1) {
235254
return ors[0];

src/serializers/comparisons-test.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
var QUnit = require("steal-qunit");
22
var comparisons = require("./comparisons");
33
var canReflect = require("can-reflect");
4+
var is = require("../types/comparisons");
5+
var ValuesNot = require("../types/values-not");
46

57
QUnit.module("can-query-logic/serializers/comparisons");
68

7-
QUnit.test("hydrate and serialize", function(assert) {
9+
QUnit.test("hydrate and serialize with custom types that work with operators", function(assert) {
810
var Type = function(value){
911
this.value = value;
1012
};
@@ -32,7 +34,6 @@ QUnit.test("hydrate and serialize", function(assert) {
3234
});
3335

3436
QUnit.test("unknown hydrator is called in all cases", function(assert) {
35-
3637
var hydrated = [];
3738
var addToHydrated = function(value){
3839
hydrated.push(value);
@@ -44,3 +45,13 @@ QUnit.test("unknown hydrator is called in all cases", function(assert) {
4445

4546
assert.deepEqual(hydrated, [1,2, "abc","x","y"], "hydrated called with the right stuff");
4647
});
48+
49+
50+
QUnit.test("$not and $all can work recursively", function(assert){
51+
// WHat if {$not: 1} //-> is.NotIn([1]) | new is.ValuesNot(new is.In([1]))
52+
var hydrated = comparisons.hydrate( {$not: {$all: ['def']}}, function(value){
53+
return value;
54+
} );
55+
56+
assert.ok(hydrated instanceof ValuesNot, "is an instance");
57+
});

src/serializers/comparisons.js

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
var is = require("../types/comparisons");
22
var Serializer = require("../serializer");
33
var canReflect = require("can-reflect");
4+
var ValuesNot = require("../types/values-not");
5+
var ValuesAnd = require("../types/values-and");
46

57
function makeNew(Constructor) {
68
return function(value){
@@ -47,10 +49,37 @@ addHydrateFrom("$gte", makeNew(is.GreaterThanEqual));
4749
addHydrateFromValues("$in", makeNew(is.In));
4850
addHydrateFrom("$lt", makeNew(is.LessThan));
4951
addHydrateFrom("$lte", makeNew(is.LessThanEqual));
50-
addHydrateFromValues("$nin", makeNew(is.GreaterThan));
5152

53+
addHydrateFromValues("$all", makeNew(is.All));
5254

55+
// This is a mapping of types to their opposite. The $not hydrator
56+
// uses this to create a more specific type, since they are logical opposites.
57+
var oppositeTypeMap = {
58+
LessThan: { Type: is.GreaterThanEqual, prop: "value" },
59+
LessThanEqual: { Type: is.GreaterThan, prop: "value" },
60+
GreaterThan: { Type: is.LessThanEqual, prop: "value" },
61+
GreaterThanEqual: { Type: is.LessThan, prop: "value" },
62+
In: { Type: is.NotIn, prop: "values" },
63+
NotIn: { Type: is.In, prop: "values" }
64+
};
65+
66+
hydrateMap["$not"] = function(value, unknownHydrator) {
67+
// Many nots can be hydrated to their opposite.
68+
var hydratedValue = hydrateValue(value["$not"], unknownHydrator);
69+
var typeName = hydratedValue.constructor.name;
5370

71+
if(oppositeTypeMap[typeName]) {
72+
var options = oppositeTypeMap[typeName];
73+
var OppositeConstructor = options.Type;
74+
var prop = options.prop;
75+
76+
return new OppositeConstructor(hydratedValue[prop]);
77+
}
78+
79+
return new ValuesNot(hydratedValue);
80+
};
81+
82+
addHydrateFromValues("$nin", makeNew(is.NotIn));
5483

5584

5685
var serializer = new Serializer([
@@ -73,7 +102,8 @@ var serializer = new Serializer([
73102
canReflect.assignMap(obj, serialize(clause) );
74103
});
75104
return obj;
76-
}]
105+
}],
106+
[is.All, function(all, serialize) { return {$all: serialize(all.values)}}]
77107
/*[is.Or, function(or, serialize){
78108
return {
79109
$or: or.values.map(function(value){
@@ -83,41 +113,44 @@ var serializer = new Serializer([
83113
}]*/
84114
]);
85115

86-
module.exports = {
87-
hydrate: function(value, hydrateUnknown){
88-
if(!hydrateUnknown) {
89-
hydrateUnknown = function(){
90-
throw new Error("can-query-logic doesn't recognize operator: "+JSON.stringify(value));
91-
}
116+
function hydrateValue(value, hydrateUnknown){
117+
if(!hydrateUnknown) {
118+
hydrateUnknown = function(){
119+
throw new Error("can-query-logic doesn't recognize operator: "+JSON.stringify(value));
92120
}
93-
if(Array.isArray(value)) {
94-
return new is.In(value.map(function(value){
95-
return hydrateUnknown(value);
96-
}));
97-
}
98-
else if(value && typeof value === "object") {
99-
var keys = Object.keys(value);
100-
var allKeysAreComparisons = keys.every(function(key){
101-
return hydrateMap[key]
121+
}
122+
if(Array.isArray(value)) {
123+
return new is.In(value.map(function(value){
124+
return hydrateUnknown(value);
125+
}));
126+
}
127+
else if(value && typeof value === "object") {
128+
var keys = Object.keys(value);
129+
var allKeysAreComparisons = keys.every(function(key){
130+
return hydrateMap[key]
131+
});
132+
if(allKeysAreComparisons) {
133+
var andClauses = keys.map(function(key){
134+
var part = {};
135+
part[key] = value[key];
136+
var hydrator = hydrateMap[key];
137+
return hydrator(part, hydrateUnknown);
102138
});
103-
if(allKeysAreComparisons) {
104-
var andClauses = keys.map(function(key){
105-
var part = {};
106-
part[key] = value[key];
107-
var hydrator = hydrateMap[key];
108-
return hydrator(part, hydrateUnknown);
109-
});
110-
if(andClauses.length > 1) {
111-
return new is.And(andClauses);
112-
} else {
113-
return andClauses[0];
114-
}
139+
if(andClauses.length > 1) {
140+
return new is.And(andClauses);
115141
} else {
116-
return hydrateUnknown(value);
142+
return andClauses[0];
117143
}
118144
} else {
119-
return new is.In([hydrateUnknown(value)]);
145+
return hydrateUnknown(value);
120146
}
121-
},
147+
} else {
148+
return new is.In([hydrateUnknown(value)]);
149+
}
150+
}
151+
152+
module.exports = {
153+
// value - something from a query, for example {$in: [1,2]}
154+
hydrate: hydrateValue,
122155
serializer: serializer
123156
};

src/types/and-or-not.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
var ValuesOr = require("./values-or");
22
var ValuesNot = require("./values-not");
3+
var ValuesAnd = require("./values-and");
34
var KeysAnd = require("./keys-and");
45

56
module.exports = {
67
KeysAnd: KeysAnd,
78
ValuesOr: ValuesOr,
8-
ValuesNot: ValuesNot
9+
ValuesNot: ValuesNot,
10+
ValuesAnd: ValuesAnd
911
};

0 commit comments

Comments
 (0)