When searching by row ID with external DBs/SQS we can get into a situation where the limit of 1 which is applied by the frontend can cause problems, with many to many relationships we need to retrieve multiple rows (all of the joined related rows). This was raised by poirazis, it exhibits itself in one part of the platform, when attempting to a row by ID in a form block that has multiple many to many relationships. The frontend needs to be able to send a limit of 1 incase it is using a form block but hasn't gotten a row ID (this can happen in preview/the builder) and it just wants to populate with a row for display.
This commit is contained in:
parent
9a181c9707
commit
9fb1c6b988
|
@ -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,
|
||||
},
|
||||
relationship: {
|
||||
name: "relationship",
|
||||
type: FieldType.LINK,
|
||||
relationshipType: RelationshipType.MANY_TO_MANY,
|
||||
tableId: toRelateTable._id!,
|
||||
fieldName: "relationship",
|
||||
},
|
||||
})
|
||||
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",
|
||||
relationship: [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])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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<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
|
||||
}
|
||||
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<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
|
||||
if (params.sort) {
|
||||
const direction =
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 => key.endsWith("_id") && searchField[key]
|
||||
)
|
||||
if (hasId) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue