@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
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/server",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.176-alpha.2",
|
|
5
5
|
"description": "Budibase Web Server",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"repository": {
|
|
@@ -68,9 +68,9 @@
|
|
|
68
68
|
"author": "Budibase",
|
|
69
69
|
"license": "AGPL-3.0-or-later",
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@budibase/auth": "^0.9.
|
|
72
|
-
"@budibase/client": "^0.9.
|
|
73
|
-
"@budibase/string-templates": "^0.9.
|
|
71
|
+
"@budibase/auth": "^0.9.176-alpha.2",
|
|
72
|
+
"@budibase/client": "^0.9.176-alpha.2",
|
|
73
|
+
"@budibase/string-templates": "^0.9.176-alpha.2",
|
|
74
74
|
"@elastic/elasticsearch": "7.10.0",
|
|
75
75
|
"@koa/router": "8.0.0",
|
|
76
76
|
"@sendgrid/mail": "7.1.1",
|
|
@@ -149,5 +149,5 @@
|
|
|
149
149
|
"typescript": "^4.3.5",
|
|
150
150
|
"update-dotenv": "^1.1.1"
|
|
151
151
|
},
|
|
152
|
-
"gitHead": "
|
|
152
|
+
"gitHead": "f7b155c7dddf0ed83341e9b0515f188eacd85b59"
|
|
153
153
|
}
|
|
@@ -7,8 +7,9 @@ const {
|
|
|
7
7
|
BudibaseInternalDB,
|
|
8
8
|
getTableParams,
|
|
9
9
|
} = require("../../db/utils")
|
|
10
|
+
const { BuildSchemaErrors } = require("../../constants")
|
|
10
11
|
const { integrations } = require("../../integrations")
|
|
11
|
-
const {
|
|
12
|
+
const { getDatasourceAndQuery } = require("./row/utils")
|
|
12
13
|
|
|
13
14
|
exports.fetch = async function (ctx) {
|
|
14
15
|
const database = new CouchDB(ctx.appId)
|
|
@@ -43,13 +44,17 @@ exports.buildSchemaFromDb = async function (ctx) {
|
|
|
43
44
|
const db = new CouchDB(ctx.appId)
|
|
44
45
|
const datasource = await db.get(ctx.params.datasourceId)
|
|
45
46
|
|
|
46
|
-
const tables = await buildSchemaHelper(datasource)
|
|
47
|
+
const { tables, error } = await buildSchemaHelper(datasource)
|
|
47
48
|
datasource.entities = tables
|
|
48
49
|
|
|
49
|
-
const
|
|
50
|
-
datasource._rev =
|
|
50
|
+
const dbResp = await db.put(datasource)
|
|
51
|
+
datasource._rev = dbResp.rev
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
const response = { datasource }
|
|
54
|
+
if (error) {
|
|
55
|
+
response.error = error
|
|
56
|
+
}
|
|
57
|
+
ctx.body = response
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
exports.update = async function (ctx) {
|
|
@@ -71,7 +76,7 @@ exports.update = async function (ctx) {
|
|
|
71
76
|
|
|
72
77
|
ctx.status = 200
|
|
73
78
|
ctx.message = "Datasource saved successfully."
|
|
74
|
-
ctx.body = datasource
|
|
79
|
+
ctx.body = { datasource }
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
exports.save = async function (ctx) {
|
|
@@ -85,13 +90,15 @@ exports.save = async function (ctx) {
|
|
|
85
90
|
...ctx.request.body.datasource,
|
|
86
91
|
}
|
|
87
92
|
|
|
93
|
+
let schemaError = null
|
|
88
94
|
if (fetchSchema) {
|
|
89
|
-
|
|
95
|
+
const { tables, error } = await buildSchemaHelper(datasource)
|
|
96
|
+
schemaError = error
|
|
90
97
|
datasource.entities = tables
|
|
91
98
|
}
|
|
92
99
|
|
|
93
|
-
const
|
|
94
|
-
datasource._rev =
|
|
100
|
+
const dbResp = await db.put(datasource)
|
|
101
|
+
datasource._rev = dbResp.rev
|
|
95
102
|
|
|
96
103
|
// Drain connection pools when configuration is changed
|
|
97
104
|
if (datasource.source) {
|
|
@@ -101,9 +108,11 @@ exports.save = async function (ctx) {
|
|
|
101
108
|
}
|
|
102
109
|
}
|
|
103
110
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
const response = { datasource }
|
|
112
|
+
if (schemaError) {
|
|
113
|
+
response.error = schemaError
|
|
114
|
+
}
|
|
115
|
+
ctx.body = response
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
exports.destroy = async function (ctx) {
|
|
@@ -129,7 +138,7 @@ exports.find = async function (ctx) {
|
|
|
129
138
|
exports.query = async function (ctx) {
|
|
130
139
|
const queryJson = ctx.request.body
|
|
131
140
|
try {
|
|
132
|
-
ctx.body = await
|
|
141
|
+
ctx.body = await getDatasourceAndQuery(ctx.appId, queryJson)
|
|
133
142
|
} catch (err) {
|
|
134
143
|
ctx.throw(400, err)
|
|
135
144
|
}
|
|
@@ -143,5 +152,28 @@ const buildSchemaHelper = async datasource => {
|
|
|
143
152
|
await connector.buildSchema(datasource._id, datasource.entities)
|
|
144
153
|
datasource.entities = connector.tables
|
|
145
154
|
|
|
146
|
-
|
|
155
|
+
// make sure they all have a display name selected
|
|
156
|
+
for (let entity of Object.values(datasource.entities)) {
|
|
157
|
+
if (entity.primaryDisplay) {
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
const notAutoColumn = Object.values(entity.schema).find(
|
|
161
|
+
schema => !schema.autocolumn
|
|
162
|
+
)
|
|
163
|
+
if (notAutoColumn) {
|
|
164
|
+
entity.primaryDisplay = notAutoColumn.name
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const errors = connector.schemaErrors
|
|
169
|
+
let error = null
|
|
170
|
+
if (errors && Object.keys(errors).length > 0) {
|
|
171
|
+
const noKeyTables = Object.entries(errors)
|
|
172
|
+
.filter(entry => entry[1] === BuildSchemaErrors.NO_KEY)
|
|
173
|
+
.map(([name]) => name)
|
|
174
|
+
error = `No primary key constraint found for the following: ${noKeyTables.join(
|
|
175
|
+
", "
|
|
176
|
+
)}`
|
|
177
|
+
}
|
|
178
|
+
return { tables: connector.tables, error }
|
|
147
179
|
}
|
|
@@ -101,7 +101,9 @@ async function enrichQueryFields(fields, parameters = {}) {
|
|
|
101
101
|
enrichedQuery[key] = await enrichQueryFields(fields[key], parameters)
|
|
102
102
|
} else if (typeof fields[key] === "string") {
|
|
103
103
|
// enrich string value as normal
|
|
104
|
-
enrichedQuery[key] = await processString(fields[key], parameters
|
|
104
|
+
enrichedQuery[key] = await processString(fields[key], parameters, {
|
|
105
|
+
noHelpers: true,
|
|
106
|
+
})
|
|
105
107
|
} else {
|
|
106
108
|
enrichedQuery[key] = fields[key]
|
|
107
109
|
}
|
|
@@ -36,7 +36,7 @@ interface RunConfig {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
module External {
|
|
39
|
-
const {
|
|
39
|
+
const { getDatasourceAndQuery } = require("./utils")
|
|
40
40
|
const {
|
|
41
41
|
DataSourceOperation,
|
|
42
42
|
FieldTypes,
|
|
@@ -46,6 +46,7 @@ module External {
|
|
|
46
46
|
const { processObjectSync } = require("@budibase/string-templates")
|
|
47
47
|
const { cloneDeep } = require("lodash/fp")
|
|
48
48
|
const CouchDB = require("../../../db")
|
|
49
|
+
const { processFormulas } = require("../../../utilities/rowProcessor/utils")
|
|
49
50
|
|
|
50
51
|
function buildFilters(
|
|
51
52
|
id: string | undefined,
|
|
@@ -225,7 +226,7 @@ module External {
|
|
|
225
226
|
manyRelationships: ManyRelationship[] = []
|
|
226
227
|
for (let [key, field] of Object.entries(table.schema)) {
|
|
227
228
|
// if set already, or not set just skip it
|
|
228
|
-
if (
|
|
229
|
+
if (row[key] == null || newRow[key] || field.autocolumn || field.type === FieldTypes.FORMULA) {
|
|
229
230
|
continue
|
|
230
231
|
}
|
|
231
232
|
// if its an empty string then it means return the column to null (if possible)
|
|
@@ -361,7 +362,7 @@ module External {
|
|
|
361
362
|
relationships
|
|
362
363
|
)
|
|
363
364
|
}
|
|
364
|
-
return Object.values(finalRows)
|
|
365
|
+
return processFormulas(table, Object.values(finalRows))
|
|
365
366
|
}
|
|
366
367
|
|
|
367
368
|
/**
|
|
@@ -428,7 +429,7 @@ module External {
|
|
|
428
429
|
const tableId = isMany ? field.through : field.tableId
|
|
429
430
|
const manyKey = field.throughFrom || primaryKey
|
|
430
431
|
const fieldName = isMany ? manyKey : field.fieldName
|
|
431
|
-
const response = await
|
|
432
|
+
const response = await getDatasourceAndQuery(this.appId, {
|
|
432
433
|
endpoint: getEndpoint(tableId, DataSourceOperation.READ),
|
|
433
434
|
filters: {
|
|
434
435
|
equal: {
|
|
@@ -479,7 +480,7 @@ module External {
|
|
|
479
480
|
: DataSourceOperation.CREATE
|
|
480
481
|
if (!found) {
|
|
481
482
|
promises.push(
|
|
482
|
-
|
|
483
|
+
getDatasourceAndQuery(appId, {
|
|
483
484
|
endpoint: getEndpoint(tableId, operation),
|
|
484
485
|
// if we're doing many relationships then we're writing, only one response
|
|
485
486
|
body,
|
|
@@ -509,7 +510,7 @@ module External {
|
|
|
509
510
|
: DataSourceOperation.UPDATE
|
|
510
511
|
const body = isMany ? null : { [colName]: null }
|
|
511
512
|
promises.push(
|
|
512
|
-
|
|
513
|
+
getDatasourceAndQuery(this.appId, {
|
|
513
514
|
endpoint: getEndpoint(tableId, op),
|
|
514
515
|
body,
|
|
515
516
|
filters,
|
|
@@ -532,16 +533,17 @@ module External {
|
|
|
532
533
|
table: Table,
|
|
533
534
|
includeRelations: IncludeRelationships = IncludeRelationships.INCLUDE
|
|
534
535
|
) {
|
|
535
|
-
function
|
|
536
|
+
function extractRealFields(table: Table, existing: string[] = []) {
|
|
536
537
|
return Object.entries(table.schema)
|
|
537
538
|
.filter(
|
|
538
539
|
column =>
|
|
539
540
|
column[1].type !== FieldTypes.LINK &&
|
|
541
|
+
column[1].type !== FieldTypes.FORMULA &&
|
|
540
542
|
!existing.find((field: string) => field === column[0])
|
|
541
543
|
)
|
|
542
544
|
.map(column => `${table.name}.${column[0]}`)
|
|
543
545
|
}
|
|
544
|
-
let fields =
|
|
546
|
+
let fields = extractRealFields(table)
|
|
545
547
|
for (let field of Object.values(table.schema)) {
|
|
546
548
|
if (field.type !== FieldTypes.LINK || !includeRelations) {
|
|
547
549
|
continue
|
|
@@ -549,7 +551,7 @@ module External {
|
|
|
549
551
|
const { tableName: linkTableName } = breakExternalTableId(field.tableId)
|
|
550
552
|
const linkTable = this.tables[linkTableName]
|
|
551
553
|
if (linkTable) {
|
|
552
|
-
const linkedFields =
|
|
554
|
+
const linkedFields = extractRealFields(linkTable, fields)
|
|
553
555
|
fields = fields.concat(linkedFields)
|
|
554
556
|
}
|
|
555
557
|
}
|
|
@@ -609,7 +611,7 @@ module External {
|
|
|
609
611
|
},
|
|
610
612
|
}
|
|
611
613
|
// can't really use response right now
|
|
612
|
-
const response = await
|
|
614
|
+
const response = await getDatasourceAndQuery(appId, json)
|
|
613
615
|
// handle many to many relationships now if we know the ID (could be auto increment)
|
|
614
616
|
if (
|
|
615
617
|
operation !== DataSourceOperation.READ &&
|
|
@@ -4,8 +4,8 @@ const CouchDB = require("../../../db")
|
|
|
4
4
|
const { InternalTables } = require("../../../db/utils")
|
|
5
5
|
const userController = require("../user")
|
|
6
6
|
const { FieldTypes } = require("../../../constants")
|
|
7
|
-
const { integrations } = require("../../../integrations")
|
|
8
7
|
const { processStringSync } = require("@budibase/string-templates")
|
|
8
|
+
const { makeExternalQuery } = require("../../../integrations/base/utils")
|
|
9
9
|
|
|
10
10
|
validateJs.extend(validateJs.validators.datetime, {
|
|
11
11
|
parse: function (value) {
|
|
@@ -17,18 +17,11 @@ validateJs.extend(validateJs.validators.datetime, {
|
|
|
17
17
|
},
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
-
exports.
|
|
20
|
+
exports.getDatasourceAndQuery = async (appId, json) => {
|
|
21
21
|
const datasourceId = json.endpoint.datasourceId
|
|
22
22
|
const db = new CouchDB(appId)
|
|
23
23
|
const datasource = await db.get(datasourceId)
|
|
24
|
-
|
|
25
|
-
// query is the opinionated function
|
|
26
|
-
if (Integration.prototype.query) {
|
|
27
|
-
const integration = new Integration(datasource.config)
|
|
28
|
-
return integration.query(json)
|
|
29
|
-
} else {
|
|
30
|
-
throw "Datasource does not support query."
|
|
31
|
-
}
|
|
24
|
+
return makeExternalQuery(datasource, json)
|
|
32
25
|
}
|
|
33
26
|
|
|
34
27
|
exports.findRow = async (ctx, db, tableId, rowId) => {
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
const CouchDB = require("../../../db")
|
|
2
|
+
const {
|
|
3
|
+
buildExternalTableId,
|
|
4
|
+
breakExternalTableId,
|
|
5
|
+
} = require("../../../integrations/utils")
|
|
6
|
+
const {
|
|
7
|
+
getTable,
|
|
8
|
+
generateForeignKey,
|
|
9
|
+
generateJunctionTableName,
|
|
10
|
+
foreignKeyStructure,
|
|
11
|
+
} = require("./utils")
|
|
12
|
+
const {
|
|
13
|
+
DataSourceOperation,
|
|
14
|
+
FieldTypes,
|
|
15
|
+
RelationshipTypes,
|
|
16
|
+
} = require("../../../constants")
|
|
17
|
+
const { makeExternalQuery } = require("../../../integrations/base/utils")
|
|
18
|
+
const { cloneDeep } = require("lodash/fp")
|
|
19
|
+
|
|
20
|
+
async function makeTableRequest(
|
|
21
|
+
datasource,
|
|
22
|
+
operation,
|
|
23
|
+
table,
|
|
24
|
+
tables,
|
|
25
|
+
oldTable = null
|
|
26
|
+
) {
|
|
27
|
+
const json = {
|
|
28
|
+
endpoint: {
|
|
29
|
+
datasourceId: datasource._id,
|
|
30
|
+
entityId: table._id,
|
|
31
|
+
operation,
|
|
32
|
+
},
|
|
33
|
+
meta: {
|
|
34
|
+
tables,
|
|
35
|
+
},
|
|
36
|
+
table,
|
|
37
|
+
}
|
|
38
|
+
if (oldTable) {
|
|
39
|
+
json.meta.table = oldTable
|
|
40
|
+
}
|
|
41
|
+
return makeExternalQuery(datasource, json)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function cleanupRelationships(table, tables, oldTable = null) {
|
|
45
|
+
const tableToIterate = oldTable ? oldTable : table
|
|
46
|
+
// clean up relationships in couch table schemas
|
|
47
|
+
for (let [key, schema] of Object.entries(tableToIterate.schema)) {
|
|
48
|
+
if (
|
|
49
|
+
schema.type === FieldTypes.LINK &&
|
|
50
|
+
(!oldTable || table.schema[key] == null)
|
|
51
|
+
) {
|
|
52
|
+
const relatedTable = Object.values(tables).find(
|
|
53
|
+
table => table._id === schema.tableId
|
|
54
|
+
)
|
|
55
|
+
const foreignKey = schema.foreignKey
|
|
56
|
+
if (!relatedTable || !foreignKey) {
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
for (let [relatedKey, relatedSchema] of Object.entries(
|
|
60
|
+
relatedTable.schema
|
|
61
|
+
)) {
|
|
62
|
+
if (
|
|
63
|
+
relatedSchema.type === FieldTypes.LINK &&
|
|
64
|
+
relatedSchema.fieldName === foreignKey
|
|
65
|
+
) {
|
|
66
|
+
delete relatedTable.schema[relatedKey]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getDatasourceId(table) {
|
|
74
|
+
if (!table) {
|
|
75
|
+
throw "No table supplied"
|
|
76
|
+
}
|
|
77
|
+
if (table.sourceId) {
|
|
78
|
+
return table.sourceId
|
|
79
|
+
}
|
|
80
|
+
return breakExternalTableId(table._id).datasourceId
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function otherRelationshipType(type) {
|
|
84
|
+
if (type === RelationshipTypes.MANY_TO_MANY) {
|
|
85
|
+
return RelationshipTypes.MANY_TO_MANY
|
|
86
|
+
}
|
|
87
|
+
return type === RelationshipTypes.ONE_TO_MANY
|
|
88
|
+
? RelationshipTypes.MANY_TO_ONE
|
|
89
|
+
: RelationshipTypes.ONE_TO_MANY
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function generateManyLinkSchema(datasource, column, table, relatedTable) {
|
|
93
|
+
const primary = table.name + table.primary[0]
|
|
94
|
+
const relatedPrimary = relatedTable.name + relatedTable.primary[0]
|
|
95
|
+
const jcTblName = generateJunctionTableName(column, table, relatedTable)
|
|
96
|
+
// first create the new table
|
|
97
|
+
const junctionTable = {
|
|
98
|
+
_id: buildExternalTableId(datasource._id, jcTblName),
|
|
99
|
+
name: jcTblName,
|
|
100
|
+
primary: [primary, relatedPrimary],
|
|
101
|
+
constrained: [primary, relatedPrimary],
|
|
102
|
+
schema: {
|
|
103
|
+
[primary]: foreignKeyStructure(primary, {
|
|
104
|
+
toTable: table.name,
|
|
105
|
+
toKey: table.primary[0],
|
|
106
|
+
}),
|
|
107
|
+
[relatedPrimary]: foreignKeyStructure(relatedPrimary, {
|
|
108
|
+
toTable: relatedTable.name,
|
|
109
|
+
toKey: relatedTable.primary[0],
|
|
110
|
+
}),
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
column.through = junctionTable._id
|
|
114
|
+
column.throughFrom = primary
|
|
115
|
+
column.throughTo = relatedPrimary
|
|
116
|
+
column.fieldName = relatedPrimary
|
|
117
|
+
return junctionTable
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function generateLinkSchema(column, table, relatedTable, type) {
|
|
121
|
+
const isOneSide = type === RelationshipTypes.ONE_TO_MANY
|
|
122
|
+
const primary = isOneSide ? relatedTable.primary[0] : table.primary[0]
|
|
123
|
+
// generate a foreign key
|
|
124
|
+
const foreignKey = generateForeignKey(column, relatedTable)
|
|
125
|
+
column.relationshipType = type
|
|
126
|
+
column.foreignKey = isOneSide ? foreignKey : primary
|
|
127
|
+
column.fieldName = isOneSide ? primary : foreignKey
|
|
128
|
+
return foreignKey
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function generateRelatedSchema(linkColumn, table, relatedTable, columnName) {
|
|
132
|
+
// generate column for other table
|
|
133
|
+
const relatedSchema = cloneDeep(linkColumn)
|
|
134
|
+
// swap them from the main link
|
|
135
|
+
if (linkColumn.foreignKey) {
|
|
136
|
+
relatedSchema.fieldName = linkColumn.foreignKey
|
|
137
|
+
relatedSchema.foreignKey = linkColumn.fieldName
|
|
138
|
+
}
|
|
139
|
+
// is many to many
|
|
140
|
+
else {
|
|
141
|
+
// don't need to copy through, already got it
|
|
142
|
+
relatedSchema.fieldName = linkColumn.throughFrom
|
|
143
|
+
relatedSchema.throughTo = linkColumn.throughFrom
|
|
144
|
+
relatedSchema.throughFrom = linkColumn.throughTo
|
|
145
|
+
}
|
|
146
|
+
relatedSchema.relationshipType = otherRelationshipType(
|
|
147
|
+
linkColumn.relationshipType
|
|
148
|
+
)
|
|
149
|
+
relatedSchema.tableId = relatedTable._id
|
|
150
|
+
relatedSchema.name = columnName
|
|
151
|
+
table.schema[columnName] = relatedSchema
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function isRelationshipSetup(column) {
|
|
155
|
+
return column.foreignKey || column.through
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
exports.save = async function (ctx) {
|
|
159
|
+
const appId = ctx.appId
|
|
160
|
+
const table = ctx.request.body
|
|
161
|
+
// can't do this
|
|
162
|
+
delete table.dataImport
|
|
163
|
+
const datasourceId = getDatasourceId(ctx.request.body)
|
|
164
|
+
let tableToSave = {
|
|
165
|
+
type: "table",
|
|
166
|
+
_id: buildExternalTableId(datasourceId, table.name),
|
|
167
|
+
...table,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let oldTable
|
|
171
|
+
if (ctx.request.body && ctx.request.body._id) {
|
|
172
|
+
oldTable = await getTable(appId, ctx.request.body._id)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const db = new CouchDB(appId)
|
|
176
|
+
const datasource = await db.get(datasourceId)
|
|
177
|
+
const oldTables = cloneDeep(datasource.entities)
|
|
178
|
+
const tables = datasource.entities
|
|
179
|
+
|
|
180
|
+
const extraTablesToUpdate = []
|
|
181
|
+
|
|
182
|
+
// check if relations need setup
|
|
183
|
+
for (let schema of Object.values(tableToSave.schema)) {
|
|
184
|
+
if (schema.type !== FieldTypes.LINK || isRelationshipSetup(schema)) {
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
const relatedTable = Object.values(tables).find(
|
|
188
|
+
table => table._id === schema.tableId
|
|
189
|
+
)
|
|
190
|
+
const relatedColumnName = schema.fieldName
|
|
191
|
+
const relationType = schema.relationshipType
|
|
192
|
+
if (relationType === RelationshipTypes.MANY_TO_MANY) {
|
|
193
|
+
const junctionTable = generateManyLinkSchema(
|
|
194
|
+
datasource,
|
|
195
|
+
schema,
|
|
196
|
+
table,
|
|
197
|
+
relatedTable
|
|
198
|
+
)
|
|
199
|
+
if (tables[junctionTable.name]) {
|
|
200
|
+
throw "Junction table already exists, cannot create another relationship."
|
|
201
|
+
}
|
|
202
|
+
tables[junctionTable.name] = junctionTable
|
|
203
|
+
extraTablesToUpdate.push(junctionTable)
|
|
204
|
+
} else {
|
|
205
|
+
const fkTable =
|
|
206
|
+
relationType === RelationshipTypes.ONE_TO_MANY ? table : relatedTable
|
|
207
|
+
const foreignKey = generateLinkSchema(
|
|
208
|
+
schema,
|
|
209
|
+
table,
|
|
210
|
+
relatedTable,
|
|
211
|
+
relationType
|
|
212
|
+
)
|
|
213
|
+
fkTable.schema[foreignKey] = foreignKeyStructure(foreignKey)
|
|
214
|
+
if (fkTable.constrained == null) {
|
|
215
|
+
fkTable.constrained = []
|
|
216
|
+
}
|
|
217
|
+
if (fkTable.constrained.indexOf(foreignKey) === -1) {
|
|
218
|
+
fkTable.constrained.push(foreignKey)
|
|
219
|
+
}
|
|
220
|
+
// foreign key is in other table, need to save it to external
|
|
221
|
+
if (fkTable._id !== table._id) {
|
|
222
|
+
extraTablesToUpdate.push(fkTable)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
generateRelatedSchema(schema, relatedTable, table, relatedColumnName)
|
|
226
|
+
schema.main = true
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
cleanupRelationships(tableToSave, tables, oldTable)
|
|
230
|
+
|
|
231
|
+
const operation = oldTable
|
|
232
|
+
? DataSourceOperation.UPDATE_TABLE
|
|
233
|
+
: DataSourceOperation.CREATE_TABLE
|
|
234
|
+
await makeTableRequest(datasource, operation, tableToSave, tables, oldTable)
|
|
235
|
+
// update any extra tables (like foreign keys in other tables)
|
|
236
|
+
for (let extraTable of extraTablesToUpdate) {
|
|
237
|
+
const oldExtraTable = oldTables[extraTable.name]
|
|
238
|
+
let op = oldExtraTable
|
|
239
|
+
? DataSourceOperation.UPDATE_TABLE
|
|
240
|
+
: DataSourceOperation.CREATE_TABLE
|
|
241
|
+
await makeTableRequest(datasource, op, extraTable, tables, oldExtraTable)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// make sure the constrained list, all still exist
|
|
245
|
+
if (Array.isArray(tableToSave.constrained)) {
|
|
246
|
+
tableToSave.constrained = tableToSave.constrained.filter(constraint =>
|
|
247
|
+
Object.keys(tableToSave.schema).includes(constraint)
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// store it into couch now for budibase reference
|
|
252
|
+
datasource.entities[tableToSave.name] = tableToSave
|
|
253
|
+
await db.put(datasource)
|
|
254
|
+
|
|
255
|
+
return tableToSave
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
exports.destroy = async function (ctx) {
|
|
259
|
+
const appId = ctx.appId
|
|
260
|
+
const tableToDelete = await getTable(appId, ctx.params.tableId)
|
|
261
|
+
const datasourceId = getDatasourceId(tableToDelete)
|
|
262
|
+
|
|
263
|
+
const db = new CouchDB(appId)
|
|
264
|
+
const datasource = await db.get(datasourceId)
|
|
265
|
+
const tables = datasource.entities
|
|
266
|
+
|
|
267
|
+
const operation = DataSourceOperation.DELETE_TABLE
|
|
268
|
+
await makeTableRequest(datasource, operation, tableToDelete, tables)
|
|
269
|
+
|
|
270
|
+
cleanupRelationships(tableToDelete, tables)
|
|
271
|
+
|
|
272
|
+
delete datasource.entities[tableToDelete.name]
|
|
273
|
+
await db.put(datasource)
|
|
274
|
+
|
|
275
|
+
return tableToDelete
|
|
276
|
+
}
|