@sap/cds-compiler 4.7.6 → 4.8.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 (93) hide show
  1. package/CHANGELOG.md +37 -2
  2. package/bin/cdsc.js +15 -1
  3. package/bin/cdshi.js +13 -3
  4. package/doc/CHANGELOG_BETA.md +5 -1
  5. package/lib/api/main.js +61 -23
  6. package/lib/api/options.js +40 -0
  7. package/lib/base/builtins.js +89 -0
  8. package/lib/base/keywords.js +5 -1
  9. package/lib/base/location.js +91 -14
  10. package/lib/base/message-registry.js +50 -33
  11. package/lib/base/messages.js +71 -16
  12. package/lib/base/model.js +0 -2
  13. package/lib/checks/actionsFunctions.js +1 -1
  14. package/lib/checks/elements.js +2 -1
  15. package/lib/checks/enricher.js +2 -2
  16. package/lib/checks/queryNoDbArtifacts.js +2 -1
  17. package/lib/checks/utils.js +1 -1
  18. package/lib/checks/validator.js +6 -22
  19. package/lib/compiler/assert-consistency.js +3 -5
  20. package/lib/compiler/builtins.js +0 -74
  21. package/lib/compiler/checks.js +61 -11
  22. package/lib/compiler/define.js +3 -3
  23. package/lib/compiler/extend.js +2 -2
  24. package/lib/compiler/index.js +9 -9
  25. package/lib/compiler/populate.js +13 -5
  26. package/lib/compiler/propagator.js +3 -0
  27. package/lib/compiler/resolve.js +6 -20
  28. package/lib/compiler/shared.js +1 -1
  29. package/lib/compiler/tweak-assocs.js +2 -2
  30. package/lib/compiler/utils.js +3 -3
  31. package/lib/compiler/{classes.js → xsn-model.js} +0 -16
  32. package/lib/edm/annotations/edmJson.js +7 -5
  33. package/lib/edm/annotations/genericTranslation.js +113 -55
  34. package/lib/edm/csn2edm.js +25 -9
  35. package/lib/edm/edm.js +3 -3
  36. package/lib/edm/edmInboundChecks.js +24 -5
  37. package/lib/edm/edmPreprocessor.js +46 -20
  38. package/lib/edm/edmUtils.js +3 -16
  39. package/lib/gen/Dictionary.json +9 -0
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +1 -1
  42. package/lib/gen/languageParser.js +1941 -1850
  43. package/lib/json/csnVersion.js +7 -4
  44. package/lib/json/from-csn.js +8 -7
  45. package/lib/json/to-csn.js +12 -7
  46. package/lib/language/antlrParser.js +1 -1
  47. package/lib/language/genericAntlrParser.js +9 -10
  48. package/lib/language/multiLineStringParser.js +2 -2
  49. package/lib/language/textUtils.js +1 -1
  50. package/lib/main.d.ts +23 -0
  51. package/lib/main.js +8 -1
  52. package/lib/model/cloneCsn.js +15 -6
  53. package/lib/model/csnRefs.js +141 -35
  54. package/lib/model/csnUtils.js +1 -4
  55. package/lib/model/enrichCsn.js +1 -1
  56. package/lib/modelCompare/compare.js +106 -92
  57. package/lib/optionProcessor.js +23 -1
  58. package/lib/render/toCdl.js +3 -2
  59. package/lib/render/toHdbcds.js +4 -48
  60. package/lib/render/toSql.js +6 -3
  61. package/lib/transform/addTenantFields.js +58 -35
  62. package/lib/transform/db/applyTransformations.js +1 -1
  63. package/lib/transform/db/expansion.js +3 -0
  64. package/lib/transform/db/flattening.js +71 -46
  65. package/lib/transform/db/views.js +1 -4
  66. package/lib/transform/effective/main.js +6 -3
  67. package/lib/transform/effective/misc.js +18 -8
  68. package/lib/transform/effective/types.js +4 -3
  69. package/lib/transform/forOdata.js +8 -7
  70. package/lib/transform/forRelationalDB.js +103 -112
  71. package/lib/transform/odata/flattening.js +82 -44
  72. package/lib/transform/odata/toFinalBaseType.js +9 -25
  73. package/lib/transform/odata/typesExposure.js +28 -15
  74. package/lib/transform/parseExpr.js +0 -3
  75. package/lib/transform/transformUtils.js +12 -8
  76. package/lib/transform/translateAssocsToJoins.js +2 -2
  77. package/lib/transform/universalCsn/coreComputed.js +2 -1
  78. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -1
  79. package/package.json +2 -2
  80. package/share/messages/README.md +4 -0
  81. package/share/messages/anno-duplicate-unrelated-layer.md +1 -1
  82. package/share/messages/check-proper-type-of.md +1 -1
  83. package/share/messages/def-duplicate-autoexposed.md +1 -1
  84. package/share/messages/extend-repeated-intralayer.md +3 -16
  85. package/share/messages/extend-unrelated-layer.md +1 -1
  86. package/share/messages/message-explanations.json +1 -0
  87. package/share/messages/redirected-to-ambiguous.md +1 -1
  88. package/share/messages/redirected-to-complex.md +1 -1
  89. package/share/messages/redirected-to-unrelated.md +1 -1
  90. package/share/messages/rewrite-not-supported.md +1 -1
  91. package/share/messages/syntax-expecting-unsigned-int.md +2 -2
  92. package/share/messages/type-missing-enum-value.md +59 -0
  93. package/share/messages/wildcard-excluding-one.md +1 -1
