@sap/cds 6.8.4 → 7.0.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 (214) hide show
  1. package/CHANGELOG.md +58 -5
  2. package/README.md +0 -1
  3. package/bin/cds-serve.js +50 -3
  4. package/bin/deploy/to-hana.js +1 -0
  5. package/bin/serve.js +16 -20
  6. package/lib/auth/basic-auth.js +6 -4
  7. package/lib/auth/index.js +4 -3
  8. package/lib/auth/jwt-auth.js +2 -5
  9. package/lib/compile/cds-compile.js +34 -89
  10. package/lib/compile/cdsc.js +11 -0
  11. package/lib/compile/etc/properties.js +2 -2
  12. package/lib/compile/for/lean_drafts.js +36 -69
  13. package/lib/compile/for/nodejs.js +2 -1
  14. package/lib/compile/load.js +1 -1
  15. package/lib/compile/minify.js +2 -0
  16. package/lib/compile/to/csn.js +74 -0
  17. package/{bin/build/provider/hana/2tabledata.js → lib/compile/to/hdbtabledata.js} +4 -6
  18. package/lib/compile/to/json.js +1 -1
  19. package/lib/compile/to/sql.js +8 -6
  20. package/lib/dbs/cds-deploy.js +174 -114
  21. package/lib/env/cds-env.js +64 -79
  22. package/lib/env/cds-requires.js +11 -28
  23. package/lib/env/defaults.js +13 -3
  24. package/lib/env/plugins.js +1 -12
  25. package/lib/env/presets.js +25 -21
  26. package/lib/index.js +121 -147
  27. package/lib/{core/reflect.js → linked/models.js} +2 -2
  28. package/lib/{core/infer.js → linked/queries.js} +2 -0
  29. package/lib/{core/index.js → linked/types.js} +2 -1
  30. package/lib/log/cds-error.js +13 -7
  31. package/lib/log/format/cf.js +1 -1
  32. package/lib/plugins.js +49 -0
  33. package/lib/ql/Query.js +0 -9
  34. package/lib/ql/STREAM.js +0 -1
  35. package/lib/req/context.js +2 -7
  36. package/lib/req/request.js +6 -2
  37. package/lib/req/response.js +23 -10
  38. package/lib/srv/middlewares/ctx-model.js +1 -1
  39. package/lib/srv/middlewares/errors.js +1 -1
  40. package/lib/srv/protocols/_legacy.js +1 -0
  41. package/lib/srv/protocols/graphql.js +7 -16
  42. package/lib/srv/protocols/index.js +59 -45
  43. package/lib/srv/protocols/odata-v2-proxy.js +2 -70
  44. package/lib/srv/srv-api.js +9 -3
  45. package/lib/srv/srv-dispatch.js +12 -9
  46. package/lib/srv/srv-models.js +4 -21
  47. package/lib/srv/srv-tx.js +15 -12
  48. package/lib/utils/cds-test.js +14 -9
  49. package/lib/utils/cds-utils.js +2 -12
  50. package/lib/utils/check-version.js +17 -0
  51. package/{bin/build → lib/utils}/csv-reader.js +23 -24
  52. package/libx/_runtime/auth/index.js +27 -23
  53. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +15 -72
  54. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  55. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +0 -2
  56. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +33 -63
  57. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +14 -18
  58. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +15 -5
  59. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +5 -4
  60. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +37 -40
  61. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +7 -1
  62. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +101 -38
  63. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/AbstractError.js +5 -1
  64. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +2 -1
  65. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +9 -8
  66. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  67. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +15 -11
  68. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +4 -0
  69. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +5 -2
  70. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -123
  71. package/libx/_runtime/cds-services/services/Service.js +79 -107
  72. package/libx/_runtime/cds-services/services/utils/columns.js +23 -19
  73. package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -1
  74. package/libx/_runtime/cds-services/services/utils/differ.js +7 -2
  75. package/libx/_runtime/cds-services/util/assert.js +65 -2
  76. package/libx/_runtime/common/composition/data.js +1 -0
  77. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  78. package/libx/_runtime/common/generic/auth/restrict.js +5 -10
  79. package/libx/_runtime/common/generic/auth/restrictions.js +40 -0
  80. package/libx/_runtime/common/generic/auth/utils.js +1 -2
  81. package/libx/_runtime/common/generic/crud.js +32 -16
  82. package/libx/_runtime/common/generic/etag.js +133 -104
  83. package/libx/_runtime/common/generic/input.js +6 -21
  84. package/libx/_runtime/common/generic/put.js +1 -1
  85. package/libx/_runtime/common/generic/stream.js +52 -0
  86. package/libx/_runtime/common/generic/temporal.js +25 -8
  87. package/libx/_runtime/common/i18n/messages.properties +0 -2
  88. package/libx/_runtime/common/utils/cqn.js +1 -1
  89. package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
  90. package/libx/_runtime/common/utils/csn.js +0 -51
  91. package/libx/_runtime/common/utils/etag.js +30 -0
  92. package/libx/_runtime/common/utils/keys.js +1 -1
  93. package/libx/_runtime/common/utils/normalizeTimestamp.js +25 -0
  94. package/libx/_runtime/common/utils/path.js +1 -1
  95. package/libx/_runtime/common/utils/resolveView.js +2 -1
  96. package/libx/_runtime/common/utils/rewriteAsterisks.js +6 -4
  97. package/libx/_runtime/common/utils/search2cqn4sql.js +12 -16
  98. package/libx/_runtime/common/utils/stream.js +140 -0
  99. package/libx/_runtime/common/utils/streamProp.js +29 -12
  100. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +0 -2
  101. package/libx/_runtime/db/generic/index.js +0 -2
  102. package/libx/_runtime/db/query/delete.js +2 -2
  103. package/libx/_runtime/db/query/insert.js +2 -2
  104. package/libx/_runtime/db/query/read.js +2 -2
  105. package/libx/_runtime/db/query/run.js +2 -2
  106. package/libx/_runtime/db/query/update.js +2 -2
  107. package/libx/_runtime/db/sql-builder/BaseBuilder.js +0 -6
  108. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +23 -12
  109. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +18 -6
  110. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -0
  111. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -7
  112. package/libx/_runtime/db/sql-builder/UpsertBuilder.js +1 -0
  113. package/libx/_runtime/db/utils/normalizeTimeData.js +7 -3
  114. package/libx/_runtime/fiori/draft.js +2 -0
  115. package/libx/_runtime/fiori/generic/activate.js +8 -9
  116. package/libx/_runtime/fiori/generic/before.js +30 -20
  117. package/libx/_runtime/fiori/generic/cancel.js +5 -3
  118. package/libx/_runtime/fiori/generic/delete.js +5 -3
  119. package/libx/_runtime/fiori/generic/edit.js +7 -7
  120. package/libx/_runtime/fiori/generic/index.js +10 -16
  121. package/libx/_runtime/fiori/generic/new.js +5 -3
  122. package/libx/_runtime/fiori/generic/patch.js +11 -8
  123. package/libx/_runtime/fiori/generic/prepare.js +13 -6
  124. package/libx/_runtime/fiori/generic/read.js +12 -6
  125. package/libx/_runtime/fiori/lean-draft.js +207 -152
  126. package/libx/_runtime/fiori/utils/delete.js +10 -5
  127. package/libx/_runtime/fiori/utils/req.js +17 -5
  128. package/libx/_runtime/fiori/utils/stream.js +36 -0
  129. package/libx/_runtime/hana/Service.js +12 -9
  130. package/libx/_runtime/hana/conversion.js +10 -15
  131. package/libx/_runtime/hana/driver.js +2 -0
  132. package/libx/_runtime/hana/execute.js +28 -6
  133. package/libx/_runtime/hana/pool.js +36 -122
  134. package/libx/_runtime/hana/search2cqn4sql.js +34 -36
  135. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -6
  136. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -1
  137. package/libx/_runtime/messaging/enterprise-messaging.js +10 -58
  138. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  139. package/libx/_runtime/remote/Service.js +20 -1
  140. package/libx/_runtime/remote/utils/client.js +3 -5
  141. package/libx/_runtime/sqlite/Service.js +4 -6
  142. package/libx/_runtime/sqlite/conversion.js +3 -13
  143. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +9 -6
  144. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +6 -1
  145. package/libx/_runtime/sqlite/execute.js +5 -16
  146. package/libx/odata/afterburner.js +22 -6
  147. package/libx/odata/grammar.pegjs +6 -1
  148. package/libx/odata/parser.js +1 -1
  149. package/libx/rest/RestAdapter.js +16 -9
  150. package/libx/rest/RestRequest.js +1 -1
  151. package/libx/rest/middleware/input.js +2 -1
  152. package/libx/rest/middleware/operation.js +1 -0
  153. package/libx/rest/middleware/parse.js +3 -2
  154. package/libx/rest/middleware/payload.js +9 -8
  155. package/libx/rest/middleware/read.js +1 -0
  156. package/package.json +9 -16
  157. package/app/fiori/preview.js +0 -270
  158. package/app/fiori/routes.js +0 -59
  159. package/bin/build/buildTaskEngine.js +0 -360
  160. package/bin/build/buildTaskFactory.js +0 -283
  161. package/bin/build/buildTaskHandler.js +0 -241
  162. package/bin/build/buildTaskProvider.js +0 -22
  163. package/bin/build/buildTaskProviderFactory.js +0 -175
  164. package/bin/build/cds.js +0 -5
  165. package/bin/build/constants.js +0 -66
  166. package/bin/build/index.js +0 -58
  167. package/bin/build/provider/buildTaskHandlerEdmx.js +0 -82
  168. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +0 -131
  169. package/bin/build/provider/buildTaskHandlerInternal.js +0 -254
  170. package/bin/build/provider/buildTaskProviderInternal.js +0 -383
  171. package/bin/build/provider/fiori/index.js +0 -171
  172. package/bin/build/provider/hana/2migration.js +0 -179
  173. package/bin/build/provider/hana/index.js +0 -505
  174. package/bin/build/provider/hana/migrationtable.js +0 -472
  175. package/bin/build/provider/hana/template/.hdiconfig-haas +0 -163
  176. package/bin/build/provider/hana/template/.hdiconfig-hanacloud +0 -137
  177. package/bin/build/provider/hana/template/.hdinamespace +0 -4
  178. package/bin/build/provider/hana/template/package.json +0 -12
  179. package/bin/build/provider/hana/template/undeploy.json +0 -5
  180. package/bin/build/provider/java/index.js +0 -111
  181. package/bin/build/provider/java-cf/index.js +0 -1
  182. package/bin/build/provider/mtx/index.js +0 -268
  183. package/bin/build/provider/mtx/resourcesTarBuilder.js +0 -95
  184. package/bin/build/provider/mtx-extension/index.js +0 -131
  185. package/bin/build/provider/mtx-sidecar/index.js +0 -137
  186. package/bin/build/provider/node-cf/index.js +0 -1
  187. package/bin/build/provider/nodejs/index.js +0 -192
  188. package/bin/build/util.js +0 -299
  189. package/bin/cds.js +0 -125
  190. package/bin/deploy/to-hana/cfUtil.js +0 -355
  191. package/bin/deploy/to-hana/gitUtil.js +0 -57
  192. package/bin/deploy/to-hana/hana.js +0 -306
  193. package/bin/deploy/to-hana/hdiDeployUtil.js +0 -153
  194. package/bin/deploy/to-hana/index.js +0 -16
  195. package/bin/deploy/to-hana/mtaUtil.js +0 -170
  196. package/bin/mtx/in-cds.js +0 -17
  197. package/bin/plugins.js +0 -32
  198. package/bin/run.js +0 -24
  199. package/bin/utils/log.js +0 -24
  200. package/bin/version.js +0 -178
  201. package/libx/_runtime/audit/Service.js +0 -222
  202. package/libx/_runtime/audit/generic/personal/access.js +0 -61
  203. package/libx/_runtime/audit/generic/personal/index.js +0 -56
  204. package/libx/_runtime/audit/generic/personal/modification.js +0 -132
  205. package/libx/_runtime/audit/generic/personal/utils.js +0 -186
  206. package/libx/_runtime/audit/utils/log.js +0 -23
  207. package/libx/_runtime/audit/utils/v2.js +0 -176
  208. package/libx/_runtime/db/data-conversion/timestamp.js +0 -9
  209. package/libx/_runtime/db/generic/integrity.js +0 -455
  210. package/srv/audit-log.cds +0 -87
  211. package/srv/mtx.cds +0 -2
  212. package/srv/mtx.js +0 -8
  213. /package/lib/{core → linked}/classes.js +0 -0
  214. /package/lib/{core → linked}/entities.js +0 -0
