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 readonly
$: type = meta.type
$: label = capitalise(meta.name)
$: type = meta?.type
$: label = meta.name ? capitalise(meta.name) : ""
</script>
{#if type === "options"}

View File

@ -31,6 +31,9 @@
const AUTO_TYPE = "auto"
const FORMULA_TYPE = FIELDS.FORMULA.type
const LINK_TYPE = FIELDS.LINK.type
const STRING_TYPE = FIELDS.STRING.type
const NUMBER_TYPE = FIELDS.NUMBER.type
const dispatch = createEventDispatcher()
const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"]
const { hide } = getContext(Context.Modal)
@ -55,6 +58,7 @@
let confirmDeleteDialog
let deletion
$: checkConstraints(field)
$: tableOptions = $tables.list.filter(
opt => opt._id !== $tables.draft._id && opt.type === table.type
)
@ -180,23 +184,26 @@
}
const thisName = truncate(table.name, { length: 14 }),
linkName = truncate(linkTable.name, { length: 14 })
return [
const options = [
{
name: `Many ${thisName} rows → many ${linkName} rows`,
alt: `Many ${table.name} rows → many ${linkTable.name} rows`,
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`,
alt: `One ${table.name} rows → many ${linkTable.name} rows`,
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() {
@ -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>
<ModalContent
@ -268,7 +293,7 @@
</div>
{/if}
{#if canBeSearched}
{#if canBeSearched && !external}
<div>
<Label grey small>Search Indexes</Label>
<Toggle
@ -328,7 +353,7 @@
getOptionLabel={table => table.name}
getOptionValue={table => table._id}
/>
{#if relationshipOptions && relationshipOptions.length > 0 && !external}
{#if relationshipOptions && relationshipOptions.length > 0}
<RadioGroup
disabled={linkEditDisabled}
label="Define the relationship"

View File

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

View File

@ -152,6 +152,16 @@ const buildSchemaHelper = async datasource => {
await connector.buildSchema(datasource._id, datasource.entities)
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
let error = null
if (errors && Object.keys(errors).length > 0) {

View File

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

View File

@ -4,7 +4,7 @@ import { Operation, QueryJson } from "../../definitions/datasource"
import { breakExternalTableId } from "../utils"
import SchemaBuilder = Knex.SchemaBuilder
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) {
let primaryKey = table && table.primary ? table.primary[0] : null
@ -12,6 +12,8 @@ function generateSchema(schema: CreateTableBuilder, table: Table, tables: Record
if (primaryKey && !oldTable) {
schema.increments(primaryKey).primary()
}
// check if any columns need added
const foreignKeys = Object.values(table.schema).map(col => col.foreignKey)
for (let [key, column] of Object.entries(table.schema)) {
// skip things that are already correct
@ -38,6 +40,10 @@ function generateSchema(schema: CreateTableBuilder, table: Table, tables: Record
schema.json(key)
break
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) {
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]}`)
}
}
// 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
}