@@ -2,9 +2,10 @@
2
2
 
3
3
  const { setProp, isBetaEnabled } = require('../base/model');
4
4
  const { forEachMemberRecursively, forAllQueries, applyTransformationsOnNonDictionary,
5
- getArtifactDatabaseNameOf, getElementDatabaseNameOf, isBuiltinType, applyTransformations,
5
+ getArtifactDatabaseNameOf, getElementDatabaseNameOf, applyTransformations,
6
6
  isAspect, walkCsnPath, isPersistedOnDatabase
7
7
  } = require('../model/csnUtils');
8
+ const { isBuiltinType } = require('../base/builtins');
8
9
  const transformUtils = require('./transformUtils');
9
10
  const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');
10
11
  const { csnRefs, pathId, traverseQuery, columnAlias} = require('../model/csnRefs');
@@ -256,25 +257,14 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
256
257
  //
257
258
  // First, gather all nodes that are arrayed: Don't replace inline, or getFinalTypeInfo()
258
259
  // may not return `.items` for types that were already processed.
260
+ // TODO: Do this in resolveTypeReferences?
259
261
  {
260
- const removeItems = new Set();
261
262
  applyTransformations(csn, {
262
263
  type: (node) => {
263
- if (node.items || node.type && csnUtils.getFinalTypeInfo(node.type)?.items)
264
- removeItems.add(node);
265
264
  renamePrimitiveTypesAndUuid(node.type, node, 'type');
266
265
  addDefaultTypeFacets(node, implicitDefaultLengths);
267
266
  },
268
- items: (node) => removeItems.add(node),
269
267
  });
270
- // no support for array-of - turn into CLOB/Text
271
- // must be done after A2J or compiler checks could change
272
- // (e.g. annotation def checks for arrayed types)
273
- for (const node of removeItems) {
274
- node.type = 'cds.LargeString';
275
- delete node.items;
276
- }
277
- removeItems.clear();
278
268
  }
279
269
 
280
270
  forEachDefinition(csn, [
@@ -294,15 +284,17 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
294
284
  // Managed associations get an on-condition - in views and entities
295
285
  doA2J && associations.attachOnConditions(csn, csnUtils, pathDelimiter);
296
286
 
297
- // (045) Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',
298
- // and make them entities
299
- forEachDefinition(csn, cdsPersistence.getPersistenceTableProcessor(csn, options, messageFunctions));
300
-
301
- // Allow using managed associations as steps in on-conditions to access their fks
302
- // To be done after handleAssociations, since then the foreign keys of the managed assocs
303
- // are part of the elements
304
- if (doA2J)
305
- forEachDefinition(csn, associations.getFKAccessFinalizer(csn, csnUtils, pathDelimiter));
287
+ {
288
+ // (045) Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',
289
+ // and make them entities
290
+ const fns = [cdsPersistence.getPersistenceTableProcessor(csn, options, messageFunctions)];
291
+ // Allow using managed associations as steps in on-conditions to access their fks
292
+ // To be done after handleAssociations, since then the foreign keys of the managed assocs
293
+ // are part of the elements
294
+ if(doA2J) fns.push(associations.getFKAccessFinalizer(csn, csnUtils, pathDelimiter));
295
+
296
+ forEachDefinition(csn, fns);
297
+ }
306
298
 
307
299
  // Create convenience views for localized entities/views.
308
300
  // To be done after getFKAccessFinalizer because associations are
@@ -328,21 +320,15 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
328
320
  // - view with parameters: ok on HANA, not allowed otherwise
329
321
  // (don't complain about action/function with parameters)
330
322
  handleChecksForWithParameters,
331
- // Remove .masked
332
- // Check that keys are not explicitly nullable
333
- // Check that Associations are not used in entities/views with parameters
334
- // (150 b) Strip inheritance
335
- // Note that this should happen after implicit redirection, because includes are required for that
336
- handleDBChecks,
323
+ checkAssocsWithParams,
324
+ // (170) Transform '$self' in backlink associations to appropriate key comparisons
325
+ // Must happen before draft processing because the artificial ON-conditions in generated
326
+ // draft shadow entities have crooked '_artifact' links, confusing the backlink processing.
327
+ // But it must also happen after flattenForeignKeys has been called for all artifacts,
328
+ // because otherwise we would produce wrong ON-conditions for the keys involved. Sigh ...
329
+ backlinks.getBacklinkTransformer(csnUtils, messageFunctions, options, pathDelimiter, doA2J)
337
330
  ]);
