diff --git a/packages/server/src/api/controllers/row/external.ts b/packages/server/src/api/controllers/row/external.ts index fb992059f4..2d4012dfcf 100644 --- a/packages/server/src/api/controllers/row/external.ts +++ b/packages/server/src/api/controllers/row/external.ts @@ -42,6 +42,7 @@ export async function handleRequest( export async function patch(ctx: UserCtx) { const source = await utils.getSource(ctx) + const table = await utils.getTableFromSource(source) const { _id, ...rowData } = ctx.request.body const { row: dataToUpdate } = await inputProcessing( @@ -58,11 +59,11 @@ export async function patch(ctx: UserCtx) { throw { validation: validateResult.errors } } - const beforeRow = await sdk.rows.external.getRow(tableId, _id, { + const beforeRow = await sdk.rows.external.getRow(table._id!, _id, { relationships: true, }) - const response = await handleRequest(Operation.UPDATE, tableId, { + const response = await handleRequest(Operation.UPDATE, source, { id: breakRowIdField(_id), row: dataToUpdate, }) @@ -70,7 +71,7 @@ export async function patch(ctx: UserCtx) { // The id might have been changed, so the refetching would fail. Recalculating the id just in case const updatedId = generateIdForRow({ ...beforeRow, ...dataToUpdate }, table) || _id - const row = await sdk.rows.external.getRow(tableId, updatedId, { + const row = await sdk.rows.external.getRow(table._id!, updatedId, { relationships: true, }) @@ -78,7 +79,6 @@ export async function patch(ctx: UserCtx) { outputProcessing(table, row, { squash: true, preserveLinks: true, - fromViewId: viewId, }), outputProcessing(table, beforeRow, { squash: true, @@ -95,9 +95,9 @@ export async function patch(ctx: UserCtx) { } export async function destroy(ctx: UserCtx) { - const { tableId } = utils.getSourceId(ctx) + const source = await utils.getSource(ctx) const _id = ctx.request.body._id - const { row } = await handleRequest(Operation.DELETE, tableId, { + const { row } = await handleRequest(Operation.DELETE, source, { id: breakRowIdField(_id), includeSqlRelationships: IncludeRelationship.EXCLUDE, }) @@ -106,11 +106,11 @@ export async function destroy(ctx: UserCtx) { export async function bulkDestroy(ctx: UserCtx) { const { rows } = ctx.request.body - const { tableId } = utils.getSourceId(ctx) + const source = await utils.getSource(ctx) let promises: Promise<{ row: Row; table: Table }>[] = [] for (let row of rows) { promises.push( - handleRequest(Operation.DELETE, tableId, { + handleRequest(Operation.DELETE, source, { id: breakRowIdField(row._id), includeSqlRelationships: IncludeRelationship.EXCLUDE, }) @@ -125,6 +125,7 @@ export async function bulkDestroy(ctx: UserCtx) { export async function fetchEnrichedRow(ctx: UserCtx) { const id = ctx.params.rowId + const source = await utils.getSource(ctx) const { tableId } = utils.getSourceId(ctx) const { datasourceId, tableName } = breakExternalTableId(tableId) const datasource: Datasource = await sdk.datasources.get(datasourceId) @@ -132,7 +133,7 @@ export async function fetchEnrichedRow(ctx: UserCtx) { ctx.throw(400, "Datasource has not been configured for plus API.") } const tables = datasource.entities - const response = await handleRequest(Operation.READ, tableId, { + const response = await handleRequest(Operation.READ, source, { id, datasource, includeSqlRelationships: IncludeRelationship.INCLUDE, @@ -156,7 +157,7 @@ export async function fetchEnrichedRow(ctx: UserCtx) { // don't support composite keys right now const linkedIds = links.map((link: Row) => breakRowIdField(link._id!)[0]) const primaryLink = linkedTable.primary?.[0] as string - const relatedRows = await handleRequest(Operation.READ, linkedTableId!, { + const relatedRows = await handleRequest(Operation.READ, linkedTable, { tables, filters: { oneOf: { diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 2e5785157d..ac2c09e7a6 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -221,7 +221,7 @@ export async function search(ctx: Ctx) { const searchParams: RowSearchParams = { ...ctx.request.body, query: enrichedQuery, - tableId, + sourceId: tableId, } ctx.status = 200 @@ -229,14 +229,15 @@ export async function search(ctx: Ctx) { } export async function validate(ctx: Ctx) { - const { tableId } = utils.getSourceId(ctx) + const source = await utils.getSource(ctx) + const table = await utils.getTableFromSource(source) // external tables are hard to validate currently - if (isExternalTableID(tableId)) { + if (isExternalTableID(table._id!)) { ctx.body = { valid: true, errors: {} } } else { ctx.body = await sdk.rows.utils.validate({ row: ctx.request.body, - tableId, + source, }) } } diff --git a/packages/server/src/api/controllers/row/utils/utils.ts b/packages/server/src/api/controllers/row/utils/utils.ts index 91c0fc966f..e5397ed4a5 100644 --- a/packages/server/src/api/controllers/row/utils/utils.ts +++ b/packages/server/src/api/controllers/row/utils/utils.ts @@ -106,19 +106,11 @@ export async function getSource(ctx: Ctx): Promise { return sdk.tables.getTable(tableId) } -export async function validate( - opts: { row: Row } & ({ tableId: string } | { table: Table }) -) { - let fetchedTable: Table - if ("tableId" in opts) { - fetchedTable = await sdk.tables.getTable(opts.tableId) - } else { - fetchedTable = opts.table +export async function getTableFromSource(source: Table | ViewV2) { + if (sdk.views.isView(source)) { + return await sdk.views.getTable(source.id) } - return sdk.rows.utils.validate({ - ...opts, - table: fetchedTable, - }) + return source } function fixBooleanFields({ row, table }: { row: Row; table: Table }) { diff --git a/packages/server/src/sdk/app/rows/external.ts b/packages/server/src/sdk/app/rows/external.ts index 7630e5638b..24ec2302e7 100644 --- a/packages/server/src/sdk/app/rows/external.ts +++ b/packages/server/src/sdk/app/rows/external.ts @@ -1,5 +1,11 @@ -import { IncludeRelationship, Operation, Row } from "@budibase/types" -import { HTTPError } from "@budibase/backend-core" +import { + IncludeRelationship, + Operation, + Row, + Table, + ViewV2, +} from "@budibase/types" +import { docIds, HTTPError } from "@budibase/backend-core" import { handleRequest } from "../../../api/controllers/row/external" import { breakRowIdField } from "../../../integrations/utils" import sdk from "../../../sdk" @@ -12,11 +18,21 @@ import isEqual from "lodash/fp/isEqual" import { tryExtractingTableAndViewId } from "./utils" export async function getRow( - tableId: string, + sourceId: string | Table | ViewV2, rowId: string, opts?: { relationships?: boolean } ) { - const response = await handleRequest(Operation.READ, tableId, { + let source: Table | ViewV2 + if (typeof sourceId === "string") { + if (docIds.isViewId(sourceId)) { + source = await sdk.views.get(sourceId) + } else { + source = await sdk.tables.getTable(sourceId) + } + } else { + source = sourceId + } + const response = await handleRequest(Operation.READ, source, { id: breakRowIdField(rowId), includeSqlRelationships: opts?.relationships ? IncludeRelationship.INCLUDE @@ -27,45 +43,50 @@ export async function getRow( } export async function save( - tableOrViewId: string, + sourceId: string, inputs: Row, userId: string | undefined ) { - const { tableId, viewId } = tryExtractingTableAndViewId(tableOrViewId) - const table = await sdk.tables.getTable(tableId) + const { tableId, viewId } = tryExtractingTableAndViewId(sourceId) + let source: Table | ViewV2 + if (viewId) { + source = await sdk.views.get(viewId) + } else { + source = await sdk.tables.getTable(tableId) + } + const { table: updatedTable, row } = await inputProcessing( userId, - cloneDeep(table), + cloneDeep(source), inputs ) const validateResult = await sdk.rows.utils.validate({ row, - tableId, + source, }) if (!validateResult.valid) { throw { validation: validateResult.errors } } - const response = await handleRequest(Operation.CREATE, tableId, { + const response = await handleRequest(Operation.CREATE, source, { row, }) - if (!isEqual(table, updatedTable)) { + if (sdk.tables.isTable(source) && !isEqual(source, updatedTable)) { await sdk.tables.saveTable(updatedTable) } const rowId = response.row._id if (rowId) { - const row = await getRow(tableId, rowId, { + const row = await getRow(source, rowId, { relationships: true, }) return { ...response, - row: await outputProcessing(table, row, { + row: await outputProcessing(source, row, { preserveLinks: true, squash: true, - fromViewId: viewId, }), } } else { @@ -76,7 +97,14 @@ export async function save( export async function find(tableOrViewId: string, rowId: string): Promise { const { tableId, viewId } = tryExtractingTableAndViewId(tableOrViewId) - const row = await getRow(tableId, rowId, { + let source: Table | ViewV2 + if (viewId) { + source = await sdk.views.get(viewId) + } else { + source = await sdk.tables.getTable(tableId) + } + + const row = await getRow(source, rowId, { relationships: true, }) @@ -84,11 +112,10 @@ export async function find(tableOrViewId: string, rowId: string): Promise { throw new HTTPError("Row not found", 404) } - const table = await sdk.tables.getTable(tableId) - // Preserving links, as the outputProcessing does not support external rows yet and we don't need it in this use case - return await outputProcessing(table, row, { + // Preserving links, as the outputProcessing does not support external rows + // yet and we don't need it in this use case + return await outputProcessing(source, row, { squash: true, preserveLinks: true, - fromViewId: viewId, }) } diff --git a/packages/server/src/sdk/app/rows/rows.ts b/packages/server/src/sdk/app/rows/rows.ts index ef25d06baf..25bdf1fd4f 100644 --- a/packages/server/src/sdk/app/rows/rows.ts +++ b/packages/server/src/sdk/app/rows/rows.ts @@ -1,9 +1,6 @@ import { db as dbCore, context, docIds } from "@budibase/backend-core" import { Database, Row } from "@budibase/types" -import { - extractViewInfoFromID, - getRowParams, -} from "../../../db/utils" +import { extractViewInfoFromID, getRowParams } from "../../../db/utils" import { isExternalTableID } from "../../../integrations/utils" import * as internal from "./internal" import * as external from "./external" @@ -36,13 +33,13 @@ function pickApi(tableOrViewId: string) { } export async function save( - tableOrViewId: string, + sourceId: string, row: Row, userId: string | undefined ) { - return pickApi(tableOrViewId).save(tableOrViewId, row, userId) + return pickApi(sourceId).save(sourceId, row, userId) } -export async function find(tableOrViewId: string, rowId: string) { - return pickApi(tableOrViewId).find(tableOrViewId, rowId) +export async function find(sourceId: string, rowId: string) { + return pickApi(sourceId).find(sourceId, rowId) } diff --git a/packages/server/src/sdk/app/rows/search/external.ts b/packages/server/src/sdk/app/rows/search/external.ts index 5584b3f110..a41ae8dcda 100644 --- a/packages/server/src/sdk/app/rows/search/external.ts +++ b/packages/server/src/sdk/app/rows/search/external.ts @@ -106,9 +106,9 @@ export async function search( includeSqlRelationships: IncludeRelationship.INCLUDE, } const [{ rows, rawResponseSize }, totalRows] = await Promise.all([ - handleRequest(Operation.READ, tableId, parameters), + handleRequest(Operation.READ, source, parameters), countRows - ? handleRequest(Operation.COUNT, tableId, parameters) + ? handleRequest(Operation.COUNT, source, parameters) : Promise.resolve(undefined), ]) @@ -200,7 +200,7 @@ export async function exportRows( } let result = await search( - { tableId, query: requestQuery, sort, sortOrder }, + { sourceId: table._id!, query: requestQuery, sort, sortOrder }, table ) let rows: Row[] = [] @@ -256,10 +256,10 @@ export async function exportRows( } export async function fetch(tableId: string): Promise { - const response = await handleRequest(Operation.READ, tableId, { + const table = await sdk.tables.getTable(tableId) + const response = await handleRequest(Operation.READ, table, { includeSqlRelationships: IncludeRelationship.INCLUDE, }) - const table = await sdk.tables.getTable(tableId) return await outputProcessing(table, response.rows, { preserveLinks: true, squash: true, @@ -267,7 +267,8 @@ export async function fetch(tableId: string): Promise { } export async function fetchRaw(tableId: string): Promise { - const response = await handleRequest(Operation.READ, tableId, { + const table = await sdk.tables.getTable(tableId) + const response = await handleRequest(Operation.READ, table, { includeSqlRelationships: IncludeRelationship.INCLUDE, }) return response.rows diff --git a/packages/server/src/sdk/app/rows/search/utils.ts b/packages/server/src/sdk/app/rows/search/utils.ts index d727e58887..6548f963b8 100644 --- a/packages/server/src/sdk/app/rows/search/utils.ts +++ b/packages/server/src/sdk/app/rows/search/utils.ts @@ -9,7 +9,6 @@ import { SearchResponse, Row, RowSearchParams, - ViewV2, } from "@budibase/types" import { db as dbCore, context } from "@budibase/backend-core" import { utils } from "@budibase/shared-core" diff --git a/packages/server/src/sdk/app/rows/utils.ts b/packages/server/src/sdk/app/rows/utils.ts index 3899009f13..f17d3e4a03 100644 --- a/packages/server/src/sdk/app/rows/utils.ts +++ b/packages/server/src/sdk/app/rows/utils.ts @@ -21,6 +21,7 @@ import sdk from "../.." import { extractViewInfoFromID, isRelationshipColumn } from "../../../db/utils" import { isSQL } from "../../../integrations/utils" import { docIds } from "@budibase/backend-core" +import { getTableFromSource } from "../../../api/controllers/row/utils" const SQL_CLIENT_SOURCE_MAP: Record = { [SourceName.POSTGRES]: SqlClient.POSTGRES, @@ -149,12 +150,7 @@ export async function validate({ valid: boolean errors: Record }> { - let table: Table - if (sdk.views.isView(source)) { - table = await sdk.views.getTable(source.id) - } else { - table = source - } + const table = await getTableFromSource(source) const errors: Record = {} const disallowArrayTypes = [ FieldType.ATTACHMENT_SINGLE, diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts index 73768fdd57..9aa53e18c1 100644 --- a/packages/server/src/utilities/rowProcessor/index.ts +++ b/packages/server/src/utilities/rowProcessor/index.ts @@ -19,6 +19,7 @@ import { RowAttachment, Table, User, + ViewV2, } from "@budibase/types" import { cloneDeep } from "lodash/fp" import { @@ -34,7 +35,11 @@ import { PROTECTED_INTERNAL_COLUMNS, } from "@budibase/shared-core" import { processString } from "@budibase/string-templates" -import { isUserMetadataTable } from "../../api/controllers/row/utils" +import { + getTableFromSource, + isUserMetadataTable, +} from "../../api/controllers/row/utils" +import sdk from "../../sdk" export * from "./utils" export * from "./attachments" @@ -170,11 +175,12 @@ export function coerce(row: any, type: string) { */ export async function inputProcessing( userId: string | null | undefined, - table: Table, + source: Table | ViewV2, row: Row, opts?: AutoColumnProcessingOpts ) { const clonedRow = cloneDeep(row) + const table = await getTableFromSource(source) const dontCleanseKeys = ["type", "_id", "_rev", "tableId"] for (const [key, value] of Object.entries(clonedRow)) { @@ -243,14 +249,13 @@ export async function inputProcessing( * @returns the enriched rows will be returned. */ export async function outputProcessing( - table: Table, + source: Table | ViewV2, rows: T, opts: { squash?: boolean preserveLinks?: boolean fromRow?: Row skipBBReferences?: boolean - fromViewId?: string aggregations?: Aggregation[] } = { squash: true,