mirror of https://github.com/Budibase/budibase.git
committed by
GitHub
27 changed files with 949 additions and 255 deletions
@ -0,0 +1,54 @@ |
|||
<script> |
|||
import { ActionButton, Modal, notifications } from "@budibase/bbui" |
|||
import CreateEditRelationship from "../../Datasources/CreateEditRelationship.svelte" |
|||
import { datasources, tables } from "../../../../stores/backend" |
|||
import { createEventDispatcher } from "svelte" |
|||
|
|||
export let table |
|||
const dispatch = createEventDispatcher() |
|||
|
|||
$: plusTables = datasource?.plus |
|||
? Object.values(datasource?.entities || {}) |
|||
: [] |
|||
$: datasource = $datasources.list.find( |
|||
source => source._id === table?.sourceId |
|||
) |
|||
|
|||
let modal |
|||
|
|||
async function saveRelationship() { |
|||
try { |
|||
// Create datasource |
|||
await datasources.save(datasource) |
|||
notifications.success(`Relationship information saved.`) |
|||
const tableList = await tables.fetch() |
|||
await tables.select(tableList.find(tbl => tbl._id === table._id)) |
|||
dispatch("updatecolumns") |
|||
} catch (err) { |
|||
notifications.error(`Error saving relationship info: ${err}`) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
{#if table.sourceId} |
|||
<div> |
|||
<ActionButton |
|||
icon="DataCorrelated" |
|||
primary |
|||
size="S" |
|||
quiet |
|||
on:click={modal.show} |
|||
> |
|||
Define existing relationship |
|||
</ActionButton> |
|||
</div> |
|||
<Modal bind:this={modal}> |
|||
<CreateEditRelationship |
|||
{datasource} |
|||
save={saveRelationship} |
|||
close={modal.hide} |
|||
{plusTables} |
|||
selectedFromTable={table} |
|||
/> |
|||
</Modal> |
|||
{/if} |
|||
@ -0,0 +1,45 @@ |
|||
<script> |
|||
import { ModalContent, Body, Input } from "@budibase/bbui" |
|||
import { tables, datasources } from "stores/backend" |
|||
import { goto } from "@roxi/routify" |
|||
|
|||
export let datasource |
|||
|
|||
let name = "" |
|||
$: valid = name && name.length > 0 && !datasource?.entities[name] |
|||
$: error = |
|||
name && datasource?.entities[name] ? "Table name already in use." : null |
|||
|
|||
function buildDefaultTable(tableName, datasourceId) { |
|||
return { |
|||
name: tableName, |
|||
type: "external", |
|||
primary: ["id"], |
|||
sourceId: datasourceId, |
|||
schema: { |
|||
id: { |
|||
autocolumn: true, |
|||
type: "number", |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
|
|||
async function saveTable() { |
|||
const table = await tables.save(buildDefaultTable(name, datasource._id)) |
|||
await datasources.fetch() |
|||
$goto(`../../table/${table._id}`) |
|||
} |
|||
</script> |
|||
|
|||
<ModalContent |
|||
title="Create new table" |
|||
confirmText="Create" |
|||
onConfirm={saveTable} |
|||
disabled={!valid} |
|||
> |
|||
<Body |
|||
>Provide a name for your new table; you can add columns once it is created.</Body |
|||
> |
|||
<Input label="Table Name" bind:error bind:value={name} /> |
|||
</ModalContent> |
|||
@ -1,43 +0,0 @@ |
|||
<script> |
|||
import { ModalContent, Select, Body } from "@budibase/bbui" |
|||
import { tables } from "stores/backend" |
|||
|
|||
export let datasource |
|||
export let plusTables |
|||
export let save |
|||
|
|||
async function saveDisplayColumns() { |
|||
// be explicit about copying over |
|||
for (let table of plusTables) { |
|||
datasource.entities[table.name].primaryDisplay = table.primaryDisplay |
|||
} |
|||
save() |
|||
await tables.fetch() |
|||
} |
|||
|
|||
function getColumnOptions(table) { |
|||
if (!table || !table.schema) { |
|||
return [] |
|||
} |
|||
return Object.entries(table.schema) |
|||
.filter(field => field[1].type !== "link") |
|||
.map(([fieldName]) => fieldName) |
|||
} |
|||
</script> |
|||
|
|||
<ModalContent |
|||
title="Edit display columns" |
|||
confirmText="Save" |
|||
onConfirm={saveDisplayColumns} |
|||
> |
|||
<Body |
|||
>Select the columns that will be shown when displaying relationships.</Body |
|||
> |
|||
{#each plusTables as table} |
|||
<Select |
|||
label={table.name} |
|||
options={getColumnOptions(table)} |
|||
bind:value={table.primaryDisplay} |
|||
/> |
|||
{/each} |
|||
</ModalContent> |
|||
@ -0,0 +1,276 @@ |
|||
const CouchDB = require("../../../db") |
|||
const { |
|||
buildExternalTableId, |
|||
breakExternalTableId, |
|||
} = require("../../../integrations/utils") |
|||
const { |
|||
getTable, |
|||
generateForeignKey, |
|||
generateJunctionTableName, |
|||
foreignKeyStructure, |
|||
} = require("./utils") |
|||
const { |
|||
DataSourceOperation, |
|||
FieldTypes, |
|||
RelationshipTypes, |
|||
} = require("../../../constants") |
|||
const { makeExternalQuery } = require("../../../integrations/base/utils") |
|||
const { cloneDeep } = require("lodash/fp") |
|||
|
|||
async function makeTableRequest( |
|||
datasource, |
|||
operation, |
|||
table, |
|||
tables, |
|||
oldTable = null |
|||
) { |
|||
const json = { |
|||
endpoint: { |
|||
datasourceId: datasource._id, |
|||
entityId: table._id, |
|||
operation, |
|||
}, |
|||
meta: { |
|||
tables, |
|||
}, |
|||
table, |
|||
} |
|||
if (oldTable) { |
|||
json.meta.table = oldTable |
|||
} |
|||
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 relatedTable.schema[relatedKey] |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
function getDatasourceId(table) { |
|||
if (!table) { |
|||
throw "No table supplied" |
|||
} |
|||
if (table.sourceId) { |
|||
return table.sourceId |
|||
} |
|||
return breakExternalTableId(table._id).datasourceId |
|||
} |
|||
|
|||
function otherRelationshipType(type) { |
|||
if (type === RelationshipTypes.MANY_TO_MANY) { |
|||
return RelationshipTypes.MANY_TO_MANY |
|||
} |
|||
return type === RelationshipTypes.ONE_TO_MANY |
|||
? RelationshipTypes.MANY_TO_ONE |
|||
: RelationshipTypes.ONE_TO_MANY |
|||
} |
|||
|
|||
function generateManyLinkSchema(datasource, column, table, relatedTable) { |
|||
const primary = table.name + table.primary[0] |
|||
const relatedPrimary = relatedTable.name + relatedTable.primary[0] |
|||
const jcTblName = generateJunctionTableName(column, table, relatedTable) |
|||
// first create the new table
|
|||
const junctionTable = { |
|||
_id: buildExternalTableId(datasource._id, jcTblName), |
|||
name: jcTblName, |
|||
primary: [primary, relatedPrimary], |
|||
constrained: [primary, relatedPrimary], |
|||
schema: { |
|||
[primary]: foreignKeyStructure(primary, { |
|||
toTable: table.name, |
|||
toKey: table.primary[0], |
|||
}), |
|||
[relatedPrimary]: foreignKeyStructure(relatedPrimary, { |
|||
toTable: relatedTable.name, |
|||
toKey: relatedTable.primary[0], |
|||
}), |
|||
}, |
|||
} |
|||
column.through = junctionTable._id |
|||
column.throughFrom = primary |
|||
column.throughTo = relatedPrimary |
|||
column.fieldName = relatedPrimary |
|||
return junctionTable |
|||
} |
|||
|
|||
function generateLinkSchema(column, table, relatedTable, type) { |
|||
const isOneSide = type === RelationshipTypes.ONE_TO_MANY |
|||
const primary = isOneSide ? relatedTable.primary[0] : table.primary[0] |
|||
// generate a foreign key
|
|||
const foreignKey = generateForeignKey(column, relatedTable) |
|||
column.relationshipType = type |
|||
column.foreignKey = isOneSide ? foreignKey : primary |
|||
column.fieldName = isOneSide ? primary : foreignKey |
|||
return foreignKey |
|||
} |
|||
|
|||
function generateRelatedSchema(linkColumn, table, relatedTable, columnName) { |
|||
// generate column for other table
|
|||
const relatedSchema = cloneDeep(linkColumn) |
|||
// swap them from the main link
|
|||
if (linkColumn.foreignKey) { |
|||
relatedSchema.fieldName = linkColumn.foreignKey |
|||
relatedSchema.foreignKey = linkColumn.fieldName |
|||
} |
|||
// is many to many
|
|||
else { |
|||
// don't need to copy through, already got it
|
|||
relatedSchema.fieldName = linkColumn.throughFrom |
|||
relatedSchema.throughTo = linkColumn.throughFrom |
|||
relatedSchema.throughFrom = linkColumn.throughTo |
|||
} |
|||
relatedSchema.relationshipType = otherRelationshipType( |
|||
linkColumn.relationshipType |
|||
) |
|||
relatedSchema.tableId = relatedTable._id |
|||
relatedSchema.name = columnName |
|||
table.schema[columnName] = relatedSchema |
|||
} |
|||
|
|||
function isRelationshipSetup(column) { |
|||
return column.foreignKey || column.through |
|||
} |
|||
|
|||
exports.save = async function (ctx) { |
|||
const appId = ctx.appId |
|||
const table = ctx.request.body |
|||
// can't do this
|
|||
delete table.dataImport |
|||
const datasourceId = getDatasourceId(ctx.request.body) |
|||
let tableToSave = { |
|||
type: "table", |
|||
_id: buildExternalTableId(datasourceId, table.name), |
|||
...table, |
|||
} |
|||
|
|||
let oldTable |
|||
if (ctx.request.body && ctx.request.body._id) { |
|||
oldTable = await getTable(appId, ctx.request.body._id) |
|||
} |
|||
|
|||
const db = new CouchDB(appId) |
|||
const datasource = await db.get(datasourceId) |
|||
const oldTables = cloneDeep(datasource.entities) |
|||
const tables = datasource.entities |
|||
|
|||
const extraTablesToUpdate = [] |
|||
|
|||
// check if relations need setup
|
|||
for (let schema of Object.values(tableToSave.schema)) { |
|||
if (schema.type !== FieldTypes.LINK || isRelationshipSetup(schema)) { |
|||
continue |
|||
} |
|||
const relatedTable = Object.values(tables).find( |
|||
table => table._id === schema.tableId |
|||
) |
|||
const relatedColumnName = schema.fieldName |
|||
const relationType = schema.relationshipType |
|||
if (relationType === RelationshipTypes.MANY_TO_MANY) { |
|||
const junctionTable = generateManyLinkSchema( |
|||
datasource, |
|||
schema, |
|||
table, |
|||
relatedTable |
|||
) |
|||
if (tables[junctionTable.name]) { |
|||
throw "Junction table already exists, cannot create another relationship." |
|||
} |
|||
tables[junctionTable.name] = junctionTable |
|||
extraTablesToUpdate.push(junctionTable) |
|||
} else { |
|||
const fkTable = |
|||
relationType === RelationshipTypes.ONE_TO_MANY ? table : relatedTable |
|||
const foreignKey = generateLinkSchema( |
|||
schema, |
|||
table, |
|||
relatedTable, |
|||
relationType |
|||
) |
|||
fkTable.schema[foreignKey] = foreignKeyStructure(foreignKey) |
|||
if (fkTable.constrained == null) { |
|||
fkTable.constrained = [] |
|||
} |
|||
if (fkTable.constrained.indexOf(foreignKey) === -1) { |
|||
fkTable.constrained.push(foreignKey) |
|||
} |
|||
// foreign key is in other table, need to save it to external
|
|||
if (fkTable._id !== table._id) { |
|||
extraTablesToUpdate.push(fkTable) |
|||
} |
|||
} |
|||
generateRelatedSchema(schema, relatedTable, table, relatedColumnName) |
|||
schema.main = true |
|||
} |
|||
|
|||
cleanupRelationships(tableToSave, tables, oldTable) |
|||
|
|||
const operation = oldTable |
|||
? DataSourceOperation.UPDATE_TABLE |
|||
: DataSourceOperation.CREATE_TABLE |
|||
await makeTableRequest(datasource, operation, tableToSave, tables, oldTable) |
|||
// update any extra tables (like foreign keys in other tables)
|
|||
for (let extraTable of extraTablesToUpdate) { |
|||
const oldExtraTable = oldTables[extraTable.name] |
|||
let op = oldExtraTable |
|||
? DataSourceOperation.UPDATE_TABLE |
|||
: DataSourceOperation.CREATE_TABLE |
|||
await makeTableRequest(datasource, op, extraTable, tables, oldExtraTable) |
|||
} |
|||
|
|||
// make sure the constrained list, all still exist
|
|||
if (Array.isArray(tableToSave.constrained)) { |
|||
tableToSave.constrained = tableToSave.constrained.filter(constraint => |
|||
Object.keys(tableToSave.schema).includes(constraint) |
|||
) |
|||
} |
|||
|
|||
// store it into couch now for budibase reference
|
|||
datasource.entities[tableToSave.name] = tableToSave |
|||
await db.put(datasource) |
|||
|
|||
return tableToSave |
|||
} |
|||
|
|||
exports.destroy = async function (ctx) { |
|||
const appId = ctx.appId |
|||
const tableToDelete = await getTable(appId, ctx.params.tableId) |
|||
const datasourceId = getDatasourceId(tableToDelete) |
|||
|
|||
const db = new CouchDB(appId) |
|||
const datasource = await db.get(datasourceId) |
|||
const tables = datasource.entities |
|||
|
|||
const operation = DataSourceOperation.DELETE_TABLE |
|||
await makeTableRequest(datasource, operation, tableToDelete, tables) |
|||
|
|||
cleanupRelationships(tableToDelete, tables) |
|||
|
|||
delete datasource.entities[tableToDelete.name] |
|||
await db.put(datasource) |
|||
|
|||
return tableToDelete |
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
const CouchDB = require("../../../db") |
|||
const linkRows = require("../../../db/linkedRows") |
|||
const { getRowParams, generateTableID } = require("../../../db/utils") |
|||
const { FieldTypes } = require("../../../constants") |
|||
const { TableSaveFunctions } = require("./utils") |
|||
|
|||
exports.save = async function (ctx) { |
|||
const appId = ctx.appId |
|||
const db = new CouchDB(appId) |
|||
const { dataImport, ...rest } = ctx.request.body |
|||
let tableToSave = { |
|||
type: "table", |
|||
_id: generateTableID(), |
|||
views: {}, |
|||
...rest, |
|||
} |
|||
|
|||
// if the table obj had an _id then it will have been retrieved
|
|||
let oldTable |
|||
if (ctx.request.body && ctx.request.body._id) { |
|||
oldTable = await db.get(ctx.request.body._id) |
|||
} |
|||
|
|||
// saving a table is a complex operation, involving many different steps, this
|
|||
// has been broken out into a utility to make it more obvious/easier to manipulate
|
|||
const tableSaveFunctions = new TableSaveFunctions({ |
|||
db, |
|||
ctx, |
|||
oldTable, |
|||
dataImport, |
|||
}) |
|||
tableToSave = await tableSaveFunctions.before(tableToSave) |
|||
|
|||
// make sure that types don't change of a column, have to remove
|
|||
// the column if you want to change the type
|
|||
if (oldTable && oldTable.schema) { |
|||
for (let propKey of Object.keys(tableToSave.schema)) { |
|||
let column = tableToSave.schema[propKey] |
|||
let oldColumn = oldTable.schema[propKey] |
|||
if (oldColumn && oldColumn.type === "internal") { |
|||
oldColumn.type = "auto" |
|||
} |
|||
if (oldColumn && oldColumn.type !== column.type) { |
|||
ctx.throw(400, "Cannot change the type of a column") |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Don't rename if the name is the same
|
|||
let { _rename } = tableToSave |
|||
/* istanbul ignore next */ |
|||
if (_rename && _rename.old === _rename.updated) { |
|||
_rename = null |
|||
delete tableToSave._rename |
|||
} |
|||
|
|||
// rename row fields when table column is renamed
|
|||
/* istanbul ignore next */ |
|||
if (_rename && tableToSave.schema[_rename.updated].type === FieldTypes.LINK) { |
|||
ctx.throw(400, "Cannot rename a linked column.") |
|||
} |
|||
|
|||
tableToSave = await tableSaveFunctions.mid(tableToSave) |
|||
|
|||
// update schema of non-statistics views when new columns are added
|
|||
for (let view in tableToSave.views) { |
|||
const tableView = tableToSave.views[view] |
|||
if (!tableView) continue |
|||
|
|||
if (tableView.schema.group || tableView.schema.field) continue |
|||
tableView.schema = tableToSave.schema |
|||
} |
|||
|
|||
// update linked rows
|
|||
try { |
|||
const linkResp = await linkRows.updateLinks({ |
|||
appId, |
|||
eventType: oldTable |
|||
? linkRows.EventType.TABLE_UPDATED |
|||
: linkRows.EventType.TABLE_SAVE, |
|||
table: tableToSave, |
|||
oldTable: oldTable, |
|||
}) |
|||
if (linkResp != null && linkResp._rev) { |
|||
tableToSave._rev = linkResp._rev |
|||
} |
|||
} catch (err) { |
|||
ctx.throw(400, err) |
|||
} |
|||
|
|||
// don't perform any updates until relationships have been
|
|||
// checked by the updateLinks function
|
|||
const updatedRows = tableSaveFunctions.getUpdatedRows() |
|||
if (updatedRows && updatedRows.length !== 0) { |
|||
await db.bulkDocs(updatedRows) |
|||
} |
|||
const result = await db.put(tableToSave) |
|||
tableToSave._rev = result.rev |
|||
|
|||
tableToSave = await tableSaveFunctions.after(tableToSave) |
|||
|
|||
return tableToSave |
|||
} |
|||
|
|||
exports.destroy = async function (ctx) { |
|||
const appId = ctx.appId |
|||
const db = new CouchDB(appId) |
|||
const tableToDelete = await db.get(ctx.params.tableId) |
|||
|
|||
// Delete all rows for that table
|
|||
const rows = await db.allDocs( |
|||
getRowParams(ctx.params.tableId, null, { |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
await db.bulkDocs(rows.rows.map(row => ({ ...row.doc, _deleted: true }))) |
|||
|
|||
// update linked rows
|
|||
await linkRows.updateLinks({ |
|||
appId, |
|||
eventType: linkRows.EventType.TABLE_DELETE, |
|||
table: tableToDelete, |
|||
}) |
|||
|
|||
// don't remove the table itself until very end
|
|||
await db.remove(tableToDelete) |
|||
|
|||
// remove table search index
|
|||
const currentIndexes = await db.getIndexes() |
|||
const existingIndex = currentIndexes.indexes.find( |
|||
existing => existing.name === `search:${ctx.params.tableId}` |
|||
) |
|||
if (existingIndex) { |
|||
await db.deleteIndex(existingIndex) |
|||
} |
|||
|
|||
return tableToDelete |
|||
} |
|||
@ -0,0 +1,167 @@ |
|||
import { Knex, knex } from "knex" |
|||
import { Table } from "../../definitions/common" |
|||
import { Operation, QueryJson } from "../../definitions/datasource" |
|||
import { breakExternalTableId } from "../utils" |
|||
import SchemaBuilder = Knex.SchemaBuilder |
|||
import CreateTableBuilder = Knex.CreateTableBuilder |
|||
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 |
|||
const columns = Object.values(table.schema) |
|||
// all columns in a junction table will be meta
|
|||
let metaCols = columns.filter(col => col.meta) |
|||
let isJunction = metaCols.length === columns.length |
|||
// can't change primary once its set for now
|
|||
if (primaryKey && !oldTable && !isJunction) { |
|||
schema.increments(primaryKey).primary() |
|||
} else if (!oldTable && isJunction) { |
|||
schema.primary(metaCols.map(col => col.name)) |
|||
} |
|||
|
|||
|
|||
// 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
|
|||
const oldColumn = oldTable ? oldTable.schema[key] : null |
|||
if ((oldColumn && oldColumn.type === column.type) || (primaryKey === key && !isJunction)) { |
|||
continue |
|||
} |
|||
switch (column.type) { |
|||
case FieldTypes.STRING: case FieldTypes.OPTIONS: case FieldTypes.LONGFORM: |
|||
schema.string(key) |
|||
break |
|||
case FieldTypes.NUMBER: |
|||
// if meta is specified then this is a junction table entry
|
|||
if (column.meta && column.meta.toKey && column.meta.toTable) { |
|||
const { toKey, toTable } = column.meta |
|||
schema.integer(key).unsigned() |
|||
schema.foreign(key).references(`${toTable}.${toKey}`) |
|||
} else if (foreignKeys.indexOf(key) === -1) { |
|||
schema.float(key) |
|||
} |
|||
break |
|||
case FieldTypes.BOOLEAN: |
|||
schema.boolean(key) |
|||
break |
|||
case FieldTypes.DATETIME: |
|||
schema.datetime(key) |
|||
break |
|||
case FieldTypes.ARRAY: |
|||
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 && |
|||
column.relationshipType !== RelationshipTypes.MANY_TO_MANY |
|||
) { |
|||
if (!column.foreignKey || !column.tableId) { |
|||
throw "Invalid relationship schema" |
|||
} |
|||
const { tableName } = breakExternalTableId(column.tableId) |
|||
// @ts-ignore
|
|||
const relatedTable = tables[tableName] |
|||
if (!relatedTable) { |
|||
throw "Referenced table doesn't exist" |
|||
} |
|||
schema.integer(column.foreignKey).unsigned() |
|||
schema.foreign(column.foreignKey).references(`${tableName}.${relatedTable.primary[0]}`) |
|||
} |
|||
break |
|||
} |
|||
} |
|||
|
|||
// 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 => { |
|||
if (oldTable.constrained && oldTable.constrained.indexOf(key) !== -1) { |
|||
schema.dropForeign(key) |
|||
} |
|||
schema.dropColumn(key) |
|||
}) |
|||
} |
|||
|
|||
return schema |
|||
} |
|||
|
|||
function buildCreateTable( |
|||
knex: Knex, |
|||
table: Table, |
|||
tables: Record<string, Table>, |
|||
): SchemaBuilder { |
|||
return knex.schema.createTable(table.name, schema => { |
|||
generateSchema(schema, table, tables) |
|||
}) |
|||
} |
|||
|
|||
function buildUpdateTable( |
|||
knex: Knex, |
|||
table: Table, |
|||
tables: Record<string, Table>, |
|||
oldTable: Table, |
|||
): SchemaBuilder { |
|||
return knex.schema.alterTable(table.name, schema => { |
|||
generateSchema(schema, table, tables, oldTable) |
|||
}) |
|||
} |
|||
|
|||
function buildDeleteTable( |
|||
knex: Knex, |
|||
table: Table, |
|||
): SchemaBuilder { |
|||
return knex.schema.dropTable(table.name) |
|||
} |
|||
|
|||
class SqlTableQueryBuilder { |
|||
private readonly sqlClient: string |
|||
|
|||
// pass through client to get flavour of SQL
|
|||
constructor(client: string) { |
|||
this.sqlClient = client |
|||
} |
|||
|
|||
getSqlClient(): string { |
|||
return this.sqlClient |
|||
} |
|||
|
|||
/** |
|||
* @param json the input JSON structure from which an SQL query will be built. |
|||
* @return {string} the operation that was found in the JSON. |
|||
*/ |
|||
_operation(json: QueryJson): Operation { |
|||
return json.endpoint.operation |
|||
} |
|||
|
|||
_tableQuery(json: QueryJson): any { |
|||
const client = knex({ client: this.sqlClient }) |
|||
let query |
|||
if (!json.table || !json.meta || !json.meta.tables) { |
|||
throw "Cannot execute without table being specified" |
|||
} |
|||
switch (this._operation(json)) { |
|||
case Operation.CREATE_TABLE: |
|||
query = buildCreateTable(client, json.table, json.meta.tables) |
|||
break |
|||
case Operation.UPDATE_TABLE: |
|||
if (!json.meta || !json.meta.table) { |
|||
throw "Must specify old table for update" |
|||
} |
|||
query = buildUpdateTable(client, json.table, json.meta.tables, json.meta.table) |
|||
break |
|||
case Operation.DELETE_TABLE: |
|||
query = buildDeleteTable(client, json.table) |
|||
break |
|||
default: |
|||
throw "Table operation is of unknown type" |
|||
} |
|||
return query.toSQL() |
|||
} |
|||
} |
|||
|
|||
export default SqlTableQueryBuilder |
|||
module.exports = SqlTableQueryBuilder |
|||
@ -0,0 +1,19 @@ |
|||
import { QueryJson } from "../../definitions/datasource" |
|||
import { Datasource } from "../../definitions/common" |
|||
|
|||
module DatasourceUtils { |
|||
const { integrations } = require("../index") |
|||
|
|||
export async function makeExternalQuery(datasource: Datasource, json: QueryJson) { |
|||
const Integration = integrations[datasource.source] |
|||
// query is the opinionated function
|
|||
if (Integration.prototype.query) { |
|||
const integration = new Integration(datasource.config) |
|||
return integration.query(json) |
|||
} else { |
|||
throw "Datasource does not support query." |
|||
} |
|||
} |
|||
|
|||
module.exports.makeExternalQuery = makeExternalQuery |
|||
} |
|||
Loading…
Reference in new issue