Skip to content

Commit 10eaa95

Browse files
authored
Merge pull request #1132 from cardstack/cs-6657-containsmany-field-support
Query support for plural fields
2 parents 7a43d36 + e94edfe commit 10eaa95

File tree

4 files changed

+587
-98
lines changed

4 files changed

+587
-98
lines changed

packages/base/card-api.gts

+2-2
Original file line numberDiff line numberDiff line change
@@ -961,7 +961,7 @@ class LinksTo<CardT extends CardDefConstructor> implements Field<CardT> {
961961
}
962962
if (!(value instanceof this.card)) {
963963
throw new Error(
964-
`tried set ${value} as field '${this.name}' but it is not an instance of ${this.card.name}`,
964+
`tried set ${value.constructor.name} as field '${this.name}' but it is not an instance of ${this.card.name}`,
965965
);
966966
}
967967
}
@@ -1313,7 +1313,7 @@ class LinksToMany<FieldT extends CardDefConstructor>
13131313
for (let value of values) {
13141314
if (!isNotLoadedValue(value) && !(value instanceof this.card)) {
13151315
throw new Error(
1316-
`tried set ${value} as field '${this.name}' but it is not an instance of ${this.card.name}`,
1316+
`tried set ${value.constructor.name} as field '${this.name}' but it is not an instance of ${this.card.name}`,
13171317
);
13181318
}
13191319
}

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

