@budibase/server 0.9.184 → 0.9.185-alpha.11

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 (290) hide show
  1. package/Dockerfile +1 -1
  2. package/__mocks__/mssql.ts +1 -1
  3. package/__mocks__/pg.ts +3 -0
  4. package/builder/assets/bb-space-man.5be51a45.svg +290 -0
  5. package/builder/assets/index.053c6f7a.js +1321 -0
  6. package/builder/assets/index.4a7c5636.css +1 -0
  7. package/builder/assets/vendor.470042a4.js +152 -0
  8. package/builder/assets/vendor.5e1fe644.css +1 -0
  9. package/builder/index.html +4 -4
  10. package/coverage/clover.xml +1357 -959
  11. package/coverage/coverage-final.json +91 -87
  12. package/coverage/lcov-report/api/controllers/analytics.js.html +2 -2
  13. package/coverage/lcov-report/api/controllers/apikeys.js.html +2 -2
  14. package/coverage/lcov-report/api/controllers/application.js.html +65 -14
  15. package/coverage/lcov-report/api/controllers/auth.js.html +19 -10
  16. package/coverage/lcov-report/api/controllers/automation.js.html +3 -3
  17. package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
  18. package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
  19. package/coverage/lcov-report/api/controllers/component.js.html +4 -4
  20. package/coverage/lcov-report/api/controllers/datasource.js.html +147 -27
  21. package/coverage/lcov-report/api/controllers/dev.js.html +16 -7
  22. package/coverage/lcov-report/api/controllers/hosting.js.html +10 -10
  23. package/coverage/lcov-report/api/controllers/index.html +90 -90
  24. package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
  25. package/coverage/lcov-report/api/controllers/layout.js.html +3 -3
  26. package/coverage/lcov-report/api/controllers/metadata.js.html +3 -3
  27. package/coverage/lcov-report/api/controllers/permission.js.html +3 -3
  28. package/coverage/lcov-report/api/controllers/query.js.html +53 -209
  29. package/coverage/lcov-report/api/controllers/role.js.html +9 -9
  30. package/coverage/lcov-report/api/controllers/routing.js.html +3 -3
  31. package/coverage/lcov-report/api/controllers/row/external.js.html +12 -6
  32. package/coverage/lcov-report/api/controllers/row/index.html +26 -26
  33. package/coverage/lcov-report/api/controllers/row/index.js.html +2 -2
  34. package/coverage/lcov-report/api/controllers/row/internal.js.html +9 -9
  35. package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +2 -2
  36. package/coverage/lcov-report/api/controllers/row/utils.js.html +21 -30
  37. package/coverage/lcov-report/api/controllers/screen.js.html +2 -2
  38. package/coverage/lcov-report/api/controllers/script.js.html +3 -3
  39. package/coverage/lcov-report/api/controllers/table/external.js.html +980 -0
  40. package/coverage/lcov-report/api/controllers/table/index.html +56 -26
  41. package/coverage/lcov-report/api/controllers/table/index.js.html +80 -350
  42. package/coverage/lcov-report/api/controllers/table/internal.js.html +545 -0
  43. package/coverage/lcov-report/api/controllers/table/utils.js.html +172 -49
  44. package/coverage/lcov-report/api/controllers/templates.js.html +4 -4
  45. package/coverage/lcov-report/api/controllers/user.js.html +390 -33
  46. package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
  47. package/coverage/lcov-report/api/controllers/view/index.html +9 -9
  48. package/coverage/lcov-report/api/controllers/view/index.js.html +5 -5
  49. package/coverage/lcov-report/api/controllers/view/utils.js.html +11 -11
  50. package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +3 -3
  51. package/coverage/lcov-report/api/controllers/webhook.js.html +4 -4
  52. package/coverage/lcov-report/api/index.html +1 -1
  53. package/coverage/lcov-report/api/index.js.html +1 -1
  54. package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
  55. package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
  56. package/coverage/lcov-report/api/routes/application.js.html +1 -1
  57. package/coverage/lcov-report/api/routes/auth.js.html +1 -1
  58. package/coverage/lcov-report/api/routes/automation.js.html +1 -1
  59. package/coverage/lcov-report/api/routes/backup.js.html +1 -1
  60. package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
  61. package/coverage/lcov-report/api/routes/component.js.html +1 -1
  62. package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
  63. package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
  64. package/coverage/lcov-report/api/routes/dev.js.html +2 -2
  65. package/coverage/lcov-report/api/routes/hosting.js.html +1 -1
  66. package/coverage/lcov-report/api/routes/index.html +4 -4
  67. package/coverage/lcov-report/api/routes/index.js.html +1 -1
  68. package/coverage/lcov-report/api/routes/integration.js.html +1 -1
  69. package/coverage/lcov-report/api/routes/layout.js.html +1 -1
  70. package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
  71. package/coverage/lcov-report/api/routes/permission.js.html +1 -1
  72. package/coverage/lcov-report/api/routes/query.js.html +5 -2
  73. package/coverage/lcov-report/api/routes/role.js.html +1 -1
  74. package/coverage/lcov-report/api/routes/routing.js.html +1 -1
  75. package/coverage/lcov-report/api/routes/row.js.html +1 -1
  76. package/coverage/lcov-report/api/routes/screen.js.html +1 -1
  77. package/coverage/lcov-report/api/routes/script.js.html +1 -1
  78. package/coverage/lcov-report/api/routes/static.js.html +2 -2
  79. package/coverage/lcov-report/api/routes/table.js.html +35 -2
  80. package/coverage/lcov-report/api/routes/templates.js.html +1 -1
  81. package/coverage/lcov-report/api/routes/user.js.html +17 -2
  82. package/coverage/lcov-report/api/routes/view.js.html +1 -1
  83. package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
  84. package/coverage/lcov-report/automations/actions.js.html +2 -2
  85. package/coverage/lcov-report/automations/automationUtils.js.html +3 -3
  86. package/coverage/lcov-report/automations/bullboard.js.html +71 -20
  87. package/coverage/lcov-report/automations/index.html +33 -48
  88. package/coverage/lcov-report/automations/index.js.html +2 -2
  89. package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
  90. package/coverage/lcov-report/automations/steps/createRow.js.html +1 -1
  91. package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
  92. package/coverage/lcov-report/automations/steps/deleteRow.js.html +1 -1
  93. package/coverage/lcov-report/automations/steps/discord.js.html +3 -3
  94. package/coverage/lcov-report/automations/steps/executeQuery.js.html +1 -1
  95. package/coverage/lcov-report/automations/steps/executeScript.js.html +3 -3
  96. package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
  97. package/coverage/lcov-report/automations/steps/index.html +14 -14
  98. package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
  99. package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +3 -3
  100. package/coverage/lcov-report/automations/steps/queryRows.js.html +3 -3
  101. package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
  102. package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
  103. package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
  104. package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
  105. package/coverage/lcov-report/automations/steps/utils.js.html +7 -7
  106. package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
  107. package/coverage/lcov-report/automations/triggerInfo/app.js.html +2 -2
  108. package/coverage/lcov-report/automations/triggerInfo/cron.js.html +2 -2
  109. package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
  110. package/coverage/lcov-report/automations/triggerInfo/index.js.html +8 -8
  111. package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +2 -2
  112. package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +2 -2
  113. package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +2 -2
  114. package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +2 -2
  115. package/coverage/lcov-report/automations/triggers.js.html +22 -10
  116. package/coverage/lcov-report/automations/utils.js.html +42 -12
  117. package/coverage/lcov-report/constants/index.html +5 -5
  118. package/coverage/lcov-report/constants/index.js.html +49 -4
  119. package/coverage/lcov-report/constants/layouts.js.html +1 -1
  120. package/coverage/lcov-report/constants/screens.js.html +1 -1
  121. package/coverage/lcov-report/db/client.js.html +2 -2
  122. package/coverage/lcov-report/db/inMemoryView.js.html +2 -2
  123. package/coverage/lcov-report/db/index.html +2 -2
  124. package/coverage/lcov-report/db/index.js.html +1 -1
  125. package/coverage/lcov-report/db/linkedRows/LinkController.js.html +6 -6
  126. package/coverage/lcov-report/db/linkedRows/index.html +10 -10
  127. package/coverage/lcov-report/db/linkedRows/index.js.html +5 -5
  128. package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +6 -6
  129. package/coverage/lcov-report/db/newid.js.html +1 -1
  130. package/coverage/lcov-report/db/utils.js.html +1 -1
  131. package/coverage/lcov-report/events/AutomationEmitter.js.html +6 -6
  132. package/coverage/lcov-report/events/index.html +6 -6
  133. package/coverage/lcov-report/events/index.js.html +1 -1
  134. package/coverage/lcov-report/events/utils.js.html +5 -5
  135. package/coverage/lcov-report/index.html +98 -83
  136. package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
  137. package/coverage/lcov-report/integrations/tests/index.html +1 -1
  138. package/coverage/lcov-report/middleware/appInfo.js.html +2 -2
  139. package/coverage/lcov-report/middleware/authorized.js.html +11 -8
  140. package/coverage/lcov-report/middleware/builder.js.html +16 -7
  141. package/coverage/lcov-report/middleware/currentapp.js.html +6 -6
  142. package/coverage/lcov-report/middleware/index.html +23 -23
  143. package/coverage/lcov-report/middleware/joi-validator.js.html +2 -2
  144. package/coverage/lcov-report/middleware/resourceId.js.html +18 -18
  145. package/coverage/lcov-report/middleware/selfhost.js.html +2 -2
  146. package/coverage/lcov-report/middleware/usageQuota.js.html +4 -4
  147. package/coverage/lcov-report/middleware/utils.js.html +1 -1
  148. package/coverage/lcov-report/{automations/thread.js.html → threads/automation.js.html} +83 -32
  149. package/coverage/lcov-report/threads/index.html +141 -0
  150. package/coverage/lcov-report/threads/index.js.html +260 -0
  151. package/coverage/lcov-report/threads/query.js.html +269 -0
  152. package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
  153. package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
  154. package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
  155. package/coverage/lcov-report/utilities/csvParser.js.html +144 -15
  156. package/coverage/lcov-report/utilities/global.js.html +26 -11
  157. package/coverage/lcov-report/utilities/index.html +49 -49
  158. package/coverage/lcov-report/utilities/index.js.html +65 -17
  159. package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +30 -9
  160. package/coverage/lcov-report/utilities/queue/index.html +14 -14
  161. package/coverage/lcov-report/utilities/routing/index.html +1 -1
  162. package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
  163. package/coverage/lcov-report/utilities/rowProcessor/index.html +4 -4
  164. package/coverage/lcov-report/utilities/rowProcessor/index.js.html +2 -2
  165. package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +3 -3
  166. package/coverage/lcov-report/utilities/scriptRunner.js.html +16 -7
  167. package/coverage/lcov-report/utilities/security.js.html +3 -3
  168. package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
  169. package/coverage/lcov-report/utilities/users.js.html +1 -1
  170. package/coverage/lcov-report/utilities/workerRequests.js.html +5 -5
  171. package/coverage/lcov.info +2301 -1599
  172. package/dist/api/controllers/application.js +16 -2
  173. package/dist/api/controllers/auth.js +3 -3
  174. package/dist/api/controllers/datasource.js +47 -16
  175. package/dist/api/controllers/deploy/index.js +2 -0
  176. package/dist/api/controllers/dev.js +4 -1
  177. package/dist/api/controllers/query.js +30 -58
  178. package/dist/api/controllers/row/ExternalRequest.js +37 -14
  179. package/dist/api/controllers/row/external.js +1 -0
  180. package/dist/api/controllers/row/utils.js +6 -12
  181. package/dist/api/controllers/static/index.js +1 -1
  182. package/dist/api/controllers/table/external.js +242 -0
  183. package/dist/api/controllers/table/index.js +49 -123
  184. package/dist/api/controllers/table/internal.js +150 -0
  185. package/dist/api/controllers/table/utils.js +62 -28
  186. package/dist/api/controllers/user.js +123 -11
  187. package/dist/api/routes/query.js +1 -1
  188. package/dist/api/routes/table.js +7 -1
  189. package/dist/api/routes/user.js +2 -1
  190. package/dist/app.js +2 -11
  191. package/dist/automations/bullboard.js +37 -10
  192. package/dist/automations/triggers.js +7 -3
  193. package/dist/automations/utils.js +11 -2
  194. package/dist/constants/index.js +13 -0
  195. package/dist/definitions/datasource.js +4 -0
  196. package/dist/integrations/base/IntegrationBase.js +2 -0
  197. package/dist/integrations/base/datasourcePlus.js +2 -0
  198. package/dist/integrations/base/sql.js +334 -180
  199. package/dist/integrations/base/sqlTable.js +143 -0
  200. package/dist/integrations/base/utils.js +30 -0
  201. package/dist/integrations/microsoftSqlServer.js +117 -9
  202. package/dist/integrations/mysql.js +14 -84
  203. package/dist/integrations/postgres.js +35 -28
  204. package/dist/integrations/utils.js +56 -9
  205. package/dist/middleware/authorized.js +2 -1
  206. package/dist/middleware/builder.js +4 -1
  207. package/dist/package.json +7 -6
  208. package/dist/tests/utilities/TestConfiguration.js +2 -1
  209. package/dist/{automations/thread.js → threads/automation.js} +23 -7
  210. package/dist/threads/index.js +58 -0
  211. package/dist/threads/query.js +61 -0
  212. package/dist/tsconfig.tsbuildinfo +1 -1
  213. package/dist/utilities/csvParser.js +46 -5
  214. package/dist/utilities/global.js +6 -2
  215. package/dist/utilities/index.js +18 -2
  216. package/dist/utilities/queue/inMemoryQueue.js +8 -0
  217. package/dist/utilities/scriptRunner.js +4 -1
  218. package/package.json +8 -7
  219. package/scripts/integrations/mssql/data/Dockerfile +9 -0
  220. package/scripts/integrations/mssql/data/entrypoint.sh +24 -0
  221. package/scripts/integrations/mssql/data/setup.sql +54 -0
  222. package/scripts/integrations/mssql/docker-compose.yaml +12 -0
  223. package/scripts/integrations/mssql/reset.sh +3 -0
  224. package/scripts/likeCypress.ts +33 -0
  225. package/src/api/controllers/application.js +19 -2
  226. package/src/api/controllers/auth.js +3 -0
  227. package/src/api/controllers/datasource.js +56 -16
  228. package/src/api/controllers/deploy/index.js +2 -0
  229. package/src/api/controllers/dev.js +4 -1
  230. package/src/api/controllers/query.js +29 -81
  231. package/src/api/controllers/row/ExternalRequest.ts +47 -16
  232. package/src/api/controllers/row/external.js +2 -0
  233. package/src/api/controllers/row/utils.js +8 -11
  234. package/src/api/controllers/static/index.js +1 -1
  235. package/src/api/controllers/table/external.js +300 -0
  236. package/src/api/controllers/table/index.js +47 -137
  237. package/src/api/controllers/table/internal.js +155 -0
  238. package/src/api/controllers/table/utils.js +74 -33
  239. package/src/api/controllers/user.js +132 -13
  240. package/src/api/routes/query.js +1 -0
  241. package/src/api/routes/table.js +11 -0
  242. package/src/api/routes/tests/datasource.spec.js +2 -2
  243. package/src/api/routes/tests/misc.spec.js +5 -1
  244. package/src/api/routes/tests/query.spec.js +0 -1
  245. package/src/api/routes/user.js +5 -0
  246. package/src/app.ts +2 -10
  247. package/src/automations/bullboard.js +28 -11
  248. package/src/automations/tests/automation.spec.js +3 -4
  249. package/src/automations/triggers.js +7 -3
  250. package/src/automations/utils.js +12 -2
  251. package/src/constants/index.js +15 -0
  252. package/src/definitions/common.ts +7 -1
  253. package/src/definitions/datasource.ts +9 -3
  254. package/src/integrations/airtable.ts +2 -1
  255. package/src/integrations/arangodb.ts +2 -1
  256. package/src/integrations/base/IntegrationBase.ts +6 -0
  257. package/src/integrations/base/datasourcePlus.ts +9 -0
  258. package/src/integrations/base/sql.ts +359 -222
  259. package/src/integrations/base/sqlTable.ts +180 -0
  260. package/src/integrations/base/utils.ts +22 -0
  261. package/src/integrations/couchdb.ts +2 -1
  262. package/src/integrations/dynamodb.ts +2 -1
  263. package/src/integrations/elasticsearch.ts +2 -1
  264. package/src/integrations/microsoftSqlServer.ts +145 -13
  265. package/src/integrations/mongodb.ts +2 -1
  266. package/src/integrations/mysql.ts +20 -94
  267. package/src/integrations/postgres.ts +45 -35
  268. package/src/integrations/rest.ts +2 -1
  269. package/src/integrations/s3.ts +2 -1
  270. package/src/integrations/tests/microsoftSqlServer.spec.js +22 -15
  271. package/src/integrations/tests/postgres.spec.js +4 -0
  272. package/src/integrations/utils.ts +60 -8
  273. package/src/middleware/authorized.js +2 -1
  274. package/src/middleware/builder.js +4 -1
  275. package/src/tests/utilities/TestConfiguration.js +2 -1
  276. package/src/{automations/thread.js → threads/automation.js} +24 -7
  277. package/src/threads/index.js +60 -0
  278. package/src/threads/query.js +63 -0
  279. package/src/utilities/csvParser.js +48 -5
  280. package/src/utilities/global.js +7 -2
  281. package/src/utilities/index.js +18 -2
  282. package/src/utilities/queue/inMemoryQueue.js +7 -0
  283. package/src/utilities/scriptRunner.js +4 -1
  284. package/src/utilities/tests/__snapshots__/csvParser.spec.js.snap +0 -6
  285. package/src/utilities/tests/csvParser.spec.js +3 -0
  286. package/tsconfig.json +1 -1
  287. package/builder/assets/index.34964222.js +0 -201
  288. package/builder/assets/index.7469e1e4.css +0 -29
  289. package/builder/assets/vendor.278a305c.js +0 -9
  290. package/builder/assets/vendor.d75d5eb7.css +0 -1
