-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* refactor: hoist find logic into Engine class * refactor: move query execution to Query class * feat(query): add $is to query input * feat(query): add $contains for string and array properties * feat: deeply nested queries * feat: index nested properties * feat: nested array index properties * feat: use [*] to index nested data in an array
- Loading branch information
1 parent
a22f397
commit fa51c34
Showing
14 changed files
with
283 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/** | ||
* persist query language features: | ||
* - value match {title: 'test'} or {title: {$is: 'test'}} | ||
* - contains match {list: {$contains: 'test'}} or {string: {$contains: 'es'}} | ||
* - nested query {list: {$contains: {slug: 'test'}}} | ||
* - deep nesting queries {list: {$contains: {string: {$contains: 'test'}}}} | ||
*/ | ||
|
||
/** | ||
* @class Query | ||
*/ | ||
class Query { | ||
query; | ||
|
||
/** | ||
* | ||
* @param {object} query | ||
*/ | ||
constructor(query) { | ||
this.query = query; | ||
} | ||
|
||
/** | ||
* Using the input query, find records in an index that match | ||
* | ||
* @param {typeof Model} model | ||
* @param {object} index | ||
*/ | ||
execute(model, index) { | ||
const matchIs = (query) => !!query?.$is; | ||
const matchPrimitive = (query) => ['string', 'number', 'boolean'].includes(typeof query); | ||
const matchContains = (query) => !!query?.$contains; | ||
|
||
const matchesQuery = (subject, inputQuery = this.query) => { | ||
if (!subject || !inputQuery) return false; | ||
|
||
if (matchPrimitive(inputQuery)) return subject === inputQuery; | ||
|
||
if (matchIs(inputQuery)) | ||
if (subject === inputQuery.$is) return true; | ||
|
||
if (matchContains(inputQuery)) { | ||
if (subject.includes?.(inputQuery.$contains)) return true; | ||
|
||
for (const value of subject) { | ||
if (matchesQuery(value, inputQuery.$contains)) return true; | ||
} | ||
} | ||
|
||
for (const key of Object.keys(inputQuery)) { | ||
if (!['$is', '$contains'].includes(key)) | ||
if (matchesQuery(subject[key], inputQuery[key])) return true; | ||
} | ||
}; | ||
|
||
return Object.values(index) | ||
.filter(m => matchesQuery(m)) | ||
.map(m => model.fromData(m)); | ||
} | ||
} | ||
|
||
export default Query; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import {MainModel} from '../test/fixtures/TestModel.js'; | ||
import Query from './Query.js'; | ||
import {TestIndex} from '../test/fixtures/TestIndex.js'; | ||
import test from 'ava'; | ||
|
||
test('new Query(query) stores the query', t => { | ||
const query = new Query({string: 'test'}); | ||
|
||
t.deepEqual(query.query, {string: 'test'}); | ||
}); | ||
|
||
test('Query.execute(index) finds exact matches with primitive types', t => { | ||
const query = new Query({string: 'test'}); | ||
const results = query.execute(MainModel, TestIndex); | ||
|
||
t.deepEqual(results, [ | ||
MainModel.fromData({ | ||
id: 'MainModel/000000000000', | ||
string: 'test', | ||
arrayOfString: ['test'], | ||
linkedMany: [{ | ||
id: 'LinkedManyModel/000000000000000', | ||
string: 'test', | ||
}], | ||
}), | ||
]); | ||
}); | ||
|
||
test('Query.execute(index) finds exact matches with $is', t => { | ||
const query = new Query({string: {$is: 'test'}}); | ||
const results = query.execute(MainModel, TestIndex); | ||
|
||
t.deepEqual(results, [ | ||
MainModel.fromData({ | ||
id: 'MainModel/000000000000', | ||
string: 'test', | ||
arrayOfString: ['test'], | ||
linkedMany: [{ | ||
id: 'LinkedManyModel/000000000000000', | ||
string: 'test', | ||
}], | ||
}), | ||
]); | ||
}); | ||
|
||
test('Query.execute(index) finds matches containing for strings', t => { | ||
const query = new Query({string: {$contains: 'test'}}); | ||
const results = query.execute(MainModel, TestIndex); | ||
|
||
t.deepEqual(results, [ | ||
MainModel.fromData({ | ||
id: 'MainModel/000000000000', | ||
string: 'test', | ||
arrayOfString: ['test'], | ||
linkedMany: [{ | ||
id: 'LinkedManyModel/000000000000000', | ||
string: 'test', | ||
}], | ||
}), | ||
MainModel.fromData({ | ||
id: 'MainModel/111111111111', | ||
string: 'testing', | ||
arrayOfString: ['testing'], | ||
linkedMany: [{ | ||
id: 'LinkedManyModel/111111111111', | ||
string: 'testing', | ||
}], | ||
}), | ||
]); | ||
}); | ||
|
||
test('Query.execute(index) finds matches containing for arrays', t => { | ||
const query = new Query({arrayOfString: {$contains: 'test'}}); | ||
const results = query.execute(MainModel, TestIndex); | ||
|
||
t.deepEqual(results, [ | ||
MainModel.fromData({ | ||
id: 'MainModel/000000000000', | ||
string: 'test', | ||
arrayOfString: ['test'], | ||
linkedMany: [{ | ||
id: 'LinkedManyModel/000000000000000', | ||
string: 'test', | ||
}], | ||
}), | ||
]); | ||
}); | ||
|
||
test('Query.execute(index) finds exact matches for elements in arrays', t => { | ||
const query = new Query({linkedMany: {$contains: {string: 'test'}}}); | ||
const results = query.execute(MainModel, TestIndex); | ||
|
||
t.deepEqual(results, [ | ||
MainModel.fromData({ | ||
id: 'MainModel/000000000000', | ||
string: 'test', | ||
arrayOfString: ['test'], | ||
linkedMany: [{ | ||
id: 'LinkedManyModel/000000000000000', | ||
string: 'test', | ||
}], | ||
}), | ||
]); | ||
}); | ||
|
||
test('Query.execute(index) finds partial matches for elements in arrays', t => { | ||
const query = new Query({linkedMany: {$contains: {string: {$contains: 'test'}}}}); | ||
const results = query.execute(MainModel, TestIndex); | ||
|
||
t.deepEqual(results, [ | ||
MainModel.fromData({ | ||
id: 'MainModel/000000000000', | ||
string: 'test', | ||
arrayOfString: ['test'], | ||
linkedMany: [{ | ||
id: 'LinkedManyModel/000000000000000', | ||
string: 'test', | ||
}], | ||
}), | ||
MainModel.fromData({ | ||
id: 'MainModel/111111111111', | ||
string: 'testing', | ||
arrayOfString: ['testing'], | ||
linkedMany: [{ | ||
id: 'LinkedManyModel/111111111111', | ||
string: 'testing', | ||
}], | ||
}), | ||
]); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.