Updating the SQL core to allow it to handle multiple relationships between the same two tables.
This commit is contained in:
parent
6037cb19b8
commit
168a126e2a
|
@ -188,18 +188,17 @@
|
||||||
{:else}
|
{:else}
|
||||||
<Body size="S"><i>No tables found.</i></Body>
|
<Body size="S"><i>No tables found.</i></Body>
|
||||||
{/if}
|
{/if}
|
||||||
{#if plusTables?.length !== 0 && integration.relationships}
|
<Divider size="S" />
|
||||||
<Divider size="S" />
|
<div class="query-header">
|
||||||
<div class="query-header">
|
|
||||||
<Heading size="S">Relationships</Heading>
|
<Heading size="S">Relationships</Heading>
|
||||||
<Button primary on:click={openRelationshipModal}>
|
<Button primary on:click={() => openRelationshipModal()}>
|
||||||
Define relationship
|
Define relationship
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Body>
|
<Body>
|
||||||
Tell budibase how your tables are related to get even more smart features.
|
Tell budibase how your tables are related to get even more smart features.
|
||||||
</Body>
|
</Body>
|
||||||
{#if relationshipInfo && relationshipInfo.length > 0}
|
{#if relationshipInfo && relationshipInfo.length > 0}
|
||||||
<Table
|
<Table
|
||||||
on:click={({ detail }) => openRelationshipModal(detail.from, detail.to)}
|
on:click={({ detail }) => openRelationshipModal(detail.from, detail.to)}
|
||||||
schema={relationshipSchema}
|
schema={relationshipSchema}
|
||||||
|
@ -208,9 +207,8 @@
|
||||||
allowEditRows={false}
|
allowEditRows={false}
|
||||||
allowSelectRows={false}
|
allowSelectRows={false}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<Body size="S"><i>No relationships configured.</i></Body>
|
<Body size="S"><i>No relationships configured.</i></Body>
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -22,6 +22,10 @@
|
||||||
|
|
||||||
let originalFromName = fromRelationship.name,
|
let originalFromName = fromRelationship.name,
|
||||||
originalToName = toRelationship.name
|
originalToName = toRelationship.name
|
||||||
|
let fromTable, toTable, through, linkTable, tableOptions
|
||||||
|
let isManyToMany, isManyToOne, relationshipTypes
|
||||||
|
let errors, valid
|
||||||
|
let currentTables = {}
|
||||||
|
|
||||||
if (fromRelationship && !fromRelationship.relationshipType) {
|
if (fromRelationship && !fromRelationship.relationshipType) {
|
||||||
fromRelationship.relationshipType = RelationshipTypes.MANY_TO_ONE
|
fromRelationship.relationshipType = RelationshipTypes.MANY_TO_ONE
|
||||||
|
@ -41,61 +45,52 @@
|
||||||
|
|
||||||
const touched = writable({})
|
const touched = writable({})
|
||||||
|
|
||||||
function checkForErrors(
|
function checkForErrors(fromRelate, toRelate) {
|
||||||
fromTable,
|
|
||||||
toTable,
|
|
||||||
throughTable,
|
|
||||||
fromRelate,
|
|
||||||
toRelate
|
|
||||||
) {
|
|
||||||
const isMany =
|
const isMany =
|
||||||
fromRelate.relationshipType === RelationshipTypes.MANY_TO_MANY
|
fromRelate.relationshipType === RelationshipTypes.MANY_TO_MANY
|
||||||
const tableNotSet = "Please specify a table"
|
const tableNotSet = "Please specify a table"
|
||||||
const errors = {}
|
const errObj = {}
|
||||||
if ($touched.from && !fromTable) {
|
if ($touched.from && !fromTable) {
|
||||||
errors.from = tableNotSet
|
errObj.from = tableNotSet
|
||||||
}
|
}
|
||||||
if ($touched.to && !toTable) {
|
if ($touched.to && !toTable) {
|
||||||
errors.to = tableNotSet
|
errObj.to = tableNotSet
|
||||||
}
|
}
|
||||||
if ($touched.through && isMany && !fromRelate.through) {
|
if ($touched.through && isMany && !fromRelate.through) {
|
||||||
errors.through = tableNotSet
|
errObj.through = tableNotSet
|
||||||
}
|
}
|
||||||
if ($touched.foreign && !isMany && !fromRelate.fieldName) {
|
if ($touched.foreign && !isMany && !fromRelate.fieldName) {
|
||||||
errors.foreign = "Please pick the foreign key"
|
errObj.foreign = "Please pick the foreign key"
|
||||||
}
|
}
|
||||||
const colNotSet = "Please specify a column name"
|
const colNotSet = "Please specify a column name"
|
||||||
if ($touched.fromCol && !fromRelate.name) {
|
if ($touched.fromCol && !fromRelate.name) {
|
||||||
errors.fromCol = colNotSet
|
errObj.fromCol = colNotSet
|
||||||
}
|
}
|
||||||
if ($touched.toCol && !toRelate.name) {
|
if ($touched.toCol && !toRelate.name) {
|
||||||
errors.toCol = colNotSet
|
errObj.toCol = colNotSet
|
||||||
}
|
}
|
||||||
if ($touched.primary && !fromPrimary) {
|
if ($touched.primary && !fromPrimary) {
|
||||||
errors.primary = "Please pick the primary key"
|
errObj.primary = "Please pick the primary key"
|
||||||
}
|
}
|
||||||
// currently don't support relationships back onto the table itself, needs to relate out
|
// currently don't support relationships back onto the table itself, needs to relate out
|
||||||
const tableError = "From/to/through tables must be different"
|
const tableError = "From/to/through tables must be different"
|
||||||
if (fromTable && (fromTable === toTable || fromTable === throughTable)) {
|
if (fromTable && (fromTable === toTable || fromTable === through)) {
|
||||||
errors.from = tableError
|
errObj.from = tableError
|
||||||
}
|
}
|
||||||
if (toTable && (toTable === fromTable || toTable === throughTable)) {
|
if (toTable && (toTable === fromTable || toTable === through)) {
|
||||||
errors.to = tableError
|
errObj.to = tableError
|
||||||
}
|
}
|
||||||
if (
|
if (through && (through === fromTable || through === toTable)) {
|
||||||
throughTable &&
|
errObj.through = tableError
|
||||||
(throughTable === fromTable || throughTable === toTable)
|
|
||||||
) {
|
|
||||||
errors.through = tableError
|
|
||||||
}
|
}
|
||||||
const colError = "Column name cannot be an existing column"
|
const colError = "Column name cannot be an existing column"
|
||||||
if (inSchema(fromTable, fromRelate.name, originalFromName)) {
|
if (inSchema(fromTable, fromRelate.name, originalFromName)) {
|
||||||
errors.fromCol = colError
|
errObj.fromCol = colError
|
||||||
}
|
}
|
||||||
if (inSchema(toTable, toRelate.name, originalToName)) {
|
if (inSchema(toTable, toRelate.name, originalToName)) {
|
||||||
errors.toCol = colError
|
errObj.toCol = colError
|
||||||
}
|
}
|
||||||
return errors
|
errors = errObj
|
||||||
}
|
}
|
||||||
|
|
||||||
let fromPrimary
|
let fromPrimary
|
||||||
|
@ -115,13 +110,7 @@
|
||||||
$: fromTable = plusTables.find(table => table._id === toRelationship?.tableId)
|
$: fromTable = plusTables.find(table => table._id === toRelationship?.tableId)
|
||||||
$: toTable = plusTables.find(table => table._id === fromRelationship?.tableId)
|
$: toTable = plusTables.find(table => table._id === fromRelationship?.tableId)
|
||||||
$: through = plusTables.find(table => table._id === fromRelationship?.through)
|
$: through = plusTables.find(table => table._id === fromRelationship?.through)
|
||||||
$: errors = checkForErrors(
|
$: checkForErrors(fromRelationship, toRelationship)
|
||||||
fromTable,
|
|
||||||
toTable,
|
|
||||||
through,
|
|
||||||
fromRelationship,
|
|
||||||
toRelationship
|
|
||||||
)
|
|
||||||
$: valid =
|
$: valid =
|
||||||
Object.keys(errors).length === 0 && Object.keys($touched).length !== 0
|
Object.keys(errors).length === 0 && Object.keys($touched).length !== 0
|
||||||
$: linkTable = through || toTable
|
$: linkTable = through || toTable
|
||||||
|
@ -239,19 +228,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function tableChanged(fromTbl, toTbl) {
|
function tableChanged(fromTbl, toTbl) {
|
||||||
|
if (
|
||||||
|
(currentTables?.from?._id === fromTbl?._id &&
|
||||||
|
currentTables?.to?._id === toTbl?._id) ||
|
||||||
|
originalFromName ||
|
||||||
|
originalToName
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
fromRelationship.name = toTbl?.name || ""
|
fromRelationship.name = toTbl?.name || ""
|
||||||
errors.fromCol = ""
|
errors.fromCol = ""
|
||||||
toRelationship.name = fromTbl?.name || ""
|
toRelationship.name = fromTbl?.name || ""
|
||||||
errors.toCol = ""
|
errors.toCol = ""
|
||||||
if (toTbl || fromTbl) {
|
currentTables = { from: fromTbl, to: toTbl }
|
||||||
checkForErrors(
|
|
||||||
fromTable,
|
|
||||||
toTable,
|
|
||||||
through,
|
|
||||||
fromRelationship,
|
|
||||||
toRelationship
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,15 @@ CREATE TABLE Persons (
|
||||||
);
|
);
|
||||||
CREATE TABLE Tasks (
|
CREATE TABLE Tasks (
|
||||||
TaskID SERIAL PRIMARY KEY,
|
TaskID SERIAL PRIMARY KEY,
|
||||||
PersonID INT,
|
ExecutorID INT,
|
||||||
|
QaID INT,
|
||||||
Completed BOOLEAN,
|
Completed BOOLEAN,
|
||||||
TaskName varchar(255),
|
TaskName varchar(255),
|
||||||
CONSTRAINT fkPersons
|
CONSTRAINT fkexecutor
|
||||||
FOREIGN KEY(PersonID)
|
FOREIGN KEY(ExecutorID)
|
||||||
|
REFERENCES Persons(PersonID),
|
||||||
|
CONSTRAINT fkqa
|
||||||
|
FOREIGN KEY(QaID)
|
||||||
REFERENCES Persons(PersonID)
|
REFERENCES Persons(PersonID)
|
||||||
);
|
);
|
||||||
CREATE TABLE Products (
|
CREATE TABLE Products (
|
||||||
|
@ -32,8 +36,9 @@ CREATE TABLE Products_Tasks (
|
||||||
PRIMARY KEY (ProductID, TaskID)
|
PRIMARY KEY (ProductID, TaskID)
|
||||||
);
|
);
|
||||||
INSERT INTO Persons (FirstName, LastName, Address, City) VALUES ('Mike', 'Hughes', '123 Fake Street', 'Belfast');
|
INSERT INTO Persons (FirstName, LastName, Address, City) VALUES ('Mike', 'Hughes', '123 Fake Street', 'Belfast');
|
||||||
INSERT INTO Tasks (PersonID, TaskName, Completed) VALUES (1, 'assembling', TRUE);
|
INSERT INTO Persons (FirstName, LastName, Address, City) Values ('John', 'Smith', '64 Updown Road', 'Dublin');
|
||||||
INSERT INTO Tasks (PersonID, TaskName, Completed) VALUES (1, 'processing', FALSE);
|
INSERT INTO Tasks (ExecutorID, QaID, TaskName, Completed) VALUES (1, 2, 'assembling', TRUE);
|
||||||
|
INSERT INTO Tasks (ExecutorID, QaID, TaskName, Completed) VALUES (2, 1, 'processing', FALSE);
|
||||||
INSERT INTO Products (ProductName) VALUES ('Computers');
|
INSERT INTO Products (ProductName) VALUES ('Computers');
|
||||||
INSERT INTO Products (ProductName) VALUES ('Laptops');
|
INSERT INTO Products (ProductName) VALUES ('Laptops');
|
||||||
INSERT INTO Products (ProductName) VALUES ('Chairs');
|
INSERT INTO Products (ProductName) VALUES ('Chairs');
|
||||||
|
|
|
@ -327,8 +327,12 @@ module External {
|
||||||
* 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
|
||||||
* to SQL and the way that SQL relationships are returned based on joins.
|
* to SQL and the way that SQL relationships are returned based on joins.
|
||||||
|
* This is complicated, but the idea is that when a SQL query returns all the relations
|
||||||
|
* will be separate rows, with all of the data in each row. We have to decipher what comes
|
||||||
|
* from where (which tables) and how to convert that into budibase columns.
|
||||||
*/
|
*/
|
||||||
updateRelationshipColumns(
|
updateRelationshipColumns(
|
||||||
|
table: Table,
|
||||||
row: Row,
|
row: Row,
|
||||||
rows: { [key: string]: Row },
|
rows: { [key: string]: Row },
|
||||||
relationships: RelationshipsJson[]
|
relationships: RelationshipsJson[]
|
||||||
|
@ -339,6 +343,13 @@ module External {
|
||||||
if (!linkedTable) {
|
if (!linkedTable) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
const fromColumn = `${table.name}.${relationship.from}`
|
||||||
|
const toColumn = `${linkedTable.name}.${relationship.to}`
|
||||||
|
// this is important when working with multiple relationships
|
||||||
|
// between the same tables, don't want to overlap/multiply the relations
|
||||||
|
if (!relationship.through && row[fromColumn] !== row[toColumn]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
let linked = basicProcessing(row, linkedTable)
|
let linked = basicProcessing(row, linkedTable)
|
||||||
if (!linked._id) {
|
if (!linked._id) {
|
||||||
continue
|
continue
|
||||||
|
@ -386,6 +397,7 @@ module External {
|
||||||
// this is a relationship of some sort
|
// this is a relationship of some sort
|
||||||
if (finalRows[rowId]) {
|
if (finalRows[rowId]) {
|
||||||
finalRows = this.updateRelationshipColumns(
|
finalRows = this.updateRelationshipColumns(
|
||||||
|
table,
|
||||||
row,
|
row,
|
||||||
finalRows,
|
finalRows,
|
||||||
relationships
|
relationships
|
||||||
|
@ -399,6 +411,7 @@ module External {
|
||||||
finalRows[thisRow._id] = thisRow
|
finalRows[thisRow._id] = thisRow
|
||||||
// do this at end once its been added to the final rows
|
// do this at end once its been added to the final rows
|
||||||
finalRows = this.updateRelationshipColumns(
|
finalRows = this.updateRelationshipColumns(
|
||||||
|
table,
|
||||||
row,
|
row,
|
||||||
finalRows,
|
finalRows,
|
||||||
relationships
|
relationships
|
||||||
|
|
|
@ -191,29 +191,70 @@ class InternalBuilder {
|
||||||
if (!relationships) {
|
if (!relationships) {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
const tableSets: Record<string, [any]> = {}
|
||||||
|
// aggregate into table sets (all the same to tables)
|
||||||
|
for (let relationship of relationships) {
|
||||||
|
const keyObj: { toTable: string; throughTable: string | undefined } = {
|
||||||
|
toTable: relationship.tableName,
|
||||||
|
throughTable: undefined,
|
||||||
|
}
|
||||||
|
if (relationship.through) {
|
||||||
|
keyObj.throughTable = relationship.through
|
||||||
|
}
|
||||||
|
const key = JSON.stringify(keyObj)
|
||||||
|
if (tableSets[key]) {
|
||||||
|
tableSets[key].push(relationship)
|
||||||
|
} else {
|
||||||
|
tableSets[key] = [relationship]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let [key, relationships] of Object.entries(tableSets)) {
|
||||||
|
const { toTable, throughTable } = JSON.parse(key)
|
||||||
|
if (!throughTable) {
|
||||||
|
// @ts-ignore
|
||||||
|
query = query.join(
|
||||||
|
toTable,
|
||||||
|
function () {
|
||||||
for (let relationship of relationships) {
|
for (let relationship of relationships) {
|
||||||
const from = relationship.from,
|
const from = relationship.from,
|
||||||
to = relationship.to,
|
to = relationship.to
|
||||||
toTable = relationship.tableName
|
|
||||||
if (!relationship.through) {
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
query = query.leftJoin(
|
this.orOn(`${fromTable}.${from}`, "=", `${toTable}.${to}`)
|
||||||
toTable,
|
}
|
||||||
`${fromTable}.${from}`,
|
},
|
||||||
`${toTable}.${to}`
|
"left"
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const throughTable = relationship.through
|
|
||||||
const fromPrimary = relationship.fromPrimary
|
|
||||||
const toPrimary = relationship.toPrimary
|
|
||||||
query = query
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
.leftJoin(
|
query = query
|
||||||
|
.join(
|
||||||
throughTable,
|
throughTable,
|
||||||
|
function () {
|
||||||
|
for (let relationship of relationships) {
|
||||||
|
const fromPrimary = relationship.fromPrimary
|
||||||
|
const from = relationship.from
|
||||||
|
// @ts-ignore
|
||||||
|
this.orOn(
|
||||||
`${fromTable}.${fromPrimary}`,
|
`${fromTable}.${fromPrimary}`,
|
||||||
|
"=",
|
||||||
`${throughTable}.${from}`
|
`${throughTable}.${from}`
|
||||||
)
|
)
|
||||||
.leftJoin(toTable, `${toTable}.${toPrimary}`, `${throughTable}.${to}`)
|
}
|
||||||
|
},
|
||||||
|
"left"
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
toTable,
|
||||||
|
function () {
|
||||||
|
for (let relationship of relationships) {
|
||||||
|
const toPrimary = relationship.toPrimary
|
||||||
|
const to = relationship.to
|
||||||
|
// @ts-ignore
|
||||||
|
this.orOn(`${toTable}.${toPrimary}`, `${throughTable}.${to}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"left"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return query.limit(BASE_LIMIT)
|
return query.limit(BASE_LIMIT)
|
||||||
|
|
Loading…
Reference in New Issue