+312-11
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,27 @@ module('Unit | query', function (hooks) {
3535
cardApi = await loader.import(`${baseRealm.url}card-api`);
3636
string = await loader.import(`${baseRealm.url}string`);
3737

38-
let { field, contains, CardDef, FieldDef } = cardApi;
38+
let {
39+
field,
40+
contains,
41+
containsMany,
42+
linksToMany,
43+
linksTo,
44+
CardDef,
45+
FieldDef,
46+
setCardAsSavedForTest,
47+
} = cardApi;
3948
let { default: StringField } = string;
4049
class Address extends FieldDef {
4150
@field street = contains(StringField);
4251
@field city = contains(StringField);
4352
}
4453
class Person extends CardDef {
4554
@field name = contains(StringField);
55+
@field nickNames = containsMany(StringField);
4656
@field address = contains(Address);
57+
@field bestFriend = linksTo(() => Person);
58+
@field friends = linksToMany(() => Person);
4759
}
4860
class FancyPerson extends Person {
4961
@field favoriteColor = contains(StringField);
@@ -56,12 +68,12 @@ module('Unit | query', function (hooks) {
5668
loader.shimModule(`${testRealmURL}fancy-person`, { FancyPerson });
5769
loader.shimModule(`${testRealmURL}cat`, { Cat });
5870

59-
let mango = new FancyPerson({
60-
id: `${testRealmURL}mango`,
61-
name: 'Mango',
71+
let ringo = new Person({
72+
id: `${testRealmURL}ringo`,
73+
name: 'Ringo',
6274
address: new Address({
63-
street: '123 Main Street',
64-
city: 'Barksville',
75+
street: '100 Treat Street',
76+
city: 'Waggington',
6577
}),
6678
});
6779
let vangogh = new Person({
@@ -71,14 +83,18 @@ module('Unit | query', function (hooks) {
7183
street: '456 Grand Blvd',
7284
city: 'Barksville',
7385
}),
86+
bestFriend: ringo,
87+
friends: [ringo],
7488
});
75-
let ringo = new Person({
76-
id: `${testRealmURL}ringo`,
77-
name: 'Ringo',
89+
let mango = new FancyPerson({
90+
id: `${testRealmURL}mango`,
91+
name: 'Mango',
7892
address: new Address({
79-
street: '100 Treat Street',
80-
city: 'Waggington',
93+
street: '123 Main Street',
94+
city: 'Barksville',
8195
}),
96+
bestFriend: vangogh,
97+
friends: [vangogh, ringo],
8298
});
8399
let paper = new Cat({ id: `${testRealmURL}paper`, name: 'Paper' });
84100
testCards = {
@@ -87,6 +103,9 @@ module('Unit | query', function (hooks) {
87103
ringo,
88104
paper,
89105
};
106+
for (let card of Object.values(testCards)) {
107+
setCardAsSavedForTest(card);
108+
}
90109

91110
adapter = new SQLiteAdapter(sqlSchema);
92111
client = new IndexerDBClient(adapter);
@@ -359,4 +378,286 @@ module('Unit | query', function (hooks) {
359378
);
360379
}
361380
});
381+
382+
test(`it can filter on a plural primitive field using 'eq'`, async function (assert) {
383+
let { mango, vangogh } = testCards;
384+
await setupIndex(client, [
385+
{
386+
card: mango,
387+
data: {
388+
search_doc: {
389+
name: 'Mango',
390+
nickNames: ['Mang Mang', 'Baby'],
391+
},
392+
},
393+
},
394+
{
395+
card: vangogh,
396+
data: {
397+
search_doc: {
398+
name: 'Van Gogh',
399+
nickNames: ['Big boy', 'Farty'],
400+
},
401+
},
402+
},
403+
]);
404+
405+
let { cards, meta } = await client.search(
406+
{
407+
filter: {
408+
on: { module: `${testRealmURL}person`, name: 'Person' },
409+
eq: { nickNames: 'Farty' },
410+
},
411+
},
412+
loader,
413+
);
414+
415+
assert.strictEqual(meta.page.total, 1, 'the total results meta is correct');
416+
assert.deepEqual(
417+
cards,
418+
[await serializeCard(vangogh)],
419+
'results are correct',
420+
);
421+
});
422+
423+
test(`it can filter on a nested field within a plural composite field using 'eq'`, async function (assert) {
424+
let { mango, vangogh } = testCards;
425+
await setupIndex(client, [
426+
{
427+
card: mango,
428+
data: {
429+
search_doc: {
430+
name: 'Mango',
431+
friends: [
432+
{
433+
name: 'Van Gogh',
434+
},
435+
{ name: 'Ringo' },
436+
],
437+
},
438+
},
439+
},
440+
{
441+
card: vangogh,
442+
data: {
443+
search_doc: {
444+
name: 'Van Gogh',
445+
friends: [{ name: 'Ringo' }],
446+
},
447+
},
448+
},
449+
]);
450+
451+
{
452+
let { cards, meta } = await client.search(
453+
{
454+
filter: {
455+
on: { module: `${testRealmURL}person`, name: 'Person' },
456+
eq: { 'friends.name': 'Van Gogh' },
457+
},
458+
},
459+
loader,
460+
);
461+
462+
assert.strictEqual(
463+
meta.page.total,
464+
1,
465+
'the total results meta is correct',
466+
);
467+
assert.deepEqual(
468+
cards,
469+
[await serializeCard(mango)],
470+
'results are correct',
471+
);
472+
}
473+
{
474+
let { cards, meta } = await client.search(
475+
{
476+
filter: {
477+
on: { module: `${testRealmURL}person`, name: 'Person' },
478+
eq: { 'friends.name': 'Ringo' },
479+
},
480+
},
481+
loader,
482+
);
483+
484+
assert.strictEqual(
485+
meta.page.total,
486+
2,
487+
'the total results meta is correct',
488+
);
489+
assert.deepEqual(
490+
cards,
491+
[await serializeCard(mango), await serializeCard(vangogh)],
492+
'results are correct',
493+
);
494+
}
495+
});
496+
497+
test('it can match a null in a plural field', async function (assert) {
498+
let { mango, vangogh } = testCards;
499+
await setupIndex(client, [
500+
{
501+
card: mango,
502+
data: {
503+
search_doc: {
504+
name: 'Mango',
505+
nickNames: ['Mang Mang', 'Baby'],
506+
},
507+
},
508+
},
509+
{
510+
card: vangogh,
511+
data: {
512+
search_doc: {
513+
name: 'Van Gogh',
514+
nickNames: null,
515+
},
516+
},
517+
},
518+
]);
519+
520+
let { cards, meta } = await client.search(
521+
{
522+
filter: {
523+
on: { module: `${testRealmURL}person`, name: 'Person' },
524+
eq: { nickNames: null },
525+
},
526+
},
527+
loader,
528+
);
529+
530+
assert.strictEqual(meta.page.total, 1, 'the total results meta is correct');
531+
assert.deepEqual(
532+
cards,
533+
[await serializeCard(vangogh)],
534+
'results are correct',
535+
);
536+
});
537+
538+
test('it can match a leaf plural field nested in a plural composite field', async function (assert) {
539+
let { mango, vangogh } = testCards;
540+
await setupIndex(client, [
541+
{
542+
card: mango,
543+
data: {
544+
search_doc: {
545+
name: 'Mango',
546+
friends: [
547+
{
548+
name: 'Van Gogh',
549+
nickNames: ['Big Baby', 'Farty'],
550+
},
551+
{ name: 'Ringo', nickNames: ['Mang Mang', 'Baby'] },
552+
],
553+
},
554+
},
555+
},
556+
{
557+
card: vangogh,
558+
data: {
559+
search_doc: {
560+
name: 'Van Gogh',
561+
friends: [{ name: 'Ringo', nickNames: ['Ring Ring'] }],
562+
},
563+
},
564+
},
565+
]);
566+
567+
let { cards, meta } = await client.search(
568+
{
569+
filter: {
570+
on: { module: `${testRealmURL}person`, name: 'Person' },
571+
eq: { 'friends.nickNames': 'Baby' },
572+
},
573+
},
574+
loader,
575+
);
576+
577+
assert.strictEqual(meta.page.total, 1, 'the total results meta is correct');
578+
assert.deepEqual(
579+
cards,
580+
[await serializeCard(mango)],
581+
'results are correct',
582+
);
583+
});
584+
585+
test('it can match thru a plural nested composite field that is field of a singular composite field', async function (assert) {
586+
let { mango, vangogh } = testCards;
587+
await setupIndex(client, [
588+
{
589+
card: mango,
590+
data: {
591+
search_doc: {
592+
name: 'Mango',
593+
bestFriend: {
594+
name: 'Van Gogh',
595+
friends: [{ name: 'Ringo' }, { name: 'Van Gogh' }],
596+
},
597+
},
598+
},
599+
},
600+
{
601+
card: vangogh,
602+
data: {
603+
search_doc: {
604+
name: 'Van Gogh',
605+
bestFriend: { name: 'Ringo', friends: [{ name: 'Lucky' }] },
606+
},
607+
},
608+
},
609+
]);
610+
611+
let { cards, meta } = await client.search(
612+
{
613+
filter: {
614+
on: { module: `${testRealmURL}person`, name: 'Person' },
615+
eq: { 'bestFriend.friends.name': 'Lucky' },
616+
},
617+
},
618+
loader,
619+
);
620+
621+
assert.strictEqual(meta.page.total, 1, 'the total results meta is correct');
622+
assert.deepEqual(
623+
cards,
624+
[await serializeCard(vangogh)],
625+
'results are correct',
626+
);
627+
});
628+
629+
test(`can return a single result for a card when there are multiple matches within a result's search doc`, async function (assert) {
630+
let { mango } = testCards;
631+
await setupIndex(client, [
632+
{
633+
card: mango,
634+
data: {
635+
search_doc: {
636+
name: 'Mango',
637+
friends: [
638+
{ name: 'Ringo', bestFriend: { name: 'Mango' } },
639+
{ name: 'Van Gogh', bestFriend: { name: 'Mango' } },
640+
],
641+
},
642+
},
643+
},
644+
]);
645+
646+
let { cards, meta } = await client.search(
647+
{
648+
filter: {
649+
on: { module: `${testRealmURL}person`, name: 'Person' },
650+
eq: { 'friends.bestFriend.name': 'Mango' },
651+
},
652+
},
653+
loader,
654+
);
655+
656+
assert.strictEqual(meta.page.total, 1, 'the total results meta is correct');
657+
assert.deepEqual(
658+
cards,
659+
[await serializeCard(mango)],
660+
'results are correct',
661+
);
662+
});
362663
});

0 commit comments

Comments
 (0)