@budibase/server 0.9.174 → 0.9.176-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/builder/assets/{index.7469e1e4.css → index.75ed4773.css} +1 -1
- package/builder/assets/{index.ba2741f9.js → index.d3780441.js} +34 -34
- package/builder/index.html +2 -2
- package/coverage/clover.xml +383 -207
- package/coverage/coverage-final.json +8 -6
- package/coverage/lcov-report/api/controllers/analytics.js.html +1 -1
- package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/controllers/application.js.html +1 -1
- package/coverage/lcov-report/api/controllers/auth.js.html +1 -1
- package/coverage/lcov-report/api/controllers/automation.js.html +1 -1
- package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
- package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
- package/coverage/lcov-report/api/controllers/component.js.html +1 -1
- package/coverage/lcov-report/api/controllers/datasource.js.html +120 -24
- package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
- package/coverage/lcov-report/api/controllers/hosting.js.html +1 -1
- package/coverage/lcov-report/api/controllers/index.html +19 -19
- package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
- package/coverage/lcov-report/api/controllers/layout.js.html +1 -1
- package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
- package/coverage/lcov-report/api/controllers/permission.js.html +1 -1
- package/coverage/lcov-report/api/controllers/query.js.html +9 -3
- package/coverage/lcov-report/api/controllers/role.js.html +1 -1
- package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/external.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/index.html +14 -14
- package/coverage/lcov-report/api/controllers/row/index.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/internal.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/utils.js.html +11 -32
- package/coverage/lcov-report/api/controllers/screen.js.html +1 -1
- package/coverage/lcov-report/api/controllers/script.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/external.js.html +908 -0
- package/coverage/lcov-report/api/controllers/table/index.html +59 -29
- package/coverage/lcov-report/api/controllers/table/index.js.html +52 -376
- package/coverage/lcov-report/api/controllers/table/internal.js.html +494 -0
- package/coverage/lcov-report/api/controllers/table/utils.js.html +70 -10
- package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
- package/coverage/lcov-report/api/controllers/user.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/utils.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +1 -1
- package/coverage/lcov-report/api/controllers/webhook.js.html +1 -1
- package/coverage/lcov-report/api/index.html +1 -1
- package/coverage/lcov-report/api/index.js.html +1 -1
- package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
- package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/routes/application.js.html +1 -1
- package/coverage/lcov-report/api/routes/auth.js.html +1 -1
- package/coverage/lcov-report/api/routes/automation.js.html +1 -1
- package/coverage/lcov-report/api/routes/backup.js.html +1 -1
- package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
- package/coverage/lcov-report/api/routes/component.js.html +1 -1
- package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
- package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
- package/coverage/lcov-report/api/routes/dev.js.html +1 -1
- package/coverage/lcov-report/api/routes/hosting.js.html +1 -1
- package/coverage/lcov-report/api/routes/index.html +1 -1
- package/coverage/lcov-report/api/routes/index.js.html +1 -1
- package/coverage/lcov-report/api/routes/integration.js.html +1 -1
- package/coverage/lcov-report/api/routes/layout.js.html +1 -1
- package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
- package/coverage/lcov-report/api/routes/permission.js.html +1 -1
- package/coverage/lcov-report/api/routes/query.js.html +1 -1
- package/coverage/lcov-report/api/routes/role.js.html +1 -1
- package/coverage/lcov-report/api/routes/routing.js.html +1 -1
- package/coverage/lcov-report/api/routes/row.js.html +1 -1
- package/coverage/lcov-report/api/routes/screen.js.html +1 -1
- package/coverage/lcov-report/api/routes/script.js.html +1 -1
- package/coverage/lcov-report/api/routes/static.js.html +1 -1
- package/coverage/lcov-report/api/routes/table.js.html +1 -1
- package/coverage/lcov-report/api/routes/templates.js.html +1 -1
- package/coverage/lcov-report/api/routes/user.js.html +1 -1
- package/coverage/lcov-report/api/routes/view.js.html +1 -1
- package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/actions.js.html +1 -1
- package/coverage/lcov-report/automations/automationUtils.js.html +1 -1
- package/coverage/lcov-report/automations/bullboard.js.html +1 -1
- package/coverage/lcov-report/automations/index.html +1 -1
- package/coverage/lcov-report/automations/index.js.html +1 -1
- package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
- package/coverage/lcov-report/automations/steps/createRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
- package/coverage/lcov-report/automations/steps/deleteRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/discord.js.html +1 -1
- package/coverage/lcov-report/automations/steps/executeQuery.js.html +1 -1
- package/coverage/lcov-report/automations/steps/executeScript.js.html +1 -1
- package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
- package/coverage/lcov-report/automations/steps/index.html +1 -1
- package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
- package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +1 -1
- package/coverage/lcov-report/automations/steps/queryRows.js.html +1 -1
- package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
- package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
- package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
- package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/utils.js.html +1 -1
- package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
- package/coverage/lcov-report/automations/thread.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/app.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/cron.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/triggers.js.html +1 -1
- package/coverage/lcov-report/automations/utils.js.html +1 -1
- package/coverage/lcov-report/constants/index.html +5 -5
- package/coverage/lcov-report/constants/index.js.html +25 -4
- package/coverage/lcov-report/constants/layouts.js.html +1 -1
- package/coverage/lcov-report/constants/screens.js.html +1 -1
- package/coverage/lcov-report/db/client.js.html +1 -1
- package/coverage/lcov-report/db/inMemoryView.js.html +1 -1
- package/coverage/lcov-report/db/index.html +1 -1
- package/coverage/lcov-report/db/index.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/LinkController.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/index.html +1 -1
- package/coverage/lcov-report/db/linkedRows/index.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +1 -1
- package/coverage/lcov-report/db/newid.js.html +1 -1
- package/coverage/lcov-report/db/utils.js.html +1 -1
- package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
- package/coverage/lcov-report/events/index.html +1 -1
- package/coverage/lcov-report/events/index.js.html +1 -1
- package/coverage/lcov-report/events/utils.js.html +1 -1
- package/coverage/lcov-report/index.html +38 -38
- package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
- package/coverage/lcov-report/integrations/tests/index.html +1 -1
- package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
- package/coverage/lcov-report/middleware/authorized.js.html +1 -1
- package/coverage/lcov-report/middleware/builder.js.html +1 -1
- package/coverage/lcov-report/middleware/currentapp.js.html +1 -1
- package/coverage/lcov-report/middleware/index.html +1 -1
- package/coverage/lcov-report/middleware/joi-validator.js.html +1 -1
- package/coverage/lcov-report/middleware/resourceId.js.html +1 -1
- package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
- package/coverage/lcov-report/middleware/usageQuota.js.html +1 -1
- package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
- package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
- package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
- package/coverage/lcov-report/utilities/csvParser.js.html +1 -1
- package/coverage/lcov-report/utilities/global.js.html +1 -1
- package/coverage/lcov-report/utilities/index.html +1 -1
- package/coverage/lcov-report/utilities/index.js.html +1 -1
- package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +1 -1
- package/coverage/lcov-report/utilities/queue/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.js.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +1 -1
- package/coverage/lcov-report/utilities/scriptRunner.js.html +1 -1
- package/coverage/lcov-report/utilities/security.js.html +1 -1
- package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
- package/coverage/lcov-report/utilities/users.js.html +1 -1
- package/coverage/lcov-report/utilities/workerRequests.js.html +1 -1
- package/coverage/lcov.info +652 -332
- package/dist/api/controllers/datasource.js +41 -14
- package/dist/api/controllers/query.js +3 -1
- package/dist/api/controllers/row/ExternalRequest.js +12 -10
- package/dist/api/controllers/row/utils.js +3 -11
- package/dist/api/controllers/table/external.js +222 -0
- package/dist/api/controllers/table/index.js +28 -121
- package/dist/api/controllers/table/internal.js +138 -0
- package/dist/api/controllers/table/utils.js +17 -0
- package/dist/constants/index.js +6 -0
- package/dist/definitions/datasource.js +3 -0
- package/dist/integrations/base/datasourcePlus.js +2 -0
- package/dist/integrations/base/sql.js +16 -11
- package/dist/integrations/base/sqlTable.js +141 -0
- package/dist/integrations/base/utils.js +30 -0
- package/dist/integrations/mysql.js +13 -7
- package/dist/integrations/postgres.js +18 -8
- package/dist/integrations/utils.js +21 -6
- package/dist/package.json +4 -4
- package/dist/tests/utilities/TestConfiguration.js +2 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/api/controllers/datasource.js +46 -14
- package/src/api/controllers/query.js +3 -1
- package/src/api/controllers/row/ExternalRequest.ts +12 -10
- package/src/api/controllers/row/utils.js +3 -10
- package/src/api/controllers/table/external.js +276 -0
- package/src/api/controllers/table/index.js +27 -135
- package/src/api/controllers/table/internal.js +138 -0
- package/src/api/controllers/table/utils.js +20 -0
- package/src/api/routes/tests/datasource.spec.js +2 -2
- package/src/api/routes/tests/query.spec.js +0 -1
- package/src/constants/index.js +7 -0
- package/src/definitions/common.ts +7 -1
- package/src/definitions/datasource.ts +5 -0
- package/src/integrations/base/datasourcePlus.ts +8 -0
- package/src/integrations/base/sql.ts +15 -16
- package/src/integrations/base/sqlTable.ts +167 -0
- package/src/integrations/base/utils.ts +19 -0
- package/src/integrations/mysql.ts +15 -9
- package/src/integrations/postgres.ts +19 -9
- package/src/integrations/utils.ts +22 -6
- package/src/tests/utilities/TestConfiguration.js +2 -1
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
const CouchDB = require("../../../db")
|
|
2
|
-
const
|
|
2
|
+
const internal = require("./internal")
|
|
3
|
+
const external = require("./external")
|
|
3
4
|
const csvParser = require("../../../utilities/csvParser")
|
|
5
|
+
const { isExternalTable } = require("../../../integrations/utils")
|
|
4
6
|
const {
|
|
5
|
-
getRowParams,
|
|
6
7
|
getTableParams,
|
|
7
|
-
generateTableID,
|
|
8
8
|
getDatasourceParams,
|
|
9
9
|
BudibaseInternalDB,
|
|
10
10
|
} = require("../../../db/utils")
|
|
11
|
-
const {
|
|
12
|
-
const { TableSaveFunctions, getTable } = require("./utils")
|
|
11
|
+
const { getTable } = require("./utils")
|
|
13
12
|
|
|
13
|
+
function pickApi({ tableId, table }) {
|
|
14
|
+
if (table && !tableId) {
|
|
15
|
+
tableId = table._id
|
|
16
|
+
}
|
|
17
|
+
if (table && table.type === "external") {
|
|
18
|
+
return external
|
|
19
|
+
} else if (tableId && isExternalTable(tableId)) {
|
|
20
|
+
return external
|
|
21
|
+
}
|
|
22
|
+
return internal
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// covers both internal and external
|
|
14
26
|
exports.fetch = async function (ctx) {
|
|
15
27
|
const db = new CouchDB(ctx.appId)
|
|
16
28
|
|
|
@@ -50,143 +62,23 @@ exports.find = async function (ctx) {
|
|
|
50
62
|
|
|
51
63
|
exports.save = async function (ctx) {
|
|
52
64
|
const appId = ctx.appId
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
let tableToSave = {
|
|
56
|
-
type: "table",
|
|
57
|
-
_id: generateTableID(),
|
|
58
|
-
views: {},
|
|
59
|
-
...rest,
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// if the table obj had an _id then it will have been retrieved
|
|
63
|
-
let oldTable
|
|
64
|
-
if (ctx.request.body && ctx.request.body._id) {
|
|
65
|
-
oldTable = await db.get(ctx.request.body._id)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// saving a table is a complex operation, involving many different steps, this
|
|
69
|
-
// has been broken out into a utility to make it more obvious/easier to manipulate
|
|
70
|
-
const tableSaveFunctions = new TableSaveFunctions({
|
|
71
|
-
db,
|
|
72
|
-
ctx,
|
|
73
|
-
oldTable,
|
|
74
|
-
dataImport,
|
|
75
|
-
})
|
|
76
|
-
tableToSave = await tableSaveFunctions.before(tableToSave)
|
|
77
|
-
|
|
78
|
-
// make sure that types don't change of a column, have to remove
|
|
79
|
-
// the column if you want to change the type
|
|
80
|
-
if (oldTable && oldTable.schema) {
|
|
81
|
-
for (let propKey of Object.keys(tableToSave.schema)) {
|
|
82
|
-
let column = tableToSave.schema[propKey]
|
|
83
|
-
let oldColumn = oldTable.schema[propKey]
|
|
84
|
-
if (oldColumn && oldColumn.type === "internal") {
|
|
85
|
-
oldColumn.type = "auto"
|
|
86
|
-
}
|
|
87
|
-
if (oldColumn && oldColumn.type !== column.type) {
|
|
88
|
-
ctx.throw(400, "Cannot change the type of a column")
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Don't rename if the name is the same
|
|
94
|
-
let { _rename } = tableToSave
|
|
95
|
-
/* istanbul ignore next */
|
|
96
|
-
if (_rename && _rename.old === _rename.updated) {
|
|
97
|
-
_rename = null
|
|
98
|
-
delete tableToSave._rename
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// rename row fields when table column is renamed
|
|
102
|
-
/* istanbul ignore next */
|
|
103
|
-
if (_rename && tableToSave.schema[_rename.updated].type === FieldTypes.LINK) {
|
|
104
|
-
ctx.throw(400, "Cannot rename a linked column.")
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
tableToSave = await tableSaveFunctions.mid(tableToSave)
|
|
108
|
-
|
|
109
|
-
// update schema of non-statistics views when new columns are added
|
|
110
|
-
for (let view in tableToSave.views) {
|
|
111
|
-
const tableView = tableToSave.views[view]
|
|
112
|
-
if (!tableView) continue
|
|
113
|
-
|
|
114
|
-
if (tableView.schema.group || tableView.schema.field) continue
|
|
115
|
-
tableView.schema = tableToSave.schema
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// update linked rows
|
|
119
|
-
try {
|
|
120
|
-
const linkResp = await linkRows.updateLinks({
|
|
121
|
-
appId,
|
|
122
|
-
eventType: oldTable
|
|
123
|
-
? linkRows.EventType.TABLE_UPDATED
|
|
124
|
-
: linkRows.EventType.TABLE_SAVE,
|
|
125
|
-
table: tableToSave,
|
|
126
|
-
oldTable: oldTable,
|
|
127
|
-
})
|
|
128
|
-
if (linkResp != null && linkResp._rev) {
|
|
129
|
-
tableToSave._rev = linkResp._rev
|
|
130
|
-
}
|
|
131
|
-
} catch (err) {
|
|
132
|
-
ctx.throw(400, err)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// don't perform any updates until relationships have been
|
|
136
|
-
// checked by the updateLinks function
|
|
137
|
-
const updatedRows = tableSaveFunctions.getUpdatedRows()
|
|
138
|
-
if (updatedRows && updatedRows.length !== 0) {
|
|
139
|
-
await db.bulkDocs(updatedRows)
|
|
140
|
-
}
|
|
141
|
-
const result = await db.put(tableToSave)
|
|
142
|
-
tableToSave._rev = result.rev
|
|
143
|
-
|
|
144
|
-
tableToSave = await tableSaveFunctions.after(tableToSave)
|
|
145
|
-
|
|
146
|
-
ctx.eventEmitter &&
|
|
147
|
-
ctx.eventEmitter.emitTable(`table:save`, appId, tableToSave)
|
|
148
|
-
|
|
65
|
+
const table = ctx.request.body
|
|
66
|
+
const savedTable = await pickApi({ table }).save(ctx)
|
|
149
67
|
ctx.status = 200
|
|
150
|
-
ctx.message = `Table ${
|
|
151
|
-
ctx.
|
|
68
|
+
ctx.message = `Table ${table.name} saved successfully.`
|
|
69
|
+
ctx.eventEmitter &&
|
|
70
|
+
ctx.eventEmitter.emitTable(`table:save`, appId, savedTable)
|
|
71
|
+
ctx.body = savedTable
|
|
152
72
|
}
|
|
153
73
|
|
|
154
74
|
exports.destroy = async function (ctx) {
|
|
155
75
|
const appId = ctx.appId
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
// Delete all rows for that table
|
|
160
|
-
const rows = await db.allDocs(
|
|
161
|
-
getRowParams(ctx.params.tableId, null, {
|
|
162
|
-
include_docs: true,
|
|
163
|
-
})
|
|
164
|
-
)
|
|
165
|
-
await db.bulkDocs(rows.rows.map(row => ({ ...row.doc, _deleted: true })))
|
|
166
|
-
|
|
167
|
-
// update linked rows
|
|
168
|
-
await linkRows.updateLinks({
|
|
169
|
-
appId,
|
|
170
|
-
eventType: linkRows.EventType.TABLE_DELETE,
|
|
171
|
-
table: tableToDelete,
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
// don't remove the table itself until very end
|
|
175
|
-
await db.remove(tableToDelete)
|
|
176
|
-
|
|
177
|
-
// remove table search index
|
|
178
|
-
const currentIndexes = await db.getIndexes()
|
|
179
|
-
const existingIndex = currentIndexes.indexes.find(
|
|
180
|
-
existing => existing.name === `search:${ctx.params.tableId}`
|
|
181
|
-
)
|
|
182
|
-
if (existingIndex) {
|
|
183
|
-
await db.deleteIndex(existingIndex)
|
|
184
|
-
}
|
|
185
|
-
|
|
76
|
+
const tableId = ctx.params.tableId
|
|
77
|
+
const deletedTable = await pickApi({ tableId }).destroy(ctx)
|
|
186
78
|
ctx.eventEmitter &&
|
|
187
|
-
ctx.eventEmitter.emitTable(`table:delete`, appId,
|
|
79
|
+
ctx.eventEmitter.emitTable(`table:delete`, appId, deletedTable)
|
|
188
80
|
ctx.status = 200
|
|
189
|
-
ctx.body = { message: `Table ${
|
|
81
|
+
ctx.body = { message: `Table ${tableId} deleted.` }
|
|
190
82
|
}
|
|
191
83
|
|
|
192
84
|
exports.validateCSVSchema = async function (ctx) {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const CouchDB = require("../../../db")
|
|
2
|
+
const linkRows = require("../../../db/linkedRows")
|
|
3
|
+
const { getRowParams, generateTableID } = require("../../../db/utils")
|
|
4
|
+
const { FieldTypes } = require("../../../constants")
|
|
5
|
+
const { TableSaveFunctions } = require("./utils")
|
|
6
|
+
|
|
7
|
+
exports.save = async function (ctx) {
|
|
8
|
+
const appId = ctx.appId
|
|
9
|
+
const db = new CouchDB(appId)
|
|
10
|
+
const { dataImport, ...rest } = ctx.request.body
|
|
11
|
+
let tableToSave = {
|
|
12
|
+
type: "table",
|
|
13
|
+
_id: generateTableID(),
|
|
14
|
+
views: {},
|
|
15
|
+
...rest,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// if the table obj had an _id then it will have been retrieved
|
|
19
|
+
let oldTable
|
|
20
|
+
if (ctx.request.body && ctx.request.body._id) {
|
|
21
|
+
oldTable = await db.get(ctx.request.body._id)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// saving a table is a complex operation, involving many different steps, this
|
|
25
|
+
// has been broken out into a utility to make it more obvious/easier to manipulate
|
|
26
|
+
const tableSaveFunctions = new TableSaveFunctions({
|
|
27
|
+
db,
|
|
28
|
+
ctx,
|
|
29
|
+
oldTable,
|
|
30
|
+
dataImport,
|
|
31
|
+
})
|
|
32
|
+
tableToSave = await tableSaveFunctions.before(tableToSave)
|
|
33
|
+
|
|
34
|
+
// make sure that types don't change of a column, have to remove
|
|
35
|
+
// the column if you want to change the type
|
|
36
|
+
if (oldTable && oldTable.schema) {
|
|
37
|
+
for (let propKey of Object.keys(tableToSave.schema)) {
|
|
38
|
+
let column = tableToSave.schema[propKey]
|
|
39
|
+
let oldColumn = oldTable.schema[propKey]
|
|
40
|
+
if (oldColumn && oldColumn.type === "internal") {
|
|
41
|
+
oldColumn.type = "auto"
|
|
42
|
+
}
|
|
43
|
+
if (oldColumn && oldColumn.type !== column.type) {
|
|
44
|
+
ctx.throw(400, "Cannot change the type of a column")
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Don't rename if the name is the same
|
|
50
|
+
let { _rename } = tableToSave
|
|
51
|
+
/* istanbul ignore next */
|
|
52
|
+
if (_rename && _rename.old === _rename.updated) {
|
|
53
|
+
_rename = null
|
|
54
|
+
delete tableToSave._rename
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// rename row fields when table column is renamed
|
|
58
|
+
/* istanbul ignore next */
|
|
59
|
+
if (_rename && tableToSave.schema[_rename.updated].type === FieldTypes.LINK) {
|
|
60
|
+
ctx.throw(400, "Cannot rename a linked column.")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
tableToSave = await tableSaveFunctions.mid(tableToSave)
|
|
64
|
+
|
|
65
|
+
// update schema of non-statistics views when new columns are added
|
|
66
|
+
for (let view in tableToSave.views) {
|
|
67
|
+
const tableView = tableToSave.views[view]
|
|
68
|
+
if (!tableView) continue
|
|
69
|
+
|
|
70
|
+
if (tableView.schema.group || tableView.schema.field) continue
|
|
71
|
+
tableView.schema = tableToSave.schema
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// update linked rows
|
|
75
|
+
try {
|
|
76
|
+
const linkResp = await linkRows.updateLinks({
|
|
77
|
+
appId,
|
|
78
|
+
eventType: oldTable
|
|
79
|
+
? linkRows.EventType.TABLE_UPDATED
|
|
80
|
+
: linkRows.EventType.TABLE_SAVE,
|
|
81
|
+
table: tableToSave,
|
|
82
|
+
oldTable: oldTable,
|
|
83
|
+
})
|
|
84
|
+
if (linkResp != null && linkResp._rev) {
|
|
85
|
+
tableToSave._rev = linkResp._rev
|
|
86
|
+
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
ctx.throw(400, err)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// don't perform any updates until relationships have been
|
|
92
|
+
// checked by the updateLinks function
|
|
93
|
+
const updatedRows = tableSaveFunctions.getUpdatedRows()
|
|
94
|
+
if (updatedRows && updatedRows.length !== 0) {
|
|
95
|
+
await db.bulkDocs(updatedRows)
|
|
96
|
+
}
|
|
97
|
+
const result = await db.put(tableToSave)
|
|
98
|
+
tableToSave._rev = result.rev
|
|
99
|
+
|
|
100
|
+
tableToSave = await tableSaveFunctions.after(tableToSave)
|
|
101
|
+
|
|
102
|
+
return tableToSave
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
exports.destroy = async function (ctx) {
|
|
106
|
+
const appId = ctx.appId
|
|
107
|
+
const db = new CouchDB(appId)
|
|
108
|
+
const tableToDelete = await db.get(ctx.params.tableId)
|
|
109
|
+
|
|
110
|
+
// Delete all rows for that table
|
|
111
|
+
const rows = await db.allDocs(
|
|
112
|
+
getRowParams(ctx.params.tableId, null, {
|
|
113
|
+
include_docs: true,
|
|
114
|
+
})
|
|
115
|
+
)
|
|
116
|
+
await db.bulkDocs(rows.rows.map(row => ({ ...row.doc, _deleted: true })))
|
|
117
|
+
|
|
118
|
+
// update linked rows
|
|
119
|
+
await linkRows.updateLinks({
|
|
120
|
+
appId,
|
|
121
|
+
eventType: linkRows.EventType.TABLE_DELETE,
|
|
122
|
+
table: tableToDelete,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// don't remove the table itself until very end
|
|
126
|
+
await db.remove(tableToDelete)
|
|
127
|
+
|
|
128
|
+
// remove table search index
|
|
129
|
+
const currentIndexes = await db.getIndexes()
|
|
130
|
+
const existingIndex = currentIndexes.indexes.find(
|
|
131
|
+
existing => existing.name === `search:${ctx.params.tableId}`
|
|
132
|
+
)
|
|
133
|
+
if (existingIndex) {
|
|
134
|
+
await db.deleteIndex(existingIndex)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return tableToDelete
|
|
138
|
+
}
|
|
@@ -315,4 +315,24 @@ exports.checkForViewUpdates = async (db, table, rename, deletedColumns) => {
|
|
|
315
315
|
}
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
+
exports.generateForeignKey = (column, relatedTable) => {
|
|
319
|
+
return `fk_${relatedTable.name}_${column.fieldName}`
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
exports.generateJunctionTableName = (column, table, relatedTable) => {
|
|
323
|
+
return `jt_${table.name}_${relatedTable.name}_${column.name}_${column.fieldName}`
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
exports.foreignKeyStructure = (keyName, meta = null) => {
|
|
327
|
+
const structure = {
|
|
328
|
+
type: FieldTypes.NUMBER,
|
|
329
|
+
constraints: {},
|
|
330
|
+
name: keyName,
|
|
331
|
+
}
|
|
332
|
+
if (meta) {
|
|
333
|
+
structure.meta = meta
|
|
334
|
+
}
|
|
335
|
+
return structure
|
|
336
|
+
}
|
|
337
|
+
|
|
318
338
|
exports.TableSaveFunctions = TableSaveFunctions
|
|
@@ -26,8 +26,8 @@ describe("/datasources", () => {
|
|
|
26
26
|
.expect('Content-Type', /json/)
|
|
27
27
|
.expect(200)
|
|
28
28
|
|
|
29
|
-
expect(res.
|
|
30
|
-
expect(res.body.
|
|
29
|
+
expect(res.body.datasource.name).toEqual("Test")
|
|
30
|
+
expect(res.body.errors).toBeUndefined()
|
|
31
31
|
})
|
|
32
32
|
})
|
|
33
33
|
|
package/src/constants/index.js
CHANGED
|
@@ -62,6 +62,9 @@ exports.DataSourceOperation = {
|
|
|
62
62
|
READ: "READ",
|
|
63
63
|
UPDATE: "UPDATE",
|
|
64
64
|
DELETE: "DELETE",
|
|
65
|
+
CREATE_TABLE: "CREATE_TABLE",
|
|
66
|
+
UPDATE_TABLE: "UPDATE_TABLE",
|
|
67
|
+
DELETE_TABLE: "DELETE_TABLE",
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
exports.SortDirection = {
|
|
@@ -152,5 +155,9 @@ exports.MetadataTypes = {
|
|
|
152
155
|
AUTOMATION_TEST_HISTORY: "automationTestHistory",
|
|
153
156
|
}
|
|
154
157
|
|
|
158
|
+
exports.BuildSchemaErrors = {
|
|
159
|
+
NO_KEY: "no_key",
|
|
160
|
+
}
|
|
161
|
+
|
|
155
162
|
// pass through the list from the auth/core lib
|
|
156
163
|
exports.ObjectStoreBuckets = ObjectStoreBuckets
|
|
@@ -17,6 +17,11 @@ export interface FieldSchema {
|
|
|
17
17
|
autocolumn?: boolean
|
|
18
18
|
throughFrom?: string
|
|
19
19
|
throughTo?: string
|
|
20
|
+
main?: boolean
|
|
21
|
+
meta?: {
|
|
22
|
+
toTable: string
|
|
23
|
+
toKey: string
|
|
24
|
+
}
|
|
20
25
|
constraints?: {
|
|
21
26
|
type?: string
|
|
22
27
|
email?: boolean
|
|
@@ -36,11 +41,12 @@ export interface TableSchema {
|
|
|
36
41
|
export interface Table extends Base {
|
|
37
42
|
type?: string
|
|
38
43
|
views?: {}
|
|
39
|
-
name
|
|
44
|
+
name: string
|
|
40
45
|
primary?: string[]
|
|
41
46
|
schema: TableSchema
|
|
42
47
|
primaryDisplay?: string
|
|
43
48
|
sourceId?: string
|
|
49
|
+
constrained?: string[]
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
export interface Row extends Base {
|
|
@@ -5,6 +5,9 @@ export enum Operation {
|
|
|
5
5
|
READ = "READ",
|
|
6
6
|
UPDATE = "UPDATE",
|
|
7
7
|
DELETE = "DELETE",
|
|
8
|
+
CREATE_TABLE = "CREATE_TABLE",
|
|
9
|
+
UPDATE_TABLE = "UPDATE_TABLE",
|
|
10
|
+
DELETE_TABLE = "DELETE_TABLE",
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
export enum SortDirection {
|
|
@@ -142,8 +145,10 @@ export interface QueryJson {
|
|
|
142
145
|
sort?: SortJson
|
|
143
146
|
paginate?: PaginationJson
|
|
144
147
|
body?: object
|
|
148
|
+
table?: Table
|
|
145
149
|
meta?: {
|
|
146
150
|
table?: Table
|
|
151
|
+
tables?: Record<string, Table>
|
|
147
152
|
}
|
|
148
153
|
extra?: {
|
|
149
154
|
idFilter?: SearchFilters
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import { Knex, knex } from "knex"
|
|
2
|
-
const BASE_LIMIT = 5000
|
|
3
2
|
import {
|
|
3
|
+
Operation,
|
|
4
4
|
QueryJson,
|
|
5
|
-
SearchFilters,
|
|
6
5
|
QueryOptions,
|
|
7
|
-
SortDirection,
|
|
8
|
-
Operation,
|
|
9
6
|
RelationshipsJson,
|
|
7
|
+
SearchFilters,
|
|
8
|
+
SortDirection,
|
|
10
9
|
} from "../../definitions/datasource"
|
|
11
10
|
import { isIsoDateString } from "../utils"
|
|
11
|
+
import SqlTableQueryBuilder from "./sqlTable"
|
|
12
|
+
|
|
13
|
+
const BASE_LIMIT = 5000
|
|
12
14
|
|
|
13
15
|
type KnexQuery = Knex.QueryBuilder | Knex
|
|
14
16
|
|
|
15
17
|
function parseBody(body: any) {
|
|
16
18
|
for (let [key, value] of Object.entries(body)) {
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
body[key] = JSON.stringify(value)
|
|
21
|
+
}
|
|
17
22
|
if (typeof value !== "string") {
|
|
18
23
|
continue
|
|
19
24
|
}
|
|
@@ -243,23 +248,14 @@ function buildDelete(
|
|
|
243
248
|
}
|
|
244
249
|
}
|
|
245
250
|
|
|
246
|
-
class SqlQueryBuilder {
|
|
247
|
-
private readonly sqlClient: string
|
|
251
|
+
class SqlQueryBuilder extends SqlTableQueryBuilder {
|
|
248
252
|
private readonly limit: number
|
|
249
253
|
// pass through client to get flavour of SQL
|
|
250
254
|
constructor(client: string, limit: number = BASE_LIMIT) {
|
|
251
|
-
|
|
255
|
+
super(client)
|
|
252
256
|
this.limit = limit
|
|
253
257
|
}
|
|
254
258
|
|
|
255
|
-
/**
|
|
256
|
-
* @param json the input JSON structure from which an SQL query will be built.
|
|
257
|
-
* @return {string} the operation that was found in the JSON.
|
|
258
|
-
*/
|
|
259
|
-
_operation(json: QueryJson): Operation {
|
|
260
|
-
return json.endpoint.operation
|
|
261
|
-
}
|
|
262
|
-
|
|
263
259
|
/**
|
|
264
260
|
* @param json The JSON query DSL which is to be converted to SQL.
|
|
265
261
|
* @param opts extra options which are to be passed into the query builder, e.g. disableReturning
|
|
@@ -267,7 +263,8 @@ class SqlQueryBuilder {
|
|
|
267
263
|
* @return {{ sql: string, bindings: object }} the query ready to be passed to the driver.
|
|
268
264
|
*/
|
|
269
265
|
_query(json: QueryJson, opts: QueryOptions = {}) {
|
|
270
|
-
const
|
|
266
|
+
const sqlClient = this.getSqlClient()
|
|
267
|
+
const client = knex({ client: sqlClient })
|
|
271
268
|
let query
|
|
272
269
|
switch (this._operation(json)) {
|
|
273
270
|
case Operation.CREATE:
|
|
@@ -282,6 +279,8 @@ class SqlQueryBuilder {
|
|
|
282
279
|
case Operation.DELETE:
|
|
283
280
|
query = buildDelete(client, json, opts)
|
|
284
281
|
break
|
|
282
|
+
case Operation.CREATE_TABLE: case Operation.UPDATE_TABLE: case Operation.DELETE_TABLE:
|
|
283
|
+
return this._tableQuery(json)
|
|
285
284
|
default:
|
|
286
285
|
throw `Operation type is not supported by SQL query builder`
|
|
287
286
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Knex, knex } from "knex"
|
|
2
|
+
import { Table } from "../../definitions/common"
|
|
3
|
+
import { Operation, QueryJson } from "../../definitions/datasource"
|
|
4
|
+
import { breakExternalTableId } from "../utils"
|
|
5
|
+
import SchemaBuilder = Knex.SchemaBuilder
|
|
6
|
+
import CreateTableBuilder = Knex.CreateTableBuilder
|
|
7
|
+
const { FieldTypes, RelationshipTypes } = require("../../constants")
|
|
8
|
+
|
|
9
|
+
function generateSchema(schema: CreateTableBuilder, table: Table, tables: Record<string, Table>, oldTable: null | Table = null) {
|
|
10
|
+
let primaryKey = table && table.primary ? table.primary[0] : null
|
|
11
|
+
const columns = Object.values(table.schema)
|
|
12
|
+
// all columns in a junction table will be meta
|
|
13
|
+
let metaCols = columns.filter(col => col.meta)
|
|
14
|
+
let isJunction = metaCols.length === columns.length
|
|
15
|
+
// can't change primary once its set for now
|
|
16
|
+
if (primaryKey && !oldTable && !isJunction) {
|
|
17
|
+
schema.increments(primaryKey).primary()
|
|
18
|
+
} else if (!oldTable && isJunction) {
|
|
19
|
+
schema.primary(metaCols.map(col => col.name))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
// check if any columns need added
|
|
24
|
+
const foreignKeys = Object.values(table.schema).map(col => col.foreignKey)
|
|
25
|
+
for (let [key, column] of Object.entries(table.schema)) {
|
|
26
|
+
// skip things that are already correct
|
|
27
|
+
const oldColumn = oldTable ? oldTable.schema[key] : null
|
|
28
|
+
if ((oldColumn && oldColumn.type === column.type) || (primaryKey === key && !isJunction)) {
|
|
29
|
+
continue
|
|
30
|
+
}
|
|
31
|
+
switch (column.type) {
|
|
32
|
+
case FieldTypes.STRING: case FieldTypes.OPTIONS: case FieldTypes.LONGFORM:
|
|
33
|
+
schema.string(key)
|
|
34
|
+
break
|
|
35
|
+
case FieldTypes.NUMBER:
|
|
36
|
+
// if meta is specified then this is a junction table entry
|
|
37
|
+
if (column.meta && column.meta.toKey && column.meta.toTable) {
|
|
38
|
+
const { toKey, toTable } = column.meta
|
|
39
|
+
schema.integer(key).unsigned()
|
|
40
|
+
schema.foreign(key).references(`${toTable}.${toKey}`)
|
|
41
|
+
} else if (foreignKeys.indexOf(key) === -1) {
|
|
42
|
+
schema.float(key)
|
|
43
|
+
}
|
|
44
|
+
break
|
|
45
|
+
case FieldTypes.BOOLEAN:
|
|
46
|
+
schema.boolean(key)
|
|
47
|
+
break
|
|
48
|
+
case FieldTypes.DATETIME:
|
|
49
|
+
schema.datetime(key)
|
|
50
|
+
break
|
|
51
|
+
case FieldTypes.ARRAY:
|
|
52
|
+
schema.json(key)
|
|
53
|
+
break
|
|
54
|
+
case FieldTypes.LINK:
|
|
55
|
+
// this side of the relationship doesn't need any SQL work
|
|
56
|
+
if (
|
|
57
|
+
column.relationshipType !== RelationshipTypes.MANY_TO_ONE &&
|
|
58
|
+
column.relationshipType !== RelationshipTypes.MANY_TO_MANY
|
|
59
|
+
) {
|
|
60
|
+
if (!column.foreignKey || !column.tableId) {
|
|
61
|
+
throw "Invalid relationship schema"
|
|
62
|
+
}
|
|
63
|
+
const { tableName } = breakExternalTableId(column.tableId)
|
|
64
|
+
// @ts-ignore
|
|
65
|
+
const relatedTable = tables[tableName]
|
|
66
|
+
if (!relatedTable) {
|
|
67
|
+
throw "Referenced table doesn't exist"
|
|
68
|
+
}
|
|
69
|
+
schema.integer(column.foreignKey).unsigned()
|
|
70
|
+
schema.foreign(column.foreignKey).references(`${tableName}.${relatedTable.primary[0]}`)
|
|
71
|
+
}
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// need to check if any columns have been deleted
|
|
77
|
+
if (oldTable) {
|
|
78
|
+
const deletedColumns = Object.entries(oldTable.schema)
|
|
79
|
+
.filter(([key, schema]) => schema.type !== FieldTypes.LINK && table.schema[key] == null)
|
|
80
|
+
.map(([key]) => key)
|
|
81
|
+
deletedColumns.forEach(key => {
|
|
82
|
+
if (oldTable.constrained && oldTable.constrained.indexOf(key) !== -1) {
|
|
83
|
+
schema.dropForeign(key)
|
|
84
|
+
}
|
|
85
|
+
schema.dropColumn(key)
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return schema
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildCreateTable(
|
|
93
|
+
knex: Knex,
|
|
94
|
+
table: Table,
|
|
95
|
+
tables: Record<string, Table>,
|
|
96
|
+
): SchemaBuilder {
|
|
97
|
+
return knex.schema.createTable(table.name, schema => {
|
|
98
|
+
generateSchema(schema, table, tables)
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function buildUpdateTable(
|
|
103
|
+
knex: Knex,
|
|
104
|
+
table: Table,
|
|
105
|
+
tables: Record<string, Table>,
|
|
106
|
+
oldTable: Table,
|
|
107
|
+
): SchemaBuilder {
|
|
108
|
+
return knex.schema.alterTable(table.name, schema => {
|
|
109
|
+
generateSchema(schema, table, tables, oldTable)
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function buildDeleteTable(
|
|
114
|
+
knex: Knex,
|
|
115
|
+
table: Table,
|
|
116
|
+
): SchemaBuilder {
|
|
117
|
+
return knex.schema.dropTable(table.name)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
class SqlTableQueryBuilder {
|
|
121
|
+
private readonly sqlClient: string
|
|
122
|
+
|
|
123
|
+
// pass through client to get flavour of SQL
|
|
124
|
+
constructor(client: string) {
|
|
125
|
+
this.sqlClient = client
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
getSqlClient(): string {
|
|
129
|
+
return this.sqlClient
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param json the input JSON structure from which an SQL query will be built.
|
|
134
|
+
* @return {string} the operation that was found in the JSON.
|
|
135
|
+
*/
|
|
136
|
+
_operation(json: QueryJson): Operation {
|
|
137
|
+
return json.endpoint.operation
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
_tableQuery(json: QueryJson): any {
|
|
141
|
+
const client = knex({ client: this.sqlClient })
|
|
142
|
+
let query
|
|
143
|
+
if (!json.table || !json.meta || !json.meta.tables) {
|
|
144
|
+
throw "Cannot execute without table being specified"
|
|
145
|
+
}
|
|
146
|
+
switch (this._operation(json)) {
|
|
147
|
+
case Operation.CREATE_TABLE:
|
|
148
|
+
query = buildCreateTable(client, json.table, json.meta.tables)
|
|
149
|
+
break
|
|
150
|
+
case Operation.UPDATE_TABLE:
|
|
151
|
+
if (!json.meta || !json.meta.table) {
|
|
152
|
+
throw "Must specify old table for update"
|
|
153
|
+
}
|
|
154
|
+
query = buildUpdateTable(client, json.table, json.meta.tables, json.meta.table)
|
|
155
|
+
break
|
|
156
|
+
case Operation.DELETE_TABLE:
|
|
157
|
+
query = buildDeleteTable(client, json.table)
|
|
158
|
+
break
|
|
159
|
+
default:
|
|
160
|
+
throw "Table operation is of unknown type"
|
|
161
|
+
}
|
|
162
|
+
return query.toSQL()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export default SqlTableQueryBuilder
|
|
167
|
+
module.exports = SqlTableQueryBuilder
|