diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts index e774158c23..bad512e5fd 100644 --- a/packages/server/src/api/routes/tests/search.spec.ts +++ b/packages/server/src/api/routes/tests/search.spec.ts @@ -5,12 +5,12 @@ import { knexClient, } from "../../../integrations/tests/utils" import { - db as dbCore, context, + db as dbCore, MAX_VALID_DATE, MIN_VALID_DATE, - utils, SQLITE_DESIGN_DOC_ID, + utils, } from "@budibase/backend-core" import * as setup from "./utilities" @@ -2560,4 +2560,48 @@ describe.each([ }).toContainExactly([{ name: "foo" }]) }) }) + + !isInMemory && + describe("search by _id", () => { + let row: Row + + beforeAll(async () => { + const toRelateTable = await createTable({ + name: { + name: "name", + type: FieldType.STRING, + }, + }) + table = await createTable({ + name: { + name: "name", + type: FieldType.STRING, + }, + rel: { + name: "rel", + type: FieldType.LINK, + relationshipType: RelationshipType.MANY_TO_MANY, + tableId: toRelateTable._id!, + fieldName: "rel", + }, + }) + const [row1, row2] = await Promise.all([ + config.api.row.save(toRelateTable._id!, { name: "tag 1" }), + config.api.row.save(toRelateTable._id!, { name: "tag 2" }), + ]) + row = await config.api.row.save(table._id!, { + name: "product 1", + rel: [row1._id, row2._id], + }) + }) + + it("can filter by the row ID with limit 1", async () => { + await expectSearch({ + query: { + equal: { _id: row._id }, + }, + limit: 1, + }).toContainExactly([row]) + }) + }) }) diff --git a/packages/server/src/sdk/app/rows/search/external.ts b/packages/server/src/sdk/app/rows/search/external.ts index c7a89bc0dd..e7fd2888de 100644 --- a/packages/server/src/sdk/app/rows/search/external.ts +++ b/packages/server/src/sdk/app/rows/search/external.ts @@ -22,21 +22,21 @@ import { HTTPError } from "@budibase/backend-core" import pick from "lodash/pick" import { outputProcessing } from "../../../../utilities/rowProcessor" import sdk from "../../../" +import { isSearchingByRowID } from "./utils" -export async function search( - options: RowSearchParams, - table: Table -): Promise> { - const { tableId } = options - const { countRows, paginate, query, ...params } = options - const { limit } = params - let bookmark = - (params.bookmark && parseInt(params.bookmark as string)) || undefined - if (paginate && !bookmark) { - bookmark = 0 - } +function getPaginationAndLimitParameters( + filters: SearchFilters, + paginate: boolean | undefined, + bookmark: number | undefined, + limit: number | undefined +): PaginationJson | undefined { let paginateObj: PaginationJson | undefined + // only try set limits/pagination if we aren't doing a row ID search + if (isSearchingByRowID(filters)) { + return + } + if (paginate && !limit) { throw new Error("Cannot paginate query without a limit") } @@ -49,11 +49,35 @@ export async function search( if (bookmark) { paginateObj.offset = limit * bookmark } - } else if (params && limit) { + } else if (limit) { paginateObj = { limit: limit, } } + + return paginateObj +} + +export async function search( + options: RowSearchParams, + table: Table +): Promise> { + const { tableId } = options + const { countRows, paginate, query, ...params } = options + const { limit } = params + let bookmark = + (params.bookmark && parseInt(params.bookmark as string)) || undefined + if (paginate && !bookmark) { + bookmark = 0 + } + + let paginateObj = getPaginationAndLimitParameters( + query, + paginate, + bookmark, + limit + ) + let sort: SortJson | undefined if (params.sort) { const direction = diff --git a/packages/server/src/sdk/app/rows/search/sqs.ts b/packages/server/src/sdk/app/rows/search/sqs.ts index c3da565c87..018b2ae4a3 100644 --- a/packages/server/src/sdk/app/rows/search/sqs.ts +++ b/packages/server/src/sdk/app/rows/search/sqs.ts @@ -42,6 +42,7 @@ import { getTableIDList, } from "./filters" import { dataFilters, PROTECTED_INTERNAL_COLUMNS } from "@budibase/shared-core" +import { isSearchingByRowID } from "./utils" const builder = new sql.Sql(SqlClient.SQL_LITE) const MISSING_COLUMN_REGEX = new RegExp(`no such column: .+`) @@ -264,6 +265,10 @@ export async function search( const relationships = buildInternalRelationships(table) + const searchFilters: SearchFilters = { + ...cleanupFilters(query, table, allTables), + documentType: DocumentType.ROW, + } const request: QueryJson = { endpoint: { // not important, we query ourselves @@ -271,10 +276,7 @@ export async function search( entityId: table._id!, operation: Operation.READ, }, - filters: { - ...cleanupFilters(query, table, allTables), - documentType: DocumentType.ROW, - }, + filters: searchFilters, table, meta: { table, @@ -304,7 +306,8 @@ export async function search( } const bookmark: number = (params.bookmark as number) || 0 - if (params.limit) { + // limits don't apply if we doing a row ID search + if (!isSearchingByRowID(searchFilters) && params.limit) { paginate = true request.paginate = { limit: params.limit + 1, diff --git a/packages/server/src/sdk/app/rows/search/utils.ts b/packages/server/src/sdk/app/rows/search/utils.ts index 797383eff0..5ffc065353 100644 --- a/packages/server/src/sdk/app/rows/search/utils.ts +++ b/packages/server/src/sdk/app/rows/search/utils.ts @@ -108,3 +108,18 @@ export function searchInputMapping(table: Table, options: RowSearchParams) { } return options } + +export function isSearchingByRowID(query: SearchFilters): boolean { + for (let searchField of Object.values(query)) { + if (typeof searchField !== "object") { + continue + } + const hasId = Object.keys(searchField).find( + key => dbCore.removeKeyNumbering(key) === "_id" && searchField[key] + ) + if (hasId) { + return true + } + } + return false +}