Skip to content

Commit 86aea28

Browse files
committed
full text compound indexes
1 parent af01977 commit 86aea28

File tree

3 files changed

+89
-55
lines changed

3 files changed

+89
-55
lines changed

spec/ParseQuery.FullTextSearch.spec.js

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ const fullTextHelper = () => {
3131
const request = {
3232
method: "POST",
3333
body: {
34-
subject: subjects[i]
34+
subject: subjects[i],
35+
comment: subjects[i],
3536
},
3637
path: "/1/classes/TestObject"
3738
};
@@ -280,42 +281,69 @@ describe('Parse.Query Full Text Search testing', () => {
280281
});
281282

282283
describe_only_db('mongo')('Parse.Query Full Text Search testing', () => {
283-
it('fullTextSearch: $search, only one text index', (done) => {
284-
return reconfigureServer({
285-
appId: 'test',
286-
restAPIKey: 'test',
287-
publicServerURL: 'http://localhost:8378/1',
288-
databaseAdapter: new MongoStorageAdapter({ uri: mongoURI })
284+
it('fullTextSearch: does not create text index if compound index exist', (done) => {
285+
fullTextHelper().then(() => {
286+
return databaseAdapter.dropAllIndexes('TestObject');
289287
}).then(() => {
290-
return rp.post({
291-
url: 'http://localhost:8378/1/batch',
292-
body: {
293-
requests: [
294-
{
295-
method: "POST",
296-
body: {
297-
subject: "coffee is java"
298-
},
299-
path: "/1/classes/TestObject"
300-
},
301-
{
302-
method: "POST",
303-
body: {
304-
subject: "java is coffee"
305-
},
306-
path: "/1/classes/TestObject"
288+
return databaseAdapter.getIndexes('TestObject');
289+
}).then((indexes) => {
290+
expect(indexes.length).toEqual(1);
291+
return databaseAdapter.createIndex('TestObject', {subject: 'text', comment: 'text'});
292+
}).then(() => {
293+
return databaseAdapter.getIndexes('TestObject');
294+
}).then((indexes) => {
295+
expect(indexes.length).toEqual(2);
296+
const where = {
297+
subject: {
298+
$text: {
299+
$search: {
300+
$term: 'coffee'
307301
}
308-
]
309-
},
310-
json: true,
302+
}
303+
}
304+
};
305+
return rp.post({
306+
url: 'http://localhost:8378/1/classes/TestObject',
307+
json: { where, '_method': 'GET' },
311308
headers: {
312309
'X-Parse-Application-Id': 'test',
313310
'X-Parse-REST-API-Key': 'test'
314311
}
315312
});
313+
}).then((resp) => {
314+
expect(resp.results.length).toEqual(3);
315+
return databaseAdapter.getIndexes('TestObject');
316+
}).then((indexes) => {
317+
expect(indexes.length).toEqual(2);
318+
done();
319+
}).catch(done.fail);
320+
});
321+
322+
it('fullTextSearch: does not create text index if schema compound index exist', (done) => {
323+
fullTextHelper().then(() => {
324+
return databaseAdapter.dropAllIndexes('TestObject');
316325
}).then(() => {
317-
return databaseAdapter.createIndex('TestObject', {random: 'text'});
326+
return databaseAdapter.getIndexes('TestObject');
327+
}).then((indexes) => {
328+
expect(indexes.length).toEqual(1);
329+
return rp.put({
330+
url: 'http://localhost:8378/1/schemas/TestObject',
331+
json: true,
332+
headers: {
333+
'X-Parse-Application-Id': 'test',
334+
'X-Parse-REST-API-Key': 'test',
335+
'X-Parse-Master-Key': 'test',
336+
},
337+
body: {
338+
indexes: {
339+
text_test: { subject: 'text', comment: 'text'},
340+
},
341+
},
342+
});
318343
}).then(() => {
344+
return databaseAdapter.getIndexes('TestObject');
345+
}).then((indexes) => {
346+
expect(indexes.length).toEqual(2);
319347
const where = {
320348
subject: {
321349
$text: {
@@ -334,12 +362,12 @@ describe_only_db('mongo')('Parse.Query Full Text Search testing', () => {
334362
}
335363
});
336364
}).then((resp) => {
337-
fail(`Should not be more than one text index: ${JSON.stringify(resp)}`);
338-
done();
339-
}).catch((err) => {
340-
expect(err.error.code).toEqual(Parse.Error.INTERNAL_SERVER_ERROR);
365+
expect(resp.results.length).toEqual(3);
366+
return databaseAdapter.getIndexes('TestObject');
367+
}).then((indexes) => {
368+
expect(indexes.length).toEqual(2);
341369
done();
342-
});
370+
}).catch(done.fail);
343371
});
344372

345373
it('fullTextSearch: $diacriticSensitive - false', (done) => {

src/Adapters/Storage/Mongo/MongoStorageAdapter.js

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ export class MongoStorageAdapter {
401401
}, {});
402402

403403
readPreference = this._parseReadPreference(readPreference);
404-
return this.createTextIndexesIfNeeded(className, query)
404+
return this.createTextIndexesIfNeeded(className, query, schema)
405405
.then(() => this._adaptiveCollection(className))
406406
.then(collection => collection.find(mongoWhere, {
407407
skip,
@@ -503,20 +503,27 @@ export class MongoStorageAdapter {
503503
return Promise.resolve();
504504
}
505505

506-
createTextIndexesIfNeeded(className, query) {
506+
createTextIndexesIfNeeded(className, query, schema) {
507507
for(const fieldName in query) {
508508
if (!query[fieldName] || !query[fieldName].$text) {
509509
continue;
510510
}
511-
const index = {
512-
[fieldName]: 'text'
511+
const existingIndexes = schema.indexes;
512+
for (const key in existingIndexes) {
513+
const index = existingIndexes[key];
514+
if (index.hasOwnProperty(fieldName)) {
515+
return Promise.resolve();
516+
}
517+
}
518+
const indexName = `${fieldName}_text`;
519+
const textIndex = {
520+
[indexName]: { [fieldName]: 'text' }
513521
};
514-
return this.createIndex(className, index)
522+
return this.setIndexes(className, textIndex, existingIndexes, schema.fields)
515523
.catch((error) => {
516-
if (error.code === 85) {
517-
throw new Parse.Error(
518-
Parse.Error.INTERNAL_SERVER_ERROR,
519-
'Only one text index is supported, please delete all text indexes to use new field.');
524+
if (error.code === 85) { // Index exist with different options
525+
return this.getIndexes(className)
526+
.then((indexes) => this.setIndexes(className, {}, indexes, schema.fields));
520527
}
521528
throw error;
522529
});
@@ -533,6 +540,11 @@ export class MongoStorageAdapter {
533540
return this._adaptiveCollection(className)
534541
.then(collection => collection._mongoCollection.dropIndex(index));
535542
}
543+
544+
dropAllIndexes(className) {
545+
return this._adaptiveCollection(className)
546+
.then(collection => collection._mongoCollection.dropIndexes());
547+
}
536548
}
537549

538550
export default MongoStorageAdapter;

src/Adapters/Storage/Postgres/PostgresStorageAdapter.js

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,29 +1457,23 @@ export class PostgresStorageAdapter {
14571457
return conn.tx(t => {
14581458
const batch = [];
14591459
indexes.forEach((index) => {
1460-
const pattern = Object.keys(index.key).map((key) => `"${key}"`);
1461-
const qs = t.none(`CREATE INDEX ${index.name} ON "${className}" (${pattern.join(',')})`);
1460+
const fieldNames = Object.keys(index.key);
1461+
const pattern = fieldNames.map((key, index) => `$${index + 3}:name`);
1462+
const qs = t.none(`CREATE INDEX $1:name ON $2:name (${pattern.join(',')})`, [index.name, className, ...fieldNames]);
14621463
batch.push(qs);
14631464
});
14641465
return t.batch(batch);
1465-
})
1466+
});
14661467
}
14671468

14681469
dropIndexes(className, indexes, conn) {
14691470
conn = conn || this._client;
1470-
return conn.tx(t => {
1471-
const batch = [];
1472-
indexes.forEach((index) => {
1473-
const qs = t.none(`DROP INDEX ${index}`);
1474-
batch.push(qs);
1475-
});
1476-
return t.batch(batch);
1477-
})
1471+
return conn.tx(t => t.batch(indexes.map(i => t.none('DROP INDEX $1:name', i))));
14781472
}
14791473

14801474
getIndexes(className) {
1481-
const qs = `SELECT * FROM pg_indexes WHERE tablename = '${className}'`;
1482-
return this._client.any(qs, []);
1475+
const qs = 'SELECT * FROM pg_indexes WHERE tablename = ${className}';
1476+
return this._client.any(qs, {className});
14831477
}
14841478
}
14851479

0 commit comments

Comments
 (0)