Merge branch 'labday/type_schema' into feature/multi-user-type-column

This commit is contained in:
Adria Navarro 2023-10-11 14:13:55 +02:00
commit 62fd9011f3
25 changed files with 324 additions and 150 deletions

View File

@ -5,8 +5,11 @@ import {
FieldType, FieldType,
FilterType, FilterType,
IncludeRelationship, IncludeRelationship,
ManyToManyRelationshipFieldMetadata,
OneToManyRelationshipFieldMetadata,
Operation, Operation,
PaginationJson, PaginationJson,
RelationshipFieldMetadata,
RelationshipsJson, RelationshipsJson,
RelationshipType, RelationshipType,
Row, Row,
@ -254,12 +257,20 @@ function fixArrayTypes(row: Row, table: Table) {
return row return row
} }
function isOneSide(field: FieldSchema) { function isOneSide(
field: RelationshipFieldMetadata
): field is OneToManyRelationshipFieldMetadata {
return ( return (
field.relationshipType && field.relationshipType.split("-")[0] === "one" field.relationshipType && field.relationshipType.split("-")[0] === "one"
) )
} }
function isManyToMany(
field: RelationshipFieldMetadata
): field is ManyToManyRelationshipFieldMetadata {
return !!(field as ManyToManyRelationshipFieldMetadata).through
}
function isEditableColumn(column: FieldSchema) { function isEditableColumn(column: FieldSchema) {
const isExternalAutoColumn = const isExternalAutoColumn =
column.autocolumn && column.autocolumn &&
@ -352,11 +363,11 @@ export class ExternalRequest<T extends Operation> {
} }
} }
// many to many // many to many
else if (field.through) { else if (isManyToMany(field)) {
// we're not inserting a doc, will be a bunch of update calls // we're not inserting a doc, will be a bunch of update calls
const otherKey: string = field.throughFrom || linkTablePrimary const otherKey: string = field.throughFrom || linkTablePrimary
const thisKey: string = field.throughTo || tablePrimary const thisKey: string = field.throughTo || tablePrimary
row[key].forEach((relationship: any) => { for (const relationship of row[key]) {
manyRelationships.push({ manyRelationships.push({
tableId: field.through || field.tableId, tableId: field.through || field.tableId,
isUpdate: false, isUpdate: false,
@ -365,14 +376,14 @@ export class ExternalRequest<T extends Operation> {
// leave the ID for enrichment later // leave the ID for enrichment later
[thisKey]: `{{ literal ${tablePrimary} }}`, [thisKey]: `{{ literal ${tablePrimary} }}`,
}) })
}) }
} }
// many to one // many to one
else { else {
const thisKey: string = "id" const thisKey: string = "id"
// @ts-ignore // @ts-ignore
const otherKey: string = field.fieldName const otherKey: string = field.fieldName
row[key].forEach((relationship: any) => { for (const relationship of row[key]) {
manyRelationships.push({ manyRelationships.push({
tableId: field.tableId, tableId: field.tableId,
isUpdate: true, isUpdate: true,
@ -381,7 +392,7 @@ export class ExternalRequest<T extends Operation> {
// leave the ID for enrichment later // leave the ID for enrichment later
[otherKey]: `{{ literal ${tablePrimary} }}`, [otherKey]: `{{ literal ${tablePrimary} }}`,
}) })
}) }
} }
} }
// we return the relationships that may need to be created in the through table // we return the relationships that may need to be created in the through table
@ -549,15 +560,12 @@ export class ExternalRequest<T extends Operation> {
if (!table.primary || !linkTable.primary) { if (!table.primary || !linkTable.primary) {
continue continue
} }
const definition: any = { const definition: RelationshipsJson = {
// if no foreign key specified then use the name of the field in other table
from: field.foreignKey || table.primary[0],
to: field.fieldName,
tableName: linkTableName, tableName: linkTableName,
// need to specify where to put this back into // need to specify where to put this back into
column: fieldName, column: fieldName,
} }
if (field.through) { if (isManyToMany(field)) {
const { tableName: throughTableName } = breakExternalTableId( const { tableName: throughTableName } = breakExternalTableId(
field.through field.through
) )
@ -567,6 +575,10 @@ export class ExternalRequest<T extends Operation> {
definition.to = field.throughFrom || linkTable.primary[0] definition.to = field.throughFrom || linkTable.primary[0]
definition.fromPrimary = table.primary[0] definition.fromPrimary = table.primary[0]
definition.toPrimary = linkTable.primary[0] definition.toPrimary = linkTable.primary[0]
} else {
// if no foreign key specified then use the name of the field in other table
definition.from = field.foreignKey || table.primary[0]
definition.to = field.fieldName
} }
relationships.push(definition) relationships.push(definition)
} }
@ -588,7 +600,7 @@ export class ExternalRequest<T extends Operation> {
const primaryKey = table.primary[0] const primaryKey = table.primary[0]
// make a new request to get the row with all its relationships // make a new request to get the row with all its relationships
// we need this to work out if any relationships need removed // we need this to work out if any relationships need removed
for (let field of Object.values(table.schema)) { for (const field of Object.values(table.schema)) {
if ( if (
field.type !== FieldTypes.LINK || field.type !== FieldTypes.LINK ||
!field.fieldName || !field.fieldName ||
@ -601,9 +613,9 @@ export class ExternalRequest<T extends Operation> {
const { tableName: relatedTableName } = breakExternalTableId(tableId) const { tableName: relatedTableName } = breakExternalTableId(tableId)
// @ts-ignore // @ts-ignore
const linkPrimaryKey = this.tables[relatedTableName].primary[0] const linkPrimaryKey = this.tables[relatedTableName].primary[0]
const manyKey = field.throughTo || primaryKey
const lookupField = isMany ? primaryKey : field.foreignKey const lookupField = isMany ? primaryKey : field.foreignKey
const fieldName = isMany ? manyKey : field.fieldName const fieldName = isMany ? field.throughTo || primaryKey : field.fieldName
if (!lookupField || !row[lookupField]) { if (!lookupField || !row[lookupField]) {
continue continue
} }

View File

@ -4,6 +4,8 @@ import { context } from "@budibase/backend-core"
import { import {
Ctx, Ctx,
FieldType, FieldType,
ManyToOneRelationshipFieldMetadata,
OneToManyRelationshipFieldMetadata,
Row, Row,
SearchFilters, SearchFilters,
Table, Table,
@ -19,7 +21,14 @@ function isForeignKey(key: string, table: Table) {
const relationships = Object.values(table.schema).filter( const relationships = Object.values(table.schema).filter(
column => column.type === FieldType.LINK column => column.type === FieldType.LINK
) )
return relationships.some(relationship => relationship.foreignKey === key) return relationships.some(
relationship =>
(
relationship as
| OneToManyRelationshipFieldMetadata
| ManyToOneRelationshipFieldMetadata
).foreignKey === key
)
} }
validateJs.extend(validateJs.validators.datetime, { validateJs.extend(validateJs.validators.datetime, {

View File

@ -1,4 +1,4 @@
import { FieldTypes, FormulaTypes } from "../../../constants" import { FormulaTypes } from "../../../constants"
import { clearColumns } from "./utils" import { clearColumns } from "./utils"
import { doesContainStrings } from "@budibase/string-templates" import { doesContainStrings } from "@budibase/string-templates"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
@ -6,12 +6,20 @@ import isEqual from "lodash/isEqual"
import uniq from "lodash/uniq" import uniq from "lodash/uniq"
import { updateAllFormulasInTable } from "../row/staticFormula" import { updateAllFormulasInTable } from "../row/staticFormula"
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import { FieldSchema, Table } from "@budibase/types" import {
FieldSchema,
FieldType,
FormulaFieldMetadata,
Table,
} from "@budibase/types"
import sdk from "../../../sdk" import sdk from "../../../sdk"
import { isRelationshipColumn } from "../../../db/utils"
function isStaticFormula(column: FieldSchema) { function isStaticFormula(
column: FieldSchema
): column is FormulaFieldMetadata & { formulaType: FormulaTypes.STATIC } {
return ( return (
column.type === FieldTypes.FORMULA && column.type === FieldType.FORMULA &&
column.formulaType === FormulaTypes.STATIC column.formulaType === FormulaTypes.STATIC
) )
} }
@ -56,8 +64,9 @@ async function checkIfFormulaNeedsCleared(
for (let removed of removedColumns) { for (let removed of removedColumns) {
let tableToUse: Table | undefined = table let tableToUse: Table | undefined = table
// if relationship, get the related table // if relationship, get the related table
if (removed.type === FieldTypes.LINK) { if (removed.type === FieldType.LINK) {
tableToUse = tables.find(table => table._id === removed.tableId) const removedTableId = removed.tableId
tableToUse = tables.find(table => table._id === removedTableId)
} }
if (!tableToUse) { if (!tableToUse) {
continue continue
@ -73,17 +82,18 @@ async function checkIfFormulaNeedsCleared(
} }
for (let relatedTableId of table.relatedFormula) { for (let relatedTableId of table.relatedFormula) {
const relatedColumns = Object.values(table.schema).filter( const relatedColumns = Object.values(table.schema).filter(
column => column.tableId === relatedTableId column =>
column.type === FieldType.LINK && column.tableId === relatedTableId
) )
const relatedTable = tables.find(table => table._id === relatedTableId) const relatedTable = tables.find(table => table._id === relatedTableId)
// look to see if the column was used in a relationship formula, // look to see if the column was used in a relationship formula,
// relationships won't be used for this // relationships won't be used for this
if (relatedTable && relatedColumns && removed.type !== FieldTypes.LINK) { if (relatedTable && relatedColumns && removed.type !== FieldType.LINK) {
let relatedFormulaToRemove: string[] = [] let relatedFormulaToRemove: string[] = []
for (let column of relatedColumns) { for (let column of relatedColumns) {
relatedFormulaToRemove = relatedFormulaToRemove.concat( relatedFormulaToRemove = relatedFormulaToRemove.concat(
getFormulaThatUseColumn(relatedTable, [ getFormulaThatUseColumn(relatedTable, [
column.fieldName!, (column as any).fieldName!,
removed.name, removed.name,
]) ])
) )
@ -116,7 +126,7 @@ async function updateRelatedFormulaLinksOnTables(
const initialTables = cloneDeep(tables) const initialTables = cloneDeep(tables)
// first find the related column names // first find the related column names
const relatedColumns = Object.values(table.schema).filter( const relatedColumns = Object.values(table.schema).filter(
col => col.type === FieldTypes.LINK isRelationshipColumn
) )
// we start by removing the formula field from all tables // we start by removing the formula field from all tables
for (let otherTable of tables) { for (let otherTable of tables) {
@ -135,6 +145,7 @@ async function updateRelatedFormulaLinksOnTables(
if (!columns || columns.length === 0) { if (!columns || columns.length === 0) {
continue continue
} }
const relatedTable = tables.find( const relatedTable = tables.find(
related => related._id === relatedCol.tableId related => related._id === relatedCol.tableId
) )

View File

@ -17,9 +17,12 @@ import { isRows, isSchema, parse } from "../../../utilities/schema"
import { import {
BulkImportRequest, BulkImportRequest,
Datasource, Datasource,
FieldSchema, ManyToManyRelationshipFieldMetadata,
ManyToOneRelationshipFieldMetadata,
OneToManyRelationshipFieldMetadata,
Operation, Operation,
QueryJson, QueryJson,
RelationshipFieldMetadata,
RelationshipType, RelationshipType,
RenameColumn, RenameColumn,
SaveTableRequest, SaveTableRequest,
@ -74,10 +77,13 @@ function cleanupRelationships(
schema.type === FieldTypes.LINK && schema.type === FieldTypes.LINK &&
(!oldTable || table.schema[key] == null) (!oldTable || table.schema[key] == null)
) { ) {
const schemaTableId = schema.tableId
const relatedTable = Object.values(tables).find( const relatedTable = Object.values(tables).find(
table => table._id === schema.tableId table => table._id === schemaTableId
) )
const foreignKey = schema.foreignKey const foreignKey =
schema.relationshipType !== RelationshipType.MANY_TO_MANY &&
schema.foreignKey
if (!relatedTable || !foreignKey) { if (!relatedTable || !foreignKey) {
continue continue
} }
@ -116,7 +122,7 @@ function otherRelationshipType(type?: string) {
function generateManyLinkSchema( function generateManyLinkSchema(
datasource: Datasource, datasource: Datasource,
column: FieldSchema, column: ManyToManyRelationshipFieldMetadata,
table: Table, table: Table,
relatedTable: Table relatedTable: Table
): Table { ): Table {
@ -151,10 +157,12 @@ function generateManyLinkSchema(
} }
function generateLinkSchema( function generateLinkSchema(
column: FieldSchema, column:
| OneToManyRelationshipFieldMetadata
| ManyToOneRelationshipFieldMetadata,
table: Table, table: Table,
relatedTable: Table, relatedTable: Table,
type: RelationshipType type: RelationshipType.ONE_TO_MANY | RelationshipType.MANY_TO_ONE
) { ) {
if (!table.primary || !relatedTable.primary) { if (!table.primary || !relatedTable.primary) {
throw new Error("Unable to generate link schema, no primary keys") throw new Error("Unable to generate link schema, no primary keys")
@ -170,20 +178,22 @@ function generateLinkSchema(
} }
function generateRelatedSchema( function generateRelatedSchema(
linkColumn: FieldSchema, linkColumn: RelationshipFieldMetadata,
table: Table, table: Table,
relatedTable: Table, relatedTable: Table,
columnName: string columnName: string
) { ) {
// generate column for other table // generate column for other table
const relatedSchema = cloneDeep(linkColumn) const relatedSchema = cloneDeep(linkColumn)
const isMany2Many =
linkColumn.relationshipType === RelationshipType.MANY_TO_MANY
// swap them from the main link // swap them from the main link
if (linkColumn.foreignKey) { if (!isMany2Many && linkColumn.foreignKey) {
relatedSchema.fieldName = linkColumn.foreignKey relatedSchema.fieldName = linkColumn.foreignKey
relatedSchema.foreignKey = linkColumn.fieldName relatedSchema.foreignKey = linkColumn.fieldName
} }
// is many to many // is many to many
else { else if (isMany2Many) {
// don't need to copy through, already got it // don't need to copy through, already got it
relatedSchema.fieldName = linkColumn.throughTo relatedSchema.fieldName = linkColumn.throughTo
relatedSchema.throughTo = linkColumn.throughFrom relatedSchema.throughTo = linkColumn.throughFrom
@ -197,8 +207,8 @@ function generateRelatedSchema(
table.schema[columnName] = relatedSchema table.schema[columnName] = relatedSchema
} }
function isRelationshipSetup(column: FieldSchema) { function isRelationshipSetup(column: RelationshipFieldMetadata) {
return column.foreignKey || column.through return (column as any).foreignKey || (column as any).through
} }
export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) { export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
@ -257,14 +267,15 @@ export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
if (schema.type !== FieldTypes.LINK || isRelationshipSetup(schema)) { if (schema.type !== FieldTypes.LINK || isRelationshipSetup(schema)) {
continue continue
} }
const schemaTableId = schema.tableId
const relatedTable = Object.values(tables).find( const relatedTable = Object.values(tables).find(
table => table._id === schema.tableId table => table._id === schemaTableId
) )
if (!relatedTable) { if (!relatedTable) {
continue continue
} }
const relatedColumnName = schema.fieldName! const relatedColumnName = schema.fieldName!
const relationType = schema.relationshipType! const relationType = schema.relationshipType
if (relationType === RelationshipType.MANY_TO_MANY) { if (relationType === RelationshipType.MANY_TO_MANY) {
const junctionTable = generateManyLinkSchema( const junctionTable = generateManyLinkSchema(
datasource, datasource,

View File

@ -79,10 +79,10 @@ export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
// make sure that types don't change of a column, have to remove // make sure that types don't change of a column, have to remove
// the column if you want to change the type // the column if you want to change the type
if (oldTable && oldTable.schema) { if (oldTable && oldTable.schema) {
for (let propKey of Object.keys(tableToSave.schema)) { for (const propKey of Object.keys(tableToSave.schema)) {
let oldColumn = oldTable.schema[propKey] let oldColumn = oldTable.schema[propKey]
if (oldColumn && oldColumn.type === FieldTypes.INTERNAL) { if (oldColumn && oldColumn.type === FieldTypes.INTERNAL) {
oldColumn.type = FieldTypes.AUTO oldTable.schema[propKey].type = FieldTypes.AUTO
} }
} }
} }

View File

@ -6,6 +6,8 @@ import * as setup from "./utilities"
import { context, InternalTable, roles, tenancy } from "@budibase/backend-core" import { context, InternalTable, roles, tenancy } from "@budibase/backend-core"
import { quotas } from "@budibase/pro" import { quotas } from "@budibase/pro"
import { import {
AutoFieldSubTypes,
FieldSchema,
FieldType, FieldType,
FieldTypeSubtypes, FieldTypeSubtypes,
MonthlyQuotaName, MonthlyQuotaName,
@ -171,7 +173,7 @@ describe.each([
"Row ID": { "Row ID": {
name: "Row ID", name: "Row ID",
type: FieldType.NUMBER, type: FieldType.NUMBER,
subtype: "autoID", subtype: AutoFieldSubTypes.AUTO_ID,
icon: "ri-magic-line", icon: "ri-magic-line",
autocolumn: true, autocolumn: true,
constraints: { constraints: {
@ -272,27 +274,27 @@ describe.each([
isInternal && isInternal &&
it("row values are coerced", async () => { it("row values are coerced", async () => {
const str = { const str: FieldSchema = {
type: FieldType.STRING, type: FieldType.STRING,
name: "str", name: "str",
constraints: { type: "string", presence: false }, constraints: { type: "string", presence: false },
} }
const attachment = { const attachment: FieldSchema = {
type: FieldType.ATTACHMENT, type: FieldType.ATTACHMENT,
name: "attachment", name: "attachment",
constraints: { type: "array", presence: false }, constraints: { type: "array", presence: false },
} }
const bool = { const bool: FieldSchema = {
type: FieldType.BOOLEAN, type: FieldType.BOOLEAN,
name: "boolean", name: "boolean",
constraints: { type: "boolean", presence: false }, constraints: { type: "boolean", presence: false },
} }
const number = { const number: FieldSchema = {
type: FieldType.NUMBER, type: FieldType.NUMBER,
name: "str", name: "str",
constraints: { type: "number", presence: false }, constraints: { type: "number", presence: false },
} }
const datetime = { const datetime: FieldSchema = {
type: FieldType.DATETIME, type: FieldType.DATETIME,
name: "datetime", name: "datetime",
constraints: { constraints: {
@ -301,7 +303,7 @@ describe.each([
datetime: { earliest: "", latest: "" }, datetime: { earliest: "", latest: "" },
}, },
} }
const arrayField = { const arrayField: FieldSchema = {
type: FieldType.ARRAY, type: FieldType.ARRAY,
constraints: { constraints: {
type: "array", type: "array",
@ -311,8 +313,7 @@ describe.each([
name: "Sample Tags", name: "Sample Tags",
sortable: false, sortable: false,
} }
const optsField = { const optsField: FieldSchema = {
fieldName: "Sample Opts",
name: "Sample Opts", name: "Sample Opts",
type: FieldType.OPTIONS, type: FieldType.OPTIONS,
constraints: { constraints: {
@ -1534,7 +1535,7 @@ describe.each([
describe.each([ describe.each([
[ [
"relationship fields", "relationship fields",
() => ({ (): Record<string, FieldSchema> => ({
user: { user: {
name: "user", name: "user",
relationshipType: RelationshipType.ONE_TO_MANY, relationshipType: RelationshipType.ONE_TO_MANY,
@ -1563,7 +1564,7 @@ describe.each([
], ],
[ [
"bb reference fields", "bb reference fields",
() => ({ (): Record<string, FieldSchema> => ({
user: { user: {
name: "user", name: "user",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,

View File

@ -3,6 +3,7 @@ import { events, context } from "@budibase/backend-core"
import { import {
FieldType, FieldType,
SaveTableRequest, SaveTableRequest,
RelationshipType,
Table, Table,
ViewCalculation, ViewCalculation,
} from "@budibase/types" } from "@budibase/types"
@ -357,9 +358,10 @@ describe("/tables", () => {
}, },
TestTable: { TestTable: {
type: FieldType.LINK, type: FieldType.LINK,
relationshipType: RelationshipType.ONE_TO_MANY,
name: "TestTable", name: "TestTable",
fieldName: "TestTable", fieldName: "TestTable",
tableId: testTable._id, tableId: testTable._id!,
constraints: { constraints: {
type: "array", type: "array",
}, },

View File

@ -1,6 +1,11 @@
import { objectStore, roles, constants } from "@budibase/backend-core" import { objectStore, roles, constants } from "@budibase/backend-core"
import { FieldType as FieldTypes } from "@budibase/types" import { FieldType as FieldTypes } from "@budibase/types"
export { FieldType as FieldTypes, RelationshipType } from "@budibase/types" export {
FieldType as FieldTypes,
RelationshipType,
AutoFieldSubTypes,
FormulaTypes,
} from "@budibase/types"
export enum FilterTypes { export enum FilterTypes {
STRING = "string", STRING = "string",
@ -39,11 +44,6 @@ export const SwitchableTypes = CanSwitchTypes.reduce((prev, current) =>
prev ? prev.concat(current) : current prev ? prev.concat(current) : current
) )
export enum FormulaTypes {
STATIC = "static",
DYNAMIC = "dynamic",
}
export enum AuthTypes { export enum AuthTypes {
APP = "app", APP = "app",
BUILDER = "builder", BUILDER = "builder",
@ -132,14 +132,6 @@ export const USERS_TABLE_SCHEMA = {
primaryDisplay: "email", primaryDisplay: "email",
} }
export enum AutoFieldSubTypes {
CREATED_BY = "createdBy",
CREATED_AT = "createdAt",
UPDATED_BY = "updatedBy",
UPDATED_AT = "updatedAt",
AUTO_ID = "autoID",
}
export enum AutoFieldDefaultNames { export enum AutoFieldDefaultNames {
CREATED_BY = "Created By", CREATED_BY = "Created By",
CREATED_AT = "Created At", CREATED_AT = "Created At",

View File

@ -7,7 +7,13 @@ import { employeeImport } from "./employeeImport"
import { jobsImport } from "./jobsImport" import { jobsImport } from "./jobsImport"
import { expensesImport } from "./expensesImport" import { expensesImport } from "./expensesImport"
import { db as dbCore } from "@budibase/backend-core" import { db as dbCore } from "@budibase/backend-core"
import { Table, Row, RelationshipType } from "@budibase/types" import {
Table,
Row,
RelationshipType,
FieldType,
TableSchema,
} from "@budibase/types"
export const DEFAULT_JOBS_TABLE_ID = "ta_bb_jobs" export const DEFAULT_JOBS_TABLE_ID = "ta_bb_jobs"
export const DEFAULT_INVENTORY_TABLE_ID = "ta_bb_inventory" export const DEFAULT_INVENTORY_TABLE_ID = "ta_bb_inventory"
@ -28,7 +34,11 @@ export const DEFAULT_BB_DATASOURCE = defaultDatasource
function syncLastIds(table: Table, rowCount: number) { function syncLastIds(table: Table, rowCount: number) {
Object.keys(table.schema).forEach(key => { Object.keys(table.schema).forEach(key => {
const entry = table.schema[key] const entry = table.schema[key]
if (entry.autocolumn && entry.subtype == "autoID") { if (
entry.autocolumn &&
entry.type === FieldType.NUMBER &&
entry.subtype == AutoFieldSubTypes.AUTO_ID
) {
entry.lastID = rowCount entry.lastID = rowCount
} }
}) })
@ -42,7 +52,7 @@ async function tableImport(table: Table, data: Row[]) {
} }
// AUTO COLUMNS // AUTO COLUMNS
const AUTO_COLUMNS = { const AUTO_COLUMNS: TableSchema = {
"Created At": { "Created At": {
name: "Created At", name: "Created At",
type: FieldTypes.DATETIME, type: FieldTypes.DATETIME,

View File

@ -7,7 +7,9 @@ import LinkDocument from "./LinkDocument"
import { import {
Database, Database,
FieldSchema, FieldSchema,
FieldType,
LinkDocumentValue, LinkDocumentValue,
RelationshipFieldMetadata,
RelationshipType, RelationshipType,
Row, Row,
Table, Table,
@ -133,7 +135,10 @@ class LinkController {
* Given the link field of this table, and the link field of the linked table, this makes sure * Given the link field of this table, and the link field of the linked table, this makes sure
* the state of relationship type is accurate on both. * the state of relationship type is accurate on both.
*/ */
handleRelationshipType(linkerField: FieldSchema, linkedField: FieldSchema) { handleRelationshipType(
linkerField: RelationshipFieldMetadata,
linkedField: RelationshipFieldMetadata
) {
if ( if (
!linkerField.relationshipType || !linkerField.relationshipType ||
linkerField.relationshipType === RelationshipType.MANY_TO_MANY linkerField.relationshipType === RelationshipType.MANY_TO_MANY
@ -183,7 +188,7 @@ class LinkController {
// if 1:N, ensure that this ID is not already attached to another record // if 1:N, ensure that this ID is not already attached to another record
const linkedTable = await this._db.get<Table>(field.tableId) const linkedTable = await this._db.get<Table>(field.tableId)
const linkedSchema = linkedTable.schema[field.fieldName!] const linkedSchema = linkedTable.schema[field.fieldName]
// We need to map the global users to metadata in each app for relationships // We need to map the global users to metadata in each app for relationships
if (field.tableId === InternalTables.USER_METADATA) { if (field.tableId === InternalTables.USER_METADATA) {
@ -200,7 +205,10 @@ class LinkController {
// iterate through the link IDs in the row field, see if any don't exist already // iterate through the link IDs in the row field, see if any don't exist already
for (let linkId of rowField) { for (let linkId of rowField) {
if (linkedSchema?.relationshipType === RelationshipType.ONE_TO_MANY) { if (
linkedSchema?.type === FieldType.LINK &&
linkedSchema?.relationshipType === RelationshipType.ONE_TO_MANY
) {
let links = ( let links = (
(await getLinkDocuments({ (await getLinkDocuments({
tableId: field.tableId, tableId: field.tableId,
@ -291,7 +299,7 @@ class LinkController {
*/ */
async removeFieldFromTable(fieldName: string) { async removeFieldFromTable(fieldName: string) {
let oldTable = this._oldTable let oldTable = this._oldTable
let field = oldTable?.schema[fieldName] as FieldSchema let field = oldTable?.schema[fieldName] as RelationshipFieldMetadata
const linkDocs = await this.getTableLinkDocs() const linkDocs = await this.getTableLinkDocs()
let toDelete = linkDocs.filter(linkDoc => { let toDelete = linkDocs.filter(linkDoc => {
let correctFieldName = let correctFieldName =
@ -351,9 +359,9 @@ class LinkController {
name: field.fieldName, name: field.fieldName,
type: FieldTypes.LINK, type: FieldTypes.LINK,
// these are the props of the table that initiated the link // these are the props of the table that initiated the link
tableId: table._id, tableId: table._id!,
fieldName: fieldName, fieldName: fieldName,
}) } as RelationshipFieldMetadata)
// update table schema after checking relationship types // update table schema after checking relationship types
schema[fieldName] = fields.linkerField schema[fieldName] = fields.linkerField

View File

@ -1,13 +1,9 @@
import { ViewName, getQueryIndex } from "../utils" import { ViewName, getQueryIndex, isRelationshipColumn } from "../utils"
import { FieldTypes } from "../../constants" import { FieldTypes } from "../../constants"
import { createLinkView } from "../views/staticViews" import { createLinkView } from "../views/staticViews"
import { context, logging } from "@budibase/backend-core" import { context, logging } from "@budibase/backend-core"
import { import { LinkDocument, LinkDocumentValue, Table } from "@budibase/types"
FieldSchema,
LinkDocument,
LinkDocumentValue,
Table,
} from "@budibase/types"
export { createLinkView } from "../views/staticViews" export { createLinkView } from "../views/staticViews"
/** /**
@ -93,7 +89,7 @@ export function getUniqueByProp(array: any[], prop: string) {
export function getLinkedTableIDs(table: Table) { export function getLinkedTableIDs(table: Table) {
return Object.values(table.schema) return Object.values(table.schema)
.filter((column: FieldSchema) => column.type === FieldTypes.LINK) .filter(isRelationshipColumn)
.map(column => column.tableId) .map(column => column.tableId)
} }
@ -113,7 +109,7 @@ export async function getLinkedTable(id: string, tables: Table[]) {
export function getRelatedTableForField(table: Table, fieldName: string) { export function getRelatedTableForField(table: Table, fieldName: string) {
// look to see if its on the table, straight in the schema // look to see if its on the table, straight in the schema
const field = table.schema[fieldName] const field = table.schema[fieldName]
if (field != null) { if (field?.type === FieldTypes.LINK) {
return field.tableId return field.tableId
} }
for (let column of Object.values(table.schema)) { for (let column of Object.values(table.schema)) {

View File

@ -1,6 +1,12 @@
import newid from "./newid" import newid from "./newid"
import { db as dbCore } from "@budibase/backend-core" import { db as dbCore } from "@budibase/backend-core"
import { DocumentType, VirtualDocumentType } from "@budibase/types" import {
DocumentType,
FieldSchema,
RelationshipFieldMetadata,
VirtualDocumentType,
} from "@budibase/types"
import { FieldTypes } from "../constants"
export { DocumentType, VirtualDocumentType } from "@budibase/types" export { DocumentType, VirtualDocumentType } from "@budibase/types"
type Optional = string | null type Optional = string | null
@ -307,3 +313,9 @@ export function extractViewInfoFromID(viewId: string) {
tableId: res!.groups!["tableId"], tableId: res!.groups!["tableId"],
} }
} }
export function isRelationshipColumn(
column: FieldSchema
): column is RelationshipFieldMetadata {
return column.type === FieldTypes.LINK
}

View File

@ -111,7 +111,7 @@ describe("postgres integrations", () => {
fieldName: oneToManyRelationshipInfo.fieldName, fieldName: oneToManyRelationshipInfo.fieldName,
name: "oneToManyRelation", name: "oneToManyRelation",
relationshipType: RelationshipType.ONE_TO_MANY, relationshipType: RelationshipType.ONE_TO_MANY,
tableId: oneToManyRelationshipInfo.table._id, tableId: oneToManyRelationshipInfo.table._id!,
main: true, main: true,
}, },
manyToOneRelation: { manyToOneRelation: {
@ -122,7 +122,7 @@ describe("postgres integrations", () => {
fieldName: manyToOneRelationshipInfo.fieldName, fieldName: manyToOneRelationshipInfo.fieldName,
name: "manyToOneRelation", name: "manyToOneRelation",
relationshipType: RelationshipType.MANY_TO_ONE, relationshipType: RelationshipType.MANY_TO_ONE,
tableId: manyToOneRelationshipInfo.table._id, tableId: manyToOneRelationshipInfo.table._id!,
main: true, main: true,
}, },
manyToManyRelation: { manyToManyRelation: {
@ -133,7 +133,7 @@ describe("postgres integrations", () => {
fieldName: manyToManyRelationshipInfo.fieldName, fieldName: manyToManyRelationshipInfo.fieldName,
name: "manyToManyRelation", name: "manyToManyRelation",
relationshipType: RelationshipType.MANY_TO_MANY, relationshipType: RelationshipType.MANY_TO_MANY,
tableId: manyToManyRelationshipInfo.table._id, tableId: manyToManyRelationshipInfo.table._id!,
main: true, main: true,
}, },
}, },
@ -250,6 +250,7 @@ describe("postgres integrations", () => {
id: { id: {
name: "id", name: "id",
type: FieldType.AUTO, type: FieldType.AUTO,
autocolumn: true,
}, },
}, },
sourceId: postgresDatasource._id, sourceId: postgresDatasource._id,

View File

@ -1,6 +1,7 @@
import { Knex, knex } from "knex" import { Knex, knex } from "knex"
import { import {
FieldSubtype, FieldSubtype,
NumberFieldMetadata,
Operation, Operation,
QueryJson, QueryJson,
RenameColumn, RenameColumn,
@ -22,7 +23,7 @@ function generateSchema(
let primaryKey = table && table.primary ? table.primary[0] : null let primaryKey = table && table.primary ? table.primary[0] : null
const columns = Object.values(table.schema) const columns = Object.values(table.schema)
// all columns in a junction table will be meta // all columns in a junction table will be meta
let metaCols = columns.filter(col => col.meta) let metaCols = columns.filter(col => (col as NumberFieldMetadata).meta)
let isJunction = metaCols.length === columns.length let isJunction = metaCols.length === columns.length
// can't change primary once its set for now // can't change primary once its set for now
if (primaryKey && !oldTable && !isJunction) { if (primaryKey && !oldTable && !isJunction) {
@ -32,7 +33,9 @@ function generateSchema(
} }
// check if any columns need added // 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 as any).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
const oldColumn = oldTable ? oldTable.schema[key] : null const oldColumn = oldTable ? oldTable.schema[key] : null

View File

@ -249,7 +249,7 @@ class OracleIntegration extends Sql implements DatasourcePlus {
) )
} }
private internalConvertType(column: OracleColumn): { type: FieldTypes } { private internalConvertType(column: OracleColumn) {
if (this.isBooleanType(column)) { if (this.isBooleanType(column)) {
return { type: FieldTypes.BOOLEAN } return { type: FieldTypes.BOOLEAN }
} }
@ -307,6 +307,7 @@ class OracleIntegration extends Sql implements DatasourcePlus {
}, },
...this.internalConvertType(oracleColumn), ...this.internalConvertType(oracleColumn),
} }
table.schema[columnName] = fieldSchema table.schema[columnName] = fieldSchema
} }

View File

@ -1,10 +1,11 @@
import cloneDeep from "lodash/cloneDeep" import cloneDeep from "lodash/cloneDeep"
import validateJs from "validate.js" import validateJs from "validate.js"
import { FieldType, Row, Table, TableSchema } from "@budibase/types" import { Row, Table, TableSchema } from "@budibase/types"
import { FieldTypes } from "../../../constants" import { FieldTypes } from "../../../constants"
import { makeExternalQuery } from "../../../integrations/base/query" import { makeExternalQuery } from "../../../integrations/base/query"
import { Format } from "../../../api/controllers/view/exporters" import { Format } from "../../../api/controllers/view/exporters"
import sdk from "../.." import sdk from "../.."
import { isRelationshipColumn } from "../../../db/utils"
export async function getDatasourceAndQuery(json: any) { export async function getDatasourceAndQuery(json: any) {
const datasourceId = json.endpoint.datasourceId const datasourceId = json.endpoint.datasourceId
@ -50,10 +51,10 @@ export function cleanExportRows(
} }
function isForeignKey(key: string, table: Table) { function isForeignKey(key: string, table: Table) {
const relationships = Object.values(table.schema).filter( const relationships = Object.values(table.schema).filter(isRelationshipColumn)
column => column.type === FieldType.LINK return relationships.some(
relationship => (relationship as any).foreignKey === key
) )
return relationships.some(relationship => relationship.foreignKey === key)
} }
export async function validate({ export async function validate({

View File

@ -1,6 +1,6 @@
import { populateExternalTableSchemas } from "../validation" import { populateExternalTableSchemas } from "../validation"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { Datasource, Table } from "@budibase/types" import { AutoReason, Datasource, Table } from "@budibase/types"
import { isEqual } from "lodash" import { isEqual } from "lodash"
const SCHEMA = { const SCHEMA = {
@ -109,7 +109,7 @@ describe("validation and update of external table schemas", () => {
const response = populateExternalTableSchemas(cloneDeep(SCHEMA) as any) const response = populateExternalTableSchemas(cloneDeep(SCHEMA) as any)
const foreignKey = getForeignKeyColumn(response) const foreignKey = getForeignKeyColumn(response)
expect(foreignKey.autocolumn).toBe(true) expect(foreignKey.autocolumn).toBe(true)
expect(foreignKey.autoReason).toBe("foreign_key") expect(foreignKey.autoReason).toBe(AutoReason.FOREIGN_KEY)
noOtherTableChanges(response) noOtherTableChanges(response)
}) })

View File

@ -1,11 +1,9 @@
import { import {
AutoReason, AutoReason,
Datasource, Datasource,
FieldSchema,
FieldType, FieldType,
RelationshipType, RelationshipType,
} from "@budibase/types" } from "@budibase/types"
import { FieldTypes } from "../../../constants"
function checkForeignKeysAreAutoColumns(datasource: Datasource) { function checkForeignKeysAreAutoColumns(datasource: Datasource) {
if (!datasource.entities) { if (!datasource.entities) {
@ -15,10 +13,11 @@ function checkForeignKeysAreAutoColumns(datasource: Datasource) {
// make sure all foreign key columns are marked as auto columns // make sure all foreign key columns are marked as auto columns
const foreignKeys: { tableId: string; key: string }[] = [] const foreignKeys: { tableId: string; key: string }[] = []
for (let table of tables) { for (let table of tables) {
const relationships = Object.values(table.schema).filter( Object.values(table.schema).forEach(column => {
column => column.type === FieldType.LINK if (column.type !== FieldType.LINK) {
) return
relationships.forEach(relationship => { }
const relationship = column
if (relationship.relationshipType === RelationshipType.MANY_TO_MANY) { if (relationship.relationshipType === RelationshipType.MANY_TO_MANY) {
const tableId = relationship.through! const tableId = relationship.through!
foreignKeys.push({ key: relationship.throughTo!, tableId }) foreignKeys.push({ key: relationship.throughTo!, tableId })
@ -36,7 +35,7 @@ function checkForeignKeysAreAutoColumns(datasource: Datasource) {
} }
// now make sure schemas are all accurate // now make sure schemas are all accurate
for (let table of tables) { for (const table of tables) {
for (let column of Object.values(table.schema)) { for (let column of Object.values(table.schema)) {
const shouldBeForeign = foreignKeys.find( const shouldBeForeign = foreignKeys.find(
options => options.tableId === table._id && options.key === column.name options => options.tableId === table._id && options.key === column.name

View File

@ -1,5 +1,11 @@
import _ from "lodash" import _ from "lodash"
import { FieldType, Table, TableSchema, ViewV2 } from "@budibase/types" import {
FieldSchema,
FieldType,
Table,
TableSchema,
ViewV2,
} from "@budibase/types"
import { generator } from "@budibase/backend-core/tests" import { generator } from "@budibase/backend-core/tests"
import { enrichSchema, syncSchema } from ".." import { enrichSchema, syncSchema } from ".."
@ -316,7 +322,7 @@ describe("table sdk", () => {
...basicView, ...basicView,
} }
const newTableSchema = { const newTableSchema: TableSchema = {
...basicTable.schema, ...basicTable.schema,
newField1: { newField1: {
type: FieldType.STRING, type: FieldType.STRING,
@ -403,7 +409,7 @@ describe("table sdk", () => {
}, },
} }
const newTableSchema = { const newTableSchema: TableSchema = {
...basicTable.schema, ...basicTable.schema,
newField1: { newField1: {
type: FieldType.STRING, type: FieldType.STRING,
@ -531,7 +537,7 @@ describe("table sdk", () => {
id: { id: {
...basicTable.schema.id, ...basicTable.schema.id,
type: FieldType.NUMBER, type: FieldType.NUMBER,
}, } as FieldSchema,
}, },
undefined undefined
) )

View File

@ -54,6 +54,7 @@ import {
FieldType, FieldType,
RelationshipType, RelationshipType,
CreateViewRequest, CreateViewRequest,
RelationshipFieldMetadata,
} from "@budibase/types" } from "@budibase/types"
import API from "./api" import API from "./api"
@ -584,10 +585,10 @@ class TestConfiguration {
tableConfig.schema[link] = { tableConfig.schema[link] = {
type: FieldType.LINK, type: FieldType.LINK,
fieldName: link, fieldName: link,
tableId: this.table._id, tableId: this.table._id!,
name: link, name: link,
relationshipType, relationshipType,
} } as RelationshipFieldMetadata
} }
if (this.datasource && !tableConfig.sourceId) { if (this.datasource && !tableConfig.sourceId) {

View File

@ -5,7 +5,13 @@ import { ObjectStoreBuckets } from "../../constants"
import { context, db as dbCore, objectStore } from "@budibase/backend-core" import { context, db as dbCore, objectStore } from "@budibase/backend-core"
import { InternalTables } from "../../db/utils" import { InternalTables } from "../../db/utils"
import { TYPE_TRANSFORM_MAP } from "./map" import { TYPE_TRANSFORM_MAP } from "./map"
import { FieldSubtype, Row, RowAttachment, Table } from "@budibase/types" import {
AutoColumnFieldMetadata,
FieldSubtype,
Row,
RowAttachment,
Table,
} from "@budibase/types"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { import {
processInputBBReferences, processInputBBReferences,

View File

@ -4,10 +4,10 @@ import { FieldSchema, FieldType, RelationshipType } from "@budibase/types"
describe("rowProcessor utility", () => { describe("rowProcessor utility", () => {
describe("fixAutoColumnSubType", () => { describe("fixAutoColumnSubType", () => {
let schema: FieldSchema = { const schema: FieldSchema = {
name: "", name: "",
type: FieldType.LINK, type: FieldType.LINK,
subtype: "", // missing subtype subtype: undefined, // missing subtype
icon: "ri-magic-line", icon: "ri-magic-line",
autocolumn: true, autocolumn: true,
constraints: { type: "array", presence: false }, constraints: { type: "array", presence: false },
@ -22,31 +22,31 @@ describe("rowProcessor utility", () => {
expect(fixAutoColumnSubType(schema).subtype).toEqual( expect(fixAutoColumnSubType(schema).subtype).toEqual(
AutoFieldSubTypes.CREATED_BY AutoFieldSubTypes.CREATED_BY
) )
schema.subtype = "" schema.subtype = undefined
schema.name = AutoFieldDefaultNames.UPDATED_BY schema.name = AutoFieldDefaultNames.UPDATED_BY
expect(fixAutoColumnSubType(schema).subtype).toEqual( expect(fixAutoColumnSubType(schema).subtype).toEqual(
AutoFieldSubTypes.UPDATED_BY AutoFieldSubTypes.UPDATED_BY
) )
schema.subtype = "" schema.subtype = undefined
schema.name = AutoFieldDefaultNames.CREATED_AT schema.name = AutoFieldDefaultNames.CREATED_AT
expect(fixAutoColumnSubType(schema).subtype).toEqual( expect(fixAutoColumnSubType(schema).subtype).toEqual(
AutoFieldSubTypes.CREATED_AT AutoFieldSubTypes.CREATED_AT
) )
schema.subtype = "" schema.subtype = undefined
schema.name = AutoFieldDefaultNames.UPDATED_AT schema.name = AutoFieldDefaultNames.UPDATED_AT
expect(fixAutoColumnSubType(schema).subtype).toEqual( expect(fixAutoColumnSubType(schema).subtype).toEqual(
AutoFieldSubTypes.UPDATED_AT AutoFieldSubTypes.UPDATED_AT
) )
schema.subtype = "" schema.subtype = undefined
schema.name = AutoFieldDefaultNames.AUTO_ID schema.name = AutoFieldDefaultNames.AUTO_ID
expect(fixAutoColumnSubType(schema).subtype).toEqual( expect(fixAutoColumnSubType(schema).subtype).toEqual(
AutoFieldSubTypes.AUTO_ID AutoFieldSubTypes.AUTO_ID
) )
schema.subtype = "" schema.subtype = undefined
}) })
it("returns the column if subtype exists", async () => { it("returns the column if subtype exists", async () => {

View File

@ -5,13 +5,20 @@ import {
FormulaTypes, FormulaTypes,
} from "../../constants" } from "../../constants"
import { processStringSync } from "@budibase/string-templates" import { processStringSync } from "@budibase/string-templates"
import { FieldSchema, Row, Table } from "@budibase/types" import {
AutoColumnFieldMetadata,
FieldSchema,
Row,
Table,
} from "@budibase/types"
/** /**
* If the subtype has been lost for any reason this works out what * If the subtype has been lost for any reason this works out what
* subtype the auto column should be. * subtype the auto column should be.
*/ */
export function fixAutoColumnSubType(column: FieldSchema) { export function fixAutoColumnSubType(
column: FieldSchema
): AutoColumnFieldMetadata | FieldSchema {
if (!column.autocolumn || !column.name || column.subtype) { if (!column.autocolumn || !column.name || column.subtype) {
return column return column
} }
@ -47,9 +54,13 @@ export function processFormulas(
rowArray = rows rowArray = rows
} }
for (let [column, schema] of Object.entries(table.schema)) { for (let [column, schema] of Object.entries(table.schema)) {
if (schema.type !== FieldTypes.FORMULA) {
continue
}
const isStatic = schema.formulaType === FormulaTypes.STATIC const isStatic = schema.formulaType === FormulaTypes.STATIC
if ( if (
schema.type !== FieldTypes.FORMULA ||
schema.formula == null || schema.formula == null ||
(dynamic && isStatic) || (dynamic && isStatic) ||
(!dynamic && !isStatic) (!dynamic && !isStatic)

View File

@ -7,3 +7,16 @@ export enum RelationshipType {
export enum AutoReason { export enum AutoReason {
FOREIGN_KEY = "foreign_key", FOREIGN_KEY = "foreign_key",
} }
export enum AutoFieldSubTypes {
CREATED_BY = "createdBy",
CREATED_AT = "createdAt",
UPDATED_BY = "updatedBy",
UPDATED_AT = "updatedAt",
AUTO_ID = "autoID",
}
export enum FormulaTypes {
STATIC = "static",
DYNAMIC = "dynamic",
}

View File

@ -1,7 +1,12 @@
// all added by grid/table when defining the // all added by grid/table when defining the
// column size, position and whether it can be viewed // column size, position and whether it can be viewed
import { FieldType } from "../row" import { FieldSubtype, FieldType } from "../row"
import { AutoReason, RelationshipType } from "./constants" import {
AutoFieldSubTypes,
AutoReason,
FormulaTypes,
RelationshipType,
} from "./constants"
export interface UIFieldMetadata { export interface UIFieldMetadata {
order?: number order?: number
@ -10,28 +15,63 @@ export interface UIFieldMetadata {
icon?: string icon?: string
} }
export interface RelationshipFieldMetadata { interface BaseRelationshipFieldMetadata
extends Omit<BaseFieldSchema, "subtype"> {
type: FieldType.LINK
main?: boolean main?: boolean
fieldName?: string fieldName: string
tableId?: string tableId: string
// below is used for SQL relationships, needed to define the foreign keys subtype?: AutoFieldSubTypes.CREATED_BY | AutoFieldSubTypes.UPDATED_BY
// or the tables used for many-to-many relationships (through)
relationshipType?: RelationshipType
through?: string
foreignKey?: string
throughFrom?: string
throughTo?: string
} }
export interface AutoColumnFieldMetadata { // External tables use junction tables, internal tables don't require them
autocolumn?: boolean type ManyToManyJunctionTableMetadata =
subtype?: string | {
through: string
throughFrom: string
throughTo: string
}
| {
through?: never
throughFrom?: never
throughTo?: never
}
export type ManyToManyRelationshipFieldMetadata =
BaseRelationshipFieldMetadata & {
relationshipType: RelationshipType.MANY_TO_MANY
} & ManyToManyJunctionTableMetadata
export interface OneToManyRelationshipFieldMetadata
extends BaseRelationshipFieldMetadata {
relationshipType: RelationshipType.ONE_TO_MANY
foreignKey?: string
}
export interface ManyToOneRelationshipFieldMetadata
extends BaseRelationshipFieldMetadata {
relationshipType: RelationshipType.MANY_TO_ONE
foreignKey?: string
}
export type RelationshipFieldMetadata =
| ManyToManyRelationshipFieldMetadata
| OneToManyRelationshipFieldMetadata
| ManyToOneRelationshipFieldMetadata
export interface AutoColumnFieldMetadata
extends Omit<BaseFieldSchema, "subtype"> {
type: FieldType.AUTO
autocolumn: true
subtype?: AutoFieldSubTypes
lastID?: number lastID?: number
// if the column was turned to an auto-column for SQL, explains why (primary, foreign etc) // if the column was turned to an auto-column for SQL, explains why (primary, foreign etc)
autoReason?: AutoReason autoReason?: AutoReason
} }
export interface NumberFieldMetadata { export interface NumberFieldMetadata extends Omit<BaseFieldSchema, "subtype"> {
type: FieldType.NUMBER
subtype?: AutoFieldSubTypes.AUTO_ID
lastID?: number
autoReason?: AutoReason.FOREIGN_KEY
// used specifically when Budibase generates external tables, this denotes if a number field // used specifically when Budibase generates external tables, this denotes if a number field
// is a foreign key used for a many-to-many relationship // is a foreign key used for a many-to-many relationship
meta?: { meta?: {
@ -40,18 +80,28 @@ export interface NumberFieldMetadata {
} }
} }
export interface DateFieldMetadata { export interface DateFieldMetadata extends Omit<BaseFieldSchema, "subtype"> {
type: FieldType.DATETIME
ignoreTimezones?: boolean ignoreTimezones?: boolean
timeOnly?: boolean timeOnly?: boolean
subtype?: AutoFieldSubTypes.CREATED_AT | AutoFieldSubTypes.UPDATED_AT
} }
export interface StringFieldMetadata { export interface LongFormFieldMetadata extends BaseFieldSchema {
type: FieldType.LONGFORM
useRichText?: boolean | null useRichText?: boolean | null
} }
export interface FormulaFieldMetadata { export interface FormulaFieldMetadata extends BaseFieldSchema {
formula?: string type: FieldType.FORMULA
formulaType?: string formula: string
formulaType?: FormulaTypes
}
export interface BBReferenceFieldMetadata
extends Omit<BaseFieldSchema, "subtype"> {
type: FieldType.BB_REFERENCE
subtype: FieldSubtype.USER
} }
export interface FieldConstraints { export interface FieldConstraints {
@ -77,22 +127,40 @@ export interface FieldConstraints {
} }
} }
export interface FieldSchema interface BaseFieldSchema extends UIFieldMetadata {
extends UIFieldMetadata,
DateFieldMetadata,
RelationshipFieldMetadata,
AutoColumnFieldMetadata,
StringFieldMetadata,
FormulaFieldMetadata,
NumberFieldMetadata {
type: FieldType type: FieldType
name: string name: string
sortable?: boolean sortable?: boolean
// only used by external databases, to denote the real type // only used by external databases, to denote the real type
externalType?: string externalType?: string
constraints?: FieldConstraints constraints?: FieldConstraints
autocolumn?: boolean
autoReason?: AutoReason.FOREIGN_KEY
subtype?: never
} }
interface OtherFieldMetadata extends BaseFieldSchema {
type: Exclude<
FieldType,
| FieldType.DATETIME
| FieldType.LINK
| FieldType.AUTO
| FieldType.FORMULA
| FieldType.NUMBER
| FieldType.LONGFORM
>
}
export type FieldSchema =
| OtherFieldMetadata
| DateFieldMetadata
| RelationshipFieldMetadata
| AutoColumnFieldMetadata
| FormulaFieldMetadata
| NumberFieldMetadata
| LongFormFieldMetadata
| BBReferenceFieldMetadata
export interface TableSchema { export interface TableSchema {
[key: string]: FieldSchema [key: string]: FieldSchema
} }