@@ -1,265 +1,314 @@
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
- import { isIsoDateString } from "../utils"
10
+ import { isIsoDateString, SqlClients } from "../utils"
11
+ import SqlTableQueryBuilder from "./sqlTable"
12
+
13
+ const BASE_LIMIT = 5000
12
14
 
13
15
  type KnexQuery = Knex.QueryBuilder | Knex
16
+ // these are invalid dates sent by the client, need to convert them to a real max date
17
+ const MIN_ISO_DATE = "0000-00-00T00:00:00.000Z"
18
+ const MAX_ISO_DATE = "9999-00-00T00:00:00.000Z"
19
+
20
+ function parse(input: any) {
21
+ if (Array.isArray(input)) {
22
+ return JSON.stringify(input)
23
+ }
24
+ if (typeof input !== "string") {
25
+ return input
26
+ }
27
+ if (input === MAX_ISO_DATE) {
28
+ return new Date(8640000000000000)
29
+ }
30
+ if (input === MIN_ISO_DATE) {
31
+ return new Date(-8640000000000000)
32
+ }
33
+ if (isIsoDateString(input)) {
34
+ return new Date(input)
35
+ }
36
+ return input
37
+ }
14
38
 
15
39
  function parseBody(body: any) {
16
40
  for (let [key, value] of Object.entries(body)) {
17
- if (typeof value !== "string") {
18
- continue
19
- }
20
- if (isIsoDateString(value)) {
21
- body[key] = new Date(value)
22
- }
41
+ body[key] = parse(value)
23
42
  }
24
43
  return body
25
44
  }
