Fixing a lot of issues around dropping columns, updating columns, relationships and bi-directionality, display columns now default to something for SQL tables as well.

This commit is contained in:
mike12345567 2021-10-29 13:34:10 +01:00
parent 949c6b8653
commit a94376ce43
6 changed files with 109 additions and 20 deletions

View File

@ -16,8 +16,8 @@
export let value = defaultValue || (meta.type === "boolean" ? false : "") export let value = defaultValue || (meta.type === "boolean" ? false : "")
export let readonly export let readonly
$: type = meta.type $: type = meta?.type
$: label = capitalise(meta.name) $: label = meta.name ? capitalise(meta.name) : ""
</script> </script>
{#if type === "options"} {#if type === "options"}

View File

@ -31,6 +31,9 @@
const AUTO_TYPE = "auto" const AUTO_TYPE = "auto"
const FORMULA_TYPE = FIELDS.FORMULA.type const FORMULA_TYPE = FIELDS.FORMULA.type
const LINK_TYPE = FIELDS.LINK.type const LINK_TYPE = FIELDS.LINK.type
const STRING_TYPE = FIELDS.STRING.type
const NUMBER_TYPE = FIELDS.NUMBER.type
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"] const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"]
const { hide } = getContext(Context.Modal) const { hide } = getContext(Context.Modal)
@ -55,6 +58,7 @@
let confirmDeleteDialog let confirmDeleteDialog
let deletion let deletion
$: checkConstraints(field)
$: tableOptions = $tables.list.filter( $: tableOptions = $tables.list.filter(
opt => opt._id !== $tables.draft._id && opt.type === table.type opt => opt._id !== $tables.draft._id && opt.type === table.type
) )
@ -180,23 +184,26 @@
} }
const thisName = truncate(table.name, { length: 14 }), const thisName = truncate(table.name, { length: 14 }),
linkName = truncate(linkTable.name, { length: 14 }) linkName = truncate(linkTable.name, { length: 14 })
return [ const options = [
{ {
name: `Many ${thisName} rows → many ${linkName} rows`, name: `Many ${thisName} rows → many ${linkName} rows`,
alt: `Many ${table.name} rows → many ${linkTable.name} rows`, alt: `Many ${table.name} rows → many ${linkTable.name} rows`,
value: RelationshipTypes.MANY_TO_MANY, value: RelationshipTypes.MANY_TO_MANY,
}, },
{
name: `One ${linkName} row → many ${thisName} rows`,
alt: `One ${linkTable.name} rows → many ${table.name} rows`,
value: RelationshipTypes.ONE_TO_MANY,
},
{ {
name: `One ${thisName} row → many ${linkName} rows`, name: `One ${thisName} row → many ${linkName} rows`,
alt: `One ${table.name} rows → many ${linkTable.name} rows`, alt: `One ${table.name} rows → many ${linkTable.name} rows`,
value: RelationshipTypes.MANY_TO_ONE, value: RelationshipTypes.MANY_TO_ONE,
}, },
] ]
if (!external) {
options.push({
name: `One ${linkName} row → many ${thisName} rows`,
alt: `One ${linkTable.name} rows → many ${table.name} rows`,
value: RelationshipTypes.ONE_TO_MANY,
})
}
return options
} }
function getAllowedTypes() { function getAllowedTypes() {
@ -219,6 +226,24 @@
] ]
} }
} }
function checkConstraints(fieldToCheck) {
// most types need this, just make sure its always present
if (fieldToCheck && !fieldToCheck.constraints) {
fieldToCheck.constraints = {}
}
// some string types may have been built by server, may not always have constraints
if (fieldToCheck.type === STRING_TYPE && !fieldToCheck.constraints.length) {
fieldToCheck.constraints.length = {}
}
// some number types made server-side will be missing constraints
if (
fieldToCheck.type === NUMBER_TYPE &&
!fieldToCheck.constraints.numericality
) {
fieldToCheck.constraints.numericality = {}
}
}
</script> </script>
<ModalContent <ModalContent
@ -268,7 +293,7 @@
</div> </div>
{/if} {/if}
{#if canBeSearched} {#if canBeSearched && !external}
<div> <div>
<Label grey small>Search Indexes</Label> <Label grey small>Search Indexes</Label>
<Toggle <Toggle
@ -328,7 +353,7 @@
getOptionLabel={table => table.name} getOptionLabel={table => table.name}
getOptionValue={table => table._id} getOptionValue={table => table._id}
/> />
{#if relationshipOptions && relationshipOptions.length > 0 && !external} {#if relationshipOptions && relationshipOptions.length > 0}
<RadioGroup <RadioGroup
disabled={linkEditDisabled} disabled={linkEditDisabled}
label="Define the relationship" label="Define the relationship"

View File

@ -8,7 +8,6 @@
Layout, Layout,
Modal, Modal,
InlineAlert, InlineAlert,
ActionButton,
} from "@budibase/bbui" } from "@budibase/bbui"
import { datasources, integrations, queries, tables } from "stores/backend" import { datasources, integrations, queries, tables } from "stores/backend"
import { notifications } from "@budibase/bbui" import { notifications } from "@budibase/bbui"
@ -200,11 +199,6 @@
/> />
{/if} {/if}
<div class="query-list"> <div class="query-list">
<div class="add-table">
<ActionButton quiet icon="TableAdd" on:click={createNewTable}>
New table
</ActionButton>
</div>
{#each plusTables as table} {#each plusTables as table}
<div class="query-list-item" on:click={() => onClickTable(table)}> <div class="query-list-item" on:click={() => onClickTable(table)}>
<p class="query-name">{table.name}</p> <p class="query-name">{table.name}</p>
@ -212,6 +206,9 @@
<p></p> <p></p>
</div> </div>
{/each} {/each}
<div class="add-table">
<Button cta on:click={createNewTable}>Create new table</Button>
</div>
</div> </div>
{#if plusTables?.length !== 0} {#if plusTables?.length !== 0}
<Divider /> <Divider />
@ -343,7 +340,6 @@
} }
.add-table { .add-table {
margin-right: 0; margin-top: var(--spacing-m);
margin-left: auto;
} }
</style> </style>

