@budibase/server 0.9.187 → 0.9.190-alpha.1

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