@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.
Files changed (203) hide show
  1. package/builder/assets/{index.7469e1e4.css → index.75ed4773.css} +1 -1
  2. package/builder/assets/{index.ba2741f9.js → index.d3780441.js} +34 -34
  3. package/builder/index.html +2 -2
  4. package/coverage/clover.xml +383 -207
  5. package/coverage/coverage-final.json +8 -6
  6. package/coverage/lcov-report/api/controllers/analytics.js.html +1 -1
  7. package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
  8. package/coverage/lcov-report/api/controllers/application.js.html +1 -1
  9. package/coverage/lcov-report/api/controllers/auth.js.html +1 -1
  10. package/coverage/lcov-report/api/controllers/automation.js.html +1 -1
  11. package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
  12. package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
  13. package/coverage/lcov-report/api/controllers/component.js.html +1 -1
  14. package/coverage/lcov-report/api/controllers/datasource.js.html +120 -24
  15. package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
  16. package/coverage/lcov-report/api/controllers/hosting.js.html +1 -1
  17. package/coverage/lcov-report/api/controllers/index.html +19 -19
  18. package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
  19. package/coverage/lcov-report/api/controllers/layout.js.html +1 -1
  20. package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
  21. package/coverage/lcov-report/api/controllers/permission.js.html +1 -1
  22. package/coverage/lcov-report/api/controllers/query.js.html +9 -3
  23. package/coverage/lcov-report/api/controllers/role.js.html +1 -1
  24. package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
  25. package/coverage/lcov-report/api/controllers/row/external.js.html +1 -1
  26. package/coverage/lcov-report/api/controllers/row/index.html +14 -14
  27. package/coverage/lcov-report/api/controllers/row/index.js.html +1 -1
  28. package/coverage/lcov-report/api/controllers/row/internal.js.html +1 -1
  29. package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +1 -1
  30. package/coverage/lcov-report/api/controllers/row/utils.js.html +11 -32
  31. package/coverage/lcov-report/api/controllers/screen.js.html +1 -1
  32. package/coverage/lcov-report/api/controllers/script.js.html +1 -1
  33. package/coverage/lcov-report/api/controllers/table/external.js.html +908 -0
  34. package/coverage/lcov-report/api/controllers/table/index.html +59 -29
  35. package/coverage/lcov-report/api/controllers/table/index.js.html +52 -376
  36. package/coverage/lcov-report/api/controllers/table/internal.js.html +494 -0
  37. package/coverage/lcov-report/api/controllers/table/utils.js.html +70 -10
  38. package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
  39. package/coverage/lcov-report/api/controllers/user.js.html +1 -1
  40. package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
  41. package/coverage/lcov-report/api/controllers/view/index.html +1 -1
  42. package/coverage/lcov-report/api/controllers/view/index.js.html +1 -1
  43. package/coverage/lcov-report/api/controllers/view/utils.js.html +1 -1
  44. package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +1 -1
  45. package/coverage/lcov-report/api/controllers/webhook.js.html +1 -1
  46. package/coverage/lcov-report/api/index.html +1 -1
  47. package/coverage/lcov-report/api/index.js.html +1 -1
  48. package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
  49. package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
  50. package/coverage/lcov-report/api/routes/application.js.html +1 -1
  51. package/coverage/lcov-report/api/routes/auth.js.html +1 -1
  52. package/coverage/lcov-report/api/routes/automation.js.html +1 -1
  53. package/coverage/lcov-report/api/routes/backup.js.html +1 -1
  54. package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
  55. package/coverage/lcov-report/api/routes/component.js.html +1 -1
  56. package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
  57. package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
  58. package/coverage/lcov-report/api/routes/dev.js.html +1 -1
  59. package/coverage/lcov-report/api/routes/hosting.js.html +1 -1
  60. package/coverage/lcov-report/api/routes/index.html +1 -1
  61. package/coverage/lcov-report/api/routes/index.js.html +1 -1
  62. package/coverage/lcov-report/api/routes/integration.js.html +1 -1
  63. package/coverage/lcov-report/api/routes/layout.js.html +1 -1
  64. package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
  65. package/coverage/lcov-report/api/routes/permission.js.html +1 -1
  66. package/coverage/lcov-report/api/routes/query.js.html +1 -1
  67. package/coverage/lcov-report/api/routes/role.js.html +1 -1
  68. package/coverage/lcov-report/api/routes/routing.js.html +1 -1
  69. package/coverage/lcov-report/api/routes/row.js.html +1 -1
  70. package/coverage/lcov-report/api/routes/screen.js.html +1 -1
  71. package/coverage/lcov-report/api/routes/script.js.html +1 -1
  72. package/coverage/lcov-report/api/routes/static.js.html +1 -1
  73. package/coverage/lcov-report/api/routes/table.js.html +1 -1
  74. package/coverage/lcov-report/api/routes/templates.js.html +1 -1
  75. package/coverage/lcov-report/api/routes/user.js.html +1 -1
  76. package/coverage/lcov-report/api/routes/view.js.html +1 -1
  77. package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
  78. package/coverage/lcov-report/automations/actions.js.html +1 -1
  79. package/coverage/lcov-report/automations/automationUtils.js.html +1 -1
  80. package/coverage/lcov-report/automations/bullboard.js.html +1 -1
  81. package/coverage/lcov-report/automations/index.html +1 -1
  82. package/coverage/lcov-report/automations/index.js.html +1 -1
  83. package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
  84. package/coverage/lcov-report/automations/steps/createRow.js.html +1 -1
  85. package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
  86. package/coverage/lcov-report/automations/steps/deleteRow.js.html +1 -1
  87. package/coverage/lcov-report/automations/steps/discord.js.html +1 -1
  88. package/coverage/lcov-report/automations/steps/executeQuery.js.html +1 -1
  89. package/coverage/lcov-report/automations/steps/executeScript.js.html +1 -1
  90. package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
  91. package/coverage/lcov-report/automations/steps/index.html +1 -1
  92. package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
  93. package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +1 -1
  94. package/coverage/lcov-report/automations/steps/queryRows.js.html +1 -1
  95. package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
  96. package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
  97. package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
  98. package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
  99. package/coverage/lcov-report/automations/steps/utils.js.html +1 -1
  100. package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
  101. package/coverage/lcov-report/automations/thread.js.html +1 -1
  102. package/coverage/lcov-report/automations/triggerInfo/app.js.html +1 -1
  103. package/coverage/lcov-report/automations/triggerInfo/cron.js.html +1 -1
  104. package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
  105. package/coverage/lcov-report/automations/triggerInfo/index.js.html +1 -1
  106. package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +1 -1
  107. package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +1 -1
  108. package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +1 -1
  109. package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +1 -1
  110. package/coverage/lcov-report/automations/triggers.js.html +1 -1
  111. package/coverage/lcov-report/automations/utils.js.html +1 -1
  112. package/coverage/lcov-report/constants/index.html +5 -5
  113. package/coverage/lcov-report/constants/index.js.html +25 -4
  114. package/coverage/lcov-report/constants/layouts.js.html +1 -1
  115. package/coverage/lcov-report/constants/screens.js.html +1 -1
  116. package/coverage/lcov-report/db/client.js.html +1 -1
  117. package/coverage/lcov-report/db/inMemoryView.js.html +1 -1
  118. package/coverage/lcov-report/db/index.html +1 -1
  119. package/coverage/lcov-report/db/index.js.html +1 -1
  120. package/coverage/lcov-report/db/linkedRows/LinkController.js.html +1 -1
  121. package/coverage/lcov-report/db/linkedRows/index.html +1 -1
  122. package/coverage/lcov-report/db/linkedRows/index.js.html +1 -1
  123. package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +1 -1
  124. package/coverage/lcov-report/db/newid.js.html +1 -1
  125. package/coverage/lcov-report/db/utils.js.html +1 -1
  126. package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
  127. package/coverage/lcov-report/events/index.html +1 -1
  128. package/coverage/lcov-report/events/index.js.html +1 -1
  129. package/coverage/lcov-report/events/utils.js.html +1 -1
  130. package/coverage/lcov-report/index.html +38 -38
  131. package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
  132. package/coverage/lcov-report/integrations/tests/index.html +1 -1
  133. package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
  134. package/coverage/lcov-report/middleware/authorized.js.html +1 -1
  135. package/coverage/lcov-report/middleware/builder.js.html +1 -1
  136. package/coverage/lcov-report/middleware/currentapp.js.html +1 -1
  137. package/coverage/lcov-report/middleware/index.html +1 -1
  138. package/coverage/lcov-report/middleware/joi-validator.js.html +1 -1
  139. package/coverage/lcov-report/middleware/resourceId.js.html +1 -1
  140. package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
  141. package/coverage/lcov-report/middleware/usageQuota.js.html +1 -1
  142. package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
  143. package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
  144. package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
  145. package/coverage/lcov-report/utilities/csvParser.js.html +1 -1
  146. package/coverage/lcov-report/utilities/global.js.html +1 -1
  147. package/coverage/lcov-report/utilities/index.html +1 -1
  148. package/coverage/lcov-report/utilities/index.js.html +1 -1
  149. package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +1 -1
  150. package/coverage/lcov-report/utilities/queue/index.html +1 -1
  151. package/coverage/lcov-report/utilities/routing/index.html +1 -1
  152. package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
  153. package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
  154. package/coverage/lcov-report/utilities/rowProcessor/index.js.html +1 -1
  155. package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +1 -1
  156. package/coverage/lcov-report/utilities/scriptRunner.js.html +1 -1
  157. package/coverage/lcov-report/utilities/security.js.html +1 -1
  158. package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
  159. package/coverage/lcov-report/utilities/users.js.html +1 -1
  160. package/coverage/lcov-report/utilities/workerRequests.js.html +1 -1
  161. package/coverage/lcov.info +652 -332
  162. package/dist/api/controllers/datasource.js +41 -14
  163. package/dist/api/controllers/query.js +3 -1
  164. package/dist/api/controllers/row/ExternalRequest.js +12 -10
  165. package/dist/api/controllers/row/utils.js +3 -11
  166. package/dist/api/controllers/table/external.js +222 -0
  167. package/dist/api/controllers/table/index.js +28 -121
  168. package/dist/api/controllers/table/internal.js +138 -0
  169. package/dist/api/controllers/table/utils.js +17 -0
  170. package/dist/constants/index.js +6 -0
  171. package/dist/definitions/datasource.js +3 -0
  172. package/dist/integrations/base/datasourcePlus.js +2 -0
  173. package/dist/integrations/base/sql.js +16 -11
  174. package/dist/integrations/base/sqlTable.js +141 -0
  175. package/dist/integrations/base/utils.js +30 -0
  176. package/dist/integrations/mysql.js +13 -7
  177. package/dist/integrations/postgres.js +18 -8
  178. package/dist/integrations/utils.js +21 -6
  179. package/dist/package.json +4 -4
  180. package/dist/tests/utilities/TestConfiguration.js +2 -1
  181. package/dist/tsconfig.tsbuildinfo +1 -1
  182. package/package.json +5 -5
  183. package/src/api/controllers/datasource.js +46 -14
  184. package/src/api/controllers/query.js +3 -1
  185. package/src/api/controllers/row/ExternalRequest.ts +12 -10
  186. package/src/api/controllers/row/utils.js +3 -10
  187. package/src/api/controllers/table/external.js +276 -0
  188. package/src/api/controllers/table/index.js +27 -135
  189. package/src/api/controllers/table/internal.js +138 -0
  190. package/src/api/controllers/table/utils.js +20 -0
  191. package/src/api/routes/tests/datasource.spec.js +2 -2
  192. package/src/api/routes/tests/query.spec.js +0 -1
  193. package/src/constants/index.js +7 -0
  194. package/src/definitions/common.ts +7 -1
  195. package/src/definitions/datasource.ts +5 -0
  196. package/src/integrations/base/datasourcePlus.ts +8 -0
  197. package/src/integrations/base/sql.ts +15 -16
  198. package/src/integrations/base/sqlTable.ts +167 -0
  199. package/src/integrations/base/utils.ts +19 -0
  200. package/src/integrations/mysql.ts +15 -9
  201. package/src/integrations/postgres.ts +19 -9
  202. package/src/integrations/utils.ts +22 -6
  203. 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.174",
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.174",
72
- "@budibase/client": "^0.9.174",
73
- "@budibase/string-templates": "^0.9.174",
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": "551be52bfb00b8d740e72b6442ba45d3abdce956"
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 { makeExternalQuery } = require("./row/utils")
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 response = await db.put(datasource)
50
- datasource._rev = response.rev
50
+ const dbResp = await db.put(datasource)
51
+ datasource._rev = dbResp.rev
51
52
 
52
- ctx.body = datasource
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
- let tables = await buildSchemaHelper(datasource)
95
+ const { tables, error } = await buildSchemaHelper(datasource)
96
+ schemaError = error
90
97
  datasource.entities = tables
91
98
  }
92
99
 
93
- const response = await db.put(datasource)
94
- datasource._rev = response.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
- ctx.status = 200
105
- ctx.message = "Datasource saved successfully."
106
- ctx.body = datasource
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 makeExternalQuery(ctx.appId, queryJson)
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
- return connector.tables
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 { makeExternalQuery } = require("./utils")
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 ((!row[key] && row[key] !== "") || newRow[key] || field.autocolumn) {
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 makeExternalQuery(this.appId, {
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
- makeExternalQuery(appId, {
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
- makeExternalQuery(this.appId, {
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 extractNonLinkFieldNames(table: Table, existing: string[] = []) {
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 = extractNonLinkFieldNames(table)
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 = extractNonLinkFieldNames(linkTable, fields)
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 makeExternalQuery(appId, json)
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.makeExternalQuery = async (appId, json) => {
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
- const Integration = integrations[datasource.source]
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
+ }