Skip to content

Commit 417c292

Browse files
authored
Merge pull request #4290 from cardstack/cs-10561-add-in-filter-operator-to-card-query-language
2 parents 832057b + 73cd257 commit 417c292

7 files changed

Lines changed: 623 additions & 1 deletion

File tree

packages/host/tests/unit/index-query-engine-test.ts

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,210 @@ module('Unit | query', function (hooks) {
740740
);
741741
});
742742

743+
test(`can filter using 'in'`, async function (assert) {
744+
let { mango, vangogh, ringo } = testCards;
745+
await setupIndex(dbAdapter, [
746+
{ card: mango, data: { search_doc: { name: 'Mango' } } },
747+
{ card: vangogh, data: { search_doc: { name: 'Van Gogh' } } },
748+
{ card: ringo, data: { search_doc: { name: 'Ringo' } } },
749+
]);
750+
751+
let type = await personCardType(testCards);
752+
let { cards: results, meta } = await indexQueryEngine.searchCards(
753+
new URL(testRealmURL),
754+
{
755+
filter: {
756+
in: { name: ['Mango', 'Ringo'] },
757+
on: type,
758+
},
759+
},
760+
);
761+
762+
assert.strictEqual(meta.page.total, 2, 'the total results meta is correct');
763+
assert.deepEqual(
764+
getIds(results),
765+
[mango.id, ringo.id],
766+
'results are correct',
767+
);
768+
});
769+
770+
test(`can filter using 'in' with a single value`, async function (assert) {
771+
let { mango, vangogh } = testCards;
772+
await setupIndex(dbAdapter, [
773+
{ card: mango, data: { search_doc: { name: 'Mango' } } },
774+
{ card: vangogh, data: { search_doc: { name: 'Van Gogh' } } },
775+
]);
776+
777+
let type = await personCardType(testCards);
778+
let { cards: results, meta } = await indexQueryEngine.searchCards(
779+
new URL(testRealmURL),
780+
{
781+
filter: {
782+
in: { name: ['Mango'] },
783+
on: type,
784+
},
785+
},
786+
);
787+
788+
assert.strictEqual(meta.page.total, 1, 'the total results meta is correct');
789+
assert.deepEqual(getIds(results), [mango.id], 'results are correct');
790+
});
791+
792+
test(`can filter using 'in' with an empty array`, async function (assert) {
793+
let { mango, vangogh } = testCards;
794+
await setupIndex(dbAdapter, [
795+
{ card: mango, data: { search_doc: { name: 'Mango' } } },
796+
{ card: vangogh, data: { search_doc: { name: 'Van Gogh' } } },
797+
]);
798+
799+
let type = await personCardType(testCards);
800+
let { cards: results, meta } = await indexQueryEngine.searchCards(
801+
new URL(testRealmURL),
802+
{
803+
filter: {
804+
in: { name: [] },
805+
on: type,
806+
},
807+
},
808+
);
809+
810+
assert.strictEqual(meta.page.total, 0, 'the total results meta is correct');
811+
assert.deepEqual(getIds(results), [], 'results are correct');
812+
});
813+
814+
test(`can filter using 'in' with null values`, async function (assert) {
815+
let { mango, vangogh, ringo } = testCards;
816+
await setupIndex(dbAdapter, [
817+
{ card: mango, data: { search_doc: { name: 'Mango' } } },
818+
{ card: vangogh, data: { search_doc: { name: null } } },
819+
{ card: ringo, data: { search_doc: { name: 'Ringo' } } },
820+
]);
821+
822+
let type = await personCardType(testCards);
823+
let { cards: results, meta } = await indexQueryEngine.searchCards(
824+
new URL(testRealmURL),
825+
{
826+
filter: {
827+
in: { name: ['Mango', null] },
828+
on: type,
829+
},
830+
},
831+
);
832+
833+
assert.strictEqual(meta.page.total, 2, 'the total results meta is correct');
834+
assert.deepEqual(
835+
getIds(results),
836+
[mango.id, vangogh.id],
837+
'results are correct',
838+
);
839+
});
840+
841+
test(`can filter using 'in' thru nested fields`, async function (assert) {
842+
let { mango, vangogh, ringo } = testCards;
843+
await setupIndex(dbAdapter, [
844+
{
845+
card: mango,
846+
data: {
847+
search_doc: {
848+
name: 'Mango',
849+
address: { city: 'Barksville' },
850+
},
851+
},
852+
},
853+
{
854+
card: vangogh,
855+
data: {
856+
search_doc: {
857+
name: 'Van Gogh',
858+
address: { city: 'Barksville' },
859+
},
860+
},
861+
},
862+
{
863+
card: ringo,
864+
data: {
865+
search_doc: {
866+
name: 'Ringo',
867+
address: { city: 'Waggington' },
868+
},
869+
},
870+
},
871+
]);
872+
873+
let type = await personCardType(testCards);
874+
let { cards: results, meta } = await indexQueryEngine.searchCards(
875+
new URL(testRealmURL),
876+
{
877+
filter: {
878+
on: type,
879+
in: { 'address.city': ['Waggington'] },
880+
},
881+
},
882+
);
883+
884+
assert.strictEqual(meta.page.total, 1, 'the total results meta is correct');
885+
assert.deepEqual(getIds(results), [ringo.id], 'results are correct');
886+
});
887+
888+
test(`can negate an 'in' filter with 'not'`, async function (assert) {
889+
let { mango, vangogh, ringo } = testCards;
890+
await setupIndex(dbAdapter, [
891+
{ card: mango, data: { search_doc: { name: 'Mango' } } },
892+
{ card: vangogh, data: { search_doc: { name: 'Van Gogh' } } },
893+
{ card: ringo, data: { search_doc: { name: 'Ringo' } } },
894+
]);
895+
896+
let type = await personCardType(testCards);
897+
let { cards: results, meta } = await indexQueryEngine.searchCards(
898+
new URL(testRealmURL),
899+
{
900+
filter: {
901+
on: type,
902+
not: { in: { name: ['Mango', 'Ringo'] } },
903+
},
904+
},
905+
);
906+
907+
assert.strictEqual(meta.page.total, 1, 'the total results meta is correct');
908+
assert.deepEqual(getIds(results), [vangogh.id], 'results are correct');
909+
});
910+
911+
test(`can filter using 'in' with the id field`, async function (assert) {
912+
let { mango, vangogh, ringo } = testCards;
913+
await setupIndex(dbAdapter, [
914+
{
915+
card: mango,
916+
data: { search_doc: { id: mango.id, name: 'Mango' } },
917+
},
918+
{
919+
card: vangogh,
920+
data: { search_doc: { id: vangogh.id, name: 'Van Gogh' } },
921+
},
922+
{
923+
card: ringo,
924+
data: { search_doc: { id: ringo.id, name: 'Ringo' } },
925+
},
926+
]);
927+
928+
let type = await personCardType(testCards);
929+
let { cards: results, meta } = await indexQueryEngine.searchCards(
930+
new URL(testRealmURL),
931+
{
932+
filter: {
933+
on: type,
934+
in: { id: [mango.id, ringo.id] },
935+
},
936+
},
937+
);
938+
939+
assert.strictEqual(meta.page.total, 2, 'the total results meta is correct');
940+
assert.deepEqual(
941+
getIds(results),
942+
[mango.id, ringo.id],
943+
'results are correct',
944+
);
945+
});
946+
743947
test(`can search with a 'not' filter`, async function (assert) {
744948
let { mango, vangogh, ringo } = testCards;
745949
await setupIndex(dbAdapter, [

packages/host/tests/unit/query-field-normalization-test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,55 @@ module('normalizeQueryDefinition', function () {
239239
});
240240
});
241241

242+
test('resolves in filter with array interpolation from containsMany field', function (assert) {
243+
let realmURL = new URL('https://realm.example/');
244+
let instance = { tags: ['red', 'blue', 'green'] };
245+
let relativeTo = new URL('https://realm.example/cards/instance');
246+
247+
let normalized = normalizeQueryDefinition({
248+
fieldDefinition,
249+
queryDefinition: {
250+
filter: { in: { color: '$this.tags' } },
251+
},
252+
realmURL,
253+
fieldName: 'matchingItems',
254+
resolvePathValue: (path) => resolvePath(instance, path),
255+
relativeTo,
256+
});
257+
258+
let targetRef = codeRefWithAbsoluteURL(
259+
fieldDefinition.fieldOrCard,
260+
relativeTo,
261+
);
262+
assert.deepEqual(normalized?.query.filter, {
263+
in: { color: ['red', 'blue', 'green'] },
264+
on: targetRef,
265+
});
266+
});
267+
268+
test('aborts in filter when interpolated array is undefined', function (assert) {
269+
let realmURL = new URL('https://realm.example/');
270+
let instance = {};
271+
let relativeTo = new URL('https://realm.example/cards/instance');
272+
273+
let normalized = normalizeQueryDefinition({
274+
fieldDefinition,
275+
queryDefinition: {
276+
filter: { in: { color: '$this.tags' } },
277+
},
278+
realmURL,
279+
fieldName: 'matchingItems',
280+
resolvePathValue: (path) => resolvePath(instance, path),
281+
relativeTo,
282+
});
283+
284+
assert.strictEqual(
285+
normalized,
286+
null,
287+
'query is aborted when in value is undefined',
288+
);
289+
});
290+
242291
test('resolves live instances via custom path resolver', function (assert) {
243292
let realmURL = new URL('https://realm.example/');
244293
let instance = { address: { city: 'Paris' } };

0 commit comments

Comments
 (0)