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:
parent
7b1114b7df
commit
eb8fde5c95
|
@ -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"}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue