From 77b2e68935136341acb8b0d5c8b1cc0bc919d88a Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 5 Jul 2022 19:54:11 +0100 Subject: [PATCH] Updating specs to state that oneOf is allowed, fixing YAML issue and making sure allOr function works for lucene. --- packages/server/specs/openapi.json | 151 ++++++++++++++++++ packages/server/specs/openapi.yaml | 115 +++++++++++++ .../src/api/controllers/row/internalSearch.js | 36 +++-- packages/server/src/api/routes/public/rows.ts | 3 +- .../server/src/api/routes/utils/validators.js | 1 + packages/server/src/definitions/openapi.ts | 78 +++++++++ 6 files changed, 373 insertions(+), 11 deletions(-) diff --git a/packages/server/specs/openapi.json b/packages/server/specs/openapi.json index e84061c41b..ed58c12525 100644 --- a/packages/server/specs/openapi.json +++ b/packages/server/specs/openapi.json @@ -2110,6 +2110,157 @@ } } }, + "/tables/{tableId}/rows/search": { + "post": { + "summary": "Search for rows", + "tags": [ + "rows" + ], + "parameters": [ + { + "$ref": "#/components/parameters/tableId" + }, + { + "$ref": "#/components/parameters/appId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "object", + "properties": { + "allOr": { + "type": "boolean", + "description": "Specifies that a row should be returned if it satisfies any of the specified options, rather than requiring it to fulfill all the search parameters. This defaults to false, meaning AND logic will be used." + }, + "string": { + "type": "object", + "example": { + "columnName1": "value", + "columnName2": "value" + }, + "description": "A map of field name to the string to search for, this will look for rows that have a value starting with the string value.", + "additionalProperties": { + "type": "string", + "description": "The value to search for in the column." + } + }, + "fuzzy": { + "type": "object", + "description": "A fuzzy search, only supported by internal tables." + }, + "range": { + "type": "object", + "description": "Searches within a range, the format of this must be in the format of an object with a \"low\" and \"high\" property.", + "example": { + "columnName1": { + "low": 10, + "high": 20 + } + } + }, + "equal": { + "type": "object", + "description": "Searches for rows that have a column value that is exactly the value set." + }, + "notEqual": { + "type": "object", + "description": "Searches for any row which does not contain the specified column value." + }, + "empty": { + "type": "object", + "description": "Searches for rows which do not contain the specified column. The object should simply contain keys of the column names, these can map to any value.", + "example": { + "columnName1": "" + } + }, + "notEmpty": { + "type": "object", + "description": "Searches for rows which have the specified column." + }, + "oneOf": { + "type": "object", + "description": "Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2]." + } + } + }, + "paginate": { + "type": "boolean", + "description": "Enables pagination, by default this is disabled." + }, + "bookmark": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ], + "description": "If retrieving another page, the bookmark from the previous request must be supplied." + }, + "limit": { + "type": "integer", + "description": "The maximum number of rows to return, useful when paginating, for internal tables this will be limited to 1000, for SQL tables it will be 5000." + }, + "sort": { + "type": "object", + "description": "A set of parameters describing the sort behaviour of the search.", + "properties": { + "order": { + "type": "string", + "enum": [ + "ascending", + "descending" + ], + "description": "The order of the sort, by default this is ascending." + }, + "column": { + "type": "string", + "description": "The name of the column by which the rows will be sorted." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number" + ], + "description": "Defines whether the column should be treated as a string or as numbers when sorting." + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The response will contain an array of rows that match the search parameters.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/searchOutput" + }, + "examples": { + "search": { + "$ref": "#/components/examples/rows" + } + } + } + } + } + } + } + }, "/tables": { "post": { "summary": "Create a table", diff --git a/packages/server/specs/openapi.yaml b/packages/server/specs/openapi.yaml index 09da4227ff..cbc7c1ea53 100644 --- a/packages/server/specs/openapi.yaml +++ b/packages/server/specs/openapi.yaml @@ -1531,6 +1531,121 @@ paths: examples: enrichedRow: $ref: "#/components/examples/enrichedRow" + "/tables/{tableId}/rows/search": + post: + summary: Search for rows + tags: + - rows + parameters: + - $ref: "#/components/parameters/tableId" + - $ref: "#/components/parameters/appId" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - query + properties: + query: + type: object + properties: + allOr: + type: boolean + description: Specifies that a row should be returned if it satisfies any of the + specified options, rather than requiring it to fulfill + all the search parameters. This defaults to false, + meaning AND logic will be used. + string: + type: object + example: + columnName1: value + columnName2: value + description: A map of field name to the string to search for, this will look for + rows that have a value starting with the string value. + additionalProperties: + type: string + description: The value to search for in the column. + fuzzy: + type: object + description: A fuzzy search, only supported by internal tables. + range: + type: object + description: Searches within a range, the format of this must be in the format + of an object with a "low" and "high" property. + example: + columnName1: + low: 10 + high: 20 + equal: + type: object + description: Searches for rows that have a column value that is exactly the + value set. + notEqual: + type: object + description: Searches for any row which does not contain the specified column + value. + empty: + type: object + description: Searches for rows which do not contain the specified column. The + object should simply contain keys of the column names, + these can map to any value. + example: + columnName1: "" + notEmpty: + type: object + description: Searches for rows which have the specified column. + oneOf: + type: object + description: Searches for rows which have a column value that is any of the + specified values. The format of this must be columnName + -> [value1, value2]. + paginate: + type: boolean + description: Enables pagination, by default this is disabled. + bookmark: + oneOf: + - type: string + - type: integer + description: If retrieving another page, the bookmark from the previous request + must be supplied. + limit: + type: integer + description: The maximum number of rows to return, useful when paginating, for + internal tables this will be limited to 1000, for SQL tables + it will be 5000. + sort: + type: object + description: A set of parameters describing the sort behaviour of the search. + properties: + order: + type: string + enum: + - ascending + - descending + description: The order of the sort, by default this is ascending. + column: + type: string + description: The name of the column by which the rows will be sorted. + type: + type: string + enum: + - string + - number + description: Defines whether the column should be treated as a string or as + numbers when sorting. + responses: + "200": + description: The response will contain an array of rows that match the search + parameters. + content: + application/json: + schema: + $ref: "#/components/schemas/searchOutput" + examples: + search: + $ref: "#/components/examples/rows" /tables: post: summary: Create a table diff --git a/packages/server/src/api/controllers/row/internalSearch.js b/packages/server/src/api/controllers/row/internalSearch.js index d262592d3b..e6090ad8f0 100644 --- a/packages/server/src/api/controllers/row/internalSearch.js +++ b/packages/server/src/api/controllers/row/internalSearch.js @@ -10,6 +10,7 @@ const { getAppId } = require("@budibase/backend-core/context") class QueryBuilder { constructor(base) { this.query = { + allOr: false, string: {}, fuzzy: {}, range: {}, @@ -146,8 +147,22 @@ class QueryBuilder { buildSearchQuery() { const builder = this - let query = "*:*" + let allOr = this.query && this.query.allOr + let query = allOr ? "" : "*:*" const allPreProcessingOpts = { escape: true, lowercase: true, wrap: true } + let tableId + if (this.query.equal.tableId) { + tableId = this.query.equal.tableId + delete this.query.equal.tableId + } + + const equal = (key, value) => { + // 0 evaluates to false, which means we would return all rows if we don't check it + if (!value && value !== 0) { + return null + } + return `${key}:${builder.preprocess(value, allPreProcessingOpts)}` + } function build(structure, queryFn) { for (let [key, value] of Object.entries(structure)) { @@ -158,7 +173,10 @@ class QueryBuilder { if (expression == null) { continue } - query += ` AND ${expression}` + if (query.length > 0) { + query += ` ${allOr ? "OR" : "AND"} ` + } + query += expression } } @@ -204,13 +222,7 @@ class QueryBuilder { }) } if (this.query.equal) { - build(this.query.equal, (key, value) => { - // 0 evaluates to false, which means we would return all rows if we don't check it - if (!value && value !== 0) { - return null - } - return `${key}:${builder.preprocess(value, allPreProcessingOpts)}` - }) + build(this.query.equal, equal) } if (this.query.notEqual) { build(this.query.notEqual, (key, value) => { @@ -248,6 +260,12 @@ class QueryBuilder { return `${key}:(${orStatement})` }) } + // make sure table ID is always added as an AND + if (tableId) { + query = `(${query})` + allOr = false + build({ tableId }, equal) + } return query } diff --git a/packages/server/src/api/routes/public/rows.ts b/packages/server/src/api/routes/public/rows.ts index cdb954717d..7c9e4f7af7 100644 --- a/packages/server/src/api/routes/public/rows.ts +++ b/packages/server/src/api/routes/public/rows.ts @@ -172,7 +172,7 @@ read.push(new Endpoint("get", "/tables/:tableId/rows/:rowId", controller.read)) * range: * type: object * description: Searches within a range, the format of this must be - * columnName -> { low: value1, high: value2 }. + * in the format of an object with a "low" and "high" property. * example: * columnName1: { low: 10, high: 20 } * equal: @@ -197,7 +197,6 @@ read.push(new Endpoint("get", "/tables/:tableId/rows/:rowId", controller.read)) * type: object * description: Searches for rows which have a column value that is any * of the specified values. The format of this must be columnName -> [value1, value2]. - * Note this is only supported for SQL based tables. * paginate: * type: boolean * description: Enables pagination, by default this is disabled. diff --git a/packages/server/src/api/routes/utils/validators.js b/packages/server/src/api/routes/utils/validators.js index 7d5de340b2..9567db1ce2 100644 --- a/packages/server/src/api/routes/utils/validators.js +++ b/packages/server/src/api/routes/utils/validators.js @@ -58,6 +58,7 @@ function filterObject() { oneOf: Joi.object().optional(), contains: Joi.object().optional(), notContains: Joi.object().optional(), + allOr: Joi.boolean().optional(), }).unknown(true) } diff --git a/packages/server/src/definitions/openapi.ts b/packages/server/src/definitions/openapi.ts index 89fd24c125..6cc0de2472 100644 --- a/packages/server/src/definitions/openapi.ts +++ b/packages/server/src/definitions/openapi.ts @@ -256,6 +256,84 @@ export interface paths { }; }; }; + "/tables/{tableId}/rows/search": { + post: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"]; + }; + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"]; + }; + }; + responses: { + /** The response will contain an array of rows that match the search parameters. */ + 200: { + content: { + "application/json": components["schemas"]["searchOutput"]; + }; + }; + }; + requestBody: { + content: { + "application/json": { + query: { + /** @description Specifies that a row should be returned if it satisfies any of the specified options, rather than requiring it to fulfill all the search parameters. This defaults to false, meaning AND logic will be used. */ + allOr?: boolean; + /** + * @description A map of field name to the string to search for, this will look for rows that have a value starting with the string value. + * @example [object Object] + */ + string?: { [key: string]: string }; + /** @description A fuzzy search, only supported by internal tables. */ + fuzzy?: { [key: string]: unknown }; + /** + * @description Searches within a range, the format of this must be in the format of an object with a "low" and "high" property. + * @example [object Object] + */ + range?: { [key: string]: unknown }; + /** @description Searches for rows that have a column value that is exactly the value set. */ + equal?: { [key: string]: unknown }; + /** @description Searches for any row which does not contain the specified column value. */ + notEqual?: { [key: string]: unknown }; + /** + * @description Searches for rows which do not contain the specified column. The object should simply contain keys of the column names, these can map to any value. + * @example [object Object] + */ + empty?: { [key: string]: unknown }; + /** @description Searches for rows which have the specified column. */ + notEmpty?: { [key: string]: unknown }; + /** @description Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2]. */ + oneOf?: { [key: string]: unknown }; + }; + /** @description Enables pagination, by default this is disabled. */ + paginate?: boolean; + /** @description If retrieving another page, the bookmark from the previous request must be supplied. */ + bookmark?: string | number; + /** @description The maximum number of rows to return, useful when paginating, for internal tables this will be limited to 1000, for SQL tables it will be 5000. */ + limit?: number; + /** @description A set of parameters describing the sort behaviour of the search. */ + sort?: { + /** + * @description The order of the sort, by default this is ascending. + * @enum {string} + */ + order?: "ascending" | "descending"; + /** @description The name of the column by which the rows will be sorted. */ + column?: string; + /** + * @description Defines whether the column should be treated as a string or as numbers when sorting. + * @enum {string} + */ + type?: "string" | "number"; + }; + }; + }; + }; + }; + }; "/tables": { /** Create a table, this could be internal or external. */ post: {