Skip to content

Commit 4e2021c

Browse files
benglLoïc Hermann
authored andcommitted
Use compound index when getting search candidates
1 parent 730fb10 commit 4e2021c

4 files changed

Lines changed: 253 additions & 6 deletions

File tree

lib/datastore.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,27 +255,44 @@ Datastore.prototype.updateIndexes = function (oldDoc, newDoc) {
255255
Datastore.prototype.getCandidates = function (query, dontExpireStaleDocs, callback) {
256256
var indexNames = Object.keys(this.indexes)
257257
, self = this
258-
, usableQueryKeys;
258+
, usableQueryKeys
259+
, basicQueryKeys
260+
, compoundQueryKeys;
259261

260262
if (typeof dontExpireStaleDocs === 'function') {
261263
callback = dontExpireStaleDocs;
262264
dontExpireStaleDocs = false;
263265
}
264266

265-
266267
async.waterfall([
267268
// STEP 1: get candidates list by checking indexes from most to least frequent usecase
268269
function (cb) {
269-
// For a basic match
270+
270271
usableQueryKeys = [];
271272
Object.keys(query).forEach(function (k) {
272273
if (typeof query[k] === 'string' || typeof query[k] === 'number' || typeof query[k] === 'boolean' || util.isDate(query[k]) || query[k] === null) {
273274
usableQueryKeys.push(k);
274275
}
275276
});
276-
usableQueryKeys = _.intersection(usableQueryKeys, indexNames);
277-
if (usableQueryKeys.length > 0) {
278-
return cb(null, self.indexes[usableQueryKeys[0]].getMatching(query[usableQueryKeys[0]]));
277+
278+
// For a basic match
279+
basicQueryKeys = _.intersection(usableQueryKeys, indexNames);
280+
if (basicQueryKeys.length > 0) {
281+
return cb(null, self.indexes[basicQueryKeys[0]].getMatching(query[basicQueryKeys[0]]));
282+
}
283+
284+
// For a compound match
285+
compoundQueryKeys = [];
286+
indexNames.forEach(function(indexName){
287+
if (indexName.indexOf(',') === -1) return;
288+
var subIndexNames = indexName.split(',');
289+
if (_.intersection(subIndexNames, usableQueryKeys).length === subIndexNames.length) {
290+
compoundQueryKeys.push(subIndexNames);
291+
}
292+
});
293+
294+
if (compoundQueryKeys.length > 0) {
295+
return cb(null, self.indexes[compoundQueryKeys[0]].getMatching(_.pick(query,compoundQueryKeys[0])));
279296
}
280297

281298
// For a $in match

test/db.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,28 @@ describe('Database', function () {
450450
});
451451
});
452452

453+
it('Can use a compound index to get docs with a basic match', function (done) {
454+
d.ensureIndex({ fieldName: ['tf', 'tg'] }, function (err) {
455+
d.insert({ tf: 4, tg: 0, foo: 1 }, function () {
456+
d.insert({ tf: 6, tg: 0, foo: 2 }, function () {
457+
d.insert({ tf: 4, tg: 1, foo: 3 }, function (err, _doc1) {
458+
d.insert({ tf: 6, tg: 1, foo: 4 }, function () {
459+
d.getCandidates({ tf: 4, tg: 1 }, function (err, data) {
460+
var doc1 = _.find(data, function (d) { return d._id === _doc1._id; })
461+
;
462+
463+
data.length.should.equal(1);
464+
assert.deepEqual(doc1, { _id: doc1._id, tf: 4, tg: 1, foo: 3 });
465+
466+
done();
467+
});
468+
});
469+
});
470+
});
471+
});
472+
});
473+
});
474+
453475
it('Can use an index to get docs with a $in match', function (done) {
454476
d.ensureIndex({ fieldName: 'tf' }, function (err) {
455477
d.insert({ tf: 4 }, function (err) {

test/indexes.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,32 @@ describe('Indexes', function () {
3434
doc3.a.should.equal(42);
3535
});
3636

37+
it('Can insert pointers to documents in the index correctly when they have compound fields', function () {
38+
var idx = new Index({ fieldName: ['tf', 'tg'] })
39+
, doc1 = { a: 5, tf: 'hello', tg: 'world' }
40+
, doc2 = { a: 8, tf: 'hello', tg: 'bloup' }
41+
, doc3 = { a: 2, tf: 'bloup', tg: 'bloup' }
42+
;
43+
44+
idx.insert(doc1);
45+
idx.insert(doc2);
46+
idx.insert(doc3);
47+
48+
49+
// The underlying BST now has 3 nodes which contain the docs where it's expected
50+
idx.tree.getNumberOfKeys().should.equal(3);
51+
console.log(idx.tree);
52+
assert.deepEqual(idx.tree.search({tf: 'hello', tg: 'world'}), [{ a: 5, tf: 'hello', tg: 'world' }]);
53+
assert.deepEqual(idx.tree.search({tf: 'hello', tg: 'bloup'}), [{ a: 8, tf: 'hello', tg: 'bloup' }]);
54+
assert.deepEqual(idx.tree.search({tf: 'bloup', tg: 'bloup'}), [{ a: 2, tf: 'bloup', tg: 'bloup' }]);
55+
56+
// The nodes contain pointers to the actual documents
57+
idx.tree.search({tf: 'hello', tg: 'bloup'})[0].should.equal(doc2);
58+
idx.tree.search({tf: 'bloup', tg: 'bloup'})[0].a = 42;
59+
doc3.a.should.equal(42);
60+
61+
});
62+
3763
it('Inserting twice for the same fieldName in a unique index will result in an error thrown', function () {
3864
var idx = new Index({ fieldName: 'tf', unique: true })
3965
, doc1 = { a: 5, tf: 'hello' }
@@ -44,6 +70,16 @@ describe('Indexes', function () {
4470
(function () { idx.insert(doc1); }).should.throw();
4571
});
4672

73+
it('Inserting twice for the same compound fieldName in a unique index will result in an error thrown', function () {
74+
var idx = new Index({ fieldName: ['tf', 'tg'], unique: true })
75+
, doc1 = { a: 5, tf: 'hello', tg: 'world' }
76+
;
77+
78+
idx.insert(doc1);
79+
idx.tree.getNumberOfKeys().should.equal(1);
80+
(function () { idx.insert(doc1); }).should.throw();
81+
});
82+
4783
it('Inserting twice for a fieldName the docs dont have with a unique index results in an error thrown', function () {
4884
var idx = new Index({ fieldName: 'nope', unique: true })
4985
, doc1 = { a: 5, tf: 'hello' }

yarn.lock

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1
3+
4+
5+
assertion-error@^1.0.1:
6+
version "1.1.0"
7+
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
8+
integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
9+
10+
async@0.2.10:
11+
version "0.2.10"
12+
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
13+
integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E=
14+
15+
binary-search-tree@0.2.5:
16+
version "0.2.5"
17+
resolved "https://registry.yarnpkg.com/binary-search-tree/-/binary-search-tree-0.2.5.tgz#7dbb3b210fdca082450dad2334c304af39bdc784"
18+
integrity sha1-fbs7IQ/coIJFDa0jNMMErzm9x4Q=
19+
dependencies:
20+
underscore "~1.4.4"
21+
22+
chai@^3.2.0:
23+
version "3.5.0"
24+
resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247"
25+
integrity sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=
26+
dependencies:
27+
assertion-error "^1.0.1"
28+
deep-eql "^0.1.3"
29+
type-detect "^1.0.0"
30+
31+
commander@0.6.1:
32+
version "0.6.1"
33+
resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06"
34+
integrity sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=
35+
36+
commander@1.1.1:
37+
version "1.1.1"
38+
resolved "https://registry.yarnpkg.com/commander/-/commander-1.1.1.tgz#50d1651868ae60eccff0a2d9f34595376bc6b041"
39+
integrity sha1-UNFlGGiuYOzP8KLZ80WVN2vGsEE=
40+
dependencies:
41+
keypress "0.1.x"
42+
43+
debug@*:
44+
version "4.3.1"
45+
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
46+
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
47+
dependencies:
48+
ms "2.1.2"
49+
50+
deep-eql@^0.1.3:
51+
version "0.1.3"
52+
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2"
53+
integrity sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=
54+
dependencies:
55+
type-detect "0.1.1"
56+
57+
diff@1.0.2:
58+
version "1.0.2"
59+
resolved "https://registry.yarnpkg.com/diff/-/diff-1.0.2.tgz#4ae73f1aee8d6fcf484f1a1ce77ce651d9b7f0c9"
60+
integrity sha1-Suc/Gu6Nb89ITxoc53zmUdm38Mk=
61+
62+
exec-time@0.0.2:
63+
version "0.0.2"
64+
resolved "https://registry.yarnpkg.com/exec-time/-/exec-time-0.0.2.tgz#6331dc860cc5aa97a63d956e0fd847df8b15708c"
65+
integrity sha1-YzHchgzFqpemPZVuD9hH34sVcIw=
66+
67+
growl@1.5.x:
68+
version "1.5.1"
69+
resolved "https://registry.yarnpkg.com/growl/-/growl-1.5.1.tgz#1decd1f22a4b30dae7d363799ec624cf40cc0070"
70+
integrity sha1-HezR8ipLMNrn02N5nsYkz0DMAHA=
71+
72+
immediate@~3.0.5:
73+
version "3.0.6"
74+
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
75+
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
76+
77+
jade@0.26.3:
78+
version "0.26.3"
79+
resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c"
80+
integrity sha1-jxDXl32NefL2/4YqgbBRPMslaGw=
81+
dependencies:
82+
commander "0.6.1"
83+
mkdirp "0.3.0"
84+
85+
keypress@0.1.x:
86+
version "0.1.0"
87+
resolved "https://registry.yarnpkg.com/keypress/-/keypress-0.1.0.tgz#4a3188d4291b66b4f65edb99f806aa9ae293592a"
88+
integrity sha1-SjGI1CkbZrT2XtuZ+AaqmuKTWSo=
89+
90+
lie@3.1.1:
91+
version "3.1.1"
92+
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
93+
integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=
94+
dependencies:
95+
immediate "~3.0.5"
96+
97+
localforage@^1.3.0:
98+
version "1.9.0"
99+
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.9.0.tgz#f3e4d32a8300b362b4634cc4e066d9d00d2f09d1"
100+
integrity sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g==
101+
dependencies:
102+
lie "3.1.1"
103+
104+
minimist@^1.2.5:
105+
version "1.2.5"
106+
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
107+
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
108+
109+
mkdirp@0.3.0:
110+
version "0.3.0"
111+
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
112+
integrity sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=
113+
114+
mkdirp@0.3.3:
115+
version "0.3.3"
116+
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.3.tgz#595e251c1370c3a68bab2136d0e348b8105adf13"
117+
integrity sha1-WV4lHBNww6aLqyE20ONIuBBa3xM=
118+
119+
mkdirp@~0.5.1:
120+
version "0.5.5"
121+
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
122+
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
123+
dependencies:
124+
minimist "^1.2.5"
125+
126+
mocha@1.4.x:
127+
version "1.4.3"
128+
resolved "https://registry.yarnpkg.com/mocha/-/mocha-1.4.3.tgz#f36630651e0686fe283d1bcac820b52acc091407"
129+
integrity sha1-82YwZR4Ghv4oPRvKyCC1KswJFAc=
130+
dependencies:
131+
commander "0.6.1"
132+
debug "*"
133+
diff "1.0.2"
134+
growl "1.5.x"
135+
jade "0.26.3"
136+
mkdirp "0.3.3"
137+
ms "0.3.0"
138+
139+
ms@0.3.0:
140+
version "0.3.0"
141+
resolved "https://registry.yarnpkg.com/ms/-/ms-0.3.0.tgz#03edc348d613e66a56486cfdac53bcbe899cbd61"
142+
integrity sha1-A+3DSNYT5mpWSGz9rFO8vomcvWE=
143+
144+
ms@2.1.2:
145+
version "2.1.2"
146+
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
147+
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
148+
149+
request@2.9.x:
150+
version "2.9.203"
151+
resolved "https://registry.yarnpkg.com/request/-/request-2.9.203.tgz#6c1711a5407fb94a114219563e44145bcbf4723a"
152+
integrity sha1-bBcRpUB/uUoRQhlWPkQUW8v0cjo=
153+
154+
sinon@1.3.x:
155+
version "1.3.4"
156+
resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.3.4.tgz#9fe5d746acb5e078f26f6598a18b13cfa066c4ab"
157+
integrity sha1-n+XXRqy14Hjyb2WYoYsTz6BmxKs=
158+
159+
type-detect@0.1.1:
160+
version "0.1.1"
161+
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822"
162+
integrity sha1-C6XsKohWQORw6k6FBZcZANrFiCI=
163+
164+
type-detect@^1.0.0:
165+
version "1.0.0"
166+
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2"
167+
integrity sha1-diIXzAbbJY7EiQihKY6LlRIejqI=
168+
169+
underscore@~1.4.4:
170+
version "1.4.4"
171+
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604"
172+
integrity sha1-YaajIBBiKvoHljvzJSA88SI51gQ=

0 commit comments

Comments
 (0)