Merge branch 'master' of github.com:Budibase/budibase into fix/sql-many-relationships

This commit is contained in:
mike12345567 2024-08-28 16:58:23 +01:00
commit 413628ca3f
18 changed files with 115 additions and 88 deletions

@ -1 +1 @@
Subproject commit 14c89a5b20ee4de07723063458a2437b0dd4f2c9 Subproject commit 51a83b791a7a11b1d51c1fdb91f2ac246298279e

View File

@ -38,7 +38,7 @@ export async function handleRequest<T extends Operation>(
} }
export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) { export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const { _id, ...rowData } = ctx.request.body const { _id, ...rowData } = ctx.request.body
const table = await sdk.tables.getTable(tableId) const table = await sdk.tables.getTable(tableId)
@ -93,7 +93,7 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
} }
export async function destroy(ctx: UserCtx) { export async function destroy(ctx: UserCtx) {
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const _id = ctx.request.body._id const _id = ctx.request.body._id
const { row } = await handleRequest(Operation.DELETE, tableId, { const { row } = await handleRequest(Operation.DELETE, tableId, {
id: breakRowIdField(_id), id: breakRowIdField(_id),
@ -104,7 +104,7 @@ export async function destroy(ctx: UserCtx) {
export async function bulkDestroy(ctx: UserCtx) { export async function bulkDestroy(ctx: UserCtx) {
const { rows } = ctx.request.body const { rows } = ctx.request.body
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
let promises: Promise<{ row: Row; table: Table }>[] = [] let promises: Promise<{ row: Row; table: Table }>[] = []
for (let row of rows) { for (let row of rows) {
promises.push( promises.push(
@ -123,7 +123,7 @@ export async function bulkDestroy(ctx: UserCtx) {
export async function fetchEnrichedRow(ctx: UserCtx) { export async function fetchEnrichedRow(ctx: UserCtx) {
const id = ctx.params.rowId const id = ctx.params.rowId
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const { datasourceId, tableName } = breakExternalTableId(tableId) const { datasourceId, tableName } = breakExternalTableId(tableId)
const datasource: Datasource = await sdk.datasources.get(datasourceId) const datasource: Datasource = await sdk.datasources.get(datasourceId)
if (!datasource || !datasource.entities) { if (!datasource || !datasource.entities) {

View File

@ -47,7 +47,7 @@ export async function patch(
ctx: UserCtx<PatchRowRequest, PatchRowResponse> ctx: UserCtx<PatchRowRequest, PatchRowResponse>
): Promise<any> { ): Promise<any> {
const appId = ctx.appId const appId = ctx.appId
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const body = ctx.request.body const body = ctx.request.body
// if it doesn't have an _id then its save // if it doesn't have an _id then its save
@ -72,7 +72,7 @@ export async function patch(
export const save = async (ctx: UserCtx<Row, Row>) => { export const save = async (ctx: UserCtx<Row, Row>) => {
const appId = ctx.appId const appId = ctx.appId
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const body = ctx.request.body const body = ctx.request.body
// user metadata doesn't exist yet - don't allow creation // user metadata doesn't exist yet - don't allow creation
@ -97,13 +97,12 @@ export const save = async (ctx: UserCtx<Row, Row>) => {
gridSocket?.emitRowUpdate(ctx, row || squashed) gridSocket?.emitRowUpdate(ctx, row || squashed)
} }
export async function fetchView(ctx: any) { export async function fetchLegacyView(ctx: any) {
const tableId = utils.getTableId(ctx)
const viewName = decodeURIComponent(ctx.params.viewName) const viewName = decodeURIComponent(ctx.params.viewName)
const { calculation, group, field } = ctx.query const { calculation, group, field } = ctx.query
ctx.body = await sdk.rows.fetchView(tableId, viewName, { ctx.body = await sdk.rows.fetchLegacyView(viewName, {
calculation, calculation,
group: calculation ? group : null, group: calculation ? group : null,
field, field,
@ -111,12 +110,12 @@ export async function fetchView(ctx: any) {
} }
export async function fetch(ctx: any) { export async function fetch(ctx: any) {
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
ctx.body = await sdk.rows.fetch(tableId) ctx.body = await sdk.rows.fetch(tableId)
} }
export async function find(ctx: UserCtx<void, GetRowResponse>) { export async function find(ctx: UserCtx<void, GetRowResponse>) {
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const rowId = ctx.params.rowId const rowId = ctx.params.rowId
ctx.body = await sdk.rows.find(tableId, rowId) ctx.body = await sdk.rows.find(tableId, rowId)
@ -132,7 +131,7 @@ function isDeleteRow(input: any): input is DeleteRow {
async function processDeleteRowsRequest(ctx: UserCtx<DeleteRowRequest>) { async function processDeleteRowsRequest(ctx: UserCtx<DeleteRowRequest>) {
let request = ctx.request.body as DeleteRows let request = ctx.request.body as DeleteRows
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const processedRows = request.rows.map(row => { const processedRows = request.rows.map(row => {
let processedRow: Row = typeof row == "string" ? { _id: row } : row let processedRow: Row = typeof row == "string" ? { _id: row } : row
@ -148,7 +147,7 @@ async function processDeleteRowsRequest(ctx: UserCtx<DeleteRowRequest>) {
} }
async function deleteRows(ctx: UserCtx<DeleteRowRequest>) { async function deleteRows(ctx: UserCtx<DeleteRowRequest>) {
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const appId = ctx.appId const appId = ctx.appId
let deleteRequest = ctx.request.body as DeleteRows let deleteRequest = ctx.request.body as DeleteRows
@ -170,7 +169,7 @@ async function deleteRows(ctx: UserCtx<DeleteRowRequest>) {
async function deleteRow(ctx: UserCtx<DeleteRowRequest>) { async function deleteRow(ctx: UserCtx<DeleteRowRequest>) {
const appId = ctx.appId const appId = ctx.appId
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const resp = await pickApi(tableId).destroy(ctx) const resp = await pickApi(tableId).destroy(ctx)
if (!tableId.includes("datasource_plus")) { if (!tableId.includes("datasource_plus")) {
@ -204,7 +203,7 @@ export async function destroy(ctx: UserCtx<DeleteRowRequest>) {
} }
export async function search(ctx: Ctx<SearchRowRequest, SearchRowResponse>) { export async function search(ctx: Ctx<SearchRowRequest, SearchRowResponse>) {
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
await context.ensureSnippetContext(true) await context.ensureSnippetContext(true)
@ -226,7 +225,7 @@ export async function search(ctx: Ctx<SearchRowRequest, SearchRowResponse>) {
} }
export async function validate(ctx: Ctx<Row, ValidateResponse>) { export async function validate(ctx: Ctx<Row, ValidateResponse>) {
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
// external tables are hard to validate currently // external tables are hard to validate currently
if (isExternalTableID(tableId)) { if (isExternalTableID(tableId)) {
ctx.body = { valid: true, errors: {} } ctx.body = { valid: true, errors: {} }
@ -239,14 +238,14 @@ export async function validate(ctx: Ctx<Row, ValidateResponse>) {
} }
export async function fetchEnrichedRow(ctx: UserCtx<void, Row>) { export async function fetchEnrichedRow(ctx: UserCtx<void, Row>) {
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
ctx.body = await pickApi(tableId).fetchEnrichedRow(ctx) ctx.body = await pickApi(tableId).fetchEnrichedRow(ctx)
} }
export const exportRows = async ( export const exportRows = async (
ctx: Ctx<ExportRowsRequest, ExportRowsResponse> ctx: Ctx<ExportRowsRequest, ExportRowsResponse>
) => { ) => {
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const format = ctx.query.format const format = ctx.query.format
@ -279,7 +278,7 @@ export const exportRows = async (
export async function downloadAttachment(ctx: UserCtx) { export async function downloadAttachment(ctx: UserCtx) {
const { columnName } = ctx.params const { columnName } = ctx.params
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const rowId = ctx.params.rowId const rowId = ctx.params.rowId
const row = await sdk.rows.find(tableId, rowId) const row = await sdk.rows.find(tableId, rowId)

View File

@ -16,7 +16,6 @@ import {
PatchRowRequest, PatchRowRequest,
PatchRowResponse, PatchRowResponse,
Row, Row,
Table,
UserCtx, UserCtx,
} from "@budibase/types" } from "@budibase/types"
import sdk from "../../../sdk" import sdk from "../../../sdk"
@ -24,7 +23,7 @@ import { getLinkedTableIDs } from "../../../db/linkedRows/linkUtils"
import { flatten } from "lodash" import { flatten } from "lodash"
export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) { export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const inputs = ctx.request.body const inputs = ctx.request.body
const isUserTable = tableId === InternalTables.USER_METADATA const isUserTable = tableId === InternalTables.USER_METADATA
let oldRow let oldRow
@ -98,7 +97,7 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
export async function destroy(ctx: UserCtx) { export async function destroy(ctx: UserCtx) {
const db = context.getAppDB() const db = context.getAppDB()
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const { _id } = ctx.request.body const { _id } = ctx.request.body
let row = await db.get<Row>(_id) let row = await db.get<Row>(_id)
let _rev = ctx.request.body._rev || row._rev let _rev = ctx.request.body._rev || row._rev
@ -137,7 +136,7 @@ export async function destroy(ctx: UserCtx) {
} }
export async function bulkDestroy(ctx: UserCtx) { export async function bulkDestroy(ctx: UserCtx) {
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const table = await sdk.tables.getTable(tableId) const table = await sdk.tables.getTable(tableId)
let { rows } = ctx.request.body let { rows } = ctx.request.body
@ -179,7 +178,7 @@ export async function bulkDestroy(ctx: UserCtx) {
export async function fetchEnrichedRow(ctx: UserCtx) { export async function fetchEnrichedRow(ctx: UserCtx) {
const fieldName = ctx.request.query.field as string | undefined const fieldName = ctx.request.query.field as string | undefined
const db = context.getAppDB() const db = context.getAppDB()
const tableId = utils.getTableId(ctx) const { tableId } = utils.getSourceId(ctx)
const rowId = ctx.params.rowId as string const rowId = ctx.params.rowId as string
// need table to work out where links go in row, as well as the link docs // need table to work out where links go in row, as well as the link docs
const [table, links] = await Promise.all([ const [table, links] = await Promise.all([
@ -197,7 +196,7 @@ export async function fetchEnrichedRow(ctx: UserCtx) {
) )
// get the linked tables // get the linked tables
const linkTableIds = getLinkedTableIDs(table as Table) const linkTableIds = getLinkedTableIDs(table.schema)
const linkTables = await sdk.tables.getTables(linkTableIds) const linkTables = await sdk.tables.getTables(linkTableIds)
// perform output processing // perform output processing

View File

@ -1,4 +1,4 @@
import { InternalTables } from "../../../../db/utils" import * as utils from "../../../../db/utils"
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import { import {
@ -67,7 +67,7 @@ export async function findRow(tableId: string, rowId: string) {
const db = context.getAppDB() const db = context.getAppDB()
let row: Row let row: Row
// TODO remove special user case in future // TODO remove special user case in future
if (tableId === InternalTables.USER_METADATA) { if (tableId === utils.InternalTables.USER_METADATA) {
row = await getFullUser(rowId) row = await getFullUser(rowId)
} else { } else {
row = await db.get(rowId) row = await db.get(rowId)
@ -78,22 +78,25 @@ export async function findRow(tableId: string, rowId: string) {
return row return row
} }
export function getTableId(ctx: Ctx): string { export function getSourceId(ctx: Ctx): { tableId: string; viewId?: string } {
// top priority, use the URL first // top priority, use the URL first
if (ctx.params?.sourceId) { if (ctx.params?.sourceId) {
return ctx.params.sourceId const { sourceId } = ctx.params
if (utils.isViewID(sourceId)) {
return {
tableId: utils.extractViewInfoFromID(sourceId).tableId,
viewId: sourceId,
}
}
return { tableId: ctx.params.sourceId }
} }
// now check for old way of specifying table ID // now check for old way of specifying table ID
if (ctx.params?.tableId) { if (ctx.params?.tableId) {
return ctx.params.tableId return { tableId: ctx.params.tableId }
} }
// check body for a table ID // check body for a table ID
if (ctx.request.body?.tableId) { if (ctx.request.body?.tableId) {
return ctx.request.body.tableId return { tableId: ctx.request.body.tableId }
}
// now check if a specific view name
if (ctx.params?.viewName) {
return ctx.params.viewName
} }
throw new Error("Unable to find table ID in request") throw new Error("Unable to find table ID in request")
} }
@ -200,7 +203,7 @@ export async function sqlOutputProcessing(
} }
export function isUserMetadataTable(tableId: string) { export function isUserMetadataTable(tableId: string) {
return tableId === InternalTables.USER_METADATA return tableId === utils.InternalTables.USER_METADATA
} }
export async function enrichArrayContext( export async function enrichArrayContext(

View File

@ -2,7 +2,7 @@ import viewTemplate from "./viewBuilder"
import { apiFileReturn } from "../../../utilities/fileSystem" import { apiFileReturn } from "../../../utilities/fileSystem"
import { csv, json, jsonWithSchema, Format, isFormat } from "./exporters" import { csv, json, jsonWithSchema, Format, isFormat } from "./exporters"
import { deleteView, getView, getViews, saveView } from "./utils" import { deleteView, getView, getViews, saveView } from "./utils"
import { fetchView } from "../row" import { fetchLegacyView } from "../row"
import { context, events } from "@budibase/backend-core" import { context, events } from "@budibase/backend-core"
import sdk from "../../../sdk" import sdk from "../../../sdk"
import { import {
@ -170,7 +170,7 @@ export async function exportView(ctx: Ctx) {
ctx.params.viewName = viewName ctx.params.viewName = viewName
} }
await fetchView(ctx) await fetchLegacyView(ctx)
let rows = ctx.body as Row[] let rows = ctx.body as Row[]
let schema: TableSchema = view && view.meta && view.meta.schema let schema: TableSchema = view && view.meta && view.meta.schema

View File

@ -9,7 +9,13 @@ import {
import tk from "timekeeper" import tk from "timekeeper"
import emitter from "../../../../src/events" import emitter from "../../../../src/events"
import { outputProcessing } from "../../../utilities/rowProcessor" import { outputProcessing } from "../../../utilities/rowProcessor"
import { context, InternalTable, tenancy } from "@budibase/backend-core" import {
context,
InternalTable,
tenancy,
withEnv as withCoreEnv,
setEnv as setCoreEnv,
} from "@budibase/backend-core"
import { quotas } from "@budibase/pro" import { quotas } from "@budibase/pro"
import { import {
AttachmentFieldMetadata, AttachmentFieldMetadata,
@ -69,6 +75,7 @@ async function waitForEvent(
describe.each([ describe.each([
["internal", undefined], ["internal", undefined],
["sqs", undefined],
[DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)], [DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
[DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)], [DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
[DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)], [DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
@ -76,6 +83,8 @@ describe.each([
[DatabaseName.ORACLE, getDatasource(DatabaseName.ORACLE)], [DatabaseName.ORACLE, getDatasource(DatabaseName.ORACLE)],
])("/rows (%s)", (providerType, dsProvider) => { ])("/rows (%s)", (providerType, dsProvider) => {
const isInternal = dsProvider === undefined const isInternal = dsProvider === undefined
const isLucene = providerType === "lucene"
const isSqs = providerType === "sqs"
const isMSSQL = providerType === DatabaseName.SQL_SERVER const isMSSQL = providerType === DatabaseName.SQL_SERVER
const isOracle = providerType === DatabaseName.ORACLE const isOracle = providerType === DatabaseName.ORACLE
const config = setup.getConfig() const config = setup.getConfig()
@ -83,9 +92,17 @@ describe.each([
let table: Table let table: Table
let datasource: Datasource | undefined let datasource: Datasource | undefined
let client: Knex | undefined let client: Knex | undefined
let envCleanup: (() => void) | undefined
beforeAll(async () => { beforeAll(async () => {
await config.init() await withCoreEnv({ SQS_SEARCH_ENABLE: "true" }, () => config.init())
if (isSqs) {
envCleanup = setCoreEnv({
SQS_SEARCH_ENABLE: "true",
SQS_SEARCH_ENABLE_TENANTS: [config.getTenantId()],
})
}
if (dsProvider) { if (dsProvider) {
const rawDatasource = await dsProvider const rawDatasource = await dsProvider
datasource = await config.createDatasource({ datasource = await config.createDatasource({
@ -97,6 +114,9 @@ describe.each([
afterAll(async () => { afterAll(async () => {
setup.afterAll() setup.afterAll()
if (envCleanup) {
envCleanup()
}
}) })
function saveTableRequest( function saveTableRequest(
@ -346,7 +366,7 @@ describe.each([
expect(ids).toEqual(expect.arrayContaining(sequence)) expect(ids).toEqual(expect.arrayContaining(sequence))
}) })
isInternal && isLucene &&
it("row values are coerced", async () => { it("row values are coerced", async () => {
const str: FieldSchema = { const str: FieldSchema = {
type: FieldType.STRING, type: FieldType.STRING,

View File

@ -46,7 +46,7 @@ router
permissions.PermissionType.TABLE, permissions.PermissionType.TABLE,
permissions.PermissionLevel.READ permissions.PermissionLevel.READ
), ),
rowController.fetchView rowController.fetchLegacyView
) )
.get("/api/views", authorized(permissions.BUILDER), viewController.v1.fetch) .get("/api/views", authorized(permissions.BUILDER), viewController.v1.fetch)
.delete( .delete(

View File

@ -18,6 +18,7 @@ import {
LinkDocumentValue, LinkDocumentValue,
Row, Row,
Table, Table,
TableSchema,
} from "@budibase/types" } from "@budibase/types"
import sdk from "../../sdk" import sdk from "../../sdk"
@ -46,8 +47,8 @@ export const EventType = {
TABLE_DELETE: "table:delete", TABLE_DELETE: "table:delete",
} }
function clearRelationshipFields(table: Table, rows: Row[]) { function clearRelationshipFields(schema: TableSchema, rows: Row[]) {
for (let [key, field] of Object.entries(table.schema)) { for (let [key, field] of Object.entries(schema)) {
if (field.type === FieldType.LINK) { if (field.type === FieldType.LINK) {
rows = rows.map(row => { rows = rows.map(row => {
delete row[key] delete row[key]
@ -158,11 +159,11 @@ export async function updateLinks(args: {
* @return returns the rows with all of the enriched relationships on it. * @return returns the rows with all of the enriched relationships on it.
*/ */
export async function attachFullLinkedDocs( export async function attachFullLinkedDocs(
table: Table, schema: TableSchema,
rows: Row[], rows: Row[],
opts?: { fromRow?: Row } opts?: { fromRow?: Row }
) { ) {
const linkedTableIds = getLinkedTableIDs(table) const linkedTableIds = getLinkedTableIDs(schema)
if (linkedTableIds.length === 0) { if (linkedTableIds.length === 0) {
return rows return rows
} }
@ -182,7 +183,7 @@ export async function attachFullLinkedDocs(
} }
const linkedTables = response[1] as Table[] const linkedTables = response[1] as Table[]
// clear any existing links that could be dupe'd // clear any existing links that could be dupe'd
rows = clearRelationshipFields(table, rows) rows = clearRelationshipFields(schema, rows)
// now get the docs and combine into the rows // now get the docs and combine into the rows
let linked: Row[] = [] let linked: Row[] = []
if (linksWithoutFromRow.length > 0) { if (linksWithoutFromRow.length > 0) {
@ -201,7 +202,7 @@ export async function attachFullLinkedDocs(
} }
if (linkedRow) { if (linkedRow) {
const linkedTableId = const linkedTableId =
linkedRow.tableId || getRelatedTableForField(table, link.fieldName) linkedRow.tableId || getRelatedTableForField(schema, link.fieldName)
const linkedTable = linkedTables.find( const linkedTable = linkedTables.find(
table => table._id === linkedTableId table => table._id === linkedTableId
) )
@ -263,7 +264,8 @@ export async function squashLinksToPrimaryDisplay(
} }
const newLinks = [] const newLinks = []
for (let link of row[column]) { for (let link of row[column]) {
const linkTblId = link.tableId || getRelatedTableForField(table, column) const linkTblId =
link.tableId || getRelatedTableForField(table.schema, column)
const linkedTable = await getLinkedTable(linkTblId!, linkedTables) const linkedTable = await getLinkedTable(linkTblId!, linkedTables)
const obj: any = { _id: link._id } const obj: any = { _id: link._id }
obj.primaryDisplay = getPrimaryDisplayValue(link, linkedTable) obj.primaryDisplay = getPrimaryDisplayValue(link, linkedTable)

View File

@ -7,6 +7,7 @@ import {
LinkDocument, LinkDocument,
LinkDocumentValue, LinkDocumentValue,
Table, Table,
TableSchema,
} from "@budibase/types" } from "@budibase/types"
import sdk from "../../sdk" import sdk from "../../sdk"
@ -121,8 +122,8 @@ export function getUniqueByProp(array: any[], prop: string) {
return filteredArray return filteredArray
} }
export function getLinkedTableIDs(table: Table): string[] { export function getLinkedTableIDs(schema: TableSchema): string[] {
return Object.values(table.schema) return Object.values(schema)
.filter(isRelationshipColumn) .filter(isRelationshipColumn)
.map(column => column.tableId) .map(column => column.tableId)
} }
@ -139,13 +140,16 @@ export async function getLinkedTable(id: string, tables: Table[]) {
return linkedTable return linkedTable
} }
export function getRelatedTableForField(table: Table, fieldName: string) { export function getRelatedTableForField(
schema: TableSchema,
fieldName: string
) {
// look to see if its on the table, straight in the schema // look to see if its on the table, straight in the schema
const field = table.schema[fieldName] const field = schema[fieldName]
if (field?.type === FieldType.LINK) { if (field?.type === FieldType.LINK) {
return field.tableId return field.tableId
} }
for (let column of Object.values(table.schema)) { for (let column of Object.values(schema)) {
if (column.type === FieldType.LINK && column.fieldName === fieldName) { if (column.type === FieldType.LINK && column.fieldName === fieldName) {
return column.tableId return column.tableId
} }

View File

@ -34,7 +34,7 @@ describe("test link functionality", () => {
}) })
describe("getRelatedTableForField", () => { describe("getRelatedTableForField", () => {
let link = basicTable() const link = basicTable()
link.schema.link = { link.schema.link = {
name: "link", name: "link",
relationshipType: RelationshipType.ONE_TO_MANY, relationshipType: RelationshipType.ONE_TO_MANY,
@ -44,11 +44,13 @@ describe("test link functionality", () => {
} }
it("should get the field from the table directly", () => { it("should get the field from the table directly", () => {
expect(linkUtils.getRelatedTableForField(link, "link")).toBe("tableID") expect(linkUtils.getRelatedTableForField(link.schema, "link")).toBe(
"tableID"
)
}) })
it("should get the field from the link", () => { it("should get the field from the link", () => {
expect(linkUtils.getRelatedTableForField(link, "otherLink")).toBe( expect(linkUtils.getRelatedTableForField(link.schema, "otherLink")).toBe(
"tableID" "tableID"
) )
}) })

View File

@ -1,33 +1,23 @@
import { Ctx, Row } from "@budibase/types" import { Ctx, Row } from "@budibase/types"
import * as utils from "../db/utils"
import sdk from "../sdk" import sdk from "../sdk"
import { Next } from "koa" import { Next } from "koa"
import { getTableId } from "../api/controllers/row/utils" import { getSourceId } from "../api/controllers/row/utils"
export default async (ctx: Ctx<Row>, next: Next) => { export default async (ctx: Ctx<Row>, next: Next) => {
const { body } = ctx.request const { body } = ctx.request
let { _viewId: viewId } = body const viewId = getSourceId(ctx).viewId ?? body._viewId
const possibleViewId = getTableId(ctx)
if (utils.isViewID(possibleViewId)) {
viewId = possibleViewId
}
// nothing to do, it is not a view (just a table ID) // nothing to do, it is not a view (just a table ID)
if (!viewId) { if (!viewId) {
return next() return next()
} }
const { tableId } = utils.extractViewInfoFromID(viewId)
// don't need to trim delete requests // don't need to trim delete requests
if (ctx?.method?.toLowerCase() !== "delete") { if (ctx?.method?.toLowerCase() !== "delete") {
await trimViewFields(ctx.request.body, viewId) await trimViewFields(ctx.request.body, viewId)
} }
ctx.params.sourceId = tableId
ctx.params.viewId = viewId
return next() return next()
} }

View File

@ -121,10 +121,9 @@ export async function fetchRaw(tableId: string): Promise<Row[]> {
return pickApi(tableId).fetchRaw(tableId) return pickApi(tableId).fetchRaw(tableId)
} }
export async function fetchView( export async function fetchLegacyView(
tableId: string,
viewName: string, viewName: string,
params: ViewParams params: ViewParams
): Promise<Row[]> { ): Promise<Row[]> {
return pickApi(tableId).fetchView(viewName, params) return internal.fetchLegacyView(viewName, params)
} }

View File

@ -272,11 +272,3 @@ export async function fetchRaw(tableId: string): Promise<Row[]> {
}) })
return response.rows return response.rows
} }
export async function fetchView(viewName: string) {
// there are no views in external datasources, shouldn't ever be called
// for now just fetch
const split = viewName.split("all_")
const tableId = split[1] ? split[1] : split[0]
return fetch(tableId)
}

View File

@ -145,7 +145,7 @@ export async function fetchRaw(tableId: string): Promise<Row[]> {
return rows as Row[] return rows as Row[]
} }
export async function fetchView( export async function fetchLegacyView(
viewName: string, viewName: string,
options: { calculation: string; group: string; field: string } options: { calculation: string; group: string; field: string }
): Promise<Row[]> { ): Promise<Row[]> {

View File

@ -3,6 +3,7 @@ import { fixAutoColumnSubType, processFormulas } from "./utils"
import { import {
cache, cache,
context, context,
db,
HTTPError, HTTPError,
objectStore, objectStore,
utils, utils,
@ -262,7 +263,7 @@ export async function outputProcessing<T extends Row[] | Row>(
} }
// attach any linked row information // attach any linked row information
let enriched = !opts.preserveLinks let enriched = !opts.preserveLinks
? await linkRows.attachFullLinkedDocs(table, safeRows, { ? await linkRows.attachFullLinkedDocs(table.schema, safeRows, {
fromRow: opts?.fromRow, fromRow: opts?.fromRow,
}) })
: safeRows : safeRows
@ -349,11 +350,19 @@ export async function outputProcessing<T extends Row[] | Row>(
} }
// remove null properties to match internal API // remove null properties to match internal API
const isExternal = isExternalTableID(table._id!) const isExternal = isExternalTableID(table._id!)
if (isExternal) { if (isExternal || db.isSqsEnabledForTenant()) {
for (const row of enriched) { for (const row of enriched) {
for (const key of Object.keys(row)) { for (const key of Object.keys(row)) {
if (row[key] === null) { if (row[key] === null) {
delete row[key] delete row[key]
} else if (row[key] && table.schema[key]?.type === FieldType.LINK) {
for (const link of row[key] || []) {
for (const linkKey of Object.keys(link)) {
if (link[linkKey] === null) {
delete link[linkKey]
}
}
}
} }
} }
} }

View File

@ -10,6 +10,14 @@ import { outputProcessing } from ".."
import { generator, structures } from "@budibase/backend-core/tests" import { generator, structures } from "@budibase/backend-core/tests"
import * as bbReferenceProcessor from "../bbReferenceProcessor" import * as bbReferenceProcessor from "../bbReferenceProcessor"
jest.mock("@budibase/backend-core", () => ({
...jest.requireActual("@budibase/backend-core"),
db: {
...jest.requireActual("@budibase/backend-core").db,
isSqsEnabledForTenant: () => true,
},
}))
jest.mock("../bbReferenceProcessor", (): typeof bbReferenceProcessor => ({ jest.mock("../bbReferenceProcessor", (): typeof bbReferenceProcessor => ({
processInputBBReference: jest.fn(), processInputBBReference: jest.fn(),
processInputBBReferences: jest.fn(), processInputBBReferences: jest.fn(),

View File

@ -4,7 +4,7 @@ import { BaseSocket } from "./websocket"
import { auth, permissions } from "@budibase/backend-core" import { auth, permissions } from "@budibase/backend-core"
import http from "http" import http from "http"
import Koa from "koa" import Koa from "koa"
import { getTableId } from "../api/controllers/row/utils" import { getSourceId } from "../api/controllers/row/utils"
import { Row, Table, View, ViewV2 } from "@budibase/types" import { Row, Table, View, ViewV2 } from "@budibase/types"
import { Socket } from "socket.io" import { Socket } from "socket.io"
import { GridSocketEvent } from "@budibase/shared-core" import { GridSocketEvent } from "@budibase/shared-core"
@ -80,7 +80,7 @@ export default class GridSocket extends BaseSocket {
} }
emitRowUpdate(ctx: any, row: Row) { emitRowUpdate(ctx: any, row: Row) {
const resourceId = ctx.params?.viewId || getTableId(ctx) const resourceId = ctx.params?.viewId || getSourceId(ctx)
const room = `${ctx.appId}-${resourceId}` const room = `${ctx.appId}-${resourceId}`
this.emitToRoom(ctx, room, GridSocketEvent.RowChange, { this.emitToRoom(ctx, room, GridSocketEvent.RowChange, {
id: row._id, id: row._id,
@ -89,7 +89,7 @@ export default class GridSocket extends BaseSocket {
} }
emitRowDeletion(ctx: any, row: Row) { emitRowDeletion(ctx: any, row: Row) {
const resourceId = ctx.params?.viewId || getTableId(ctx) const resourceId = ctx.params?.viewId || getSourceId(ctx)
const room = `${ctx.appId}-${resourceId}` const room = `${ctx.appId}-${resourceId}`
this.emitToRoom(ctx, room, GridSocketEvent.RowChange, { this.emitToRoom(ctx, room, GridSocketEvent.RowChange, {
id: row._id, id: row._id,