@sap/cds 5.5.2 → 5.6.0

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 (228) hide show
  1. package/CHANGELOG.md +150 -17
  2. package/apis/services.d.ts +27 -1
  3. package/app/index.js +22 -11
  4. package/bin/build/buildTaskFactory.js +1 -1
  5. package/bin/build/provider/buildTaskProviderInternal.js +1 -1
  6. package/bin/build/provider/fiori/index.js +1 -1
  7. package/bin/build/provider/hana/2migration.js +8 -7
  8. package/bin/build/provider/java-cf/index.js +1 -1
  9. package/bin/deploy/to-hana/hana.js +1 -17
  10. package/common.cds +8 -0
  11. package/lib/compile/index.js +1 -1
  12. package/lib/compile/to/sql.js +22 -2
  13. package/lib/connect/bindings.js +2 -1
  14. package/lib/connect/index.js +1 -1
  15. package/lib/core/infer.js +1 -1
  16. package/lib/core/reflect.js +3 -1
  17. package/lib/env/index.js +175 -41
  18. package/lib/env/requires.js +24 -3
  19. package/lib/i18n/localize.js +31 -4
  20. package/lib/index.js +7 -6
  21. package/lib/log/format/kibana.js +6 -2
  22. package/lib/ql/DELETE.js +1 -1
  23. package/lib/ql/INSERT.js +1 -1
  24. package/lib/ql/Query.js +13 -10
  25. package/lib/ql/SELECT.js +15 -8
  26. package/lib/ql/UPDATE.js +1 -1
  27. package/lib/ql/Whereable.js +5 -0
  28. package/lib/req/context.js +87 -37
  29. package/lib/req/{impl.js → request.js} +1 -1
  30. package/lib/req/{res.js → response.js} +0 -0
  31. package/lib/serve/Service-api.js +1 -1
  32. package/lib/serve/Service-dispatch.js +12 -2
  33. package/lib/serve/Service-handlers.js +21 -7
  34. package/lib/serve/Service-methods.js +1 -1
  35. package/lib/serve/Transaction.js +7 -6
  36. package/lib/serve/index.js +1 -1
  37. package/lib/utils/axios.js +7 -0
  38. package/lib/utils/data.js +1 -1
  39. package/lib/utils/tests.js +5 -3
  40. package/libx/_runtime/audit/Service.js +18 -18
  41. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  42. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  43. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  44. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +6 -0
  45. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  46. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  47. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  48. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  49. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -1
  50. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  51. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  52. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
  53. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  54. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  55. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  56. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  57. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  58. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  59. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  60. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  63. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
  64. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  65. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  66. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  67. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  68. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +9 -2
  69. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +14 -19
  70. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  71. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  72. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  73. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  74. package/libx/_runtime/cds-services/util/assert.js +29 -13
  75. package/libx/_runtime/cds.js +2 -1
  76. package/libx/_runtime/common/aspects/Association.js +72 -0
  77. package/libx/_runtime/common/aspects/any.js +8 -45
  78. package/libx/_runtime/common/aspects/entity.js +0 -1
  79. package/libx/_runtime/common/aspects/relation.js +40 -0
  80. package/libx/_runtime/common/aspects/utils.js +73 -1
  81. package/libx/_runtime/common/auth/strategies/utils/uaa.js +1 -12
  82. package/libx/_runtime/common/composition/data.js +3 -2
  83. package/libx/_runtime/common/composition/delete.js +3 -1
  84. package/libx/_runtime/common/composition/tree.js +23 -18
  85. package/libx/_runtime/common/composition/utils.js +34 -8
  86. package/libx/_runtime/common/error/frontend.js +6 -1
  87. package/libx/_runtime/common/generic/auth.js +15 -13
  88. package/libx/_runtime/common/generic/crud.js +2 -2
  89. package/libx/_runtime/common/generic/etag.js +11 -8
  90. package/libx/_runtime/common/generic/input.js +3 -3
  91. package/libx/_runtime/common/generic/paging.js +9 -5
  92. package/libx/_runtime/common/generic/put.js +3 -2
  93. package/libx/_runtime/common/generic/sorting.js +3 -3
  94. package/libx/_runtime/common/generic/temporal.js +3 -3
  95. package/libx/_runtime/common/toggles/alpha.js +1 -1
  96. package/libx/_runtime/common/utils/cqn.js +20 -1
  97. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  98. package/libx/_runtime/common/utils/csn.js +50 -52
  99. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  100. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  101. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  102. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  103. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  104. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  105. package/libx/_runtime/common/utils/resolveView.js +20 -10
  106. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  107. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  108. package/libx/_runtime/common/utils/template.js +54 -46
  109. package/libx/_runtime/db/Service.js +9 -2
  110. package/libx/_runtime/db/expand/expandCQNToJoin.js +11 -25
  111. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  112. package/libx/_runtime/db/generic/create.js +1 -0
  113. package/libx/_runtime/db/generic/input.js +7 -11
  114. package/libx/_runtime/db/generic/integrity.js +2 -2
  115. package/libx/_runtime/db/generic/rewrite.js +2 -5
  116. package/libx/_runtime/db/generic/update.js +1 -0
  117. package/libx/_runtime/db/query/read.js +10 -5
  118. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +6 -0
  119. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  120. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  121. package/libx/_runtime/db/utils/columns.js +14 -43
  122. package/libx/_runtime/db/utils/deep.js +5 -7
  123. package/libx/_runtime/fiori/generic/activate.js +3 -2
  124. package/libx/_runtime/fiori/generic/before.js +2 -2
  125. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  126. package/libx/_runtime/fiori/generic/delete.js +3 -2
  127. package/libx/_runtime/fiori/generic/edit.js +2 -2
  128. package/libx/_runtime/fiori/generic/new.js +2 -2
  129. package/libx/_runtime/fiori/generic/patch.js +2 -2
  130. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  131. package/libx/_runtime/fiori/generic/read.js +17 -63
  132. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  133. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  134. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  135. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  136. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  137. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  138. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  139. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  140. package/libx/_runtime/fiori/utils/handler.js +3 -13
  141. package/libx/_runtime/fiori/utils/where.js +6 -1
  142. package/libx/_runtime/hana/Service.js +5 -2
  143. package/libx/_runtime/hana/execute.js +1 -1
  144. package/libx/_runtime/hana/pool.js +12 -11
  145. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  146. package/libx/_runtime/hana/searchToContains.js +3 -3
  147. package/libx/_runtime/index.js +5 -2
  148. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  149. package/libx/_runtime/messaging/common-utils/AMQPClient.js +9 -1
  150. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  151. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  152. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  153. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  154. package/libx/_runtime/messaging/message-queuing.js +18 -0
  155. package/libx/_runtime/remote/Service.js +14 -2
  156. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  157. package/libx/_runtime/remote/utils/client.js +117 -23
  158. package/libx/_runtime/sqlite/Service.js +4 -3
  159. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  160. package/libx/_runtime/sqlite/execute.js +1 -1
  161. package/libx/gql/GraphQLAdapter.js +33 -0
  162. package/libx/gql/constants/adapter.js +69 -0
  163. package/libx/gql/constants/cds.js +18 -0
  164. package/libx/gql/constants/graphql.js +33 -0
  165. package/libx/gql/resolvers/crud/create.js +15 -0
  166. package/libx/gql/resolvers/crud/delete.js +24 -0
  167. package/libx/gql/resolvers/crud/index.js +6 -0
  168. package/libx/gql/resolvers/crud/read.js +25 -0
  169. package/libx/gql/resolvers/crud/update.js +31 -0
  170. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  171. package/libx/gql/resolvers/field.js +5 -0
  172. package/libx/gql/resolvers/index.js +7 -0
  173. package/libx/gql/resolvers/mutation.js +23 -0
  174. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  175. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  176. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  177. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  178. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  179. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  180. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  181. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  182. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  183. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  184. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  185. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  186. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  187. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  188. package/libx/gql/resolvers/query.js +13 -0
  189. package/libx/gql/resolvers/root.js +34 -0
  190. package/libx/gql/schema/generate.js +18 -0
  191. package/libx/gql/schema/index.js +5 -0
  192. package/libx/gql/schema/mutation.js +76 -0
  193. package/libx/gql/schema/query.js +108 -0
  194. package/libx/gql/schema/typeDefMap.js +45 -0
  195. package/libx/gql/schema/utils/index.js +54 -0
  196. package/libx/gql/utils/index.js +12 -0
  197. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  198. package/libx/odata/index.js +80 -0
  199. package/libx/odata/odata2cqn/afterburner.js +170 -0
  200. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  201. package/libx/odata/odata2cqn/index.js +3 -0
  202. package/libx/odata/odata2cqn/parser.js +1 -0
  203. package/libx/odata/utils/index.js +64 -0
  204. package/libx/rest/RestAdapter.js +101 -0
  205. package/libx/rest/RestRequest.js +30 -0
  206. package/libx/rest/index.js +3 -0
  207. package/libx/rest/middleware/auth.js +22 -0
  208. package/libx/rest/middleware/content.js +15 -0
  209. package/libx/rest/middleware/create.js +40 -0
  210. package/libx/rest/middleware/delete.js +20 -0
  211. package/libx/rest/middleware/error.js +56 -0
  212. package/libx/rest/middleware/operation.js +39 -0
  213. package/libx/rest/middleware/parse.js +90 -0
  214. package/libx/rest/middleware/read.js +29 -0
  215. package/libx/rest/middleware/update.js +42 -0
  216. package/libx/rest/utils/data.js +65 -0
  217. package/package.json +4 -1
  218. package/server.js +42 -29
  219. package/lib/req/cls.js +0 -39
  220. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  221. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  222. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  223. package/libx/_runtime/common/utils/backlinks.js +0 -83
  224. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  225. package/libx/_runtime/odata/index.js +0 -55
  226. package/libx/_runtime/odata/odata2cqn.js +0 -1
  227. package/libx/_runtime/odata/readToCqn.js +0 -129
  228. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -2,6 +2,7 @@ const cds = require('../../cds')
