diff --git a/packages/server/src/api/controllers/search/utils.js b/packages/server/src/api/controllers/search/utils.js index 5c662a0077..9fc10dabf9 100644 --- a/packages/server/src/api/controllers/search/utils.js +++ b/packages/server/src/api/controllers/search/utils.js @@ -33,6 +33,7 @@ class QueryBuilder { this.limit = 50 this.sortOrder = "ascending" this.sortType = "string" + this.includeDocs = true } setTable(tableId) { @@ -65,6 +66,11 @@ class QueryBuilder { return this } + excludeDocs() { + this.includeDocs = false + return this + } + addString(key, partial) { this.query.string[key] = partial return this @@ -103,15 +109,16 @@ class QueryBuilder { return this } - buildSearchURL(excludeDocs = false) { - let output = "*:*" + buildSearchQuery() { + let query = "*:*" + function build(structure, queryFn) { for (let [key, value] of Object.entries(structure)) { const expression = queryFn(luceneEscape(key.replace(/ /, "_")), value) if (expression == null) { continue } - output += ` AND ${expression}` + query += ` AND ${expression}` } } @@ -157,35 +164,43 @@ class QueryBuilder { build(this.query.notEmpty, key => `${key}:["" TO *]`) } - // Build the full search URL - let url = `${env.COUCH_DB_URL}/${this.appId}/_design/database/_search` - url += `/${SearchIndexes.ROWS}?q=${output}` - url += `&limit=${Math.min(this.limit, 200)}` - if (!excludeDocs) { - url += "&include_docs=true" - } - if (this.sort) { - const orderChar = this.sortOrder === "descending" ? "-" : "" - url += `&sort="${orderChar}${this.sort.replace(/ /, "_")}` - url += `<${this.sortType}>"` + return query + } + + buildSearchBody() { + let body = { + q: this.buildSearchQuery(), + limit: Math.min(this.limit, 200), + include_docs: this.includeDocs, } if (this.bookmark) { - url += `&bookmark=${this.bookmark}` + body.bookmark = this.bookmark } + if (this.sort) { + const order = this.sortOrder === "descending" ? "-" : "" + const type = `<${this.sortType}>` + body.sort = `${order}${this.sort.replace(/ /, "_")}${type}` + } + return body + } - // Fix any double slashes in the URL - return checkSlashesInUrl(url) + async run() { + const url = `${env.COUCH_DB_URL}/${this.appId}/_design/database/_search/${SearchIndexes.ROWS}` + const body = this.buildSearchBody() + return await runQuery(url, body) } } /** * Executes a lucene search query. - * @param query The query URL + * @param url The query URL + * @param body The request body defining search criteria * @returns {Promise<{rows: []}>} */ -const runQuery = async query => { - const response = await fetch(query, { - method: "GET", +const runQuery = async (url, body) => { + const response = await fetch(url, { + body: JSON.stringify(body), + method: "POST", }) const json = await response.json() let output = { @@ -227,15 +242,14 @@ const recursiveSearch = async (appId, query, params) => { if (rows.length > params.limit - 200) { pageSize = params.limit - rows.length } - const url = new QueryBuilder(appId, query) + const page = await new QueryBuilder(appId, query) .setTable(params.tableId) .setBookmark(bookmark) .setLimit(pageSize) .setSort(params.sort) .setSortOrder(params.sortOrder) .setSortType(params.sortType) - .buildSearchURL() - const page = await runQuery(url) + .run() if (!page.rows.length) { return rows } @@ -272,24 +286,22 @@ exports.paginatedSearch = async (appId, query, params) => { limit = 50 } limit = Math.min(limit, 200) - const builder = new QueryBuilder(appId, query) + const search = new QueryBuilder(appId, query) .setTable(params.tableId) .setSort(params.sort) .setSortOrder(params.sortOrder) .setSortType(params.sortType) - const searchUrl = builder + const searchResults = await search .setBookmark(params.bookmark) .setLimit(limit) - .buildSearchURL() - const searchResults = await runQuery(searchUrl) + .run() // Try fetching 1 row in the next page to see if another page of results // exists or not - const nextUrl = builder + const nextResults = await search .setBookmark(searchResults.bookmark) .setLimit(1) - .buildSearchURL() - const nextResults = await runQuery(nextUrl) + .run() return { ...searchResults,