@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
@@ -1,16 +1,28 @@
1
1
  const CouchDB = require("../../../db")
2
- const linkRows = require("../../../db/linkedRows")
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 { FieldTypes } = require("../../../constants")
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 db = new CouchDB(appId)
54
- const { dataImport, ...rest } = ctx.request.body
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 ${ctx.request.body.name} saved successfully.`
151
- ctx.body = tableToSave
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 db = new CouchDB(appId)
157
- const tableToDelete = await db.get(ctx.params.tableId)
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, tableToDelete)
79
+ ctx.eventEmitter.emitTable(`table:delete`, appId, deletedTable)
188
80
  ctx.status = 200
189
- ctx.body = { message: `Table ${ctx.params.tableId} deleted.` }
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.res.statusMessage).toEqual("Datasource saved successfully.")
30
- expect(res.body.name).toEqual("Test")
29
+ expect(res.body.datasource.name).toEqual("Test")
30
+ expect(res.body.errors).toBeUndefined()
31
31
  })
32
32
  })
33
33
 
@@ -1,7 +1,6 @@
1
1
  // mock out postgres for this
2
2
  jest.mock("pg")
3
3
 
4
- const { findLastKey } = require("lodash/fp")
5
4
  const setup = require("./utilities")
6
5
  const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
7
6
  const { basicQuery, basicDatasource } = setup.structures
@@ -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?: string
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
@@ -0,0 +1,8 @@
1
+ import { Table } from "../../definitions/common"
2
+
3
+ export interface DatasourcePlus {
4
+ tables: Record<string, Table>
5
+ schemaErrors: Record<string, string>
6
+
7
+ buildSchema(datasourceId: string, entities: Record<string, Table>): any
8
+ }
@@ -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
- this.sqlClient = client
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 client = knex({ client: this.sqlClient })
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