Merge pull request #14257 from Budibase/fix/search-by-row-id

Fix for searching by row ID (with a limit) when the row has many related rows
This commit is contained in:
Michael Drury 2024-07-30 12:41:29 +01:00 committed by GitHub
commit ba3f69ead9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 106 additions and 20 deletions

View File

@ -5,12 +5,12 @@ import {
knexClient, knexClient,
} from "../../../integrations/tests/utils" } from "../../../integrations/tests/utils"
import { import {
db as dbCore,
context, context,
db as dbCore,
MAX_VALID_DATE, MAX_VALID_DATE,
MIN_VALID_DATE, MIN_VALID_DATE,
utils,
SQLITE_DESIGN_DOC_ID, SQLITE_DESIGN_DOC_ID,
utils,
} from "@budibase/backend-core" } from "@budibase/backend-core"
import * as setup from "./utilities" import * as setup from "./utilities"
@ -2560,4 +2560,48 @@ describe.each([
}).toContainExactly([{ name: "foo" }]) }).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])
})
})
}) })

View File

@ -22,21 +22,21 @@ import { HTTPError } from "@budibase/backend-core"
import pick from "lodash/pick" import pick from "lodash/pick"
import { outputProcessing } from "../../../../utilities/rowProcessor" import { outputProcessing } from "../../../../utilities/rowProcessor"
import sdk from "../../../" import sdk from "../../../"
import { isSearchingByRowID } from "./utils"
export async function search( function getPaginationAndLimitParameters(
options: RowSearchParams, filters: SearchFilters,
table: Table paginate: boolean | undefined,
): Promise<SearchResponse<Row>> { bookmark: number | undefined,
const { tableId } = options limit: number | undefined
const { countRows, paginate, query, ...params } = options ): PaginationJson | undefined {
const { limit } = params
let bookmark =
(params.bookmark && parseInt(params.bookmark as string)) || undefined
if (paginate && !bookmark) {
bookmark = 0
}
let paginateObj: 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) { if (paginate && !limit) {
throw new Error("Cannot paginate query without a limit") throw new Error("Cannot paginate query without a limit")
} }
@ -49,11 +49,35 @@ export async function search(
if (bookmark) { if (bookmark) {
paginateObj.offset = limit * bookmark paginateObj.offset = limit * bookmark
} }
} else if (params && limit) { } else if (limit) {
paginateObj = { paginateObj = {
limit: limit, limit: limit,
} }
} }
return paginateObj
}
export async function search(
options: RowSearchParams,
table: Table
): Promise<SearchResponse<Row>> {
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 let sort: SortJson | undefined
if (params.sort) { if (params.sort) {
const direction = const direction =

View File

@ -42,6 +42,7 @@ import {
getTableIDList, getTableIDList,
} from "./filters" } from "./filters"
import { dataFilters, PROTECTED_INTERNAL_COLUMNS } from "@budibase/shared-core" import { dataFilters, PROTECTED_INTERNAL_COLUMNS } from "@budibase/shared-core"
import { isSearchingByRowID } from "./utils"
const builder = new sql.Sql(SqlClient.SQL_LITE) const builder = new sql.Sql(SqlClient.SQL_LITE)
const MISSING_COLUMN_REGEX = new RegExp(`no such column: .+`) const MISSING_COLUMN_REGEX = new RegExp(`no such column: .+`)
@ -264,6 +265,10 @@ export async function search(
const relationships = buildInternalRelationships(table) const relationships = buildInternalRelationships(table)
const searchFilters: SearchFilters = {
...cleanupFilters(query, table, allTables),
documentType: DocumentType.ROW,
}
const request: QueryJson = { const request: QueryJson = {
endpoint: { endpoint: {
// not important, we query ourselves // not important, we query ourselves
@ -271,10 +276,7 @@ export async function search(
entityId: table._id!, entityId: table._id!,
operation: Operation.READ, operation: Operation.READ,
}, },
filters: { filters: searchFilters,
...cleanupFilters(query, table, allTables),
documentType: DocumentType.ROW,
},
table, table,
meta: { meta: {
table, table,
@ -304,7 +306,8 @@ export async function search(
} }
const bookmark: number = (params.bookmark as number) || 0 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 paginate = true
request.paginate = { request.paginate = {
limit: params.limit + 1, limit: params.limit + 1,

View File

@ -108,3 +108,18 @@ export function searchInputMapping(table: Table, options: RowSearchParams) {
} }
return options 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
}