338
331
 
339
- // (170) Transform '$self' in backlink associations to appropriate key comparisons
340
- // Must happen before draft processing because the artificial ON-conditions in generated
341
- // draft shadow entities have crooked '_artifact' links, confusing the backlink processing.
342
- // But it must also happen after flattenForeignKeys has been called for all artifacts,
343
- // because otherwise we would produce wrong ON-conditions for the keys involved. Sigh ...
344
- forEachDefinition(csn, backlinks.getBacklinkTransformer(csnUtils, messageFunctions, options, pathDelimiter, doA2J));
345
-
346
332
  /**
347
333
  * Referential Constraints are only supported for sql-dialect "hana" and "sqlite".
348
334
  * For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
@@ -367,16 +353,14 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
367
353
  // Apply view-specific transformations
368
354
  // (160) Projections now finally become views
369
355
  // Replace managed association in group/order by with foreign keys
370
- const transformEntityOrViewPass2 = getViewTransformer(csn, options, messageFunctions, transformCommon);
356
+ const transformEntityOrViewPass2 = getViewTransformer(csn, options, messageFunctions);
371
357
  forEachDefinition(csn, transformViews);
372
358
 
373
- // Recursively apply transformCommon and attach @cds.persistence.name
374
- forEachDefinition(csn, recursivelyApplyCommon);
375
-
376
359
  const checkConstraintIdentifiers = (artifact, artifactName, prop, path) => {
377
360
  assertConstraintIdentifierUniqueness(artifact, artifactName, path, error);
378
361
  };
379
362
 
363
+ // TODO: Could we maybe merge this with the final applyTransformations?
380
364
  forEachDefinition(csn, [
381
365
  /* assert that there will be no conflicting unique- and foreign key constraint identifiers */
382
366
  checkConstraintIdentifiers,
@@ -386,9 +370,26 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
386
370
  removeKeyPropInType,
387
371
  ]);
388
372
 
389
- // Remove leading $self to keep renderer-diffs smaller
390
- if(doA2J)
391
- flattening.removeLeadingSelf(csn);
373
+
374
+ // TODO: Could we maybe merge this with the final applyTransformations?
375
+ applyTransformations(csn, {
376
+ elements: (parent, prop, elements, path) => {
377
+ // Attach @cds.persistence.name to elements, replace enums by their base value
378
+ const artifact = csn.definitions[path[1]];
379
+ forEach(elements, (name, element) => {
380
+ replaceEnums(element, name, path.concat(['elements', name]));
381
+ if ((!element.virtual || artifact.query))
382
+ csnUtils.addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(name, options.sqlMapping, options.sqlDialect), element);
383
+ });
384
+ // Remove leading $self to keep renderer-diffs smaller
385
+ if(doA2J && options.transformation === 'hdbcds')
386
+ flattening.removeLeadingSelf(parent, prop, elements);
387
+ }
388
+ }, [(definitions, artifactName, artifact) => {
389
+ // Attach @cds.persistence.name to artifacts
390
+ if (!artifact.$ignore && artifact.kind !== 'service' && artifact.kind !== 'context')
391
+ csnUtils.addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
392
+ }], { allowArtifact: artifact => artifact.kind === 'entity'});
392
393
 
393
394
  throwWithAnyError();
394
395
 
@@ -434,7 +435,10 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
434
435
  }
435
436
  }
436
437
  },
437
-
438
+ includes: killProp,
439
+ masked: killProp,
440
+ localized: killProp,
441
+ enum: killProp
438
442
  }
439
443
 
440
444
  if(options.sqlDialect === 'postgres') {
@@ -449,8 +453,25 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
449
453
  killers.target = killParent;
450
454
  }
