diff --git a/packages/accel-record-core/src/relation/where.ts b/packages/accel-record-core/src/relation/where.ts index 3152d1b3..2ba9fb61 100644 --- a/packages/accel-record-core/src/relation/where.ts +++ b/packages/accel-record-core/src/relation/where.ts @@ -41,8 +41,16 @@ export class Where { const newOptions = JSON.parse(JSON.stringify(this.options)); for (const key in input) { const column = this.model.attributeToColumn(key); - if (!column) throw new Error(`Attribute not found: ${key}`); - if (Array.isArray(input[key])) { + if (!column) { + const associationWheres = this.getAssocationWhere(key, input[key]); + if (associationWheres) { + for (const where of associationWheres) { + newOptions["wheres"].push(where); + } + } else { + throw new Error(`Attribute not found: ${key}`); + } + } else if (Array.isArray(input[key])) { newOptions["wheres"].push([column, "in", input[key]]); } else if (input[key] != null && typeof input[key] === "object") { for (const operator in input[key]) { @@ -59,6 +67,35 @@ export class Where { return new Relation(this.model, newOptions); } + private getAssocationWhere( + this: Relation, + key: Extract, + value: any, + op: string = "in" + ) { + const field = this.model.findField(key); + if (field?.relationName == undefined) return; + + const where: any = {}; + + const records = [value].flat(); + for (let i = 0; i < field.foreignKeys.length; i++) { + const column = this.model.attributeToColumn(field.foreignKeys[i]); + if (!column) return; + where[column] ||= []; + + for (const record of records) { + where[column].push(record[field.primaryKeys[i]]); + } + } + + return Object.entries(where).map(([column, values]) => [ + column, + op, + values, + ]); + } + /** * Adds a "where not" condition to the current relation. * @@ -72,8 +109,20 @@ export class Where { const newOptions = JSON.parse(JSON.stringify(this.options)); for (const key in input) { const column = this.model.attributeToColumn(key); - if (!column) throw new Error(`Attribute not found: ${key}`); - if (input[key] != null && typeof input[key] === "object") { + if (!column) { + const associationWheres = this.getAssocationWhere( + key, + input[key], + "not in" + ); + if (associationWheres) { + for (const where of associationWheres) { + newOptions["wheres"].push(where); + } + } else { + throw new Error(`Attribute not found: ${key}`); + } + } else if (input[key] != null && typeof input[key] === "object") { for (const operator in input[key]) { if (operator === "in") { newOptions["wheres"].push([column, "not in", input[key][operator]]); diff --git a/packages/prisma-generator-accel-record/src/generators/type.ts b/packages/prisma-generator-accel-record/src/generators/type.ts index 3c81fd3a..7e1f5276 100644 --- a/packages/prisma-generator-accel-record/src/generators/type.ts +++ b/packages/prisma-generator-accel-record/src/generators/type.ts @@ -161,18 +161,6 @@ declare module "accel-record" { return ` & ({ ${f.name}: ${f.type} } | { ${foreignKeys} })`; }) .join(""); - const whereInputs = - model.fields - .filter(reject) - .filter( - (field) => field.relationName == undefined && field.type != "Json" - ) - .map((field) => { - const type = field.typeName; - const filter = getFilterType(type); - return `\n ${field.name}?: ${type} | ${type}[] | ${filter} | null;`; - }) - .join("") + "\n "; const orderInputs = model.fields .filter(reject) @@ -197,7 +185,7 @@ type ${model.meta} = { CreateInput: { ${columns} }${associationColumns}; - WhereInput: {${whereInputs}}; + WhereInput: {${whereInputs(model)}}; OrderInput: {${orderInputs}}; }; registerModel(${model.persistedModel}); @@ -297,3 +285,22 @@ const columnDefines = (model: ModelWrapper) => };`; }) .join("\n"); + +const whereInputs = (model: ModelWrapper) => + model.fields + .filter( + (field) => + field.relationName == undefined || + (field.relationFromFields?.length ?? 0) > 0 + ) + .filter((field) => field.type != "Json") + .map((field) => { + const type = field.typeName; + const filter = getFilterType(type); + if (field.relationName) { + if (field.name == "posts") console.log(field); + return `\n ${field.name}?: ${field.type} | ${field.type}[];`; + } + return `\n ${field.name}?: ${type} | ${type}[] | ${filter} | null;`; + }) + .join("") + "\n "; diff --git a/tests/models/_types.ts b/tests/models/_types.ts index 81f2fde6..58a18f1a 100644 --- a/tests/models/_types.ts +++ b/tests/models/_types.ts @@ -185,7 +185,9 @@ type UserTeamMeta = { assignedBy: string; } & ({ user: User } | { userId: number }) & ({ team: Team } | { teamId: number }); WhereInput: { + user?: User | User[]; userId?: number | number[] | Filter | null; + team?: Team | Team[]; teamId?: number | number[] | Filter | null; assignedAt?: Date | Date[] | Filter | null; assignedBy?: string | string[] | StringFilter | null; @@ -239,6 +241,7 @@ type PostMeta = { title?: string | string[] | StringFilter | null; content?: string | string[] | StringFilter | null; published?: boolean | boolean[] | undefined | null; + author?: User | User[]; authorId?: number | number[] | Filter | null; }; OrderInput: { @@ -321,6 +324,7 @@ type SettingMeta = { } & ({ user: User } | { userId: number }); WhereInput: { settingId?: number | number[] | Filter | null; + user?: User | User[]; userId?: number | number[] | Filter | null; threshold?: number | number[] | Filter | null; createdAt?: Date | Date[] | Filter | null; @@ -372,6 +376,7 @@ type ProfileMeta = { } & ({ user: User } | { userId: number }); WhereInput: { id?: number | number[] | Filter | null; + user?: User | User[]; userId?: number | number[] | Filter | null; bio?: string | string[] | StringFilter | null; point?: number | number[] | Filter | null; @@ -461,6 +466,7 @@ type EmployeeMeta = { id?: number | number[] | Filter | null; name?: string | string[] | StringFilter | null; companyId?: number | number[] | Filter | null; + company?: Company | Company[]; }; OrderInput: { id?: SortOrder; diff --git a/tests/models/relation.test.ts b/tests/models/relation.test.ts index b7bf6b2e..85c1f875 100644 --- a/tests/models/relation.test.ts +++ b/tests/models/relation.test.ts @@ -134,6 +134,15 @@ describe("Relation", () => { ).toHaveLength(2); }); + test("#where() with association", () => { + const users = $user.createList(2); + $post.createList(2, { author: users[0] }); + $post.createList(1, { author: users[1] }); + expect(Post.all().where({ author: users[0] }).count()).toBe(2); + expect(Post.all().where({ author: users }).count()).toBe(3); + expect(Post.all().where({ author: [] }).count()).toBe(0); + }); + test("#whereNot()", () => { $user.create({ name: "hoge", age: 20 }); $user.create({ name: "fuga", age: 30 }); @@ -152,6 +161,15 @@ describe("Relation", () => { expect(User.all().whereNot({ age: null }).first()?.name).toBe("hoge"); }); + test("#whereNot() with association", () => { + const users = $user.createList(2); + $post.createList(2, { author: users[0] }); + $post.createList(1, { author: users[1] }); + expect(Post.all().whereNot({ author: users[0] }).count()).toBe(1); + expect(Post.all().whereNot({ author: users }).count()).toBe(0); + expect(Post.all().whereNot({ author: [] }).count()).toBe(3); + }); + test("#whereRaw()", () => { $user.create({ name: "hoge", age: 20 }); $user.create({ name: "fuga", age: 30 });