diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts index d9036b22fb..ab4c28a90d 100644 --- a/packages/server/src/api/routes/tests/search.spec.ts +++ b/packages/server/src/api/routes/tests/search.spec.ts @@ -780,6 +780,32 @@ describe.each([ it("fails to find nonexistent row", async () => { await expectQuery({ oneOf: { name: ["none"] } }).toFindNothing() }) + + it("can have multiple values for same column", async () => { + await expectQuery({ + oneOf: { + name: ["foo", "bar"], + }, + }).toContainExactly([{ name: "foo" }, { name: "bar" }]) + }) + + it("splits comma separated strings", async () => { + await expectQuery({ + oneOf: { + // @ts-ignore + name: "foo,bar", + }, + }).toContainExactly([{ name: "foo" }, { name: "bar" }]) + }) + + it("trims whitespace", async () => { + await expectQuery({ + oneOf: { + // @ts-ignore + name: "foo, bar", + }, + }).toContainExactly([{ name: "foo" }, { name: "bar" }]) + }) }) describe("fuzzy", () => { @@ -1002,6 +1028,32 @@ describe.each([ it("fails to find nonexistent row", async () => { await expectQuery({ oneOf: { age: [2] } }).toFindNothing() }) + + // I couldn't find a way to make this work in Lucene and given that + // we're getting rid of Lucene soon I wasn't inclined to spend time on + // it. + !isLucene && + it("can convert from a string", async () => { + await expectQuery({ + oneOf: { + // @ts-ignore + age: "1", + }, + }).toContainExactly([{ age: 1 }]) + }) + + // I couldn't find a way to make this work in Lucene and given that + // we're getting rid of Lucene soon I wasn't inclined to spend time on + // it. + !isLucene && + it("can find multiple values for same column", async () => { + await expectQuery({ + oneOf: { + // @ts-ignore + age: "1,10", + }, + }).toContainExactly([{ age: 1 }, { age: 10 }]) + }) }) describe("range", () => { diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index 286a88054c..e2ec743d0e 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -66,37 +66,12 @@ export function removeEmptyFilters(filters: SearchFilters) { return filters } -// The frontend can send single values for array fields sometimes, so to handle -// this we convert them to arrays at the controller level so that nothing below -// this has to worry about the non-array values. -function fixupFilterArrays(filters: SearchFilters) { - const arrayFields = [ - SearchFilterOperator.ONE_OF, - SearchFilterOperator.CONTAINS, - SearchFilterOperator.NOT_CONTAINS, - SearchFilterOperator.CONTAINS_ANY, - ] - for (const searchField of arrayFields) { - const field = filters[searchField] - if (field == null) { - continue - } - - for (const key of Object.keys(field)) { - if (!Array.isArray(field[key])) { - field[key] = [field[key]] - } - } - } - return filters -} - export async function search( options: RowSearchParams ): Promise> { const isExternalTable = isExternalTableID(options.tableId) options.query = removeEmptyFilters(options.query || {}) - options.query = fixupFilterArrays(options.query) + options.query = dataFilters.fixupFilterArrays(options.query) if ( !dataFilters.hasFilters(options.query) && options.query.onEmptyFilter === EmptyFilterOption.RETURN_NONE diff --git a/packages/shared-core/src/filters.ts b/packages/shared-core/src/filters.ts index 3c6901e195..d4d5918bbb 100644 --- a/packages/shared-core/src/filters.ts +++ b/packages/shared-core/src/filters.ts @@ -327,6 +327,35 @@ export const buildQuery = (filter: SearchFilter[]) => { return query } +// The frontend can send single values for array fields sometimes, so to handle +// this we convert them to arrays at the controller level so that nothing below +// this has to worry about the non-array values. +export function fixupFilterArrays(filters: SearchFilters) { + const arrayFields = [ + SearchFilterOperator.ONE_OF, + SearchFilterOperator.CONTAINS, + SearchFilterOperator.NOT_CONTAINS, + SearchFilterOperator.CONTAINS_ANY, + ] + for (const searchField of arrayFields) { + const field = filters[searchField] + if (field == null) { + continue + } + + for (const key of Object.keys(field)) { + if (!Array.isArray(field[key])) { + if (typeof field[key] !== "string") { + field[key] = [field[key]] + } else { + field[key] = field[key].split(",").map(x => x.trim()) + } + } + } + } + return filters +} + export const search = ( docs: Record[], query: RowSearchParams @@ -360,6 +389,7 @@ export const runQuery = (docs: Record[], query: SearchFilters) => { } query = cleanupQuery(query) + query = fixupFilterArrays(query) if ( !hasFilters(query) && @@ -528,9 +558,10 @@ export const runQuery = (docs: Record[], query: SearchFilters) => { (docValue: any, testValue: any) => { if (typeof testValue === "string") { testValue = testValue.split(",") - if (typeof docValue === "number") { - testValue = testValue.map((item: string) => parseFloat(item)) - } + } + + if (typeof docValue === "number") { + testValue = testValue.map((item: string) => parseFloat(item)) } if (!Array.isArray(testValue)) {