451
455
 
456
+ const killTypes = [];
457
+
458
+ if(doA2J) { // replace types and aspects with dummies to shrink overall CSN size
459
+ killers.kind = (parent, prop, kind, path) => {
460
+ if(kind === 'type' || kind === 'aspect') {
461
+ const artifactName = path[1];
462
+ killTypes.push(() => {
463
+ csn.definitions[artifactName] = {
464
+ kind,
465
+ type: 'cds.Integer'
466
+ };
467
+ })
468
+ }
469
+ }
470
+ }
471
+
452
472
  applyTransformations(csn, killers, [], { skipIgnore: false });
453
473
 
474
+ killTypes.forEach(fn => fn());
454
475
  redoProjections.forEach(fn => fn());
455
476
 
456
477
  timetrace.stop('Transform CSN');
@@ -577,34 +598,12 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
577
598
  }
578
599
  }
579
600
 
580
- /**
581
- * @param {CSN.Artifact} artifact
582
- * @param {string} artifactName
583
- */
584
- function recursivelyApplyCommon(artifact, artifactName) {
585
- if (!artifact.$ignore) {
586
- if (artifact.kind !== 'service' && artifact.kind !== 'context')
587
- csnUtils.addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
588
-
589
- forEachMemberRecursively(artifact, (member, memberName, property, path) => {
590
- if (property === 'returns')
591
- return; // ignore "returns" type
592
- transformCommon(member, memberName, path);
593
- // (240 b) Annotate elements, foreign keys, parameters etc with their DB names
594
- // Virtual elements in entities and types are not annotated, as they have no DB representation.
595
- // In views they are, as we generate a null expression for them (null as <colname>)
596
- if ((!member.virtual || artifact.query))
597
- csnUtils.addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), member);
598
- }, [ 'definitions', artifactName ]);
599
- }
600
- }
601
-
602
601
  /**
603
602
  * @param {CSN.Artifact} artifact
604
603
  * @param {string} artifactName
605
604
  */