26
45
 
27
- // right now we only do filters on the specific table being queried
28
- function addFilters(
29
- tableName: string,
30
- query: KnexQuery,
31
- filters: SearchFilters | undefined
32
- ): KnexQuery {
33
- function iterate(
34
- structure: { [key: string]: any },
35
- fn: (key: string, value: any) => void
36
- ) {
37
- for (let [key, value] of Object.entries(structure)) {
38
- fn(`${tableName}.${key}`, value)
46
+ function parseFilters(filters: SearchFilters): SearchFilters {
47
+ for (let [key, value] of Object.entries(filters)) {
48
+ let parsed
49
+ if (typeof value === "object") {
50
+ parsed = parseFilters(value)
51
+ } else {
52
+ parsed = parse(value)
39
53
  }
54
+ // @ts-ignore
55
+ filters[key] = parsed
40
56
  }
41
- if (!filters) {
42
- return query
43
- }
44
- // if all or specified in filters, then everything is an or
45
- const allOr = filters.allOr
46
- if (filters.oneOf) {
47
- iterate(filters.oneOf, (key, array) => {
48
- const fnc = allOr ? "orWhereIn" : "whereIn"
49
- query = query[fnc](key, array)
50
- })
51
- }
52
- if (filters.string) {
53
- iterate(filters.string, (key, value) => {
54
- const fnc = allOr ? "orWhere" : "where"
55
- query = query[fnc](key, "ilike", `${value}%`)
56
- })
57
- }
58
- if (filters.fuzzy) {
59
- iterate(filters.fuzzy, (key, value) => {
60
- const fnc = allOr ? "orWhere" : "where"
61
- query = query[fnc](key, "ilike", `%${value}%`)
62
- })
63
- }
64
- if (filters.range) {
65
- iterate(filters.range, (key, value) => {
66
- if (!value.high || !value.low) {
67
- return
68
- }
69
- const fnc = allOr ? "orWhereBetween" : "whereBetween"
70
- query = query[fnc](key, [value.low, value.high])
71
- })
72
- }
73
- if (filters.equal) {
74
- iterate(filters.equal, (key, value) => {
75
- const fnc = allOr ? "orWhere" : "where"
76
- query = query[fnc]({ [key]: value })
77
- })
78
- }
79
- if (filters.notEqual) {
80
- iterate(filters.notEqual, (key, value) => {
81
- const fnc = allOr ? "orWhereNot" : "whereNot"
82
- query = query[fnc]({ [key]: value })
83
- })
84
- }
85
- if (filters.empty) {
86
- iterate(filters.empty, key => {
87
- const fnc = allOr ? "orWhereNull" : "whereNull"
88
- query = query[fnc](key)
89
- })
90
- }
91
- if (filters.notEmpty) {
92
- iterate(filters.notEmpty, key => {
93
- const fnc = allOr ? "orWhereNotNull" : "whereNotNull"
94
- query = query[fnc](key)
95
- })
96
- }
97
- return query
57
+ return filters
98
58
  }
99
59
 
100
- function addRelationships(
101
- knex: Knex,
102
- query: KnexQuery,
103
- fields: string | string[],
104
- fromTable: string,
105
- relationships: RelationshipsJson[] | undefined
106
- ): KnexQuery {
107
- if (!relationships) {
60
+ class InternalBuilder {
61
+ private readonly client: string
62
+
63
+ constructor(client: string) {
64
+ this.client = client
65
+ }
66
+
67
+ // right now we only do filters on the specific table being queried
68
+ addFilters(
69
+ tableName: string,
70
+ query: KnexQuery,
71
+ filters: SearchFilters | undefined
72
+ ): KnexQuery {
73
+ function iterate(
74
+ structure: { [key: string]: any },
75
+ fn: (key: string, value: any) => void
76
+ ) {
77
+ for (let [key, value] of Object.entries(structure)) {
78
+ fn(`${tableName}.${key}`, value)
79
+ }
80
+ }
81
+ if (!filters) {
82
+ return query
83
+ }
84
+ filters = parseFilters(filters)
85
+ // if all or specified in filters, then everything is an or
86
+ const allOr = filters.allOr
87
+ if (filters.oneOf) {
88
+ iterate(filters.oneOf, (key, array) => {
89
+ const fnc = allOr ? "orWhereIn" : "whereIn"
90
+ query = query[fnc](key, array)
91
+ })
92
+ }
93
+ if (filters.string) {
94
+ iterate(filters.string, (key, value) => {
95
+ const fnc = allOr ? "orWhere" : "where"
96
+ // postgres supports ilike, nothing else does
97
+ if (this.client === SqlClients.POSTGRES) {
98
+ query = query[fnc](key, "ilike", `${value}%`)
99
+ } else {
100
+ const rawFnc = `${fnc}Raw`
101
+ // @ts-ignore
102
+ query = query[rawFnc](`LOWER(${key}) LIKE ?`, [`${value}%`])
103
+ }
104
+ })
105
+ }
106
+ if (filters.fuzzy) {
107
+ iterate(filters.fuzzy, (key, value) => {
108
+ const fnc = allOr ? "orWhere" : "where"
109
+ // postgres supports ilike, nothing else does
110
+ if (this.client === SqlClients.POSTGRES) {
111
+ query = query[fnc](key, "ilike", `%${value}%`)
112
+ } else {
113
+ const rawFnc = `${fnc}Raw`
114
+ // @ts-ignore
115
+ query = query[rawFnc](`LOWER(${key}) LIKE ?`, [`%${value}%`])
116
+ }
117
+ })
118
+ }
119
+ if (filters.range) {
120
+ iterate(filters.range, (key, value) => {
121
+ if (!value.high || !value.low) {
122
+ return
123
+ }
124
+ const fnc = allOr ? "orWhereBetween" : "whereBetween"
125
+ query = query[fnc](key, [value.low, value.high])
126
+ })
127
+ }
128
+ if (filters.equal) {
129
+ iterate(filters.equal, (key, value) => {
130
+ const fnc = allOr ? "orWhere" : "where"
131
+ query = query[fnc]({ [key]: value })
132
+ })
133
+ }
134
+ if (filters.notEqual) {
135
+ iterate(filters.notEqual, (key, value) => {
136
+ const fnc = allOr ? "orWhereNot" : "whereNot"
137
+ query = query[fnc]({ [key]: value })
138
+ })
139
+ }
140
+ if (filters.empty) {
141
+ iterate(filters.empty, key => {
142
+ const fnc = allOr ? "orWhereNull" : "whereNull"
143
+ query = query[fnc](key)
144
+ })
145
+ }
146
+ if (filters.notEmpty) {
147
+ iterate(filters.notEmpty, key => {
148
+ const fnc = allOr ? "orWhereNotNull" : "whereNotNull"
149
+ query = query[fnc](key)
150
+ })
151
+ }
108
152
  return query
109
153
  }
110
- for (let relationship of relationships) {
111
- const from = relationship.from,
112
- to = relationship.to,
113
- toTable = relationship.tableName
114
- if (!relationship.through) {
115
- // @ts-ignore
116
- query = query.leftJoin(
117
- toTable,
118
- `${fromTable}.${from}`,
119
- `${toTable}.${to}`
120
- )
121
- } else {
122
- const throughTable = relationship.through
123
- const fromPrimary = relationship.fromPrimary
124
- const toPrimary = relationship.toPrimary
125
- query = query
154
+
155
+ addRelationships(
156
+ knex: Knex,
157
+ query: KnexQuery,
158
+ fields: string | string[],
159
+ fromTable: string,
160
+ relationships: RelationshipsJson[] | undefined
161
+ ): KnexQuery {
162
+ if (!relationships) {
163
+ return query
164
+ }
165
+ for (let relationship of relationships) {
166
+ const from = relationship.from,
167
+ to = relationship.to,
168
+ toTable = relationship.tableName
169
+ if (!relationship.through) {
126
170
  // @ts-ignore
127
- .leftJoin(
128
- throughTable,
129
- `${fromTable}.${fromPrimary}`,
130
- `${throughTable}.${from}`
171
+ query = query.leftJoin(
172
+ toTable,
173
+ `${fromTable}.${from}`,
174
+ `${toTable}.${to}`
131
175
  )
132
- .leftJoin(toTable, `${toTable}.${toPrimary}`, `${throughTable}.${to}`)
176
+ } else {
177
+ const throughTable = relationship.through
178
+ const fromPrimary = relationship.fromPrimary
179
+ const toPrimary = relationship.toPrimary
180
+ query = query
181
+ // @ts-ignore
182
+ .leftJoin(
183
+ throughTable,
184
+ `${fromTable}.${fromPrimary}`,
185
+ `${throughTable}.${from}`
186
+ )
187
+ .leftJoin(toTable, `${toTable}.${toPrimary}`, `${throughTable}.${to}`)
188
+ }
133
189
  }
190
+ return query.limit(BASE_LIMIT)
134
191
  }
135
- return query.limit(BASE_LIMIT)
136
- }
137
192
 
138
- function buildCreate(
139
- knex: Knex,
140
- json: QueryJson,
141
- opts: QueryOptions
142
- ): KnexQuery {
143
- const { endpoint, body } = json
144
- let query: KnexQuery = knex(endpoint.entityId)
145
- const parsedBody = parseBody(body)
146
- // make sure no null values in body for creation
147
- for (let [key, value] of Object.entries(parsedBody)) {
148
- if (value == null) {
149
- delete parsedBody[key]
193
+ create(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery {
194
+ const { endpoint, body } = json
195
+ let query: KnexQuery = knex(endpoint.entityId)
196
+ const parsedBody = parseBody(body)
197
+ // make sure no null values in body for creation
198
+ for (let [key, value] of Object.entries(parsedBody)) {
199
+ if (value == null) {
200
+ delete parsedBody[key]
201
+ }
202
+ }
203
+ // mysql can't use returning
204
+ if (opts.disableReturning) {
205
+ return query.insert(parsedBody)
206
+ } else {
207
+ return query.insert(parsedBody).returning("*")
150
208
  }
151
209
  }
152
- // mysql can't use returning
153
- if (opts.disableReturning) {
210
+
211
+ bulkCreate(knex: Knex, json: QueryJson): KnexQuery {
212
+ const { endpoint, body } = json
213
+ let query: KnexQuery = knex(endpoint.entityId)
214
+ if (!Array.isArray(body)) {
215
+ return query
216
+ }
217
+ const parsedBody = body.map(row => parseBody(row))
154
218
  return query.insert(parsedBody)
155
- } else {
156
- return query.insert(parsedBody).returning("*")
157
219
  }
158
- }
159
220
 
160
- function buildRead(knex: Knex, json: QueryJson, limit: number): KnexQuery {
161
- let { endpoint, resource, filters, sort, paginate, relationships } = json
162
- const tableName = endpoint.entityId
163
- // select all if not specified
164
- if (!resource) {
165
- resource = { fields: [] }
166
- }
167
- let selectStatement: string | string[] = "*"
168
- // handle select
169
- if (resource.fields && resource.fields.length > 0) {
170
- // select the resources as the format "table.columnName" - this is what is provided
171
- // by the resource builder further up
172
- selectStatement = resource.fields.map(field => `${field} as ${field}`)
173
- }
174
- let foundLimit = limit || BASE_LIMIT
175
- // handle pagination
176
- let foundOffset: number | null = null
177
- if (paginate && paginate.page && paginate.limit) {
178
- // @ts-ignore
179
- const page = paginate.page <= 1 ? 0 : paginate.page - 1
180
- const offset = page * paginate.limit
181
- foundLimit = paginate.limit
182
- foundOffset = offset
183
- } else if (paginate && paginate.limit) {
184
- foundLimit = paginate.limit
185
- }
186
- // start building the query
187
- let query: KnexQuery = knex(tableName).limit(foundLimit)
188
- if (foundOffset) {
189
- query = query.offset(foundOffset)
190
- }
191
- if (sort) {
192
- for (let [key, value] of Object.entries(sort)) {
193
- const direction = value === SortDirection.ASCENDING ? "asc" : "desc"
194
- query = query.orderBy(key, direction)
221
+ read(knex: Knex, json: QueryJson, limit: number): KnexQuery {
222
+ let { endpoint, resource, filters, sort, paginate, relationships } = json
223
+ const tableName = endpoint.entityId
224
+ // select all if not specified
225
+ if (!resource) {
226
+ resource = { fields: [] }
195
227
  }
196
- }
197
- query = addFilters(tableName, query, filters)
198
- // @ts-ignore
199
- let preQuery: KnexQuery = knex({
228
+ let selectStatement: string | string[] = "*"
229
+ // handle select
230
+ if (resource.fields && resource.fields.length > 0) {
231
+ // select the resources as the format "table.columnName" - this is what is provided
232
+ // by the resource builder further up
233
+ selectStatement = resource.fields.map(field => `${field} as ${field}`)
234
+ }
235
+ let foundLimit = limit || BASE_LIMIT
236
+ // handle pagination
237
+ let foundOffset: number | null = null
238
+ if (paginate && paginate.page && paginate.limit) {
239
+ // @ts-ignore
240
+ const page = paginate.page <= 1 ? 0 : paginate.page - 1
241
+ const offset = page * paginate.limit
242
+ foundLimit = paginate.limit
243
+ foundOffset = offset
244
+ } else if (paginate && paginate.limit) {
245
+ foundLimit = paginate.limit
246
+ }
247
+ // start building the query
248
+ let query: KnexQuery = knex(tableName).limit(foundLimit)
249
+ if (foundOffset) {
250
+ query = query.offset(foundOffset)
251
+ }
252
+ if (sort) {
253
+ for (let [key, value] of Object.entries(sort)) {
254
+ const direction = value === SortDirection.ASCENDING ? "asc" : "desc"
255
+ query = query.orderBy(key, direction)
256
+ }
257
+ }
258
+ if (this.client === SqlClients.MS_SQL && !sort && paginate?.limit) {
259
+ // @ts-ignore
260
+ query = query.orderBy(json.meta?.table?.primary[0])
261
+ }
262
+ query = this.addFilters(tableName, query, filters)
200
263
  // @ts-ignore
201
- [tableName]: query,
202
- }).select(selectStatement)
203
- // handle joins
204
- return addRelationships(
205
- knex,
206
- preQuery,
207
- selectStatement,
208
- tableName,
209
- relationships
210
- )
211
- }
264
+ let preQuery: KnexQuery = knex({
265
+ // @ts-ignore
266
+ [tableName]: query,
267
+ }).select(selectStatement)
268
+ // handle joins
269
+ return this.addRelationships(
270
+ knex,
271
+ preQuery,
272
+ selectStatement,
273
+ tableName,
274
+ relationships
275
+ )
276
+ }
212
277
 
213
- function buildUpdate(
214
- knex: Knex,
215
- json: QueryJson,
216
- opts: QueryOptions
217
- ): KnexQuery {
218
- const { endpoint, body, filters } = json
219
- let query: KnexQuery = knex(endpoint.entityId)
220
- const parsedBody = parseBody(body)
221
- query = addFilters(endpoint.entityId, query, filters)
222
- // mysql can't use returning
223
- if (opts.disableReturning) {
224
- return query.update(parsedBody)
225
- } else {
226
- return query.update(parsedBody).returning("*")
278
+ update(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery {
279
+ const { endpoint, body, filters } = json
280
+ let query: KnexQuery = knex(endpoint.entityId)
281
+ const parsedBody = parseBody(body)
282
+ query = this.addFilters(endpoint.entityId, query, filters)
283
+ // mysql can't use returning
284
+ if (opts.disableReturning) {
285
+ return query.update(parsedBody)
286
+ } else {
287
+ return query.update(parsedBody).returning("*")
288
+ }
227
289
  }
228
- }
229
290
 
230
- function buildDelete(
231
- knex: Knex,
232
- json: QueryJson,
233
- opts: QueryOptions
234
- ): KnexQuery {
235
- const { endpoint, filters } = json
236
- let query: KnexQuery = knex(endpoint.entityId)
237
- query = addFilters(endpoint.entityId, query, filters)
238
- // mysql can't use returning
239
- if (opts.disableReturning) {
240
- return query.delete()
241
- } else {
242
- return query.delete().returning("*")
291
+ delete(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery {
292
+ const { endpoint, filters } = json
293
+ let query: KnexQuery = knex(endpoint.entityId)
294
+ query = this.addFilters(endpoint.entityId, query, filters)
295
+ // mysql can't use returning
296
+ if (opts.disableReturning) {
297
+ return query.delete()
298
+ } else {
299
+ return query.delete().returning("*")
300
+ }
243
301
  }
244
302
  }
245
303
 
246
- class SqlQueryBuilder {
247
- private readonly sqlClient: string
304
+ class SqlQueryBuilder extends SqlTableQueryBuilder {
248
305
  private readonly limit: number
249
306
  // pass through client to get flavour of SQL
250
307
  constructor(client: string, limit: number = BASE_LIMIT) {
251
- this.sqlClient = client
308
+ super(client)
252
309
  this.limit = limit
253
310
  }
254
311
 
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
312
  /**
264
313
  * @param json The JSON query DSL which is to be converted to SQL.
265
314
  * @param opts extra options which are to be passed into the query builder, e.g. disableReturning
@@ -267,21 +316,30 @@ class SqlQueryBuilder {
267
316
  * @return {{ sql: string, bindings: object }} the query ready to be passed to the driver.
268
317
  */
269
318
  _query(json: QueryJson, opts: QueryOptions = {}) {
270
- const client = knex({ client: this.sqlClient })
319
+ const sqlClient = this.getSqlClient()
320
+ const client = knex({ client: sqlClient })
271
321
  let query
322
+ const builder = new InternalBuilder(sqlClient)
272
323
  switch (this._operation(json)) {
273
324
  case Operation.CREATE:
274
- query = buildCreate(client, json, opts)
325
+ query = builder.create(client, json, opts)
275
326
  break
276
327
  case Operation.READ:
277
- query = buildRead(client, json, this.limit)
328
+ query = builder.read(client, json, this.limit)
278
329
  break
279
330
  case Operation.UPDATE:
280
- query = buildUpdate(client, json, opts)
331
+ query = builder.update(client, json, opts)
281
332
  break
282
333
  case Operation.DELETE:
283
- query = buildDelete(client, json, opts)
334
+ query = builder.delete(client, json, opts)
284
335
  break
336
+ case Operation.BULK_CREATE:
337
+ query = builder.bulkCreate(client, json)
338
+ break
339
+ case Operation.CREATE_TABLE:
340
+ case Operation.UPDATE_TABLE:
341
+ case Operation.DELETE_TABLE:
342
+ return this._tableQuery(json)
285
343
  default:
286
344
  throw `Operation type is not supported by SQL query builder`
287
345
  }
@@ -289,6 +347,85 @@ class SqlQueryBuilder {
289
347
  // @ts-ignore
290
348
  return query.toSQL().toNative()
291
349
  }
350
+
351
+ async getReturningRow(queryFn: Function, json: QueryJson) {
352
+ if (!json.extra || !json.extra.idFilter) {
353
+ return {}
354
+ }
355
+ const input = this._query({
356
+ endpoint: {
357
+ ...json.endpoint,
358
+ operation: Operation.READ,
359
+ },
360
+ resource: {
361
+ fields: [],
362
+ },
363
+ filters: json.extra.idFilter,
364
+ paginate: {
365
+ limit: 1,
366
+ },
367
+ meta: json.meta,
368
+ })
369
+ return queryFn(input, Operation.READ)
370
+ }
371
+
372
+ // when creating if an ID has been inserted need to make sure
373
+ // the id filter is enriched with it before trying to retrieve the row
374
+ checkLookupKeys(id: any, json: QueryJson) {
375
+ if (!id || !json.meta?.table || !json.meta.table.primary) {
376
+ return json
377
+ }
378
+ const primaryKey = json.meta.table.primary?.[0]
379
+ json.extra = {
380
+ idFilter: {
381
+ equal: {
382
+ [primaryKey]: id,
383
+ },
384
+ },
385
+ }
386
+ return json
387
+ }
388
+
389
+ // this function recreates the returning functionality of postgres
390
+ async queryWithReturning(
391
+ json: QueryJson,
392
+ queryFn: Function,
393
+ processFn: Function = (result: any) => result
394
+ ) {
395
+ const sqlClient = this.getSqlClient()
396
+ const operation = this._operation(json)
397
+ const input = this._query(json, { disableReturning: true })
398
+ if (Array.isArray(input)) {
399
+ const responses = []
400
+ for (let query of input) {
401
+ responses.push(await queryFn(query, operation))
402
+ }
403
+ return responses
404
+ }
405
+ let row
406
+ // need to manage returning, a feature mySQL can't do
407
+ if (operation === Operation.DELETE) {
408
+ row = processFn(await this.getReturningRow(queryFn, json))
409
+ }
410
+ const response = await queryFn(input, operation)
411
+ const results = processFn(response)
412
+ // same as delete, manage returning
413
+ if (operation === Operation.CREATE || operation === Operation.UPDATE) {
414
+ let id
415
+ if (sqlClient === SqlClients.MS_SQL) {
416
+ id = results?.[0].id
417
+ } else if (sqlClient === SqlClients.MY_SQL) {
418
+ id = results?.insertId
419
+ }
420
+ row = processFn(
421
+ await this.getReturningRow(queryFn, this.checkLookupKeys(id, json))
422
+ )
423
+ }
424
+ if (operation !== Operation.READ) {
425
+ return row
426
+ }
427
+ return results.length ? results : [{ [operation.toLowerCase()]: true }]
428
+ }
292
429
  }
293
430
 
294
431
  module.exports = SqlQueryBuilder