Skip to content

Commit

Permalink
feat(core): support filtering relation fields
Browse files Browse the repository at this point in the history
  • Loading branch information
Hieuzest committed Sep 3, 2024
1 parent 49450e1 commit 4381b4e
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 9 deletions.
24 changes: 16 additions & 8 deletions packages/core/src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ export class Database<S = {}, N = {}, C extends Context = Context> extends Servi
select(table: any, query?: any, include?: any) {
let sel = new Selection(this.getDriver(table), table, query)
if (typeof table !== 'string') return sel
const whereOnly = include === null
const whereOnly = include === null, isAssoc = !!include?.$assoc
const rawquery = typeof query === 'function' ? query : () => query
const modelFields = this.tables[table].fields
if (include) include = filterKeys(include, (key) => !!modelFields[key]?.relation)
Expand Down Expand Up @@ -364,13 +364,20 @@ export class Database<S = {}, N = {}, C extends Context = Context> extends Servi
for (const key in include) {
if (!include[key] || !modelFields[key]?.relation) continue
const relation: Relation.Config<S> = modelFields[key]!.relation as any
const relmodel = this.tables[relation.table]
if (relation.type === 'oneToOne' || relation.type === 'manyToOne') {
sel = whereOnly ? sel : sel.join(key, this.select(relation.table, {}, include[key]), (self, other) => Eval.and(
sel = whereOnly ? sel : sel.join(key, this.select(relation.table,
typeof include[key] === 'object' ? filterKeys(include[key], (k) => !relmodel.fields[k]?.relation) : {} as any,
typeof include[key] === 'object' ? filterKeys(include[key], (k) => !!relmodel.fields[k]?.relation) : include[key],
), (self, other) => Eval.and(
...relation.fields.map((k, i) => Eval.eq(self[k], other[relation.references[i]])),
), true)
), !isAssoc)
sel = applyQuery(sel, key)
} else if (relation.type === 'oneToMany') {
sel = whereOnly ? sel : sel.join(key, this.select(relation.table, {}, include[key]), (self, other) => Eval.and(
sel = whereOnly ? sel : sel.join(key, this.select(relation.table,
typeof include[key] === 'object' ? filterKeys(include[key], (k) => !relmodel.fields[k]?.relation) : {} as any,
typeof include[key] === 'object' ? filterKeys(include[key], (k) => !!relmodel.fields[k]?.relation) : include[key],
), (self, other) => Eval.and(
...relation.fields.map((k, i) => Eval.eq(self[k], other[relation.references[i]])),
), true)
sel = applyQuery(sel, key)
Expand All @@ -387,10 +394,11 @@ export class Database<S = {}, N = {}, C extends Context = Context> extends Servi
field: x,
reference: y,
}] as const)
sel = whereOnly ? sel : sel.join(key, this.select(assocTable, {}, { [relation.table]: include[key] } as any), (self, other) => Eval.and(
...shared.map(([k, v]) => Eval.eq(self[v.field], other[k])),
...relation.fields.map((k, i) => Eval.eq(self[k], other[references[i]])),
), true)
sel = whereOnly ? sel : sel.join(key, this.select(assocTable, {}, { $assoc: true, [relation.table]: include[key] } as any),
(self, other) => Eval.and(
...shared.map(([k, v]) => Eval.eq(self[v.field], other[k])),
...relation.fields.map((k, i) => Eval.eq(self[k], other[references[i]])),
), true)
sel = applyQuery(sel, key)
sel = whereOnly ? sel : sel.groupBy([
...Object.entries(modelFields).filter(([k, field]) => !extraFields.some(x => k.startsWith(`${x}.`)) && Field.available(field)).map(([k]) => k),
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export namespace Relation {
}

export type Include<T, S> = boolean | {
[P in keyof T]?: T[P] extends MaybeArray<infer U> | undefined ? U extends S ? Include<U, S> : never : never
[P in keyof T]?: T[P] extends MaybeArray<infer U> | undefined ? U extends S ? Include<U, S> : Query.Expr<Flatten<U>> : never
}

export type SetExpr<S extends object = any> = ((row: Row<S>) => Update<S>) | {
Expand Down
53 changes: 53 additions & 0 deletions packages/tests/src/relation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,59 @@ namespace RelationTests {
},
}])
})

it('filter on relations', async () => {
const users = await setup(database, 'user', userTable)
const posts = await setup(database, 'post', postTable)
const tags = await setup(database, 'tag', tagTable)
const post2tags = await setup(database, 'post2tag', post2TagTable)
await setup(database, Relation.buildAssociationTable('post', 'tag') as any, post2TagTable2)

await expect(database.get('user', {
posts: {
$some: {
id2: 1,
},
},
}, {
include: {
posts: {
id2: 1,
},
},
})).to.eventually.have.shape(users.slice(0, 1).map(user => ({
...user,
posts: posts.filter(post => post.id2 === 1),
})))

await expect(database.get('user', {
posts: {
$some: {
tags: {
$some: {
id: 1,
},
},
},
},
}, {
include: {
posts: {
tags: {
id: 1,
},
},
},
})).to.eventually.have.shape([users[0]].map(user => ({
...user,
posts: posts.filter(post => post.author?.id === user.id).map(post => ({
...post,
tags: post2tags.filter(p2t => p2t.post?.id === post.id2)
.map(p2t => tags.find(tag => tag.id === p2t.tag?.id))
.filter(tag => tag?.id === 1),
})),
})))
})
}

export function create(database: Database<Tables>, options: RelationOptions = {}) {
Expand Down

0 comments on commit 4381b4e

Please sign in to comment.