diff --git a/packages/backend-core/src/sql/sql.ts b/packages/backend-core/src/sql/sql.ts index cd1bd6167c..1ed8666e21 100644 --- a/packages/backend-core/src/sql/sql.ts +++ b/packages/backend-core/src/sql/sql.ts @@ -12,21 +12,21 @@ import { BBReferenceFieldMetadata, FieldSchema, FieldType, + INTERNAL_TABLE_SOURCE_ID, JsonFieldMetadata, + JsonTypes, Operation, + prefixed, QueryJson, + QueryOptions, RelationshipsJson, SearchFilters, + SortOrder, SqlClient, SqlQuery, SqlQueryBinding, Table, TableSourceType, - INTERNAL_TABLE_SOURCE_ID, - QueryOptions, - JsonTypes, - prefixed, - SortOrder, } from "@budibase/types" import environment from "../environment" import { helpers } from "@budibase/shared-core" @@ -824,6 +824,9 @@ class SqlQueryBuilder extends SqlTableQueryBuilder { await this.getReturningRow(queryFn, this.checkLookupKeys(id, json)) ) } + if (operation === Operation.COUNT) { + return results + } if (operation !== Operation.READ) { return row } diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts index f651908c01..534c08c7c9 100644 --- a/packages/server/src/api/routes/tests/search.spec.ts +++ b/packages/server/src/api/routes/tests/search.spec.ts @@ -18,6 +18,7 @@ import { User, Row, RelationshipType, + SearchResponse, } from "@budibase/types" import _ from "lodash" import tk from "timekeeper" @@ -105,6 +106,17 @@ describe.each([ } } + private async performSearchFullResponse(): Promise> { + if (isInMemory) { + return { rows: dataFilters.search(_.cloneDeep(rows), this.query) } + } else { + return config.api.row.search(table._id!, { + ...this.query, + tableId: table._id!, + }) + } + } + // We originally used _.isMatch to compare rows, but found that when // comparing arrays it would return true if the source array was a subset of // the target array. This would sometimes create false matches. This @@ -205,6 +217,34 @@ describe.each([ ) } + // Asserts that the query returns some property values - this cannot be used + // to check row values, however this shouldn't be important for checking properties + async toHaveProperty( + properties: { + key: keyof SearchResponse + value?: any + }[] + ) { + const response = await this.performSearchFullResponse() + for (let property of properties) { + // eslint-disable-next-line jest/no-standalone-expect + expect(response[property.key]).toBeDefined() + if (property.value) { + // eslint-disable-next-line jest/no-standalone-expect + expect(response[property.key]).toEqual(property.value) + } + } + } + + // Asserts that the query doesn't return a property, e.g. pagination parameters. + async toNotHaveProperty(properties: (keyof SearchResponse)[]) { + const response = await this.performSearchFullResponse() + for (let property of properties) { + // eslint-disable-next-line jest/no-standalone-expect + expect(response[property]).toBeUndefined() + } + } + // Asserts that the query returns rows matching the set of rows passed in. // The order of the rows is not important. Extra rows will not cause the // assertion to fail. @@ -1797,4 +1837,40 @@ describe.each([ { two: "foo", other: [{ _id: otherRows[0]._id }] }, ])) }) + + // lucene can't count, and in memory there is no point + !isLucene && + !isInMemory && + describe("row counting", () => { + beforeAll(async () => { + table = await createTable({ + name: { + name: "name", + type: FieldType.STRING, + }, + }) + await createRows([{ name: "a" }, { name: "b" }]) + }) + + it("should be able to count rows when option set", () => + expectSearch({ + countRows: true, + query: { + notEmpty: { + name: true, + }, + }, + }).toHaveProperty([{ key: "totalRows", value: 2 }, { key: "rows" }])) + + it("shouldn't count rows when option is not set", () => { + expectSearch({ + countRows: false, + query: { + notEmpty: { + name: true, + }, + }, + }).toNotHaveProperty(["totalRows"]) + }) + }) }) diff --git a/packages/server/src/sdk/app/rows/search/sqs.ts b/packages/server/src/sdk/app/rows/search/sqs.ts index 684e4bd9a6..be365c9ed6 100644 --- a/packages/server/src/sdk/app/rows/search/sqs.ts +++ b/packages/server/src/sdk/app/rows/search/sqs.ts @@ -230,10 +230,12 @@ export async function search( nextRow = processed.pop() } - let rowCount: number | undefined + let totalRows: number | undefined if (options.countRows) { // get the total count of rows - rowCount = await runSqlQuery(request, allTables, { countTotalRows: true }) + totalRows = await runSqlQuery(request, allTables, { + countTotalRows: true, + }) } // get the rows @@ -248,24 +250,18 @@ export async function search( finalRows = finalRows.map((r: any) => pick(r, fields)) } - // check for pagination - if (paginate) { - const response: SearchResponse = { - rows: finalRows, - } - if (nextRow) { - response.hasNextPage = true - response.bookmark = bookmark + 1 - } - if (rowCount != null) { - response.totalRows = rowCount - } - return response - } else { - return { - rows: finalRows, - } + const response: SearchResponse = { + rows: finalRows, } + if (totalRows) { + response.totalRows = totalRows + } + // check for pagination + if (paginate && nextRow) { + response.hasNextPage = true + response.bookmark = bookmark + 1 + } + return response } catch (err: any) { const msg = typeof err === "string" ? err : err.message if (err.status === 404 && msg?.includes(SQLITE_DESIGN_DOC_ID)) {