@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
@@ -0,0 +1,108 @@
1
+ const { ARGUMENT, HELPER_TYPES, EQUALITY_OPERATOR, STRING_MATCH_OPERATOR } = require('../constants/adapter')
2
+ const { GQL_ROOT, GQL_KEYWORDS, SCALAR_TYPES } = require('../constants/graphql')
3
+ const {
4
+ generateSchemaObject,
5
+ typeToArgumentType,
6
+ generateArgumentsForType,
7
+ isTypeScalar,
8
+ typeWithoutListBrackets
9
+ } = require('./utils')
10
+
11
+ const generateScalarFilterTypes = () => Object.values(SCALAR_TYPES).flatMap(scalarType => fullFilter(scalarType))
12
+
13
+ const fullFilter = type => {
14
+ const operations = Object.values(EQUALITY_OPERATOR)
15
+ if (type === SCALAR_TYPES.STRING) {
16
+ operations.push(...Object.values(STRING_MATCH_OPERATOR))
17
+ }
18
+ const entries = operations.reduce((acc, operation) => {
19
+ acc[operation] = type
20
+ return acc
21
+ }, {})
22
+ return generateSchemaObject(GQL_KEYWORDS.INPUT, `${type}_${ARGUMENT.FILTER}`, entries)
23
+ }
24
+
25
+ const typeDefMapToQueryStringArray = typeDefs => {
26
+ let schema = []
27
+
28
+ // Create root query (containing services)
29
+ schema.push(
30
+ ...generateSchemaObject(
31
+ GQL_KEYWORDS.TYPE,
32
+ GQL_ROOT.QUERY,
33
+ Object.keys(typeDefs).reduce((fields, serviceName) => {
34
+ fields[serviceName] = serviceName
35
+ return fields
36
+ }, {})
37
+ )
38
+ )
39
+
40
+ // Create types from services (each containing entities)
41
+ for (const [serviceName, entities] of Object.entries(typeDefs)) {
42
+ const fields = {}
43
+ for (const entityName of Object.keys(entities)) {
44
+ const entityNameWithoutServicePrefix = entityName.replace(`${serviceName}_`, '')
45
+ const argsString = generateArgumentsForType(entityName, [
46
+ ARGUMENT.FILTER,
47
+ ARGUMENT.ORDER_BY,
48
+ ARGUMENT.TOP,
49
+ ARGUMENT.SKIP
50
+ ])
51
+ fields[`${entityNameWithoutServicePrefix}${argsString}`] = `[${entityName}]`
52
+ }
53
+ schema.push(...generateSchemaObject(GQL_KEYWORDS.TYPE, serviceName, fields))
54
+ }
55
+
56
+ // Create types from entities (each containing elements)
57
+ for (const entities of Object.values(typeDefs)) {
58
+ for (const [entityName, elements] of Object.entries(entities)) {
59
+ const fields = {}
60
+ for (const [elementName, elementType] of Object.entries(elements)) {
61
+ const argsString = generateArgumentsForType(elementType, [
62
+ ARGUMENT.FILTER,
63
+ ARGUMENT.ORDER_BY,
64
+ ARGUMENT.TOP,
65
+ ARGUMENT.SKIP
66
+ ])
67
+ fields[`${elementName}${argsString}`] = elementType
68
+ }
69
+ schema.push(...generateSchemaObject(GQL_KEYWORDS.TYPE, entityName, fields))
70
+ }
71
+ }
72
+
73
+ // Create filter input types for scalars
74
+ schema.push(...generateScalarFilterTypes())
75
+
76
+ // Create filter input types for entity types
77
+ for (const entities of Object.values(typeDefs)) {
78
+ for (const [entityName, elements] of Object.entries(entities)) {
79
+ const fields = {}
80
+ for (const [elementName, elementType] of Object.entries(elements)) {
81
+ if (isTypeScalar(typeWithoutListBrackets(elementType))) {
82
+ fields[elementName] = typeToArgumentType(elementType, ARGUMENT.FILTER)
83
+ }
84
+ }
85
+ if (Object.keys(fields).length > 0) {
86
+ schema.push(...generateSchemaObject(GQL_KEYWORDS.INPUT, `${entityName}_${ARGUMENT.FILTER}`, fields))
87
+ }
88
+ }
89
+ }
90
+
91
+ // Create sort direction enum types for order by
92
+ schema.push(...generateSchemaObject(GQL_KEYWORDS.ENUM, HELPER_TYPES.SORT_DIRECTION, ['asc', 'desc']))
93
+
94
+ // Create orderBy input types for entity types
95
+ for (const entities of Object.values(typeDefs)) {
96
+ for (const [entityName, elements] of Object.entries(entities)) {
97
+ const fields = {}
98
+ for (const [elementName, elementType] of Object.entries(elements)) {
99
+ fields[elementName] = typeToArgumentType(elementType, ARGUMENT.ORDER_BY)
100
+ }
101
+ schema.push(...generateSchemaObject(GQL_KEYWORDS.INPUT, `${entityName}_${ARGUMENT.ORDER_BY}`, fields))
102
+ }
103
+ }
104
+
105
+ return schema
106
+ }
107
+
108
+ module.exports = { typeDefMapToQueryStringArray }
@@ -0,0 +1,45 @@
1
+ const { CDS_TO_GRAPHQL_TYPES } = require('../constants/adapter')
2
+ const { gqlName } = require('../utils')
3
+
4
+ const servicesToTypeDefMap = services => {
5
+ const typeDefs = {}
6
+
7
+ // Create nested map of services, their entities, and their respective elements
8
+ for (const service of services) {
9
+ const serviceDefs = (typeDefs[gqlName(service.name)] = {})
10
+
11
+ const serviceNamePrefix = `${service.name}.`
12
+ const entitiesKV = Object.entries(service.model.definitions).filter(
13
+ // eslint-disable-next-line no-unused-vars
14
+ ([k, _]) => k.startsWith(serviceNamePrefix) && service.model.definitions[k].kind === 'entity'
15
+ )
16
+
17
+ // eslint-disable-next-line no-unused-vars
18
+ for (const [_, entity] of entitiesKV) {
19
+ const def = (serviceDefs[gqlName(entity.name)] = {})
20
+ for (const ele of Object.values(entity.elements)) {
21
+ if (ele.name.startsWith('up_') || ele.name === 'localized' || ele.name === 'texts') {
22
+ continue
23
+ } else if (ele.isAssociation || ele.isComposition) {
24
+ if (!ele.target.startsWith(serviceNamePrefix)) {
25
+ // TODO entities in other namespaces
26
+ continue
27
+ }
28
+ def[ele.name] = ele.is2one ? gqlName(ele.target) : `[${gqlName(ele.target)}]`
29
+ } else if (ele.elements) {
30
+ // TODO structured types
31
+ continue
32
+ } else {
33
+ if (CDS_TO_GRAPHQL_TYPES[ele.type]) {
34
+ def[ele.name] = CDS_TO_GRAPHQL_TYPES[ele.type]
35
+ }
36
+ // TODO aspects
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ return typeDefs
43
+ }
44
+
45
+ module.exports = { servicesToTypeDefMap }
@@ -0,0 +1,54 @@
1
+ const { ARGUMENT, HELPER_TYPES } = require('../../constants/adapter')
2
+ const { GQL_LIST_REGEX, SCALAR_TYPES } = require('../../constants/graphql')
3
+
4
+ const generateSchemaObject = (type, name, fields) => {
5
+ const schema = [`${type} ${name} {`]
6
+ if (Array.isArray(fields)) {
7
+ schema.push(...fields.map(e => ` ${e}`))
8
+ } else {
9
+ schema.push(...Object.entries(fields).map(([k, v]) => ` ${k}: ${v}`))
10
+ }
11
+ schema.push('}', '')
12
+ return schema
13
+ }
14
+
15
+ const generateArgumentsForType = (type, args) =>
16
+ `(${args.map(action => `${action}: ${typeToArgumentType(type, action)}`).join(', ')})`
17
+
18
+ const typeToArgumentType = (type, action) => {
19
+ switch (action) {
20
+ case ARGUMENT.FILTER:
21
+ return appendSuffixToType(type, ARGUMENT.FILTER, true)
22
+ case ARGUMENT.ORDER_BY:
23
+ return isTypeScalar(type) ? HELPER_TYPES.SORT_DIRECTION : appendSuffixToType(type, ARGUMENT.ORDER_BY, true)
24
+ case ARGUMENT.TOP:
25
+ return SCALAR_TYPES.INT
26
+ case ARGUMENT.SKIP:
27
+ return SCALAR_TYPES.INT
28
+ }
29
+ }
30
+
31
+ const isTypeScalar = type => Object.values(SCALAR_TYPES).includes(type)
32
+
33
+ const isTypeList = type => type.match(GQL_LIST_REGEX)
34
+
35
+ // Type without list brackets even if type isn't a list
36
+ const typeWithoutListBrackets = type => type.replace(GQL_LIST_REGEX, '$1')
37
+
38
+ const appendSuffixToType = (type, suffix, forceToList) => {
39
+ if (isTypeList(type) || forceToList) {
40
+ return `[${typeWithoutListBrackets(type)}_${suffix}]`
41
+ } else {
42
+ return `${type}_${suffix}`
43
+ }
44
+ }
45
+
46
+ module.exports = {
47
+ generateSchemaObject,
48
+ generateArgumentsForType,
49
+ typeToArgumentType,
50
+ isTypeScalar,
51
+ isTypeList,
52
+ typeWithoutListBrackets,
53
+ appendSuffixToType
54
+ }
@@ -0,0 +1,12 @@
1
+ const cdsName = gqlName => {
2
+ return gqlName.replace(/_/g, '.')
3
+ }
4
+
5
+ const gqlName = cdsName => {
6
+ return cdsName.replace(/\./g, '_')
7
+ }
8
+
9
+ module.exports = {
10
+ cdsName,
11
+ gqlName
12
+ }
@@ -1,3 +1,5 @@
1
+ const { formatVal } = require('../utils')
2
+
1
3
  const OPERATORS = {
2
4
  '=': 'eq',
3
5
  '!=': 'ne',
@@ -10,23 +12,13 @@ const OPERATORS = {
10
12
 
11
13
  const LAMBDA_VARIABLE = 'd'
12
14
 
13
- const getSafeNumber = str => {
14
- const n = Number(str)
15
- return Number.isSafeInteger(n) || String(n) === str ? n : str
16
- }
17
-
18
15
  const needArrayProps = Object.fromEntries(
19
- ['where', 'search', 'xpr', 'columns', 'expand', 'orderBy', 'ref', 'args'].map(propName => [
16
+ ['where', 'search', 'xpr', 'columns', 'orderBy', 'ref', 'args'].map(propName => [
20
17
  propName,
21
- cur =>
22
- (Array.isArray(cur) && (cur.length !== 0 || propName === 'expand' || propName === 'ref')) ||
23
- (propName === 'expand' && cur === '*')
18
+ cur => Array.isArray(cur) && (cur.length !== 0 || propName === 'expand' || propName === 'ref')
24
19
  ])
25
20
  )
26
21
 
27
- const V4UUIDREGEXP = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
28
- const isV4UUID = val => V4UUIDREGEXP.test(val)
29
-
30
22
  const validators = {
31
23
  SELECT: SELECT => SELECT && SELECT.from,
32
24
  INSERT: INSERT => {
@@ -50,63 +42,13 @@ const validators = {
50
42
  func: func => typeof func === 'string',
51
43
  one: count => typeof count === 'boolean',
52
44
  as: any => typeof any === 'string',
45
+ expand: any => any === '*' || Array.isArray(any),
53
46
  ...needArrayProps
54
47
  }
55
48
 
56
49
  // strip service & namespace prefixes
57
50
  const _entityUrl = path => path.match(/^(\w*\.)*(.*)$/)[2]
58
51
 
59
- const formatVal = (val, element, csnTarget, kind) => {
60
- if (val === null || val === 'null') return 'null'
61
- if (typeof val === 'boolean') return val
62
- if (typeof val === 'number') return getSafeNumber(val)
63
- if (!csnTarget && typeof val === 'string' && isV4UUID(val)) return kind === 'odata-v2' ? `guid'${val}'` : val
64
-
65
- const csnElement = (csnTarget && csnTarget.elements && csnTarget.elements[element]) || { type: undefined }
66
- return kind === 'odata-v2' ? _odataV2Val(val, csnElement.type) : _val(val, csnElement.type)
67
- }
68
-
69
- const _isTimestamp = val =>
70
- /^\d+-\d\d-\d\d(T\d\d:\d\d(:\d\d(\.\d+)?)?(Z|([+-]{1}\d\d:\d\d))?)?$/.test(val) && !isNaN(Date.parse(val))
71
-
72
- const _odataV2Val = (val, type) => {
73
- switch (type) {
74
- case 'cds.Binary':
75
- case 'cds.LargeBinary':
76
- return `binary'${val}'`
77
- case 'cds.Date':
78
- case 'cds.DateTime':
79
- return `datetime'${val}'`
80
- case 'cds.Time':
81
- // eslint-disable-next-line no-case-declarations
82
- const [hh, mm, ss] = val.split(':')
83
- return `time'PT${hh}H${mm}M${ss}S'`
84
- case 'cds.Timestamp':
85
- return `datetimeoffset'${val}'`
86
- case 'cds.UUID':
87
- return `guid'${val}'`
88
- default:
89
- return `'${val}'`
90
- }
91
- }
92
-
93
- const _val = (val, type) => {
94
- switch (type) {
95
- case 'cds.Decimal':
96
- case 'cds.Integer64':
97
- return getSafeNumber(val)
98
- case 'cds.Boolean':
99
- case 'cds.DateTime':
100
- case 'cds.Date':
101
- case 'cds.Timestamp':
102
- case 'cds.Time':
103
- case 'cds.UUID':
104
- return val
105
- default:
106
- return _isTimestamp(val) ? val : `'${val}'`
107
- }
108
- }
109
-
110
52
  function getProp(obj, propName) {
111
53
  const validate = validators[propName]
112
54
  const isValid = validate && validate(obj[propName])
@@ -166,13 +108,25 @@ const _in = (column, /* in */ collection, target, kind, isLambda) => {
166
108
  }
167
109
  }
168
110
 
111
+ const _odataV2Func = (func, args) => {
112
+ switch (func) {
113
+ case 'contains':
114
+ // this doesn't support the contains signature with two collections as args, introduced in odata v4.01
115
+ return `substringof(${_args([args[1], args[0]])})`
116
+ default:
117
+ return `${func}(${_args(args)})`
118
+ }
119
+ }
120
+
169
121
  const _format = (cur, element, target, kind, isLambda) => {
170
122
  if (typeof cur !== 'object') return formatVal(cur, element, target, kind)
171
123
  if (hasValidProps(cur, 'ref')) return isLambda ? [LAMBDA_VARIABLE, ...cur.ref].join('/') : cur.ref.join('/')
172
124
  if (hasValidProps(cur, 'val')) return formatVal(cur.val, element, target, kind)
173
125
  if (hasValidProps(cur, 'xpr')) return `(${_xpr(cur.xpr, target, kind, isLambda)})`
174
126
  // REVISIT: How to detect the types for all functions?
175
- if (hasValidProps(cur, 'func', 'args')) return `${cur.func}(${_args(cur.args)})`
127
+ if (hasValidProps(cur, 'func', 'args')) {
128
+ return kind === 'odata-v2' ? _odataV2Func(cur.func, cur.args) : `${cur.func}(${_args(cur.args)})`
129
+ }
176
130
  }
177
131
 
178
132
  const _isLambda = (cur, next) => {
@@ -328,62 +282,48 @@ const _parseColumnsV2 = (columns, prefix = []) => {
328
282
  return { select, expand }
329
283
  }
330
284
 
331
- const _parseColumns = (columns, options) => {
332
- const isExpand = options.expand
285
+ const _parseColumns = columns => {
333
286
  const select = []
334
287
  const expand = []
335
288
 
336
- if (columns === '*') {
337
- return { select, expand }
338
- }
339
-
340
- const isSelectAll = select =>
341
- select.length === 1 && (select[0] === '*' || (select[0].ref && select[0].ref[0] === '*'))
342
-
343
289
  for (const column of columns) {
344
290
  if (hasValidProps(column, 'ref')) {
345
- const refName = column.ref.join('/')
346
- let refNameWithOptions = refName
347
-
291
+ let refName = column.ref.join('/')
348
292
  if (hasValidProps(column, 'expand')) {
293
+ // REVISIT: incomplete, see test Foo?$expand=invoices($count=true;$expand=item($search="some"))
294
+ if (!columns.some(c => !c.expand)) select.push(refName)
349
295
  const curOptions = getOptions(column).join(';')
350
- refNameWithOptions += curOptions ? `(${curOptions})` : ''
351
- expand.push(refNameWithOptions)
352
- if (!isSelectAll(select) && !isExpand) select.push(refName)
296
+ refName += curOptions ? `(${curOptions})` : ''
297
+ expand.push(refName)
353
298
  // REVISIT: expand to one & limit in options
354
- // > const expanded = $expand(column.expand)
355
- // > expand.push(expanded ? `${refNameWithOptions}(${expanded})` : ref)
299
+ // > const expanded = $expand(col.expand)
300
+ // > expand.push(expanded ? `${ref}(${expanded})` : ref)
356
301
  // see xtest('READ with expand'... in custom handler test
357
302
  } else {
358
- select.push(refNameWithOptions)
303
+ select.push(refName)
359
304
  }
305
+ } else if (hasValidProps(column, 'expand') && column.expand === '*') {
306
+ expand.push('*')
360
307
  }
361
-
362
308
  if (column === '*') {
363
309
  select.push(column)
364
310
  }
365
311
  }
366
-
367
- // omit $select query option if it only contains '*'
368
- if (isSelectAll(select)) {
312
+ // omit '$select' option if contains only '*'
313
+ if (select.length === 1 && (select[0] === '*' || (select[0].ref && select[0].ref[0] === '*'))) {
369
314
  select.pop()
370
315
  }
371
-
372
- const uniqueSelect = [...new Set(select)]
373
- return { select: uniqueSelect, expand }
316
+ return { select, expand }
374
317
  }
375
318
 
376
- function $select(columns, kind, separator = '&', isExpand = false) {
377
- const { select, expand } =
378
- kind === 'odata-v2' ? _parseColumnsV2(columns) : _parseColumns(columns, { expand: isExpand })
379
-
319
+ function $select(columns, kind, separator = '&') {
320
+ const { select, expand } = kind === 'odata-v2' ? _parseColumnsV2(columns) : _parseColumns(columns)
380
321
  const res = []
381
322
  if (expand.length) res.unshift('$expand=' + expand.join(','))
382
323
  if (select.length) res.unshift('$select=' + select.join(','))
383
324
  return res.join(separator)
384
325
  }
385
-
386
- const $expand = columns => $select(columns, 'odata', ';', true)
326
+ const $expand = columns => $select(columns, 'odata', ';')
387
327
 
388
328
  function $count(count, kind) {
389
329
  if (count !== true) return ''
@@ -476,7 +416,9 @@ const _isOdataUrlWithKeys = (url, kind) => kind !== 'rest' && /^[\w\.]+\(.*\)/.t
476
416
  const parsers = {
477
417
  columns: (cqnPart, url, kind, target, isCount) => !isCount && $select(cqnPart, kind),
478
418
  expand: (cqnPart, url, kind, target, isCount) => !isCount && $expand(cqnPart),
419
+ // eslint-disable-next-line no-unused-vars
479
420
  where: (cqnPart, url, kind, target, isCount) => $where(cqnPart, target, kind),
421
+ // eslint-disable-next-line no-unused-vars
480
422
  search: (cqnPart, url, kind, target, isCount) => $search(cqnPart, kind),
481
423
  orderBy: (cqnPart, url, kind, target, isCount) => !isCount && $orderBy(cqnPart),
482
424
  count: (cqnPart, url, kind, target, isCount) => !isCount && $count(cqnPart, kind),
@@ -503,9 +445,7 @@ function getOptions(cqnPart, url, kind, target, isCount) {
503
445
  const _isCount = SELECT => {
504
446
  if (SELECT.columns) {
505
447
  const columns = getProp(SELECT, 'columns')
506
- return columns.some(
507
- c => c.func === 'count' && c.as === '$count' && c.args && c.args.length === 1 && c.args[0] === '*'
508
- )
448
+ return columns.some(c => c.func === 'count' && c.as === '$count')
509
449
  }
510
450
  return false
511
451
  }
@@ -535,7 +475,6 @@ const _copyData = data => {
535
475
  ? data[property].val
536
476
  : data[property]
537
477
  }
538
-
539
478
  return copied
540
479
  }
541
480
 
@@ -581,4 +520,4 @@ function cqn2odata(cqn, kind, model) {
581
520
  throw new Error('Unknown CQN object cannot be translated to URL: ' + JSON.stringify(cqn))
582
521
  }
583
522
 
584
- module.exports = { cqn2odata, formatVal, getSafeNumber }
523
+ module.exports = cqn2odata
@@ -0,0 +1,80 @@
1
+ const cds = require('../_runtime/cds')
2
+ const { SELECT } = cds.ql
3
+
4
+ const odata2cqn = require('./odata2cqn')
5
+ const cqn2odata = require('./cqn2odata')
6
+
7
+ const afterburner = require('./odata2cqn/afterburner')
8
+ const { getSafeNumber: safeNumber } = require('./utils')
9
+
10
+ const strict = {
11
+ functions: {
12
+ contains: 1,
13
+ startswith: 1,
14
+ endswith: 1,
15
+ tolower: 1,
16
+ toupper: 1,
17
+ length: 1,
18
+ indexof: 1,
19
+ substring: 1,
20
+ trim: 1,
21
+ concat: 1,
22
+ year: 1,
23
+ month: 1,
24
+ day: 1,
25
+ hour: 1,
26
+ minute: 1,
27
+ second: 1,
28
+ time: 1,
29
+ now: 1
30
+ }
31
+ }
32
+
33
+ /*
34
+ * cds.odata API
35
+ */
36
+ module.exports = {
37
+ parse: (url, options = {}) => {
38
+ // first arg may also be req
39
+ if (url.url) url = url.url
40
+ // REVISIT: for okra, remove when no longer needed
41
+ else if (url.getIncomingRequest) url = url.getIncomingRequest().url
42
+ url = decodeURIComponent(url)
43
+
44
+ options = options === 'strict' ? { strict } : options.strict ? { ...options, strict } : options
45
+ if (options.service) Object.assign(options, { minimal: true, afterburner: afterburner.for(options.service) })
46
+ options.safeNumber = safeNumber
47
+
48
+ let cqn
49
+ try {
50
+ cqn = odata2cqn(url, options)
51
+ } catch (e) {
52
+ // REVISIT: additional try in catch isn't nice -> find better way
53
+ // known gaps -> e.message is a stringified error -> use that
54
+ // unknown errors -> e is the error to keep
55
+ let err = e
56
+ try {
57
+ err = JSON.parse(e.message)
58
+ } catch {
59
+ /* nothing to do */
60
+ }
61
+ err.message = 'Parsing URL failed with error: ' + err.message
62
+ err.statusCode = err.statusCode || 400
63
+ throw err
64
+ }
65
+
66
+ if (typeof options.afterburner === 'function') cqn = options.afterburner(cqn)
67
+
68
+ const query = cqn.SELECT.one ? SELECT.one(cqn.SELECT.from) : SELECT.from(cqn.SELECT.from)
69
+ Object.assign(query.SELECT, cqn.SELECT)
70
+
71
+ // REVISIT: _target vs __target, i.e., pseudo csn vs actual csn
72
+ // DO NOT USE __target outside of libx/rest!!!
73
+ query.__target = cqn.__target
74
+
75
+ return query
76
+ },
77
+ urlify: (cqn, options = {}) => {
78
+ return cqn2odata(cqn, options.kind, options.model)
79
+ }
80
+ }