@@ -1,7 +1,7 @@
1
1
  const cds = require('..')
2
2
  const TRACE = cds.debug('trace')
3
3
 
4
- module.exports = exports = function cds_load (files, options) {
4
+ module.exports = exports = function load (files, options) {
5
5
  const all = cds.resolve(files,options)
6
6
  if (!all) return Promise.reject (new cds.error ({
7
7
  message: `Couldn't find a CDS model for '${files}' in ${cds.root}`,
@@ -1,4 +1,5 @@
1
1
  const cds = require('../index')
2
+ const DEBUG = cds.debug('minify')
2
3
 
3
4
  module.exports = function cds_minify (csn, _roots) { // IMPORTANT: don't add cds.env.features.skip_unused as default for _roots here, as that will break VSCode's IntelliSense
4
5
  const roots = _roots !== undefined ? _roots : cds.env.features.skip_unused
@@ -58,6 +59,7 @@ module.exports = function cds_minify (csn, _roots) { // IMPORTANT: don't add cds
58
59
  const minified = Object.create (csn.__proto__, Object.getOwnPropertyDescriptors(csn))
59
60
  const less = minified.definitions = {}
60
61
  for (let n in all) if (reached.has(all[n])) less[n] = all[n]
62
+ else DEBUG?.('skipping', all[n].kind, n)
61
63
  Object.defineProperty (minified,_minified,{value:true})
62
64
  return minified
63
65
  }
@@ -0,0 +1,74 @@
1
+ const cds = require ('../../index')
2
+
3
+ /**
4
+ * This is the central function to compile sources to CSN.
5
+ * @param {string|string[]|{}} model one of:
6
+ * - a single filename starting with 'file:'
7
+ * - a single CDL source string
8
+ * - an object with multiple CDL or CSN sources
9
+ * - an array of one or more filenames
10
+ * @param { _flavor | {flavor:_flavor, ...}} options
11
+ * - an options object or a string specifying the flavor of CSN to generate
12
+ * @param { 'inferred'|'xtended'|'parsed' } _flavor - for internal use only(!)
13
+ * @returns {{ namespace?:string, definitions:{}, extensions?:[], meta:{ flavor:_flavor }}} CSN
14
+ */
15
+ function cds_compile_to_csn (model, options, _flavor) {
16
+
17
+ if (!model) throw cds.error (`Argument 'model' must be specified`)
18
+ if (_is_csn(model) && _assert_flavor(model,_flavor,options)) return model //> already parsed csn
19
+
20
+ const o = _options4 (options,_flavor)
21
+ const cwd = o.cwd || cds.root
22
+ const files = _is_files (model,cwd)
23
+ const cdsc = require ('../cdsc')
24
+ if (files && o.sync) return _finalize (cdsc.compileSync(files,cwd,o)) //> compile files synchroneously
25
+ if (files) return cdsc.compile(files,cwd,o) .then (_finalize) //> compile files asynchroneously
26
+ else return _finalize (cdsc.compileSources(model,o)) //> compile CDL sources
27
+
28
+ function _finalize (csn) {
29
+ if (o.min) csn = cds.minify(csn)
30
+ // REVISIT: experimental implementation to detect external APIs
31
+ for (let each in csn.definitions) {
32
+ const d = csn.definitions[each]
33
+ if (d.kind === 'service' && cds.requires[each]?.external && (!o.mocked || cds.requires[each].credentials)) {
34
+ Object.defineProperty (d,'@cds.external', { value: cds.requires[each].kind || true })
35
+ }
36
+ }
37
+ if (!csn.meta) csn.meta = {}
38
+ csn.meta.flavor = o.flavor
39
+ return csn
40
+ }
41
+ }
42
+
43
+
44
+ const _is_csn = (x) => (x.definitions || x.extensions) && !x.$builtins
45
+ const _is_files = (m,root) => {
46
+ if (m === '*' || Array.isArray(m) || /^file:/.test(m) && (m = m.slice(5)))
47
+ return cds.resolve(m,{root}) || cds.error ( `Couldn't find a CDS model for '${m}' in ${root||cds.root}`,{ code:'MODEL_NOT_FOUND', model: m })
48
+ }
49
+
50
+ const _assert_flavor = (m,_flavor,options) => {
51
+ if (!m.meta) return true; const f = _flavor || _flavor4 (options)
52
+ return !f || f === m.meta.flavor || cds.error (`cds.compile(...,{flavor:'${f}'}) called on csn with different meta.flavor='${m.meta.flavor}'`)
53
+ }
54
+ const _flavors = {
55
+ 'parsed': { level:1, cdsc_options: { parseCdl:true } },
56
+ 'xtended': { level:2, cdsc_options: { csnFlavor:'gensrc' } },
57
+ 'inferred': { level:3 },
58
+ }
59
+ const _flavor4 = (o) => {
60
+ const f = typeof o === 'string' ? o : o && o.flavor
61
+ return !f || f in _flavors ? f : cds.error (`Option 'flavor' must be one of ${Object.keys(_flavors)}; got: '${f}'`)
62
+ }
63
+
64
+ const _options4 = (_o, _flavor) => {
65
+ const flavor = _flavor ? _flavor4(_flavor) : _flavor4(_o) || 'inferred'
66
+ const spec = _flavors[flavor]
67
+ const o = { ..._o, flavor, ...spec.cdsc_options, ...cds.env.cdsc, cdsHome: cds.home } // cdsHome is for the compiler resolving @sap/cds/... files
68
+ if (o.docs) o.docComment = true
69
+ if (o.locations) o.withLocations = true
70
+ if (!o.messages) o.messages = []
71
+ return o
72
+ }
73
+
74
+ module.exports = cds_compile_to_csn
@@ -1,10 +1,8 @@
1
- const cds = require('../../cds')
2
- const CSV = require('../../csv-reader')
1
+ const cds = require('../../../lib')
3
2
  const { getElementCdsPersistenceName, getArtifactCdsPersistenceName } = require('@sap/cds-compiler')
4
- const { LOG_MODULE_NAMES } = require('../../constants')
5
- const { fs, path, isdir } = cds.utils
3
+ const { fs, path, isdir, csv } = cds.utils
6
4
  const { readdir } = fs.promises
7
- let logger = cds.log(LOG_MODULE_NAMES)
5
+ let logger = cds.log('cds')
8
6
 
9
7
  module.exports = async (model, options = {}) => {
10
8
  if (options.logger) {
@@ -52,7 +50,7 @@ async function _tabledata4(dir, csvFile, model, baseDir, naming) {
52
50
 
53
51
  const file = path.join(dir, csvFile)
54
52
  const reader = fs.createReadStream(file)
55
- const { cols, delimiter } = await CSV.readHeader(reader)
53
+ const { cols, delimiter } = await csv.readHeader(reader)
56
54
  if (cols.length === 0) return // no columns at all -> skip import
57
55
 
58
56
  cols.forEach(csvCol => {
@@ -34,7 +34,7 @@ module.exports = (csn,o={}) => {
34
34
  return v
35
35
 
36
36
  }
37
- return JSON.stringify (csn, resolver, o && o.indents || 2)
37
+ return JSON.stringify (csn, resolver, o?.indents ?? 2)
38
38
  }
39
39
 
40
40
  // go upwards, find a package.json and try resolving with this module name
@@ -1,12 +1,13 @@
1
1
  const cds = require ('../..')
2
2
  const cdsc = require ('../cdsc')
3
3
  const keywords = require("@sap/cds-compiler").to.sql.sqlite.keywords
4
+ const { unfold_ddl } = require ('../etc/_localized')
4
5
 
5
6
  function cds_compile_to_sql (csn,_o) {
6
7
  csn = _extended(cds.minify(csn))
7
8
  const o = cdsc._options.for.sql(_o) //> used twice below...
8
9
  const all = cdsc.to.sql(csn,o) .map (each => each.replace(/^-- .+\n/,'')) //> strip comments
9
- const sql = cds.compile._localized.unfold_ddl(all, csn, o)
10
+ const sql = unfold_ddl(all, csn, o)
10
11
  if (o.as === 'str') return `\n${sql.join('\n\n')}\n`
11
12
  return sql
12
13
  }
@@ -18,12 +19,13 @@ function cds_compile_to_hdbtable (csn,o) {
18
19
  }
19
20
 
20
21
  function cds_compile_to_deltaSql (csn, o, beforeCsn) {
21
- const options = cdsc._options.for.sql(o);
22
+ const options = cdsc._options.for.sql(o)
23
+ if (typeof beforeCsn === 'string') beforeCsn = JSON.parse(beforeCsn)
22
24
  const { afterImage, drops, createsAndAlters } = cdsc.to.deltaSql (csn, options, beforeCsn || {definitions: {}, $version: '2.0'} ); // FIXME: As default value in compiler API?
23
- return {
24
- afterImage,
25
- drops: cds.compile._localized.unfold_ddl(drops.map (each => each.replace(/^-- .+\n/,'')), csn, options),
26
- createsAndAlters: cds.compile._localized.unfold_ddl(createsAndAlters.map (each => each.replace(/^-- .+\n/,'')), csn, options)
25
+ return {
26
+ afterImage,
27
+ drops: unfold_ddl(drops.map (each => each.replace(/^-- .+\n/,'')), csn, options),
28
+ createsAndAlters: unfold_ddl(createsAndAlters.map (each => each.replace(/^-- .+\n/,'')), csn, options)
27
29
  };
28
30
  }
29
31
 
@@ -1,21 +1,9 @@
1
+ #!/usr/bin/env node
1
2
  const cds = require('../index'), { local } = cds.utils
2
3
  const COLORS = !!process.stdout.isTTY && !!process.stderr.isTTY
3
4
  const GREY = COLORS ? '\x1b[2m' : ''
4
5
  const RESET = COLORS ? '\x1b[0m' : ''
5
- const DEBUG = cds.debug('deploy')
6
- const TRACE = cds.debug('trace')
7
- const nativeSchevoSqls = {
8
- 'postgres': {
9
- create: `CREATE table cds_model (csn text)`,
10
- insert: `INSERT into cds_model values ('null')`,
11
- drop: `DROP table if exists cds_model;`
12
- },
13
- 'sqlite': {
14
- create: `CREATE table cds_Model (csn CLOB)`,
15
- insert: `INSERT into cds_Model values ('null')`,
16
- drop: `DROP table if exists cds_Model;`
17
- }
18
- }
6
+ let DEBUG // IMPORTANT: initialized later after await cds.plugins
19
7
 
20
8
  /**
21
9
  * Implementation of `cds.deploy` common to all databases.
@@ -23,43 +11,148 @@ const nativeSchevoSqls = {
23
11
  * deploy create tables and views in case of a SQL database, then fills
24
12
  * in initial data, if present.
25
13
  */
26
- exports = module.exports = function cds_deploy (model,options,csvs) { return {
14
+ module.exports = exports = function cds_deploy (model,options,csvs) {
15
+ DEBUG = cds.debug('deploy')
16
+ return {
17
+ /** @param {import('@sap/cds/lib/srv/srv-api')} db */
18
+ async to(db, o = options || cds.options || {}) {
19
+
20
+ const TRACE = cds.debug('trace')
21
+ TRACE?.time('cds.deploy db ')
22
+
23
+ if (!model) throw new Error('Must provide a model or a path to model, received: ' + model)
24
+ if (model && !model.definitions) model = await cds.load(model).then(cds.minify)
25
+
26
+ if (o.mocked) exports.include_external_entities_in(model)
27
+ else exports.exclude_external_entities_in(model)
28
+
29
+ if (!db.run) db = await cds.connect.to(db)
30
+ if (!cds.db) cds.db = cds.services.db = db
31
+ if (!db.model) db.model = model
32
+
33
+ // eslint-disable-next-line no-console
34
+ const LOG = o.silent || o.dry || !cds.log('deploy')._info ? () => {} : console.log
35
+ const _deploy = async tx => {
36
+ // create / update schema
37
+ let any = await exports.create(tx, model, o)
38
+ if (!any && !csvs) return db
39
+ // fill in initial data
40
+ await exports.init(tx, model, o, csvs, file => LOG(GREY, ` > init from ${local(file)}`, RESET))
41
+ }
27
42
 
28
- /** @param {cds.Service} db */
29
- async to (db, o = options || cds.options || {}) {
30
- TRACE?.time ('cds.deploy db ')
43
+ let url = db.url4(cds.context?.tenant)
44
+ if (url === ':memory:') url = 'in-memory database.'
45
+ else if (!url.startsWith('http:')) url = local(url)
46
+ try {
47
+ await (o.dry ? _deploy(db) : db.run(_deploy))
48
+ LOG('/> successfully deployed to', url, '\n')
49
+ } catch (e) {
50
+ LOG('/> deployment to', url, 'failed\n')
51
+ throw e
52
+ }
31
53
 
32
- if (!model) throw new Error('Must provide a model or a path to model, received: ' + model)
33
- if (model && !model.definitions) model = await cds.load(model).then(cds.minify)
54
+ TRACE?.timeEnd('cds.deploy db ')
55
+ return db
56
+ },
57
+
58
+ // continue to support cds.deploy() as well...
59
+ then(n, e) {
60
+ return this.to(cds.db || 'db').then(n, e)
61
+ },
62
+ catch(e) {
63
+ return this.to(cds.db || 'db').catch(e)
64
+ },
65
+ }
66
+ }
34
67
 
35
- if (o.mocked) exports.include_external_entities_in (model)
36
- else exports.exclude_external_entities_in (model)
68
+ exports.create = async function cds_deploy_create (db, csn=db.model, o) {
37
69
 
38
- if (!db.run) db = await cds.connect.to(db)
39
- if (!cds.db) cds.db = cds.services.db = db
40
- if (!db.model) db.model = model
70
+ /* eslint-disable no-console */
41
71
 
42
- // create tables & views...
43
- const LOG = o.silent || !cds.log('deploy')._info ? ()=>{} : console.log
44
- const any = await exports.create (db,model,o)
45
- if (!any && !csvs) return db
72
+ if (!o.to || o.to === db.options.kind) o = { ...db.options, ...o }
73
+ if (o.impl === '@cap-js/sqlite') {
74
+ // REVISIT: Move that to configuration or to cds.compile -> currently cds compile creates wrong output
75
+ o.betterSqliteSessionVariables = true
76
+ o.sqlDialect = 'sqlite'
77
+ }
46
78
 
47
- // fill in initial data...
48
- await exports.init (db,model,o,csvs, file => LOG (GREY,` > init from ${local(file)}`, RESET))
79
+ let drops, creas
80
+ let schevo = o.schema_evolution === 'auto' || o['with-auto-schema-evolution'] || o['model-only'] || o['delta-from'] || (o.kind === 'postgres' && o.schema_evolution !== false);
81
+ if (schevo) {
82
+ const { prior, table_exists } = await get_prior_model()
83
+ const { afterImage, drops: d, createsAndAlters } = cds.compile.to.sql.delta(csn, o, prior && JSON.parse(prior))
84
+ const after = JSON.stringify(afterImage)
85
+ if (!o.dry && after != prior) {
86
+ if (!table_exists) {
87
+ const CLOB = o.dialect === 'postgres' || o.kind === 'postgres' ? 'text' : 'CLOB'
88
+ await db.run(`CREATE table cds_model (csn ${CLOB})`)
89
+ await db.run(`INSERT into cds_model values (?)`, after)
90
+ } else {
91
+ await db.run(`UPDATE cds_model SET csn = ?`, after)
92
+ }
93
+ db.options.schema_evolution = 'auto' // for updating package.json
94
+ }
95
+ // cds deploy --model-only > fills in table cds_model above
96
+ if (o['model-only']) return o.dry && console.log(after)
97
+ // cds deploy -- with auto schema evolution > upgrade by applying delta to former model
98
+ creas = createsAndAlters
99
+ drops = d
100
+ } else {
101
+ // cds deploy -- w/o auto schema evoution > drop-create db
102
+ creas = cds.compile.to.sql(csn, o)
103
+ }
49
104
 
50
- // done
51
- let url = db.url4 (cds.context?.tenant); if (url === ':memory:') url = 'in-memory database.'
52
- LOG ('/> successfully deployed to', url, '\n')
105
+ if (!drops)
106
+ drops = creas
107
+ .map(each => {
108
+ let [, kind, entity] = each.match(/^CREATE (TABLE|VIEW) ("[^"]+"|[^\s(]+)/im) || []
109
+ return `DROP ${kind} IF EXISTS ${entity};`
110
+ })
111
+ .reverse()
53
112
 
54
- TRACE?.timeEnd ('cds.deploy db ')
55
- return db
56
- },
113
+ if (!drops.length && !creas.length) return !o.dry
57
114
 
58
- // continue to support cds.deploy() as well...
59
- then(n,e) { return this.to (cds.db||'db') .then (n,e) },
60
- catch(e) { return this.to (cds.db||'db') .catch (e) },
61
- }}
115
+ if (o.dry) {
116
+ console.log()
117
+ for (let each of drops) console.log(each)
118
+ console.log()
119
+ for (let each of creas) console.log(each, '\n')
120
+ return
121
+ }
62
122
 
123
+ // Set the context model while deploying for cqn42sql in new db layers
124
+ db.model = cds.compile.for.nodejs(csn)
125
+ await db.run(drops)
126
+ await db.run(creas)
127
+ return true
128
+
129
+ async function get_prior_model() {
130
+ let file = o['delta-from']
131
+ if (file) {
132
+ let prior = await cds.utils.read(file)
133
+ return { prior }
134
+ }
135
+ if (o.dry) return {}
136
+
137
+ let [table_exists] = await db.run(
138
+ // REVISIT: prettier forced this horrible, unreadable formatting:
139
+ db.kind === 'postgres'
140
+ ? `SELECT 1 from pg_tables WHERE tablename = 'cds_model' and schemaname = current_schema()`
141
+ : db.kind === 'sqlite'
142
+ ? `SELECT 1 from sqlite_schema WHERE name = 'cds_model'`
143
+ : cds.error`Schema evolution is not supported for ${db.kind} databases`,
144
+ )
145
+
146
+ if (o['model-only'])
147
+ return { table_exists };
148
+
149
+ if (table_exists) {
150
+ let [{ csn }] = await db.run('SELECT csn from cds_model')
151
+ return { prior: csn, table_exists }
152
+ }
153
+ return { table_exists } // no prior csn
154
+ }
155
+ }
63
156
 
64
157
 
65
158
  const { fs, path, read } = cds.utils
@@ -73,7 +166,7 @@ exports.include_external_entities_in = function (model) {
73
166
  const def = model.definitions[each]
74
167
  if (def['@cds.persistence.mock'] === false) continue
75
168
  if (def['@cds.persistence.skip'] === true) {
76
- DEBUG && DEBUG ('including mocked', each)
169
+ DEBUG?.('including mocked', each)
77
170
  delete def['@cds.persistence.skip']
78
171
  }
79
172
  }
@@ -86,7 +179,7 @@ exports.exclude_external_entities_in = function (csn) { // NOSONAR
86
179
  for (let [each,{service=each,model,credentials}] of Object.entries (cds.env.requires)) {
87
180
  if (!model) continue //> not for internal services like cds.requires.odata
88
181
  if (!credentials && csn._mocked) continue //> not for mocked unbound services
89
- DEBUG && DEBUG ('excluding external entities for', service, '...')
182
+ DEBUG?.('excluding external entities for', service, '...')
90
183
  const prefix = service+'.'
91
184
  for (let each in csn.definitions) if (each.startsWith(prefix)) _exclude (each)
92
185
  }
@@ -95,7 +188,7 @@ exports.exclude_external_entities_in = function (csn) { // NOSONAR
95
188
  function _exclude (each) {
96
189
  const def = csn.definitions[each]; if (def.kind !== 'entity') return
97
190
  if (def['@cds.persistence.table'] === true) return // do not exclude replica table
98
- DEBUG && DEBUG ('excluding external entity', each)
191
+ DEBUG?.('excluding external entity', each)
99
192
  def['@cds.persistence.skip'] = true
100
193
  // propagate to all views on top...
101
194
  for (let other in csn.definitions) {
@@ -107,66 +200,6 @@ exports.exclude_external_entities_in = function (csn) { // NOSONAR
107
200
  }
108
201
 
109
202
 
110
- exports.create = async function cds_deploy_create (db, csn=db.model, o) {
111
- // REVISIT: How to find out if we use better-sqlite?
112
- if (db._source === '@cap-js/sqlite') {
113
- // it's required to set both properties
114
- o.betterSqliteSessionVariables = true
115
- o.sqlDialect = 'sqlite'
116
- }
117
-
118
- const sqls = nativeSchevoSqls[db.options?.dialect || db.options?.kind || 'sqlite'];
119
- let drops, creas, schevo = db.options?.schema_evolution === 'auto' || o.schema_evolution === 'auto'
120
- if (schevo) {
121
- // REVISIT: replace by db-specific ways to read/updated recent deployed model
122
- let before;
123
- try {
124
- const [{csn}] = await db.read('cds.Model');
125
- before = JSON.parse(csn);
126
- } catch(e) {
127
- if (!e.message.includes('no such table') && !e.message.includes('relation "cds_model" does not exist')) throw e
128
- // TODO: Put both into one tx - read can not be part of it, as the transaction gets killed in PG once an error appears
129
- await db.run(sqls.create)
130
- await db.run(sqls.insert)
131
- before = null;
132
- }
133
- const { afterImage, drops:_drops, createsAndAlters:_creas } = cds.compile.to.sql.delta (csn, {...o, ...db.options}, before)
134
- creas = _creas.concat (o.dry ? [] : UPDATE('cds.Model').with({ csn: JSON.stringify(afterImage) }))
135
- drops = before ? _drops : _drops4(_creas)
136
- } else {
137
- creas = cds.compile.to.sql (csn, {...o, ...db.options})
138
- drops = _drops4(creas) .concat (sqls.drop)
139
- }
140
- if (!creas || creas.length === 0) return
141
-
142
- function _drops4 (creas) {
143
- return creas.map (each => {
144
- let [, kind, entity] = each.match(/^CREATE (TABLE|VIEW) ("[^"]+"|[^\s(]+)/im) || []
145
- return `DROP ${kind} IF EXISTS ${entity};`
146
- }).reverse()
147
- }
148
-
149
- if (o.dry) {
150
- console.log(); for (let each of drops) console.log(each)
151
- console.log(); for (let each of creas) console.log(each,'\n')
152
- }
153
- else return db.run (async tx => {
154
- // This runs in a new transaction if called from CLI, while joining
155
- // existing root tx, e.g. when called from DeploymentService.
156
- if (!schevo && db.kind == 'sqlite' && await tx.exists('sqlite.schema').where({name:'cds_xt_Extensions'})) {
157
- // Poor man's schema evolution for extensions in drop-create deployments
158
- drops = drops.filter(d => !d.includes('cds_xt_Extensions'))
159
- creas = creas.filter(c => !c.includes('cds_xt_Extensions'))
160
- }
161
- // Set the context model while deploying for cqn42sql in new db layers
162
- tx.model = cds.compile.for.nodejs(csn)
163
- await tx.run(drops)
164
- await tx.run(creas)
165
- return true
166
- })
167
- }
168
-
169
-
170
203
  exports.init = async function cds_deploy_init (db, csn=db.model, o, srces, log=()=>{}) {
171
204
  const t = cds.context?.tenant; if (t && t === cds.requires.multitenancy?.t0) return
172
205
  return db.run (async tx => {
@@ -184,7 +217,7 @@ exports.init = async function cds_deploy_init (db, csn=db.model, o, srces, log=(
184
217
  throw Object.assign (e, { message: 'in cds.deploy(): ' + e.message +'\n'+ cds.utils.inspect(q) })
185
218
  }
186
219
  } else { //> init.js/ts case
187
- if (typeof src === 'function') src(tx,csn)
220
+ if (typeof src === 'function') await src(tx,csn)
188
221
  }
189
222
  }
190
223
  })
@@ -199,16 +232,19 @@ exports.data = async function cds_deploy_prepare_data (csn, srces) {
199
232
  return [ file, e, src ]
200
233
  })
201
234
  // If not, we load them from cds.deploy.resources(csn)
235
+ const data = []
202
236
  const resources = await exports.resources(csn, { testdata: cds.env.features.test_data })
203
- return Object.entries(resources) .map (async ([file,e]) => {
237
+ const resEntries = Object.entries(resources).reverse() // reversed $sources, relevant as UPSERT order
238
+ for (const [file,e] of resEntries) {
204
239
  if (e === '*') {
205
240
  let init_js = await cds.utils._import (file)
206
- return [ file, null, init_js.default || init_js ]
241
+ data.push([ file, null, init_js.default || init_js ])
207
242
  } else {
208
243
  let src = await read (file, 'utf8')
209
- return [ file, e, src ]
244
+ data.push([ file, e, src ])
210
245
  }
211
- })
246
+ }
247
+ return data
212
248
  }
213
249
 
214
250
 
@@ -230,14 +266,14 @@ exports.resources = async function cds_deploy_resources (csn, opts) {
230
266
  const f = fx.slice(0,-ext.length)
231
267
  if (/[._]texts$/.test(f) && files.some(g => g.startsWith(f+'_'))) {
232
268
  // ignores 'Books_texts.csv/json' if there is any 'Books_texts_LANG.csv/json'
233
- DEBUG && DEBUG (`ignoring '${fx}' in favor of translated ones`)
269
+ DEBUG?.(`ignoring '${fx}' in favor of translated ones`)
234
270
  continue
235
271
  }
236
272
  const e = _entity4(f,csn); if (_skip(e)) continue
237
273
  if (cds.env.features.deploy_data_onconflict === 'replace' && !/[._]texts_/.test(f)) {
238
274
  const seenBefore = Object.entries(found).find(([_, entity]) => entity === e.name )
239
275
  if (seenBefore) {
240
- DEBUG && DEBUG(`Conflict for '${e.name}': replacing '${local(seenBefore[0])}' with '${local(path.join(subdir,fx))}'`)
276
+ DEBUG?.(`Conflict for '${e.name}': replacing '${local(seenBefore[0])}' with '${local(path.join(subdir,fx))}'`)
241
277
  continue
242
278
  }
243
279
  }
@@ -267,7 +303,7 @@ const _entity4 = (file,csn) => {
267
303
  const base = csn.definitions [RegExp.$1]
268
304
  return base?.elements?.texts && _entity4 (base.elements.texts.target, csn)
269
305
  }
270
- else return DEBUG && DEBUG (`warning: ${name} not in model`)
306
+ else return DEBUG?.(`warning: ${name} not in model`)
271
307
  }
272
308
  // We also support insert into simple views if they have no projection
273
309
  const p = entity.query && entity.query.SELECT || entity.projection
@@ -305,7 +341,7 @@ const _queries4 = (db,csn) => !db.cqn2sql ? q => q : q => {
305
341
 
306
342
 
307
343
  const INSERT_from4 = (db,o) => {
308
- const schevo = db.options?.schema_evolution === 'auto' || o?.schema_evolution === 'auto'
344
+ const schevo = db.options?.schema_evolution === 'auto' || cds.env.requires.db?.schema_evolution === 'auto' || o?.schema_evolution === 'auto'
309
345
  const INSERT_into = (schevo ? UPSERT : INSERT).into
310
346
  return (file) => ({
311
347
  '.json': { into (entity, json) {
@@ -320,4 +356,28 @@ const INSERT_from4 = (db,o) => {
320
356
  }
321
357
 
322
358
  const _skip = e => !e || e['@cds.persistence.skip'] === true
323
- /* eslint-disable no-console */
359
+
360
+
361
+
362
+ if (!module.parent) (async () => {
363
+ await cds.plugins // IMPORTANT: that has to go before any call to cds.env, like through cds.deploy or cds.requires below
364
+ let db = cds.requires.db
365
+ try {
366
+ let o={}, recent
367
+ for (let each of process.argv.slice(2)) {
368
+ if (each.startsWith('--')) o[(recent = each.slice(2))] = true
369
+ else o[recent] = each
370
+ }
371
+ if (o.to) {
372
+ db = { kind: o.to, dialect: o.to }
373
+ if (o.url) (db.credentials ??= {}).url = o.url
374
+ if (o.host) (db.credentials ??= {}).host = o.host
375
+ if (o.port) (db.credentials ??= {}).port = o.port
376
+ if (o.username) (db.credentials ??= {}).username = o.username
377
+ if (o.password) (db.credentials ??= {}).password = o.password
378
+ }
379
+ db = await cds.deploy('*',o).to(db)
380
+ } finally {
381
+ await db?.disconnect?.()
382
+ }
383
+ })().catch(console.error)