606
605
  function removeKeyPropInType(artifact, artifactName) {
607
- if (!artifact.$ignore && artifact.kind === 'type') {
606
+ if (!doA2J && !artifact.$ignore && artifact.kind === 'type') {
608
607
  forEachMemberRecursively(artifact, (member) => {
609
608
  if (member.key)
610
609
  delete member.key;
@@ -616,47 +615,42 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
616
615
  * @param {CSN.Artifact} artifact
617
616
  * @param {string} artifactName
618
617
  */
619
- function handleDBChecks(artifact, artifactName) {
620
- // Strip inheritance
621
- if (artifact.includes)
622
- delete artifact.includes;
623
-
624
- // Process the artifact's members
625
- forEachMemberRecursively(artifact, (member, memberName, prop, path) => {
626
- // (100 a) Ignore the property 'masked' itself (but not its effect on projections)
627
- if (member.masked)
628
- delete member.masked;
629
- // Report an error on
630
- // - view with parameters that has an element of type association/composition
631
- // - association that points to entity with parameters
632
- if (member.target && csnUtils.isAssocOrComposition(member) && !isBetaEnabled(options, 'assocsWithParams')) {
633
- if (artifact.params) {
634
- // HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
635
- // SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
636
- message('def-unexpected-paramview-assoc', path, { '#': 'source' });
637
- }
638
- else if(artifact['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
639
- // UDF/CVs w/o params don't support 'WITH ASSOCIATIONS'
640
- const anno = artifact['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
641
- message('def-unexpected-calcview-assoc', path, { '#': 'source', anno });
642
- }
643
- if (csn.definitions[member.target].params) {
644
- // HANA does not allow association targets with parameters or to UDFs/CVs w/o parameters:
645
- // SAP DBTech JDBC: [7]: feature not supported: cannot support create association to a parameterized view
646
- message('def-unexpected-paramview-assoc', path, { '#': 'target' });
647
- }
648
- else if(csn.definitions[member.target]['@cds.persistence.udf'] || csn.definitions[member.target]['@cds.persistence.calcview']) {
649
- // HANA won't check the assoc target but when querying an association with target UDF, this is the error:
650
- // SAP DBTech JDBC: [259]: invalid table name: target object SYSTEM.UDF does not exist: line 3 col 6 (at pos 43)
651
- // CREATE TABLE F (id INTEGER NOT NULL);
652
- // CREATE FUNCTION UDF RETURNS TABLE (ID INTEGER) LANGUAGE SQLSCRIPT SQL SECURITY DEFINER AS BEGIN RETURN SELECT ID FROM F; END;
653
- // CREATE TABLE Y ( id INTEGER NOT NULL, toUDF_id INTEGER) WITH ASSOCIATIONS (MANY TO ONE JOIN UDF AS toUDF ON (toUDF.id = toUDF_id));
654
- // CREATE VIEW U AS SELECT id, toUDF.a FROM Y;
655
- const anno = csn.definitions[member.target]['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
656
- message('def-unexpected-calcview-assoc', path, { '#': 'target', anno });
618
+ function checkAssocsWithParams(artifact, artifactName) {
619
+ if(options.transformation === 'hdbcds') {
620
+ forEachMemberRecursively(artifact, (member, memberName, prop, path) => {
621
+ // Report an error on
622
+ // - view with parameters that has an element of type association/composition
623
+ // - association that points to entity with parameters
624
+ if (member.target && csnUtils.isAssocOrComposition(member)) {
625
+ if (artifact.params) {
626
+ // HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
627
+ // SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
628
+ message('def-unexpected-paramview-assoc', path, { '#': 'source' });
629
+ }
630
+ else if(artifact['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
631
+ // UDF/CVs w/o params don't support 'WITH ASSOCIATIONS'
632
+ const anno = artifact['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
633
+ message('def-unexpected-calcview-assoc', path, { '#': 'source', anno });
634
+ }
635
+ if (csn.definitions[member.target].params) {
636
+ // HANA does not allow association targets with parameters or to UDFs/CVs w/o parameters:
637
+ // SAP DBTech JDBC: [7]: feature not supported: cannot support create association to a parameterized view
638
+ message('def-unexpected-paramview-assoc', path, { '#': 'target' });
639
+ }
640
+ else if(csn.definitions[member.target]['@cds.persistence.udf'] || csn.definitions[member.target]['@cds.persistence.calcview']) {
641
+ // HANA won't check the assoc target but when querying an association with target UDF, this is the error:
642
+ // SAP DBTech JDBC: [259]: invalid table name: target object SYSTEM.UDF does not exist: line 3 col 6 (at pos 43)
643
+ // CREATE TABLE F (id INTEGER NOT NULL);
644
+ // CREATE FUNCTION UDF RETURNS TABLE (ID INTEGER) LANGUAGE SQLSCRIPT SQL SECURITY DEFINER AS BEGIN RETURN SELECT ID FROM F; END;
645
+ // CREATE TABLE Y ( id INTEGER NOT NULL, toUDF_id INTEGER) WITH ASSOCIATIONS (MANY TO ONE JOIN UDF AS toUDF ON (toUDF.id = toUDF_id));
646
+ // CREATE VIEW U AS SELECT id, toUDF.a FROM Y;
647
+ const anno = csn.definitions[member.target]['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
648
+ message('def-unexpected-calcview-assoc', path, { '#': 'target', anno });
649
+ }
657
650
  }
658
- }
659
- }, [ 'definitions', artifactName ]);
651
+ }, [ 'definitions', artifactName ]);
652
+ }
653
+
660
654
  }
661
655
 
662
656
  /**
@@ -731,7 +725,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
731
725
 
732
726
 
733
727
  /**
734
- * Remove `localized` from elements and replace Enum symbols by their values.
728
+ * Replace Enum symbols by their values.
735
729
  *
736
730
  * Only applies to elements.
737
731
  *
@@ -739,11 +733,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
739
733
  * @param {String} objName
740
734
  * @param {CSN.Path} path
741
735
  */
742
- function transformCommon(obj, objName, path) {
743
- // (100 b) Remove attribute 'localized'
744
- if (obj.localized)
745
- delete obj.localized;
746
-
736
+ function replaceEnums(obj, objName, path) {
747
737
  // (190 a) Replace enum symbols by their value (if found)
748
738
  replaceEnumSymbolsByValues(obj, path);
749
739
 
@@ -832,6 +822,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
832
822
  /**
833
823
  * @param {CSN.Artifact} artifact
834
824
  * @param {string} artifactName
825
+ * @todo can we do this earlier? Together with another forEachMemberRecursively?
835
826
  */
836
827
  function checkTypeParameters(artifact, artifactName) {
837
828
  forEachMemberRecursively(artifact, (member, memberName, prop, path) => {
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const { isBuiltinType, isMagicVariable, forEachDefinition,
3
+ const { forEachDefinition,
4
4
  copyAnnotations, forEachMemberRecursively,
5
5
  transformExpression, findAnnotationExpression } = require('../../model/csnUtils');
6
+ const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
6
7
  const transformUtils = require('../transformUtils');
7
8
  const { setProp, isBetaEnabled } = require('../../base/model');
8
9
  const { applyTransformationsOnDictionary,
@@ -26,25 +27,32 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
26
27
  const csnPath = ['definitions', defName, dictName];
27
28
  const orderedElementList = [];
28
29
 
29
- forEach(dict, (eltName, elt) => {
30
- const location = [ ...csnPath, eltName ];
31
- const resolvedElt = (elt.type && !elt.elements) ? csnUtils.getFinalTypeInfo(elt.type) : elt;
30
+ forEach(dict, (childName, child) => {
31
+ const location = [ ...csnPath, childName ];
32
+ let rootPrefix = [ defName ];
33
+ let resolvedElt = child;
34
+ let typeIdx = 0;
35
+ if(child.type && !child.elements) {
36
+ resolvedElt = csnUtils.getFinalTypeInfo(child.type);
37
+ if(resolvedElt.elements)
38
+ typeIdx = rootPrefix.length + 1;
39
+ }
32
40
  if(resolvedElt.elements) {
33
- const flattenedSubTree = recurseIntoElement(dictName, elt, resolvedElt,
34
- !!elt.notNull, location, [ defName, eltName ]);
41
+ const flattenedSubTree = recurseIntoElement(dictName, child, resolvedElt,
42
+ !!child.notNull, location, [ ...rootPrefix, childName ], typeIdx);
35
43
 
36
44
  flattenedSubTree.forEach(([flatEltName, flatElt]) => {
37
45
  if (dict[flatEltName] || orderedElementList.some(elt => elt[0] === flatEltName))
38
46
  error('name-duplicate-element', location,
39
47
  { '#': 'flatten-element-exist', name: flatEltName });
40
- propagateToFlatElem(elt, flatElt);
48
+ propagateToFlatElem(child, flatElt);
41
49
  rewriteOnCondition(flatElt, flattenedSubTree);
42
50
  orderedElementList.push([flatEltName, flatElt]);
43
51
  });
44
52
  }
45
53
  else {
46
- const flatElt = cloneElt(dictName, elt, location, [ defName, eltName ]);
47
- orderedElementList.push([eltName, flatElt]);
54
+ const flatElt = cloneElt(dictName, child, location, [ defName, childName ]);
55
+ orderedElementList.push([childName, flatElt]);
48
56
  }
49
57
  });
50
58
 
@@ -60,7 +68,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
60
68
  elements[flatEltName] = flatElt;
61
69
  return elements;
62
70
  }, Object.create(null));
63
- setProp(def, `$${dictName}`, flatDict);
71
+ setProp(def, `$flat${dictName}`, flatDict);
64
72
  }
65
73
  });
66
74
  const flatAnnos = Object.create(null);
@@ -91,7 +99,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
91
99
  }
92
100
  flattenedSubTree = flattenedSubTree.concat(recurseIntoElement(scope, child, resolvedElt,
93
101
  !!(child.notNull && rootPathIsNotNull),
94
- [... location, 'elements', childName],
102
+ [... location, 'elements', childName],
95
103
  [ ...rootPrefix, childName ], typeIdx));
96
104
  });
97
105
  // 1) rename, 2) filter duplicates and finalize new elements
@@ -178,11 +186,11 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
178
186
  !isODataV4BuiltinFromService(elt.type, location)
179
187
  && !isItemsType(elt.type)) {
180
188
  let resolvedType = csnUtils.getFinalTypeInfo(elt);
181
-
189
+
182
190
  delete resolvedType.kind;
183
191
  if(resolvedType.items)
184
192
  delete resolvedType.type;
185
-
193
+
186
194
  if(elt.items) {
187
195
  if(resolvedType.items) {
188
196
  elt.items = resolvedType;
@@ -200,16 +208,16 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
200
208
  elt.type = resolvedType;
201
209
  }
202
210
  }
203
-
211
+
204
212
  function isItemsType(typeName) {
205
213
  let typeDef = csn.definitions[typeName];
206
214
  return !!typeDef?.items;
207
215
  }
208
-
216
+
209
217
  function isODataV4BuiltinFromService( typeName, path ) {
210
218
  if (options.odataVersion === 'v2' || typeof typeName !== 'string')
211
219
  return false;
212
-
220
+
213
221
  const typeServiceName = csnUtils.getServiceName(typeName);
214
222
  let finalBaseType = csnUtils.getFinalTypeInfo(typeName).type;
215
223
  if(!isBuiltinType(finalBaseType)) {
@@ -224,20 +232,19 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
224
232
  }
225
233
 
226
234
  // The path rewriting must be done with the current CSN path of that exact annotation location
227
- // in the element tree and done exactly after the initial copy, (otherwise, csnRefs is not able
235
+ // in the element tree and done exactly after the initial copy, (otherwise, csnRefs is not able
228
236
  // to locate the relative location of the path expression in this element).
229
237
  // At this time both annotations and values must be rewritten.
230
238
  //
231
239
  // Later, the query can/must be rewritten as long as a flat OData CSN is published
232
240
  // but this then operates on the entity/view which has all struct infos available
233
241
  function flattenAndPrefixExprPaths(carrier, propNames, csnPath, rootPrefix, typeIdx, refParentIsItems = false) {
234
- refFlattener.$refParentIsItems = refParentIsItems;
235
242
  const refCheck = {
236
- ref: (parent, prop, xpr, path) => {
237
- const { art } = inspectRef(path);
243
+ ref: (elemref, prop, xpr, path) => {
244
+ const { art } = (elemref._art ? { art: elemref._art } : inspectRef(path));
238
245
  const ft = csnUtils.getFinalTypeInfo(art?.type);
239
246
  if(!isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
240
- error('odata-anno-xpr-ref', path, { elemref: parent, name: refCheck.eltLocationStr, anno: refCheck.anno, '#': 'flatten_builtin' });
247
+ error('odata-anno-xpr-ref', path, { anno: refCheck.anno, elemref, name: refCheck.eltLocationStr, '#': 'flatten_builtin' });
241
248
  }
242
249
  }
243
250
  }
@@ -250,9 +257,14 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
250
257
  const absolutifier = {
251
258
  ref : (parent, prop, xpr) => {
252
259
  const head = xpr[0].id || xpr[0];
253
- if(typeIdx > 0 && head === '$self' && !isMagicVariable(head)) {
260
+ if(typeIdx < rootPrefix.length && head === '$self' && !isMagicVariable(head)) {
254
261
  const [xprHead, ...xprTail] = xpr.slice(1, xpr.length);
255
- parent[prop] = [ rootPrefix.slice(1, typeIdx).concat(xprHead).join('_'), ...xprTail];
262
+ if(xprHead.id) {
263
+ xprHead.id = rootPrefix.slice(1, typeIdx).concat(xprHead.id).join('_');
264
+ parent[prop] = [ xprHead, ...xprTail ];
265
+ }
266
+ else
267
+ parent[prop] = [ rootPrefix.slice(1, typeIdx).concat(xprHead).join('_'), ...xprTail];
256
268
  if(carrier.$scope === 'params')
257
269
  parent.param = true;
258
270
  else
@@ -260,8 +272,14 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
260
272
  }
261
273
  else if(rootPrefix.length > 2 && head !== '$self' && !parent.param && !isMagicVariable(head)) {
262
274
  const [xprHead, ...xprTail] = xpr;
263
- if(!refParentIsItems)
264
- parent[prop] = [ rootPrefix.slice(1, -1).concat(xprHead).join('_'), ...xprTail];
275
+ if(!refParentIsItems) {
276
+ if(xprHead.id) {
277
+ xprHead.id = rootPrefix.slice(1, -1).concat(xprHead.id).join('_');
278
+ parent[prop] = [ xprHead, ...xprTail ];
279
+ }
280
+ else
281
+ parent[prop] = [ rootPrefix.slice(1, -1).concat(xprHead).join('_'), ...xprTail];
282
+ }
265
283
  else
266
284
  parent[prop] = [ ...rootPrefix.slice(0, rootPrefix.length-1), ...xpr];
267
285
  if(carrier.$scope === 'params')
@@ -271,14 +289,13 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
271
289
  }
272
290
  }
273
291
  }
274
-
292
+
275
293
  propNames.forEach(pn => {
276
294
  refCheck.anno = pn;
277
295
  transformExpression(carrier, pn, [ refCheck, refFlattener ], csnPath);
278
296
  });
279
- adaptRefs.forEach(fn => fn());
297
+ adaptRefs.forEach(fn => fn(refParentIsItems));
280
298
  adaptRefs.length = 0;
281
- refFlattener.$refParentIsItems = false;
282
299
  propNames.forEach(pn => {
283
300
  transformExpression(carrier, pn, absolutifier, csnPath)
284
301
  })
@@ -303,17 +320,10 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
303
320
  }
304
321
  }
305
322
 
306
- /**
307
- * Rewrite all structured references in the model such that it matches their flat counterparts
308
- * @param {CSN.Model} csn
309
- * @param {CSN.Options} options
310
- * @param {WeakMap} resolved Cache for resolved refs
311
- * @param {string} pathDelimiter
312
- * @param {object} iterateOptions
313
- */
314
- function flattenAllStructStepsInRefs( csn, refFlattener, adaptRefs, iterateOptions = {} ) {
323
+ function flattenAllStructStepsInRefs( csn, refFlattener, adaptRefs, inspectRef, effectiveType, csnUtils, error, options, iterateOptions = {} ) {
315
324
 
316
325
  // All anno path flattening is already done, don't do it on locations where we don't want it!
326
+ const typeNames = [];
317
327
  forEachDefinition(csn, (def, defName) => {
318
328
  if(def.kind === 'entity') {
319
329
  ['query', 'projection'].forEach(dictName => {
@@ -322,16 +332,45 @@ function flattenAllStructStepsInRefs( csn, refFlattener, adaptRefs, iterateOptio
322
332
  if(csn.definitions[defName].actions)
323
333
  applyTransformationsOnDictionary(csn.definitions[defName].actions, refFlattener, iterateOptions, [ 'definitions', defName, 'actions' ]);
324
334
  }
325
- /*
326
- TODO: In real hybrid flat/struct OData transformation, ref rewriting in types must not be done
327
- together with non-rewriting ON condition refs during foreign key creation
328
- */
335
+
329
336
  if(['type'].includes(def.kind)) {
337
+ typeNames.push(defName);
330
338
  applyTransformationsOnNonDictionary(csn.definitions, defName, refFlattener, iterateOptions, [ 'definitions' ]);
331
339
  }
332
340
  });
333
341
  adaptRefs.forEach(fn => fn());
334
342
  adaptRefs.length = 0;
343
+ if(isBetaEnabled(options, 'odataPathsInAnnotationExpressions')) {
344
+ const refCheck = {
345
+ ref: (elemref, prop, xpr, path) => {
346
+ const { links, art } = (elemref._links && elemref._art ? { links: elemref._links, art: elemref._art } : inspectRef(path) );
347
+
348
+ let i = links.length-2;
349
+ const getProp = (propName) =>
350
+ (links[i].art?.[propName] ||
351
+ effectiveType(links[i].art)[propName]);
352
+
353
+ const ft = csnUtils.getFinalTypeInfo(art?.type);
354
+ let target = undefined;
355
+ for(; i >= 0 && !getProp('items') && !target; i--) {
356
+ target = getProp('target');
357
+ }
358
+ if(target && csn.definitions[target].$flatelements && !isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
359
+ error('odata-anno-xpr-ref', path, { anno: refCheck.anno, elemref, '#': 'flatten_builtin_type' });
360
+ }
361
+ }
362
+ }
363
+ typeNames.forEach(tn => {
364
+ forEachMemberRecursively(csn.definitions[tn], (member, memberName, prop, csnPath) => {
365
+ Object.keys(member).filter(pn => pn[0] === '@').forEach(pn => {
366
+ refCheck.anno = pn;
367
+ transformExpression(member, pn, [ refCheck, refFlattener ], csnPath);
368
+ });
369
+ }, [ 'definitions', tn ]);
370
+ })
371
+ adaptRefs.forEach(fn => fn(true, 1));
372
+ adaptRefs.length = 0;
373
+ }
335
374
  }
336
375
 
337
376
  function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, options, resolved, pathDelimiter) {
@@ -355,19 +394,18 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
355
394
  }
356
395
  const adaptRefs = [];
357
396
  const transformer = {
358
- $refParentIsItems: false,
359
397
  ref: (parent, prop, ref, path) => {
360
398
  const { links, art, scope } = inspectRef(path);
361
399
  const resolvedLinkTypes = resolveLinkTypes(links);
362
400
  setProp(parent, '$path', [ ...path ]);
363
401
  const lastRef = ref[ref.length - 1];
364
- const fn = () => {
402
+ const fn = (suspend=false, suspendPos=0) => {
365
403
  const scopedPath = [ ...parent.$path ];
366
404
  // TODO: If foreign key annotations should be assigned via
367
405
  // full path into target, uncomment this line and
368
406
  // comment/remove setProp in expansion.js
369
407
  // setProp(parent, '$structRef', parent.ref);
370
- parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes, transformer.$refParentIsItems);
408
+ parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes, suspend, suspendPos);
371
409
  resolved.set(parent, { links, art, scope });
372
410
  // Explicitly set implicit alias for things that are now flattened - but only in columns
373
411
  // TODO: Can this be done elegantly during expand phase already?