Initial implementation - needs testing.

This commit is contained in:
mike12345567 2024-07-08 18:42:11 +01:00
parent 37d53cff20
commit c0b85c6379
3 changed files with 59 additions and 15 deletions

View File

@ -22,6 +22,18 @@ export function isManyToMany(
return !!(field as ManyToManyRelationshipFieldMetadata).through return !!(field as ManyToManyRelationshipFieldMetadata).through
} }
function isCorrectRelationship(
relationship: RelationshipsJson,
row: Row
): boolean {
const junctionTableId = relationship.through!
const possibleColumns = [
`${junctionTableId}.doc1.fieldName`,
`${junctionTableId}.doc2.fieldName`,
]
return !!possibleColumns.find(col => row[col] === relationship.column)
}
/** /**
* This iterates through the returned rows and works out what elements of the rows * This iterates through the returned rows and works out what elements of the rows
* actually match up to another row (based on primary keys) - this is pretty specific * actually match up to another row (based on primary keys) - this is pretty specific
@ -64,8 +76,10 @@ export async function updateRelationshipColumns(
if (!linked._id) { if (!linked._id) {
continue continue
} }
if (opts?.sqs && isCorrectRelationship(relationship, row)) {
columns[relationship.column] = linked columns[relationship.column] = linked
} }
}
for (let [column, related] of Object.entries(columns)) { for (let [column, related] of Object.entries(columns)) {
if (!row._id) { if (!row._id) {
continue continue

View File

@ -5,6 +5,7 @@ import {
Operation, Operation,
QueryJson, QueryJson,
RelationshipFieldMetadata, RelationshipFieldMetadata,
RelationshipsJson,
Row, Row,
RowSearchParams, RowSearchParams,
SearchFilters, SearchFilters,
@ -30,7 +31,10 @@ import {
SQLITE_DESIGN_DOC_ID, SQLITE_DESIGN_DOC_ID,
SQS_DATASOURCE_INTERNAL, SQS_DATASOURCE_INTERNAL,
} from "@budibase/backend-core" } from "@budibase/backend-core"
import { CONSTANT_INTERNAL_ROW_COLS } from "../../../../db/utils" import {
CONSTANT_INTERNAL_ROW_COLS,
generateJunctionTableID,
} from "../../../../db/utils"
import AliasTables from "../sqlAlias" import AliasTables from "../sqlAlias"
import { outputProcessing } from "../../../../utilities/rowProcessor" import { outputProcessing } from "../../../../utilities/rowProcessor"
import pick from "lodash/pick" import pick from "lodash/pick"
@ -52,7 +56,7 @@ const USER_COLUMN_PREFIX_REGEX = new RegExp(
function buildInternalFieldList( function buildInternalFieldList(
table: Table, table: Table,
tables: Table[], tables: Table[],
opts: { relationships: boolean } = { relationships: true } opts?: { relationships?: RelationshipsJson[] }
) { ) {
let fieldList: string[] = [] let fieldList: string[] = []
fieldList = fieldList.concat( fieldList = fieldList.concat(
@ -60,20 +64,31 @@ function buildInternalFieldList(
) )
for (let col of Object.values(table.schema)) { for (let col of Object.values(table.schema)) {
const isRelationship = col.type === FieldType.LINK const isRelationship = col.type === FieldType.LINK
if (!opts.relationships && isRelationship) { if (!opts?.relationships && isRelationship) {
continue continue
} }
if (isRelationship) { if (isRelationship) {
const linkCol = col as RelationshipFieldMetadata const linkCol = col as RelationshipFieldMetadata
const relatedTable = tables.find(table => table._id === linkCol.tableId)! const relatedTable = tables.find(table => table._id === linkCol.tableId)!
fieldList = fieldList.concat( // no relationships provided, don't go more than a layer deep
buildInternalFieldList(relatedTable, tables, { relationships: false }) fieldList = fieldList.concat(buildInternalFieldList(relatedTable, tables))
fieldList.push(
`${generateJunctionTableID(
table._id!,
relatedTable._id!
)}.doc1.fieldName`
)
fieldList.push(
`${generateJunctionTableID(
table._id!,
relatedTable._id!
)}.doc2.fieldName`
) )
} else { } else {
fieldList.push(`${table._id}.${mapToUserColumn(col.name)}`) fieldList.push(`${table._id}.${mapToUserColumn(col.name)}`)
} }
} }
return fieldList return [...new Set(fieldList)]
} }
function cleanupFilters( function cleanupFilters(
@ -165,18 +180,27 @@ function reverseUserColumnMapping(rows: Row[]) {
}) })
} }
function runSqlQuery(json: QueryJson, tables: Table[]): Promise<Row[]>
function runSqlQuery( function runSqlQuery(
json: QueryJson, json: QueryJson,
tables: Table[], tables: Table[],
relationships: RelationshipsJson[]
): Promise<Row[]>
function runSqlQuery(
json: QueryJson,
tables: Table[],
relationships: RelationshipsJson[],
opts: { countTotalRows: true } opts: { countTotalRows: true }
): Promise<number> ): Promise<number>
async function runSqlQuery( async function runSqlQuery(
json: QueryJson, json: QueryJson,
tables: Table[], tables: Table[],
relationships: RelationshipsJson[],
opts?: { countTotalRows?: boolean } opts?: { countTotalRows?: boolean }
) { ) {
const alias = new AliasTables(tables.map(table => table.name)) const relationshipJunctionTableIds = relationships.map(rel => rel.through!)
const alias = new AliasTables(
tables.map(table => table.name).concat(relationshipJunctionTableIds)
)
if (opts?.countTotalRows) { if (opts?.countTotalRows) {
json.endpoint.operation = Operation.COUNT json.endpoint.operation = Operation.COUNT
} }
@ -193,8 +217,13 @@ async function runSqlQuery(
let bindings = query.bindings let bindings = query.bindings
// quick hack for docIds // quick hack for docIds
sql = sql.replace(/`doc1`.`rowId`/g, "`doc1.rowId`")
sql = sql.replace(/`doc2`.`rowId`/g, "`doc2.rowId`") const fixJunctionDocs = (field: string) =>
["doc1", "doc2"].forEach(doc => {
sql = sql.replaceAll(`\`${doc}\`.\`${field}\``, `\`${doc}.${field}\``)
})
fixJunctionDocs("rowId")
fixJunctionDocs("fieldName")
if (Array.isArray(query)) { if (Array.isArray(query)) {
throw new Error("SQS cannot currently handle multiple queries") throw new Error("SQS cannot currently handle multiple queries")
@ -260,7 +289,7 @@ export async function search(
columnPrefix: USER_COLUMN_PREFIX, columnPrefix: USER_COLUMN_PREFIX,
}, },
resource: { resource: {
fields: buildInternalFieldList(table, allTables), fields: buildInternalFieldList(table, allTables, { relationships }),
}, },
relationships, relationships,
} }
@ -292,11 +321,11 @@ export async function search(
try { try {
const queries: Promise<Row[] | number>[] = [] const queries: Promise<Row[] | number>[] = []
queries.push(runSqlQuery(request, allTables)) queries.push(runSqlQuery(request, allTables, relationships))
if (options.countRows) { if (options.countRows) {
// get the total count of rows // get the total count of rows
queries.push( queries.push(
runSqlQuery(request, allTables, { runSqlQuery(request, allTables, relationships, {
countTotalRows: true, countTotalRows: true,
}) })
) )

View File

@ -111,7 +111,8 @@ export default class AliasTables {
aliasField(field: string) { aliasField(field: string) {
const tableNames = this.tableNames const tableNames = this.tableNames
if (field.includes(".")) { if (field.includes(".")) {
const [tableName, column] = field.split(".") const [tableName, ...rest] = field.split(".")
const column = rest.join(".")
const foundTableName = tableNames.find(name => { const foundTableName = tableNames.find(name => {
const idx = tableName.indexOf(name) const idx = tableName.indexOf(name)
if (idx === -1 || idx > 1) { if (idx === -1 || idx > 1) {