2
2
  let LOG = cds.log('app')
3
3
  let _event
4
4
  const PERSISTENCE_TABLE = '@cds.persistence.table'
5
+ const { rewriteAsterisks } = require('../../common/utils/rewriteAsterisks')
5
6
 
6
7
  const getError = require('../error')
7
8
  const { getEntityNameFromDeleteCQN, getEntityNameFromUpdateCQN } = require('../utils/cqn')
@@ -172,7 +173,7 @@ const _newColumns = (columns = [], transition, service, withAlias = false) => {
172
173
  }
173
174
 
174
175
  // ensure that renaming of a redirected assoc are also respected
175
- if (column.expand) {
176
+ if (mapped && column.expand) {
176
177
  // column.ref might be structured elements
177
178
  let def
178
179
  column.ref.forEach((ref, i) => {
@@ -186,9 +187,7 @@ const _newColumns = (columns = [], transition, service, withAlias = false) => {
186
187
  // reuse _newColumns with new transition
187
188
  const expandTarget = def._target
188
189
  const subtransition = getTransition(expandTarget, service)
189
-
190
- // REVISIT: in edom-retailer case, mapped was undefined which lead to a type error
191
- if (mapped) mapped.transition = subtransition
190
+ mapped.transition = subtransition
192
191
 
193
192
  newColumn.expand = _newColumns(column.expand, subtransition, service, withAlias)
194
193
  }
@@ -338,8 +337,11 @@ const _newSelect = (query, transitions, service) => {
338
337
  ref: _rewriteQueryPath(query.SELECT.from, transitions)
339
338
  }
340
339
  if (!newSelect.columns && targetTransition.mapping.size) newSelect.columns = _initialColumns(targetTransition)
341
- if (newSelect.columns)
340
+ if (newSelect.columns) {
341
+ const isDB = service instanceof cds.DatabaseService
342
+ rewriteAsterisks({ SELECT: newSelect }, targetTransition.queryTarget, isDB)
342
343
  newSelect.columns = _newColumns(newSelect.columns, targetTransition, service, service.kind !== 'app-service')
344
+ }
343
345
  if (newSelect.having) newSelect.having = _newColumns(newSelect.having, targetTransition)
344
346
  if (newSelect.groupBy) newSelect.groupBy = _newColumns(newSelect.groupBy, targetTransition)
345
347
  if (newSelect.orderBy) newSelect.orderBy = _newColumns(newSelect.orderBy, targetTransition)
@@ -417,7 +419,7 @@ const _queryColumns = (target, columns = [], persistenceTable = false, force = f
417
419
  if (!(target && target.query && target.query.SELECT)) return columns
418
420
  const cqnColumns = target.query.SELECT.columns || []
419
421
  const from = target.query.SELECT.from
420
- const isTargetAliased = from.as && !target.query._target.query && cqnColumns.some(c => c.ref && c.ref[0] === from.as)
422
+ const isTargetAliased = from.as && cqnColumns.some(c => c.ref && c.ref[0] === from.as)
421
423
  if (!columns.length) columns = Object.keys(target.elements).map(e => ({ ref: [e], as: e }))
422
424
  return columns.reduce((res, column) => {
423
425
  const renamed = _findRenamed(cqnColumns, column)
@@ -500,18 +502,26 @@ const _getTransitionData = (target, columns, service, skipForbiddenViewCheck) =>
500
502
  if (!skipForbiddenViewCheck) _checkForForbiddenViews(target)
501
503
  const targetStartsWithSrvName = service.namespace && target.name.startsWith(`${service.namespace}.`)
502
504
  const persistenceTable = _isPersistenceTable(target)
503
- columns = _queryColumns(target, columns, persistenceTable, service.name !== 'db' && !targetStartsWithSrvName)
504
- if (persistenceTable && service.name === 'db') {
505
+ columns = _queryColumns(
506
+ target,
507
+ columns,
508
+ persistenceTable,
509
+ !(service instanceof cds.DatabaseService) && !targetStartsWithSrvName
510
+ )
511
+ if (persistenceTable && service instanceof cds.DatabaseService) {
505
512
  return { target, transitionColumns: columns }
506
513
  }
507
514
  // stop projection resolving if it starts with the service name prefix
508
- if (service.name !== 'db' && targetStartsWithSrvName) {
515
+ if (!(service instanceof cds.DatabaseService) && targetStartsWithSrvName) {
509
516
  return { target, transitionColumns: columns }
510
517
  }
511
518
  // continue projection resolving if the target is a projection
512
519
  if (target.query && target.query._target) {
513
520
  const newTarget = target.query._target
514
- if (!(service.namespace && newTarget.name.startsWith(`${service.namespace}.`))) {
521
+ if (
522
+ service instanceof cds.DatabaseService ||
523
+ !(service.namespace && newTarget.name.startsWith(`${service.namespace}.`))
524
+ ) {
515
525
  return _getTransitionData(newTarget, columns, service, skipForbiddenViewCheck)
516
526
  }
517
527
  return { target: newTarget, transitionColumns: columns }
@@ -0,0 +1,94 @@
1
+ const { getNavigationIfStruct } = require('./structured')
2
+ const getColumns = require('../../db/utils/columns')
3
+ const { ensureDraftsSuffix } = require('./draft')
4
+
5
+ const isAsteriskColumn = col => col === '*' || (col.ref && col.ref[0] === '*' && !col.expand)
6
+
7
+ const _isDuplicate = newColumn => column => {
8
+ if (newColumn.as) return column.as && column.as === newColumn.as
9
+ if (!column.ref) return
10
+ if (Array.isArray(newColumn)) newColumn = { ref: newColumn }
11
+ return newColumn.ref ? newColumn.ref.join('_') === column.ref.join('_') : newColumn === column.ref.join('_')
12
+ }
13
+
14
+ const _cqlDraftColumns = target => {
15
+ if (target.name.endsWith('DraftAdministrativeData')) return []
16
+ const draftName = ensureDraftsSuffix(target.name)
17
+ const subSelect = SELECT.from(draftName).columns([1])
18
+ for (const key in target.keys) {
19
+ if (key !== 'IsActiveEntity') subSelect.where([{ ref: [target.name, key] }, '=', { ref: [draftName, key] }])
20
+ }
21
+ return [
22
+ { val: true, as: 'IsActiveEntity', cast: { type: 'cds.Boolean' } },
23
+ { val: false, as: 'HasActiveEntity', cast: { type: 'cds.Boolean' } },
24
+ {
25
+ xpr: ['case', 'when', 'exists', subSelect, 'then', 'true', 'else', 'false', 'end'],
26
+ as: 'HasDraftEntity',
27
+ cast: { type: 'cds.Boolean' }
28
+ }
29
+ ]
30
+ }
31
+
32
+ const _expandColumn = (column, target, db) => {
33
+ if (!(column.ref && column.expand)) return
34
+ const nextTarget = getNavigationIfStruct(target, column.ref)
35
+ if (nextTarget && nextTarget._target && nextTarget._target.elements) _rewriteAsterisks(column, nextTarget._target, db)
36
+ return column
37
+ }
38
+
39
+ const rewriteExpandAsterisk = (columns, target) => {
40
+ const expandAllColIdx = columns.findIndex(col => {
41
+ if (col.ref || !col.expand) return
42
+ return !Array.isArray(col.expand) ? col.expand === '*' : col.expand.indexOf('*') > -1
43
+ })
44
+ if (expandAllColIdx > -1) {
45
+ const { expand } = columns.splice(expandAllColIdx, 1)[0]
46
+ for (const elName in target.elements) {
47
+ if (target.elements[elName]._target && !columns.find(col => col.expand && col.ref && col.ref[0] === elName)) {
48
+ columns.push({ ref: [elName], expand: [...expand] })
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ const _rewriteAsterisk = (columns, target, db, isRoot) => {
55
+ const asteriskColumnIndex = columns.findIndex(col => isAsteriskColumn(col))
56
+ if (asteriskColumnIndex > -1) {
57
+ columns.splice(
58
+ asteriskColumnIndex,
59
+ 1,
60
+ ...getColumns(target, { db })
61
+ .map(c => ({ ref: [c.name] }))
62
+ .filter(c => !columns.find(_isDuplicate(c)) && (isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID'))
63
+ )
64
+ }
65
+ }
66
+
67
+ const _rewriteAsterisks = (cqn, target, db, isRoot) => {
68
+ if (cqn.expand === '*') cqn.expand = ['*']
69
+ const columns = cqn.expand || cqn.columns
70
+ _rewriteAsterisk(columns, target, db, isRoot)
71
+ rewriteExpandAsterisk(columns, target)
72
+ for (const column of columns) {
73
+ _expandColumn(column, target, db)
74
+ }
75
+ return columns
76
+ }
77
+
78
+ const rewriteAsterisks = (query, target, db = false, isDraft = false, onlyKeys = false) => {
79
+ if (!target || target.name.endsWith('_drafts')) return
80
+ if (!query.SELECT.columns || (query.SELECT.columns && !query.SELECT.columns.length)) {
81
+ if (isDraft || db) {
82
+ query.SELECT.columns = getColumns(target, { db, onlyKeys }).map(col => ({ ref: [col.name] }))
83
+ if (db && target._isDraftEnabled) query.SELECT.columns.push(..._cqlDraftColumns(target))
84
+ }
85
+ return
86
+ }
87
+ query.SELECT.columns = _rewriteAsterisks(query.SELECT, target, db, true)
88
+ }
89
+
90
+ module.exports = {
91
+ rewriteAsterisks,
92
+ isAsteriskColumn,
93
+ rewriteExpandAsterisk
94
+ }
@@ -3,23 +3,24 @@ const searchToLike = require('./searchToLike')
3
3
 
4
4
  // convert $search system query option to WHERE/HAVING clause using
5
5
  // the operator LIKE or CONTAINS
6
- const search2cqn4sql = (cqn, model, options) => {
7
- const { search2cqn4sql, targetName = cqn.SELECT.from.ref[0] } = options
6
+ const search2cqn4sql = (query, model, options) => {
7
+ const { search2cqn4sql, targetName = query.SELECT.from.ref[0] } = options
8
8
  const entity = model.definitions[targetName]
9
- const columns = computeColumnsToBeSearched(cqn, entity)
9
+ const columns = computeColumnsToBeSearched(query, entity)
10
10
 
11
- // call custom (optimized search to cqn for sql implementation) that tries
11
+ // Call custom (optimized search to cqn for sql implementation) that tries
12
12
  // to optimize the search behavior for a specific database service.
13
- if (typeof search2cqn4sql === 'function') {
13
+ // Note: $search query option combined with $filter is not currently optimized
14
+ if (typeof search2cqn4sql === 'function' && !query.SELECT.where) {
14
15
  const search2cqnOptions = { columns, locale: options.locale }
15
- return search2cqn4sql(cqn, entity, search2cqnOptions)
16
+ return search2cqn4sql(query, entity, search2cqnOptions)
16
17
  }
17
18
 
18
- const cqnSearchPhrase = cqn.SELECT.search
19
+ const cqnSearchPhrase = query.SELECT.search
19
20
  const expression = searchToLike(cqnSearchPhrase, columns)
20
21
 
21
22
  // REVISIT: find out here if where or having must be used
22
- cqn._aggregated ? cqn.having(expression) : cqn.where(expression)
23
+ query._aggregated ? query.having(expression) : query.where(expression)
23
24
  }
24
25
 
25
26
  module.exports = search2cqn4sql
@@ -48,57 +48,60 @@ const _getNextTarget = (model, element, currentPath = []) => {
48
48
  nextTargetName,
49
49
  nextTarget: model.definitions[nextTargetName]
50
50
  }
51
- } else if (_isInlineStructured(element)) {
51
+ }
52
+
53
+ if (_isInlineStructured(element)) {
52
54
  return {
53
55
  nextTargetName: [...currentPath, element.name].join(DELIMITER),
54
56
  nextTarget: element.items || element
55
57
  }
56
58
  }
59
+
57
60
  return {}
58
61
  }
59
62
 
60
63
  /**
61
64
  *
62
- * @param {CSN} model: Model
63
- * @param {*} cache: Internal - do not use
64
- * @param {CSN} target: target entity which needs to be traversed
65
- * @param {*} param1.pick: Function to pick items. If it returns a truthy value, the item will be picked. The returned value is part of the template.
66
- * @param {*} param1.ignore: Function to ignore items. If it returns a truthy value, the item will be ignored.
67
- * @param {*} parent: The parent entity
68
- * @param {*} entityMap: Internal - do not use
69
- * @param model
70
- * @param targetName
71
- * @param parent
72
- * @param entityMap
65
+ * @param {import('@sap/cds-compiler/lib/api/main').CSN} model Model
66
+ * @param {Map} cache Internal - do not use
67
+ * @param {object} targetEntity The target entity which needs to be traversed
68
+ * @param {object} callbacks
69
+ * @param {function} callbacks.pick Callback function to pick elements. If it returns a truthy value, the element will be picked. The returned value is part of the template.
70
+ * @param {function} callbacks.ignore Callback function to ignore elements. If it returns a truthy value, the element will be ignored.
71
+ * @param {object} [parent=null] The parent entity
72
+ * @param {Map} [_entityMap] This parameter is an implementation side-effect — don't use it
73
+ * @param {array} [targetPath=[]]
73
74
  */
74
- function _getTemplate(model, cache, target, { pick, ignore }, parent = null, entityMap = new Map(), targetPath = []) {
75
+ function _getTemplate(model, cache, targetEntity, callbacks, parent = null, _entityMap = new Map(), targetPath = []) {
76
+ const { pick, ignore } = callbacks
75
77
  const templateElements = new Map()
76
- const template = { target, elements: templateElements }
77
- const currentPath = [...targetPath, target.name]
78
- entityMap.set(currentPath.join(DELIMITER), { template })
79
-
80
- if (target.elements) {
81
- for (const elementName in target.elements) {
82
- const element = target.elements[elementName]
83
- if (ignore && ignore(element, target, parent)) continue
84
-
85
- _pick(pick, element, target, parent, templateElements, elementName)
86
- if (element.items) {
87
- _pick(pick, element.items, target, parent, templateElements, ['_itemsOf', elementName].join(DELIMITER))
88
- }
89
-
90
- const { nextTargetName, nextTarget } = _getNextTarget(model, element, currentPath)
91
- const nextTargetCached = entityMap.get(nextTargetName)
92
- if (nextTargetCached) {
93
- _addCacheToTemplateElements(templateElements, elementName, nextTargetCached)
94
- } else if (nextTarget) {
95
- // For associations and _typed_ structured elements, there's a (cacheable) target,
96
- // inline structures must be handled separately.
97
- const subTemplate = _isInlineStructured(element)
98
- ? _getTemplate(model, cache, nextTarget, { pick, ignore }, target, entityMap, currentPath)
99
- : cache.for(nextTarget, getTemplate(model, { pick, ignore }, target, entityMap))
100
- _addSubTemplate(templateElements, elementName, subTemplate)
101
- }
78
+ const template = { target: targetEntity, elements: templateElements }
79
+ const currentPath = [...targetPath, targetEntity.name]
80
+ _entityMap.set(currentPath.join(DELIMITER), { template })
81
+ if (!targetEntity.elements) return template
82
+
83
+ for (const elementName in targetEntity.elements) {
84
+ const element = targetEntity.elements[elementName]
85
+ if (ignore && ignore(element, targetEntity, parent)) continue
86
+
87
+ _pick(pick, element, targetEntity, parent, templateElements, elementName)
88
+
89
+ if (element.items) {
90
+ _pick(pick, element.items, targetEntity, parent, templateElements, ['_itemsOf', elementName].join(DELIMITER))
91
+ }
92
+
93
+ const { nextTargetName, nextTarget } = _getNextTarget(model, element, currentPath)
94
+ const nextTargetCached = _entityMap.get(nextTargetName)
95
+
96
+ if (nextTargetCached) {
97
+ _addCacheToTemplateElements(templateElements, elementName, nextTargetCached)
98
+ } else if (nextTarget) {
99
+ // For associations and _typed_ structured elements, there's a (cacheable) target,
100
+ // inline structures must be handled separately.
101
+ const subTemplate = _isInlineStructured(element)
102
+ ? _getTemplate(model, cache, nextTarget, { pick, ignore }, targetEntity, _entityMap, currentPath)
103
+ : cache.for(nextTarget, getTemplate(model, { pick, ignore }, targetEntity, _entityMap))
104
+ _addSubTemplate(templateElements, elementName, subTemplate)
102
105
  }
103
106
  }
104
107
 
@@ -112,11 +115,11 @@ const getTemplate =
112
115
 
113
116
  const getCache = (anything, cache, newCacheFn) => {
114
117
  let _cached = cache.get(anything)
115
- if (!_cached) {
116
- _cached = (typeof newCacheFn === 'function' && newCacheFn(anything, cache)) || new Map()
117
- _cached.for = (_usecase, _newCacheFn) => getCache(_usecase, _cached, _newCacheFn)
118
- cache.set(anything, _cached)
119
- }
118
+ if (_cached) return _cached
119
+
120
+ _cached = (typeof newCacheFn === 'function' && newCacheFn(anything, cache)) || new Map()
121
+ _cached.for = (_usecase, _newCacheFn) => getCache(_usecase, _cached, _newCacheFn)
122
+ cache.set(anything, _cached)
120
123
  return _cached
121
124
  }
122
125
 
@@ -124,11 +127,13 @@ module.exports = (usecase, tx, target, ...args) => {
124
127
  // get model first as it may be added to tx (cf. "_ensureModel")
125
128
  const model = tx.model
126
129
  if (!model) return
130
+
127
131
  // double-check with get target from model
128
132
  // since target might come from anywhere like via cqn etc
129
133
  if (!target) return
130
134
  const root = (model && model.definitions[target.name]) || (target.elements && target)
131
135
  if (!root) return
136
+
132
137
  // tx could be the service itself
133
138
  // prefer ApplicationService (i.e., tx.context._tx.__proto__)
134
139
  // REVISIT: context._tx is not a stable API -> pls do not rely on that
@@ -136,10 +141,13 @@ module.exports = (usecase, tx, target, ...args) => {
136
141
  ? (tx.context._tx && Object.getPrototypeOf(tx.context._tx)) || Object.getPrototypeOf(tx)
137
142
  : tx
138
143
  if (!service) return
144
+
139
145
  // cache templates at service for garbage collection
140
- if (!service._templateCache) service._templateCache = new Map()
146
+ if (usecase && !service._templateCache) service._templateCache = new Map()
141
147
  // model can be also a subset from tx
142
- return getCache(usecase, service._templateCache)
148
+
149
+ // if no usecase, don't save cache on the service object
150
+ return getCache(usecase, usecase ? service._templateCache : new Map())
143
151
  .for(model)
144
152
  .for(root, getTemplate(model, ...args))
145
153
  }
@@ -109,6 +109,7 @@ class DatabaseService extends cds.Service {
109
109
  if (query && (!streamQuery.SELECT.columns || streamQuery.SELECT.columns.length !== 0)) {
110
110
  streamQuery.columns([query])
111
111
  }
112
+
112
113
  delete streamQuery.SELECT.one
113
114
  streamQuery._streaming = true
114
115
 
@@ -119,12 +120,18 @@ class DatabaseService extends cds.Service {
119
120
  }
120
121
  })
121
122
 
122
- if (!streamQuery.SELECT.where) {
123
+ if (
124
+ !streamQuery.SELECT.where &&
125
+ !(
126
+ streamQuery.SELECT.from &&
127
+ streamQuery.SELECT.from.ref &&
128
+ streamQuery.SELECT.from.ref[streamQuery.SELECT.from.ref.length - 1].where
129
+ )
130
+ ) {
123
131
  return {
124
132
  where: (...args) => {
125
133
  streamQuery.where(...args)
126
134
  this._runStream(streamQuery, result)
127
-
128
135
  return result
129
136
  }
130
137
  }
@@ -1,11 +1,11 @@
1
1
  const cds = require('../../cds')
2
2
 
3
3
  const { getAllKeys } = require('../../cds-services/adapter/odata-v4/odata-to-cqn/utils')
4
- const { getOnCond } = require('../../common/utils/generateOnCond')
5
4
 
6
5
  const { getNavigationIfStruct } = require('../../common/utils/structured')
7
6
  const { ensureNoDraftsSuffix, ensureDraftsSuffix, ensureUnlocalized } = require('../../common/utils/draft')
8
7
  const { filterKeys } = require('../../fiori/utils/handler')
8
+ const { isAsteriskColumn } = require('../../common/utils/rewriteAsterisks')
9
9
 
10
10
  // Symbols are used to add extra information in response structure
11
11
  const GET_KEY_VALUE = Symbol.for('sap.cds.getKeyValue')
@@ -656,7 +656,7 @@ class JoinCQNFromExpanded {
656
656
  SELECT: {
657
657
  columns: cols,
658
658
  from: unionFrom,
659
- as: 'b'
659
+ as: tableAlias
660
660
  }
661
661
  }
662
662
  }
@@ -822,23 +822,12 @@ class JoinCQNFromExpanded {
822
822
  // No sub select
823
823
  const subSelectColumns = this._getSubSelectColumns(readToOneCQN)
824
824
 
825
- const navigation = getNavigationIfStruct(entity, tableAlias === columns[0] ? columns.slice(1) : columns)
826
- const onConditionOptions = {
827
- associationNames: columns,
828
- csn: this._csn,
829
- aliases: {
830
- select: tableAlias,
831
- join: parentAlias
832
- },
833
- resolveView: false
834
- }
835
-
836
825
  if (subSelectColumns.length === 0) {
837
- return getOnCond(navigation, onConditionOptions)
826
+ return entity._relations[tableAlias === columns[0] ? columns.slice(1) : columns].join(tableAlias, parentAlias)
838
827
  }
839
828
 
840
829
  const aliases = this._getAliases(subSelectColumns)
841
- const on = getOnCond(navigation, onConditionOptions)
830
+ const on = entity._relations[tableAlias === columns[0] ? columns.slice(1) : columns].join(tableAlias, parentAlias)
842
831
 
843
832
  for (const element of on) {
844
833
  if (element.ref && aliases[element.ref[0]] && aliases[element.ref[0]][element.ref[1]]) {
@@ -1069,15 +1058,7 @@ class JoinCQNFromExpanded {
1069
1058
  !this._csn.definitions[colTarget]._isDraftEnabled
1070
1059
  const ref = this._getJoinRef(entity.elements, colRef[0], expandActive, defaultLanguageThis)
1071
1060
  const tableAlias = this._createAlias(toManyTree.concat(colRef).join(':'))
1072
- const onConditionOptions = {
1073
- associationNames: colRef[0],
1074
- csn: this._csn,
1075
- aliases: {
1076
- select: tableAlias,
1077
- join: 'filterExpand'
1078
- }
1079
- }
1080
- const on = getOnCond(element, onConditionOptions)
1061
+ const on = entity._relations[colRef[0]].join(tableAlias, 'filterExpand')
1081
1062
  const filterExpand = this._getFilterExpandCQN(readToOneCQN, on, parentAlias, entity.keys)
1082
1063
  const expandedEntity = this._csn.definitions[colTarget]
1083
1064
  const joinColumns = this._getJoinColumnsFromOnAddToMapping(mappings[colRef[0]], parentAlias, on, entity)
@@ -1529,8 +1510,13 @@ class JoinCQNFromExpanded {
1529
1510
  }
1530
1511
 
1531
1512
  _isNotIncludedIn(columns) {
1513
+ if (columns.some(column => isAsteriskColumn(column))) return _ => false
1532
1514
  return entry =>
1533
- !columns.some(column => (column.ref && column.ref[1] === entry) || ('val' in column && column.as === entry))
1515
+ !columns.some(
1516
+ column =>
1517
+ (typeof column === 'object' && column.ref && column.ref[1] === entry) ||
1518
+ ('val' in column && column.as === entry)
1519
+ )
1534
1520
  }
1535
1521
 
1536
1522
  /**
@@ -95,7 +95,7 @@ class RawToExpanded {
95
95
 
96
96
  // the expanded items may include the actives of the deleted drafts -> filter out
97
97
  if (rootIsActiveEntity !== null) {
98
- if (mapping[TO_ACTIVE]) expandedItems = expandedItems.filter(ele => ele.IsActiveEntity === true)
98
+ if (mapping[TO_ACTIVE]) expandedItems = expandedItems.filter(ele => ele.IsActiveEntity !== false)
99
99
  else expandedItems = expandedItems.filter(ele => ele.IsActiveEntity === rootIsActiveEntity)
100
100
  }
101
101
 
@@ -119,6 +119,7 @@ class RawToExpanded {
119
119
  row[key] = parsed || null
120
120
  } else if (rootIsActiveEntity !== null) {
121
121
  if (mapping[TO_ACTIVE]) row[key] = parsed && parsed.IsActiveEntity !== false ? parsed : null
122
+ else if (rootIsActiveEntity) row[key] = parsed && parsed.IsActiveEntity !== false ? parsed : null
122
123
  else row[key] = parsed && parsed.IsActiveEntity === rootIsActiveEntity ? parsed : null
123
124
  }
124
125
  } else {
@@ -31,6 +31,7 @@ module.exports = async function (req) {
31
31
  // If entry is available, reject event
32
32
  // REVISIT: db specifics
33
33
  if (err.message.match(/unique constraint/i)) {
34
+ err.originalMessage = err.message
34
35
  err.message = 'ENTITY_ALREADY_EXISTS'
35
36
  err.code = 400
36
37
  }
@@ -13,8 +13,8 @@ const cds = require('../../cds')
13
13
 
14
14
  const normalizeTimeData = require('../utils/normalizeTimeData')
15
15
 
16
- const enrichDataWithKeysFromWhere = require('../../common/utils/enrichWithKeysFromWhere')
17
- const { foreignKeyPropagations, propagateForeignKeys } = require('../../common/utils/foreignKeyPropagations')
16
+ const { enrichDataWithKeysFromWhere } = require('../../common/utils/keys')
17
+ const { propagateForeignKeys } = require('../../common/utils/propagateForeignKeys')
18
18
  const getTemplate = require('../../common/utils/template')
19
19
  const templateProcessor = require('../../common/utils/templateProcessor')
20
20
 
@@ -25,13 +25,13 @@ const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
25
25
  const _isManaged = (category, event) =>
26
26
  (category === '@cds.on.insert' && event === 'CREATE') || (category === '@cds.on.update' && event === 'UPDATE')
27
27
 
28
- const _processComplexCategory = ({ row, key, val, category, req }) => {
28
+ const _processComplexCategory = ({ row, key, val, category, req, element }) => {
29
29
  const categoryArgs = category.args
30
30
  category = category.category
31
31
 
32
32
  // propagate keys
33
33
  if (category === 'propagateForeignKeys') {
34
- propagateForeignKeys(key, row, categoryArgs.foreignKeyPropagations, { onlyWriteCompositionEffective: true })
34
+ propagateForeignKeys(key, row, element._foreignKeys, element._isCompositionEffective)
35
35
  return
36
36
  }
37
37
 
@@ -63,7 +63,7 @@ const _processComplexCategory = ({ row, key, val, category, req }) => {
63
63
  const _processCategory = ({ category, row, key, element, val, req }) => {
64
64
  // use args only inside this if (sonar type error warning)
65
65
  if (typeof category === 'object') {
66
- _processComplexCategory({ category, row, key, val, req })
66
+ _processComplexCategory({ category, row, key, val, req, element })
67
67
  return
68
68
  }
69
69
 
@@ -136,12 +136,8 @@ const _pick = element => {
136
136
  categories.push('associationEffective')
137
137
  }
138
138
 
139
- if (element.isAssociation) {
140
- const _foreignKeyPropagations = foreignKeyPropagations(element)
141
-
142
- if (_foreignKeyPropagations) {
143
- categories.push({ category: 'propagateForeignKeys', args: { foreignKeyPropagations: _foreignKeyPropagations } })
144
- }
139
+ if (element.isAssociation && element._foreignKeys.length) {
140
+ categories.push({ category: 'propagateForeignKeys' })
145
141
  }
146
142
 
147
143
  // generate uuid
@@ -1,7 +1,7 @@
1
1
  const cds = require('../../cds')
2
2
  const { SELECT } = cds.ql
3
3
 
4
- const cqn2cqn4sql = require('../../common/utils/cqn2cqn4sql')
4
+ const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
5
5
  const { getDependents } = require('../../common/utils/csn')
6
6
 
7
7
  const CRUD = {
@@ -54,7 +54,7 @@ async function beforeDelete(req) {
54
54
  select = select.where(req.query.DELETE.where)
55
55
  }
56
56
 
57
- select = cqn2cqn4sql(select, this.model)
57
+ select = cqn2cqn4sql(select, this.model, { service: this })
58
58
 
59
59
  req._beforeDeleteData = await this._read(this.model, this.dbc, select, req.context || req)
60
60
  }
@@ -1,6 +1,5 @@
1
- const cqn2cqn4sql = require('../../common/utils/cqn2cqn4sql')
1
+ const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
2
2
  const generateAliases = require('../utils/generateAliases')
3
- const rewriteAsterisk = require('../../common/utils/rewriteAsterisk')
4
3
  const { restoreLink } = require('../../common/utils/resolveView')
5
4
 
6
5
  const _isLinked = req => {
@@ -21,13 +20,11 @@ function handler(req) {
21
20
  const streaming = req.query._streaming
22
21
  const validationQuery = req.query._validationQuery
23
22
 
24
- rewriteAsterisk(req)
25
-
26
23
  // for restore link to req.data
27
24
  const linked = _isLinked(req)
28
25
 
29
26
  // convert to sql cqn
30
- req.query = cqn2cqn4sql(req.query, this.model)
27
+ req.query = cqn2cqn4sql(req.query, this.model, { service: this })
31
28
 
32
29
  // REVISIT: should not be necessary
33
30
  // restore link to req.data
@@ -75,6 +75,7 @@ module.exports = async function (req) {
75
75
  } catch (err) {
76
76
  // REVISIT: db specifics
77
77
  if (err.message.match(/unique constraint/i)) {
78
+ err.originalMessage = err.message
78
79
  err.message = 'UNIQUE_CONSTRAINT_VIOLATION'
79
80
  err.code = 400
80
81
  }
@@ -10,8 +10,8 @@ function _arrayWithCount(a, count) {
10
10
  }
11
11
 
12
12
  function _createCountQuery(query) {
13
- const _query = JSON.parse(JSON.stringify(query))
14
- _query.SELECT.columns = [{ func: 'count', args: [{ val: 1 }], as: '_counted_' }]
13
+ const _query = JSON.parse(JSON.stringify(query)) // REVISIT: Use query.clone() instead
14
+ _query.SELECT.columns = [{ func: 'count', args: [{ val: 1 }], as: '$count' }]
15
15
  delete _query.SELECT.groupBy
16
16
  delete _query.SELECT.limit
17
17
  delete _query.SELECT.orderBy // not necessary to keep that
@@ -25,6 +25,11 @@ function _createCountQuery(query) {
25
25
  return _query
26
26
  }
27
27
 
28
+ const countValue = countResult => {
29
+ if (countResult._counted_ != null) return countResult._counted_
30
+ if (countResult.$count != null) return countResult.$count
31
+ }
32
+
28
33
  const read = (executeSelectCQN, executeStreamCQN) => (model, dbc, query, req) => {
29
34
  const { user, locale, timestamp } = req
30
35
  const isoTs = timestampToISO(timestamp)
@@ -37,7 +42,7 @@ const read = (executeSelectCQN, executeStreamCQN) => (model, dbc, query, req) =>
37
42
  }
38
43
 
39
44
  // needed in case of expand
40
- query._rootEntity = req.target
45
+ if (query._target !== req.target) query._target = req.target
41
46
 
42
47
  if (query.SELECT.count) {
43
48
  if (query.SELECT.limit) {
@@ -45,11 +50,11 @@ const read = (executeSelectCQN, executeStreamCQN) => (model, dbc, query, req) =>
45
50
  const countResultPromise = executeSelectCQN(model, dbc, countQuery, user, locale, isoTs)
46
51
  if (query.SELECT.limit.rows && query.SELECT.limit.rows.val === 0) {
47
52
  // We don't need to perform our result query
48
- return countResultPromise.then(countResult => _arrayWithCount([], countResult[0]._counted_))
53
+ return countResultPromise.then(countResult => _arrayWithCount([], countValue(countResult[0])))
49
54
  } else {
50
55
  const resultPromise = executeSelectCQN(model, dbc, query, user, locale, isoTs)
51
56
  return Promise.all([countResultPromise, resultPromise]).then(([countResult, result]) =>
52
- _arrayWithCount(result, countResult[0]._counted_)
57
+ _arrayWithCount(result, countValue(countResult[0]))
53
58
  )
54
59
  }
55
60
  } else {
@@ -227,6 +227,12 @@ class ExpressionBuilder extends BaseBuilder {
227
227
  * @private
228
228
  */
229
229
  _addInOrNotIn(reference, operator, values) {
230
+ if (values.val === null) {
231
+ this._addToOutputObj(new this.ReferenceBuilder(reference, this._options, this._csn).build(), false)
232
+ this._outputObj.sql.push('is null')
233
+ return true
234
+ }
235
+
230
236
  if (Array.isArray(values.val)) {
231
237
  this._addArrayForInQuery(reference, operator, values.val)
232
238
  return true