View File

@ -152,6 +152,16 @@ const buildSchemaHelper = async datasource => {
await connector.buildSchema(datasource._id, datasource.entities) await connector.buildSchema(datasource._id, datasource.entities)
datasource.entities = connector.tables datasource.entities = connector.tables
// make sure they all have a display name selected
for (let entity of Object.values(datasource.entities)) {
if (entity.primaryDisplay) {
continue
}
entity.primaryDisplay = Object.values(entity.schema).find(
schema => !schema.autocolumn
).name
}
const errors = connector.schemaErrors const errors = connector.schemaErrors
let error = null let error = null
if (errors && Object.keys(errors).length > 0) { if (errors && Object.keys(errors).length > 0) {

View File

@ -36,6 +36,35 @@ async function makeTableRequest(
return makeExternalQuery(datasource, json) return makeExternalQuery(datasource, json)
} }
function cleanupRelationships(table, tables, oldTable = null) {
const tableToIterate = oldTable ? oldTable : table
// clean up relationships in couch table schemas
for (let [key, schema] of Object.entries(tableToIterate.schema)) {
if (
schema.type === FieldTypes.LINK &&
(!oldTable || table.schema[key] == null)
) {
const relatedTable = Object.values(tables).find(
table => table._id === schema.tableId
)
const foreignKey = schema.foreignKey
if (!relatedTable || !foreignKey) {
continue
}
for (let [relatedKey, relatedSchema] of Object.entries(
relatedTable.schema
)) {
if (
relatedSchema.type === FieldTypes.LINK &&
relatedSchema.fieldName === foreignKey
) {
delete relatedSchema[relatedKey]
}
}
}
}
}
function getDatasourceId(table) { function getDatasourceId(table) {
if (!table) { if (!table) {
throw "No table supplied" throw "No table supplied"
@ -57,6 +86,14 @@ function generateRelatedSchema(linkColumn, table) {
return relatedSchema return relatedSchema
} }
function oneToManyRelationshipNeedsSetup(column) {
return (
column.type === FieldTypes.LINK &&
column.relationshipType === RelationshipTypes.ONE_TO_MANY &&
!column.foreignKey
)
}
exports.save = async function (ctx) { exports.save = async function (ctx) {
const appId = ctx.appId const appId = ctx.appId
const table = ctx.request.body const table = ctx.request.body
@ -81,7 +118,7 @@ exports.save = async function (ctx) {
// check if relations need setup // check if relations need setup
for (let schema of Object.values(tableToSave.schema)) { for (let schema of Object.values(tableToSave.schema)) {
// TODO: many to many handling // TODO: many to many handling
if (schema.type === FieldTypes.LINK) { if (oneToManyRelationshipNeedsSetup(schema)) {
const relatedTable = Object.values(tables).find( const relatedTable = Object.values(tables).find(
table => table._id === schema.tableId table => table._id === schema.tableId
) )
@ -104,6 +141,8 @@ exports.save = async function (ctx) {
} }
} }
cleanupRelationships(tableToSave, tables, oldTable)
const operation = oldTable const operation = oldTable
? DataSourceOperation.UPDATE_TABLE ? DataSourceOperation.UPDATE_TABLE
: DataSourceOperation.CREATE_TABLE : DataSourceOperation.CREATE_TABLE
@ -128,6 +167,8 @@ exports.destroy = async function (ctx) {
const operation = DataSourceOperation.DELETE_TABLE const operation = DataSourceOperation.DELETE_TABLE
await makeTableRequest(datasource, operation, tableToDelete, tables) await makeTableRequest(datasource, operation, tableToDelete, tables)
cleanupRelationships(tableToDelete, tables)
delete datasource.entities[tableToDelete.name] delete datasource.entities[tableToDelete.name]
await db.put(datasource) await db.put(datasource)

View File

@ -4,7 +4,7 @@ import { Operation, QueryJson } from "../../definitions/datasource"
import { breakExternalTableId } from "../utils" import { breakExternalTableId } from "../utils"
import SchemaBuilder = Knex.SchemaBuilder import SchemaBuilder = Knex.SchemaBuilder
import CreateTableBuilder = Knex.CreateTableBuilder import CreateTableBuilder = Knex.CreateTableBuilder
const { FieldTypes } = require("../../constants") const { FieldTypes, RelationshipTypes } = require("../../constants")
function generateSchema(schema: CreateTableBuilder, table: Table, tables: Record<string, Table>, oldTable: null | Table = null) { function generateSchema(schema: CreateTableBuilder, table: Table, tables: Record<string, Table>, oldTable: null | Table = null) {
let primaryKey = table && table.primary ? table.primary[0] : null let primaryKey = table && table.primary ? table.primary[0] : null
@ -12,6 +12,8 @@ function generateSchema(schema: CreateTableBuilder, table: Table, tables: Record
if (primaryKey && !oldTable) { if (primaryKey && !oldTable) {
schema.increments(primaryKey).primary() schema.increments(primaryKey).primary()
} }
// check if any columns need added
const foreignKeys = Object.values(table.schema).map(col => col.foreignKey) const foreignKeys = Object.values(table.schema).map(col => col.foreignKey)
for (let [key, column] of Object.entries(table.schema)) { for (let [key, column] of Object.entries(table.schema)) {
// skip things that are already correct // skip things that are already correct
@ -38,6 +40,10 @@ function generateSchema(schema: CreateTableBuilder, table: Table, tables: Record
schema.json(key) schema.json(key)
break break
case FieldTypes.LINK: case FieldTypes.LINK:
// this side of the relationship doesn't need any SQL work
if (column.relationshipType === RelationshipTypes.MANY_TO_ONE) {
break
}
if (!column.foreignKey || !column.tableId) { if (!column.foreignKey || !column.tableId) {
throw "Invalid relationship schema" throw "Invalid relationship schema"
} }
@ -51,6 +57,17 @@ function generateSchema(schema: CreateTableBuilder, table: Table, tables: Record
schema.foreign(column.foreignKey).references(`${tableName}.${relatedTable.primary[0]}`) schema.foreign(column.foreignKey).references(`${tableName}.${relatedTable.primary[0]}`)
} }
} }
// need to check if any columns have been deleted
if (oldTable) {
const deletedColumns = Object.entries(oldTable.schema)
.filter(([key, schema]) => schema.type !== FieldTypes.LINK && table.schema[key] == null)
.map(([key]) => key)
deletedColumns.forEach(key => {
schema.dropColumn(key)
})
}
return schema return schema
} }