From 6a798ee6f5cbf82ee038484f5d2610ddc532bae6 Mon Sep 17 00:00:00 2001 From: Dirk de Visser Date: Fri, 5 Feb 2021 21:04:45 +0100 Subject: [PATCH] code-gen: support multiple 'viaXxx' in the query builder (#661) This works by intersecting the result of multiple traverses. This guarantees the same 'AND' behaviour as usual. It also works when the 'xxIn' property is already set, by converting it inline to a 'select from values' expression. Closes #660 --- .../src/generator/sql/query-builder.js | 27 +++- .../code-gen/src/generator/sql/structure.js | 15 +- packages/code-gen/test/sql.test.js | 17 +++ packages/store/src/generated/database/file.js | 44 +++++- .../store/src/generated/database/fileGroup.js | 132 ++++++++++++++++-- .../src/generated/database/fileGroupView.js | 132 ++++++++++++++++-- 6 files changed, 331 insertions(+), 36 deletions(-) diff --git a/packages/code-gen/src/generator/sql/query-builder.js b/packages/code-gen/src/generator/sql/query-builder.js index 0e30590a67..762773f286 100644 --- a/packages/code-gen/src/generator/sql/query-builder.js +++ b/packages/code-gen/src/generator/sql/query-builder.js @@ -5,6 +5,7 @@ import { addToData } from "../../generate.js"; import { upperCaseFirst } from "../../utils.js"; import { js } from "../tag/tag.js"; import { getTypeNameForType } from "../types.js"; +import { typeTable } from "./structure.js"; import { getPrimaryKeyWithType, getQueryEnabledObjects } from "./utils.js"; /** @@ -397,13 +398,35 @@ if (!isNil(builder.${key}.limit)) { } `; + // Note that we need to the xxxIn params first before we can add xxxIn and set it + // to a query. The user may have set it to an array or another traverser may have + // set a query object already. To get the same guarantees ('AND') we convert + // arrays with values to a query part & if a query part exists, add 'INTERSECT'. + let sqlCastType = typeTable[type.keys[ownKey].type]; + if (typeof sqlCastType === "function") { + sqlCastType = sqlCastType(type.keys[ownKey].type, false); + } const traverseJoinPart = js` if (builder.via${upperCaseFirst(relationKey)}) { builder.where = builder.where ?? {}; + // Prepare ${ownKey}In + if (isQueryPart(builder.where.${ownKey}In)) { + builder.where.${ownKey}In.append(query\` INTERSECT \`); + } else if (Array.isArray(builder.where.${ownKey}In) && builder.where.${ownKey}In.length > 0) { + builder.where.${ownKey}In = + query([ + "(SELECT value::${sqlCastType} FROM(values (", + ...builder.where.${ownKey}In.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT " + ], ...builder.where.${ownKey}In) + } else { + builder.where.${ownKey}In = query\`\`; + } + ${getLimitOffset(true)} - builder.where.${ownKey}In = query\` + builder.where.${ownKey}In.append(query\` SELECT DISTINCT ${otherShortName}."${referencedKey}" $\{internalQuery${ upperCaseFirst(otherSide.name) + @@ -413,7 +436,7 @@ if (!isNil(builder.${key}.limit)) { }( builder.via${upperCaseFirst(relationKey)})} $\{offsetLimitQb} - \`; + \`); } `; diff --git a/packages/code-gen/src/generator/sql/structure.js b/packages/code-gen/src/generator/sql/structure.js index f0ebcb4e20..f8664915cb 100644 --- a/packages/code-gen/src/generator/sql/structure.js +++ b/packages/code-gen/src/generator/sql/structure.js @@ -3,7 +3,7 @@ import { upperCaseFirst } from "../../utils.js"; import { getQueryEnabledObjects, getSortedKeysForType } from "./utils.js"; import { getSearchableFields } from "./where-type.js"; -const typeTable = { +export const typeTable = { any: "jsonb", anyOf: "jsonb", array: "jsonb", @@ -13,16 +13,19 @@ const typeTable = { /** * * @param {CodeGenNumberType} type + * @param {boolean} skipPrimary * @returns {string} - */ number: (type) => - !type.sql?.primary + */ number: (type, skipPrimary) => + !type.sql?.primary || skipPrimary ? type.validator.floatingPoint ? "float" : "int" : "BIGSERIAL PRIMARY KEY", object: "jsonb", - string: (type) => (type.sql?.primary ? "varchar PRIMARY KEY" : "varchar"), - uuid: (type) => (type.sql?.primary ? "uuid PRIMARY KEY" : "uuid"), + string: (type, skipPrimary) => + type.sql?.primary && !skipPrimary ? "varchar PRIMARY KEY" : "varchar", + uuid: (type, skipPrimary) => + type.sql?.primary && !skipPrimary ? "uuid PRIMARY KEY" : "uuid", }; /** @@ -77,7 +80,7 @@ function getFields(object) { let sqlType = typeTable[type.type]; if (typeof sqlType === "function") { - sqlType = sqlType(type); + sqlType = sqlType(type, false); } let defaultValue = ""; diff --git a/packages/code-gen/test/sql.test.js b/packages/code-gen/test/sql.test.js index 8423e4e310..decbc7b5e2 100644 --- a/packages/code-gen/test/sql.test.js +++ b/packages/code-gen/test/sql.test.js @@ -335,6 +335,23 @@ test("code-gen/e2e/sql", async (t) => { t.equal(dbUser.id, user.id); }); + t.test("traverse with 'via' and idIn", async (t) => { + const [dbUser] = await client + .queryUser({ + where: { + idIn: [post.writer], + }, + viaPosts: { + where: { + id: post.id, + }, + }, + }) + .exec(sql); + + t.equal(dbUser.id, user.id); + }); + t.test("traverse via queryCategory", async (t) => { const builder = { viaPosts: { diff --git a/packages/store/src/generated/database/file.js b/packages/store/src/generated/database/file.js index c6642814d0..3aa813abd3 100644 --- a/packages/store/src/generated/database/file.js +++ b/packages/store/src/generated/database/file.js @@ -588,6 +588,24 @@ export function internalQueryFile(builder = {}, wherePartial) { const joinQb = query``; if (builder.viaGroup) { builder.where = builder.where ?? {}; + // Prepare idIn + if (isQueryPart(builder.where.idIn)) { + builder.where.idIn.append(query` INTERSECT `); + } else if ( + Array.isArray(builder.where.idIn) && + builder.where.idIn.length > 0 + ) { + builder.where.idIn = query( + [ + "(SELECT value::uuid FROM(values (", + ...builder.where.idIn.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT ", + ], + ...builder.where.idIn, + ); + } else { + builder.where.idIn = query``; + } const offsetLimitQb = !isNil(builder.viaGroup.offset) ? query`OFFSET ${builder.viaGroup.offset}` : query``; @@ -596,14 +614,32 @@ export function internalQueryFile(builder = {}, wherePartial) { query`FETCH NEXT ${builder.viaGroup.limit} ROWS ONLY`, ); } - builder.where.idIn = query` + builder.where.idIn.append(query` SELECT DISTINCT fg."file" ${internalQueryFileGroup(builder.viaGroup)} ${offsetLimitQb} -`; +`); } if (builder.viaGroupView) { builder.where = builder.where ?? {}; + // Prepare idIn + if (isQueryPart(builder.where.idIn)) { + builder.where.idIn.append(query` INTERSECT `); + } else if ( + Array.isArray(builder.where.idIn) && + builder.where.idIn.length > 0 + ) { + builder.where.idIn = query( + [ + "(SELECT value::uuid FROM(values (", + ...builder.where.idIn.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT ", + ], + ...builder.where.idIn, + ); + } else { + builder.where.idIn = query``; + } const offsetLimitQb = !isNil(builder.viaGroupView.offset) ? query`OFFSET ${builder.viaGroupView.offset}` : query``; @@ -612,11 +648,11 @@ ${offsetLimitQb} query`FETCH NEXT ${builder.viaGroupView.limit} ROWS ONLY`, ); } - builder.where.idIn = query` + builder.where.idIn.append(query` SELECT DISTINCT fgv."file" ${internalQueryFileGroupView(builder.viaGroupView)} ${offsetLimitQb} -`; +`); } if (builder.group) { const joinedKeys = []; diff --git a/packages/store/src/generated/database/fileGroup.js b/packages/store/src/generated/database/fileGroup.js index 6e01fe0642..259896118b 100644 --- a/packages/store/src/generated/database/fileGroup.js +++ b/packages/store/src/generated/database/fileGroup.js @@ -637,6 +637,24 @@ export function internalQueryFileGroup2(builder = {}, wherePartial) { const joinQb = query``; if (builder.viaFile) { builder.where = builder.where ?? {}; + // Prepare fileIn + if (isQueryPart(builder.where.fileIn)) { + builder.where.fileIn.append(query` INTERSECT `); + } else if ( + Array.isArray(builder.where.fileIn) && + builder.where.fileIn.length > 0 + ) { + builder.where.fileIn = query( + [ + "(SELECT value::uuid FROM(values (", + ...builder.where.fileIn.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT ", + ], + ...builder.where.fileIn, + ); + } else { + builder.where.fileIn = query``; + } const offsetLimitQb = !isNil(builder.viaFile.offset) ? query`OFFSET ${builder.viaFile.offset}` : query``; @@ -645,14 +663,32 @@ export function internalQueryFileGroup2(builder = {}, wherePartial) { query`FETCH NEXT ${builder.viaFile.limit} ROWS ONLY`, ); } - builder.where.fileIn = query` + builder.where.fileIn.append(query` SELECT DISTINCT f."id" ${internalQueryFile(builder.viaFile)} ${offsetLimitQb} -`; +`); } if (builder.viaParent) { builder.where = builder.where ?? {}; + // Prepare parentIn + if (isQueryPart(builder.where.parentIn)) { + builder.where.parentIn.append(query` INTERSECT `); + } else if ( + Array.isArray(builder.where.parentIn) && + builder.where.parentIn.length > 0 + ) { + builder.where.parentIn = query( + [ + "(SELECT value::uuid FROM(values (", + ...builder.where.parentIn.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT ", + ], + ...builder.where.parentIn, + ); + } else { + builder.where.parentIn = query``; + } const offsetLimitQb = !isNil(builder.viaParent.offset) ? query`OFFSET ${builder.viaParent.offset}` : query``; @@ -661,14 +697,32 @@ ${offsetLimitQb} query`FETCH NEXT ${builder.viaParent.limit} ROWS ONLY`, ); } - builder.where.parentIn = query` + builder.where.parentIn.append(query` SELECT DISTINCT fg."id" ${internalQueryFileGroup(builder.viaParent)} ${offsetLimitQb} -`; +`); } if (builder.viaChildren) { builder.where = builder.where ?? {}; + // Prepare idIn + if (isQueryPart(builder.where.idIn)) { + builder.where.idIn.append(query` INTERSECT `); + } else if ( + Array.isArray(builder.where.idIn) && + builder.where.idIn.length > 0 + ) { + builder.where.idIn = query( + [ + "(SELECT value::uuid FROM(values (", + ...builder.where.idIn.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT ", + ], + ...builder.where.idIn, + ); + } else { + builder.where.idIn = query``; + } const offsetLimitQb = !isNil(builder.viaChildren.offset) ? query`OFFSET ${builder.viaChildren.offset}` : query``; @@ -677,11 +731,11 @@ ${offsetLimitQb} query`FETCH NEXT ${builder.viaChildren.limit} ROWS ONLY`, ); } - builder.where.idIn = query` + builder.where.idIn.append(query` SELECT DISTINCT fg."parent" ${internalQueryFileGroup(builder.viaChildren)} ${offsetLimitQb} -`; +`); } if (builder.file) { const joinedKeys = []; @@ -802,6 +856,24 @@ export function internalQueryFileGroup(builder = {}, wherePartial) { const joinQb = query``; if (builder.viaFile) { builder.where = builder.where ?? {}; + // Prepare fileIn + if (isQueryPart(builder.where.fileIn)) { + builder.where.fileIn.append(query` INTERSECT `); + } else if ( + Array.isArray(builder.where.fileIn) && + builder.where.fileIn.length > 0 + ) { + builder.where.fileIn = query( + [ + "(SELECT value::uuid FROM(values (", + ...builder.where.fileIn.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT ", + ], + ...builder.where.fileIn, + ); + } else { + builder.where.fileIn = query``; + } const offsetLimitQb = !isNil(builder.viaFile.offset) ? query`OFFSET ${builder.viaFile.offset}` : query``; @@ -810,14 +882,32 @@ export function internalQueryFileGroup(builder = {}, wherePartial) { query`FETCH NEXT ${builder.viaFile.limit} ROWS ONLY`, ); } - builder.where.fileIn = query` + builder.where.fileIn.append(query` SELECT DISTINCT f."id" ${internalQueryFile(builder.viaFile)} ${offsetLimitQb} -`; +`); } if (builder.viaParent) { builder.where = builder.where ?? {}; + // Prepare parentIn + if (isQueryPart(builder.where.parentIn)) { + builder.where.parentIn.append(query` INTERSECT `); + } else if ( + Array.isArray(builder.where.parentIn) && + builder.where.parentIn.length > 0 + ) { + builder.where.parentIn = query( + [ + "(SELECT value::uuid FROM(values (", + ...builder.where.parentIn.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT ", + ], + ...builder.where.parentIn, + ); + } else { + builder.where.parentIn = query``; + } const offsetLimitQb = !isNil(builder.viaParent.offset) ? query`OFFSET ${builder.viaParent.offset}` : query``; @@ -826,14 +916,32 @@ ${offsetLimitQb} query`FETCH NEXT ${builder.viaParent.limit} ROWS ONLY`, ); } - builder.where.parentIn = query` + builder.where.parentIn.append(query` SELECT DISTINCT fg2."id" ${internalQueryFileGroup2(builder.viaParent)} ${offsetLimitQb} -`; +`); } if (builder.viaChildren) { builder.where = builder.where ?? {}; + // Prepare idIn + if (isQueryPart(builder.where.idIn)) { + builder.where.idIn.append(query` INTERSECT `); + } else if ( + Array.isArray(builder.where.idIn) && + builder.where.idIn.length > 0 + ) { + builder.where.idIn = query( + [ + "(SELECT value::uuid FROM(values (", + ...builder.where.idIn.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT ", + ], + ...builder.where.idIn, + ); + } else { + builder.where.idIn = query``; + } const offsetLimitQb = !isNil(builder.viaChildren.offset) ? query`OFFSET ${builder.viaChildren.offset}` : query``; @@ -842,11 +950,11 @@ ${offsetLimitQb} query`FETCH NEXT ${builder.viaChildren.limit} ROWS ONLY`, ); } - builder.where.idIn = query` + builder.where.idIn.append(query` SELECT DISTINCT fg2."parent" ${internalQueryFileGroup2(builder.viaChildren)} ${offsetLimitQb} -`; +`); } if (builder.file) { const joinedKeys = []; diff --git a/packages/store/src/generated/database/fileGroupView.js b/packages/store/src/generated/database/fileGroupView.js index 311a78f6cf..ac102be6b3 100644 --- a/packages/store/src/generated/database/fileGroupView.js +++ b/packages/store/src/generated/database/fileGroupView.js @@ -491,6 +491,24 @@ export function internalQueryFileGroupView2(builder = {}, wherePartial) { const joinQb = query``; if (builder.viaFile) { builder.where = builder.where ?? {}; + // Prepare fileIn + if (isQueryPart(builder.where.fileIn)) { + builder.where.fileIn.append(query` INTERSECT `); + } else if ( + Array.isArray(builder.where.fileIn) && + builder.where.fileIn.length > 0 + ) { + builder.where.fileIn = query( + [ + "(SELECT value::uuid FROM(values (", + ...builder.where.fileIn.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT ", + ], + ...builder.where.fileIn, + ); + } else { + builder.where.fileIn = query``; + } const offsetLimitQb = !isNil(builder.viaFile.offset) ? query`OFFSET ${builder.viaFile.offset}` : query``; @@ -499,14 +517,32 @@ export function internalQueryFileGroupView2(builder = {}, wherePartial) { query`FETCH NEXT ${builder.viaFile.limit} ROWS ONLY`, ); } - builder.where.fileIn = query` + builder.where.fileIn.append(query` SELECT DISTINCT f."id" ${internalQueryFile(builder.viaFile)} ${offsetLimitQb} -`; +`); } if (builder.viaParent) { builder.where = builder.where ?? {}; + // Prepare parentIn + if (isQueryPart(builder.where.parentIn)) { + builder.where.parentIn.append(query` INTERSECT `); + } else if ( + Array.isArray(builder.where.parentIn) && + builder.where.parentIn.length > 0 + ) { + builder.where.parentIn = query( + [ + "(SELECT value::uuid FROM(values (", + ...builder.where.parentIn.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT ", + ], + ...builder.where.parentIn, + ); + } else { + builder.where.parentIn = query``; + } const offsetLimitQb = !isNil(builder.viaParent.offset) ? query`OFFSET ${builder.viaParent.offset}` : query``; @@ -515,14 +551,32 @@ ${offsetLimitQb} query`FETCH NEXT ${builder.viaParent.limit} ROWS ONLY`, ); } - builder.where.parentIn = query` + builder.where.parentIn.append(query` SELECT DISTINCT fgv."id" ${internalQueryFileGroupView(builder.viaParent)} ${offsetLimitQb} -`; +`); } if (builder.viaChildren) { builder.where = builder.where ?? {}; + // Prepare idIn + if (isQueryPart(builder.where.idIn)) { + builder.where.idIn.append(query` INTERSECT `); + } else if ( + Array.isArray(builder.where.idIn) && + builder.where.idIn.length > 0 + ) { + builder.where.idIn = query( + [ + "(SELECT value::uuid FROM(values (", + ...builder.where.idIn.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT ", + ], + ...builder.where.idIn, + ); + } else { + builder.where.idIn = query``; + } const offsetLimitQb = !isNil(builder.viaChildren.offset) ? query`OFFSET ${builder.viaChildren.offset}` : query``; @@ -531,11 +585,11 @@ ${offsetLimitQb} query`FETCH NEXT ${builder.viaChildren.limit} ROWS ONLY`, ); } - builder.where.idIn = query` + builder.where.idIn.append(query` SELECT DISTINCT fgv."parent" ${internalQueryFileGroupView(builder.viaChildren)} ${offsetLimitQb} -`; +`); } if (builder.file) { const joinedKeys = []; @@ -662,6 +716,24 @@ export function internalQueryFileGroupView(builder = {}, wherePartial) { const joinQb = query``; if (builder.viaFile) { builder.where = builder.where ?? {}; + // Prepare fileIn + if (isQueryPart(builder.where.fileIn)) { + builder.where.fileIn.append(query` INTERSECT `); + } else if ( + Array.isArray(builder.where.fileIn) && + builder.where.fileIn.length > 0 + ) { + builder.where.fileIn = query( + [ + "(SELECT value::uuid FROM(values (", + ...builder.where.fileIn.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT ", + ], + ...builder.where.fileIn, + ); + } else { + builder.where.fileIn = query``; + } const offsetLimitQb = !isNil(builder.viaFile.offset) ? query`OFFSET ${builder.viaFile.offset}` : query``; @@ -670,14 +742,32 @@ export function internalQueryFileGroupView(builder = {}, wherePartial) { query`FETCH NEXT ${builder.viaFile.limit} ROWS ONLY`, ); } - builder.where.fileIn = query` + builder.where.fileIn.append(query` SELECT DISTINCT f."id" ${internalQueryFile(builder.viaFile)} ${offsetLimitQb} -`; +`); } if (builder.viaParent) { builder.where = builder.where ?? {}; + // Prepare parentIn + if (isQueryPart(builder.where.parentIn)) { + builder.where.parentIn.append(query` INTERSECT `); + } else if ( + Array.isArray(builder.where.parentIn) && + builder.where.parentIn.length > 0 + ) { + builder.where.parentIn = query( + [ + "(SELECT value::uuid FROM(values (", + ...builder.where.parentIn.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT ", + ], + ...builder.where.parentIn, + ); + } else { + builder.where.parentIn = query``; + } const offsetLimitQb = !isNil(builder.viaParent.offset) ? query`OFFSET ${builder.viaParent.offset}` : query``; @@ -686,14 +776,32 @@ ${offsetLimitQb} query`FETCH NEXT ${builder.viaParent.limit} ROWS ONLY`, ); } - builder.where.parentIn = query` + builder.where.parentIn.append(query` SELECT DISTINCT fgv2."id" ${internalQueryFileGroupView2(builder.viaParent)} ${offsetLimitQb} -`; +`); } if (builder.viaChildren) { builder.where = builder.where ?? {}; + // Prepare idIn + if (isQueryPart(builder.where.idIn)) { + builder.where.idIn.append(query` INTERSECT `); + } else if ( + Array.isArray(builder.where.idIn) && + builder.where.idIn.length > 0 + ) { + builder.where.idIn = query( + [ + "(SELECT value::uuid FROM(values (", + ...builder.where.idIn.map(() => "").join("), ("), + ")) as ids(value)) INTERSECT ", + ], + ...builder.where.idIn, + ); + } else { + builder.where.idIn = query``; + } const offsetLimitQb = !isNil(builder.viaChildren.offset) ? query`OFFSET ${builder.viaChildren.offset}` : query``; @@ -702,11 +810,11 @@ ${offsetLimitQb} query`FETCH NEXT ${builder.viaChildren.limit} ROWS ONLY`, ); } - builder.where.idIn = query` + builder.where.idIn.append(query` SELECT DISTINCT fgv2."parent" ${internalQueryFileGroupView2(builder.viaChildren)} ${offsetLimitQb} -`; +`); } if (builder.file) { const joinedKeys = [];