1
0
Fork 0
mirror of synced 2024-09-10 06:26:02 +12:00

Some work towards improving performance of internal DB enrichment, there is a problem with cyclic enrichment due to the outputProcessing, need to decide how to handle formulas on enrichment.

This commit is contained in:
mike12345567 2023-10-04 19:18:21 +01:00
parent 96b077f8a5
commit 069fd33964
6 changed files with 134 additions and 75 deletions

View file

@ -1,6 +1,7 @@
import * as linkRows from "../../../db/linkedRows" import * as linkRows from "../../../db/linkedRows"
import { import {
generateRowID, generateRowID,
getMultiIDParams,
getTableIDFromRowID, getTableIDFromRowID,
InternalTables, InternalTables,
} from "../../../db/utils" } from "../../../db/utils"
@ -24,6 +25,8 @@ import {
UserCtx, UserCtx,
} from "@budibase/types" } from "@budibase/types"
import sdk from "../../../sdk" import sdk from "../../../sdk"
import { getLinkedTableIDs } from "../../../db/linkedRows/linkUtils"
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.getTableId(ctx)
@ -224,57 +227,43 @@ export async function fetchEnrichedRow(ctx: UserCtx) {
const db = context.getAppDB() const db = context.getAppDB()
const tableId = utils.getTableId(ctx) const tableId = utils.getTableId(ctx)
const rowId = ctx.params.rowId const rowId = ctx.params.rowId
// need table to work out where links go in row // need table to work out where links go in row, as well as the link docs
let [table, row] = await Promise.all([ let response = await Promise.all([
sdk.tables.getTable(tableId), sdk.tables.getTable(tableId),
utils.findRow(ctx, tableId, rowId), utils.findRow(ctx, tableId, rowId),
linkRows.getLinkDocuments({ tableId, rowId }),
]) ])
// get the link docs const table = response[0] as Table
const linkVals = (await linkRows.getLinkDocuments({ const row = response[1] as Row
tableId, const linkVals = response[2] as LinkDocumentValue[]
rowId,
})) as LinkDocumentValue[]
// look up the actual rows based on the ids // look up the actual rows based on the ids
let response = ( const params = getMultiIDParams(linkVals.map(linkVal => linkVal.id))
await db.allDocs({ let linkedRows = (await db.allDocs(params)).rows.map(row => row.doc)
include_docs: true,
keys: linkVals.map(linkVal => linkVal.id), // get the linked tables
}) const linkTableIds = getLinkedTableIDs(table as Table)
).rows.map(row => row.doc) const linkTables = await sdk.tables.getTables(linkTableIds)
// group responses by table
let groups: any = {}, // perform output processing
tables: Record<string, Table> = {} let final = []
for (let row of response) { for (let linkTable of linkTables) {
if (!row.tableId) { const relatedRows = linkedRows.filter(row => row.tableId === linkTable._id)
row.tableId = getTableIDFromRowID(row._id) final = final.concat(
} outputProcessing(linkTable, relatedRows, { preserveLinks: true })
const linkedTableId = row.tableId
if (groups[linkedTableId] == null) {
groups[linkedTableId] = [row]
tables[linkedTableId] = await db.get(linkedTableId)
} else {
groups[linkedTableId].push(row)
}
}
let linkedRows: Row[] = []
for (let [tableId, rows] of Object.entries(groups)) {
// need to include the IDs in these rows for any links they may have
linkedRows = linkedRows.concat(
await outputProcessing(tables[tableId], rows as Row[])
) )
} }
// finalise the promises
linkedRows = flatten(await Promise.all(final))
// insert the link rows in the correct place throughout the main row // insert the link rows in the correct place throughout the main row
for (let fieldName of Object.keys(table.schema)) { for (let fieldName of Object.keys(table.schema)) {
let field = table.schema[fieldName] let field = table.schema[fieldName]
if (field.type === FieldTypes.LINK) { if (field.type === FieldTypes.LINK) {
// find the links that pertain to this field, get their indexes // find the links that pertain to this field
const linkIndexes = linkVals const links = linkVals.filter(link => link.fieldName === fieldName)
.filter(link => link.fieldName === fieldName)
.map(link => linkVals.indexOf(link))
// find the rows that the links state are linked to this field // find the rows that the links state are linked to this field
row[fieldName] = linkedRows.filter((linkRow, index) => row[fieldName] = linkedRows.filter(linkRow =>
linkIndexes.includes(index) links.find(link => link.id === linkRow._id)
) )
} }
} }

View file

@ -1,13 +1,11 @@
import { generateUserFlagID } from "../../db/utils" import { generateUserFlagID, InternalTables } from "../../db/utils"
import { InternalTables } from "../../db/utils"
import { getFullUser } from "../../utilities/users" import { getFullUser } from "../../utilities/users"
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import { Ctx, UserCtx } from "@budibase/types" import { Ctx, UserCtx } from "@budibase/types"
import sdk from "../../sdk" import sdk from "../../sdk"
export async function fetchMetadata(ctx: Ctx) { export async function fetchMetadata(ctx: Ctx) {
const users = await sdk.users.fetchMetadata() ctx.body = await sdk.users.fetchMetadata()
ctx.body = users
} }
export async function updateSelfMetadata(ctx: UserCtx) { export async function updateSelfMetadata(ctx: UserCtx) {

View file

@ -9,13 +9,13 @@ import {
getLinkedTable, getLinkedTable,
} from "./linkUtils" } from "./linkUtils"
import flatten from "lodash/flatten" import flatten from "lodash/flatten"
import { FieldTypes } from "../../constants"
import { getMultiIDParams, USER_METDATA_PREFIX } from "../utils" import { getMultiIDParams, USER_METDATA_PREFIX } from "../utils"
import partition from "lodash/partition" import partition from "lodash/partition"
import { getGlobalUsersFromMetadata } from "../../utilities/global" import { getGlobalUsersFromMetadata } from "../../utilities/global"
import { processFormulas } from "../../utilities/rowProcessor" import { processFormulas } from "../../utilities/rowProcessor"
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import { Table, Row, LinkDocumentValue } from "@budibase/types" import { Table, Row, LinkDocumentValue, FieldType } from "@budibase/types"
import sdk from "../../sdk"
export { IncludeDocs, getLinkDocuments, createLinkView } from "./linkUtils" export { IncludeDocs, getLinkDocuments, createLinkView } from "./linkUtils"
@ -35,7 +35,7 @@ export const EventType = {
function clearRelationshipFields(table: Table, rows: Row[]) { function clearRelationshipFields(table: Table, rows: Row[]) {
for (let [key, field] of Object.entries(table.schema)) { for (let [key, field] of Object.entries(table.schema)) {
if (field.type === FieldTypes.LINK) { if (field.type === FieldType.LINK) {
rows = rows.map(row => { rows = rows.map(row => {
delete row[key] delete row[key]
return row return row
@ -45,7 +45,7 @@ function clearRelationshipFields(table: Table, rows: Row[]) {
return rows return rows
} }
async function getLinksForRows(rows: Row[]) { async function getLinksForRows(rows: Row[]): Promise<LinkDocumentValue[]> {
const tableIds = [...new Set(rows.map(el => el.tableId))] const tableIds = [...new Set(rows.map(el => el.tableId))]
// start by getting all the link values for performance reasons // start by getting all the link values for performance reasons
const promises = tableIds.map(tableId => const promises = tableIds.map(tableId =>
@ -153,15 +153,19 @@ export async function attachFullLinkedDocs(table: Table, rows: Row[]) {
if (linkedTableIds.length === 0) { if (linkedTableIds.length === 0) {
return rows return rows
} }
// get all the links // get tables and links
const links = (await getLinksForRows(rows)).filter(link => let response = await Promise.all([
getLinksForRows(rows),
sdk.tables.getTables(linkedTableIds),
])
const links = (response[0] as LinkDocumentValue[]).filter(link =>
rows.some(row => row._id === link.thisId) rows.some(row => row._id === link.thisId)
) )
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(table, rows)
// now get the docs and combine into the rows // now get the docs and combine into the rows
let linked = await getFullLinkedDocs(links) let linked = await getFullLinkedDocs(links)
const linkedTables: Table[] = []
for (let row of rows) { for (let row of rows) {
for (let link of links.filter(link => link.thisId === row._id)) { for (let link of links.filter(link => link.thisId === row._id)) {
if (row[link.fieldName] == null) { if (row[link.fieldName] == null) {
@ -171,7 +175,9 @@ export async function attachFullLinkedDocs(table: Table, rows: Row[]) {
if (linkedRow) { if (linkedRow) {
const linkedTableId = const linkedTableId =
linkedRow.tableId || getRelatedTableForField(table, link.fieldName) linkedRow.tableId || getRelatedTableForField(table, link.fieldName)
const linkedTable = await getLinkedTable(linkedTableId, linkedTables) const linkedTable = linkedTables.find(
table => table._id === linkedTableId
)
if (linkedTable) { if (linkedTable) {
row[link.fieldName].push(processFormulas(linkedTable, linkedRow)) row[link.fieldName].push(processFormulas(linkedTable, linkedRow))
} }
@ -199,13 +205,13 @@ export async function squashLinksToPrimaryDisplay(
// this only fetches the table if its not already in array // this only fetches the table if its not already in array
const rowTable = await getLinkedTable(row.tableId!, linkedTables) const rowTable = await getLinkedTable(row.tableId!, linkedTables)
for (let [column, schema] of Object.entries(rowTable?.schema || {})) { for (let [column, schema] of Object.entries(rowTable?.schema || {})) {
if (schema.type !== FieldTypes.LINK || !Array.isArray(row[column])) { if (schema.type !== FieldType.LINK || !Array.isArray(row[column])) {
continue continue
} }
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, 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 }
if (linkedTable?.primaryDisplay && link[linkedTable.primaryDisplay]) { if (linkedTable?.primaryDisplay && link[linkedTable.primaryDisplay]) {
obj.primaryDisplay = link[linkedTable.primaryDisplay] obj.primaryDisplay = link[linkedTable.primaryDisplay]

View file

@ -91,10 +91,10 @@ export function getUniqueByProp(array: any[], prop: string) {
}) })
} }
export function getLinkedTableIDs(table: Table) { export function getLinkedTableIDs(table: Table): string[] {
return Object.values(table.schema) return Object.values(table.schema)
.filter((column: FieldSchema) => column.type === FieldTypes.LINK) .filter((column: FieldSchema) => column.type === FieldTypes.LINK)
.map(column => column.tableId) .map(column => column.tableId!)
} }
export async function getLinkedTable(id: string, tables: Table[]) { export async function getLinkedTable(id: string, tables: Table[]) {

View file

@ -23,10 +23,13 @@ import {
getTableParams, getTableParams,
} from "../../../db/utils" } from "../../../db/utils"
import sdk from "../../index" import sdk from "../../index"
import datasource from "../../../api/routes/datasource"
const ENV_VAR_PREFIX = "env." const ENV_VAR_PREFIX = "env."
export async function fetch() { export async function fetch(opts?: {
enriched: boolean
}): Promise<Datasource[]> {
// Get internal tables // Get internal tables
const db = context.getAppDB() const db = context.getAppDB()
const internalTables = await db.allDocs( const internalTables = await db.allDocs(
@ -44,7 +47,7 @@ export async function fetch() {
const bbInternalDb = { const bbInternalDb = {
...BudibaseInternalDB, ...BudibaseInternalDB,
} } as Datasource
// Get external datasources // Get external datasources
const datasources = ( const datasources = (
@ -66,7 +69,18 @@ export async function fetch() {
} }
} }
return [bbInternalDb, ...datasources] if (opts?.enriched) {
const envVars = await getEnvironmentVariables()
const promises = datasources.map(datasource =>
enrichDatasourceWithValues(datasource, envVars)
)
const enriched = (await Promise.all(promises)).map(
result => result.datasource
)
return [bbInternalDb, ...enriched]
} else {
return [bbInternalDb, ...datasources]
}
} }
export function areRESTVariablesValid(datasource: Datasource) { export function areRESTVariablesValid(datasource: Datasource) {
@ -107,9 +121,12 @@ export function checkDatasourceTypes(schema: Integration, config: any) {
return config return config
} }
async function enrichDatasourceWithValues(datasource: Datasource) { async function enrichDatasourceWithValues(
datasource: Datasource,
variables?: Record<string, string>
) {
const cloned = cloneDeep(datasource) const cloned = cloneDeep(datasource)
const env = await getEnvironmentVariables() const env = variables ? variables : await getEnvironmentVariables()
//Do not process entities, as we do not want to process formulas //Do not process entities, as we do not want to process formulas
const { entities, ...clonedWithoutEntities } = cloned const { entities, ...clonedWithoutEntities } = cloned
const processed = processObjectSync( const processed = processObjectSync(
@ -235,9 +252,9 @@ export function mergeConfigs(update: Datasource, old: Datasource) {
if (value !== PASSWORD_REPLACEMENT) { if (value !== PASSWORD_REPLACEMENT) {
continue continue
} }
if (old.config?.[key]) { if (update.config && old.config && old.config?.[key]) {
update.config[key] = old.config?.[key] update.config[key] = old.config?.[key]
} else { } else if (update.config) {
delete update.config[key] delete update.config[key]
} }
} }

View file

@ -1,11 +1,16 @@
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import { BudibaseInternalDB, getTableParams } from "../../../db/utils" import {
BudibaseInternalDB,
getMultiIDParams,
getTableParams,
} from "../../../db/utils"
import { import {
breakExternalTableId, breakExternalTableId,
isExternalTable, isExternalTable,
isSQL, isSQL,
} from "../../../integrations/utils" } from "../../../integrations/utils"
import { import {
AllDocsResponse,
Database, Database,
Table, Table,
TableResponse, TableResponse,
@ -15,6 +20,14 @@ import datasources from "../datasources"
import { populateExternalTableSchemas } from "./validation" import { populateExternalTableSchemas } from "./validation"
import sdk from "../../../sdk" import sdk from "../../../sdk"
function processInternalTables(docs: AllDocsResponse<Table[]>): Table[] {
return docs.rows.map((tableDoc: any) => ({
...tableDoc.doc,
type: "internal",
sourceId: tableDoc.doc.sourceId || BudibaseInternalDB._id,
}))
}
async function getAllInternalTables(db?: Database): Promise<Table[]> { async function getAllInternalTables(db?: Database): Promise<Table[]> {
if (!db) { if (!db) {
db = context.getAppDB() db = context.getAppDB()
@ -24,15 +37,49 @@ async function getAllInternalTables(db?: Database): Promise<Table[]> {
include_docs: true, include_docs: true,
}) })
) )
return internalTables.rows.map((tableDoc: any) => ({ return processInternalTables(internalTables)
...tableDoc.doc,
type: "internal",
sourceId: tableDoc.doc.sourceId || BudibaseInternalDB._id,
}))
} }
async function getAllExternalTables( async function getAllExternalTables(): Promise<Table[]> {
datasourceId: any const datasources = await sdk.datasources.fetch({ enriched: true })
const allEntities = datasources.map(datasource => datasource.entities)
let final = []
for (let entities of allEntities) {
final = final.concat(Object.values(entities))
}
return final
}
async function getAllTables() {
const [internal, external] = await Promise.all([
getAllInternalTables(),
getAllExternalTables(),
])
return [...internal, external]
}
async function getTables(tableIds: string[]): Promise<Table[]> {
const externalTableIds = tableIds.filter(tableId => isExternalTable(tableId)),
internalTableIds = tableIds.filter(tableId => !isExternalTable(tableId))
let tables = []
if (externalTableIds.length) {
const externalTables = await getAllExternalTables()
tables = tables.concat(
externalTables.filter(
table => externalTableIds.indexOf(table._id!) !== -1
)
)
}
if (internalTableIds.length) {
const db = context.getAppDB()
const internalTables = await db.allDocs(getMultiIDParams(internalTableIds))
tables = tables.concat(processInternalTables(internalTables))
}
return tables
}
async function getExternalTablesInDatasource(
datasourceId: string
): Promise<Record<string, Table>> { ): Promise<Record<string, Table>> {
const datasource = await datasources.get(datasourceId, { enriched: true }) const datasource = await datasources.get(datasourceId, { enriched: true })
if (!datasource || !datasource.entities) { if (!datasource || !datasource.entities) {
@ -42,14 +89,14 @@ async function getAllExternalTables(
} }
async function getExternalTable( async function getExternalTable(
datasourceId: any, datasourceId: string,
tableName: any tableName: string
): Promise<Table> { ): Promise<Table> {
const entities = await getAllExternalTables(datasourceId) const entities = await getExternalTablesInDatasource(datasourceId)
return entities[tableName] return entities[tableName]
} }
async function getTable(tableId: any): Promise<Table> { async function getTable(tableId: string): Promise<Table> {
const db = context.getAppDB() const db = context.getAppDB()
if (isExternalTable(tableId)) { if (isExternalTable(tableId)) {
let { datasourceId, tableName } = breakExternalTableId(tableId) let { datasourceId, tableName } = breakExternalTableId(tableId)
@ -86,9 +133,11 @@ async function saveTable(table: Table) {
export default { export default {
getAllInternalTables, getAllInternalTables,
getAllExternalTables, getExternalTablesInDatasource,
getExternalTable, getExternalTable,
getTable, getTable,
getAllTables,
getTables,
populateExternalTableSchemas, populateExternalTableSchemas,
enrichViewSchemas, enrichViewSchemas,
saveTable, saveTable,